|
1 | 1 | //! `UUID` types. |
2 | 2 |
|
3 | | -use displaydoc::Display; |
4 | | -use thiserror::Error; |
5 | | -use uuid::Uuid; |
6 | | - |
7 | 3 | mod uuid_v4; |
8 | 4 | mod uuid_v7; |
9 | 5 |
|
| 6 | +use minicbor::data::Tag; |
10 | 7 | pub use uuid_v4::UuidV4 as V4; |
11 | 8 | pub use uuid_v7::UuidV7 as V7; |
12 | 9 |
|
13 | 10 | /// Invalid Doc Type UUID |
14 | 11 | pub const INVALID_UUID: uuid::Uuid = uuid::Uuid::from_bytes([0x00; 16]); |
15 | 12 |
|
16 | | -/// CBOR tag for UUID content. |
17 | | -pub const UUID_CBOR_TAG: u64 = 37; |
18 | | - |
19 | | -/// Errors that can occur when decoding CBOR-encoded UUIDs. |
20 | | -#[derive(Display, Debug, Error)] |
21 | | -pub enum CborUuidError { |
22 | | - /// Invalid CBOR encoded UUID type |
23 | | - InvalidCborType, |
24 | | - /// Invalid CBOR encoded UUID type: invalid bytes size |
25 | | - InvalidByteSize, |
26 | | - /// UUID {uuid} is not `v{expected_version}` |
27 | | - InvalidVersion { |
28 | | - /// The decoded UUID that was checked. |
29 | | - uuid: Uuid, |
30 | | - /// The expected version of the UUID, which did not match the decoded one. |
31 | | - expected_version: usize, |
32 | | - }, |
| 13 | +/// UUID CBOR tag <https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml/>. |
| 14 | +#[allow(dead_code)] |
| 15 | +const UUID_CBOR_TAG: u64 = 37; |
| 16 | + |
| 17 | +/// Context for `CBOR` encoding and decoding |
| 18 | +pub enum CborContext { |
| 19 | + /// Untagged bytes |
| 20 | + Untagged, |
| 21 | + /// IANA CBOR tag and bytes |
| 22 | + Tagged, |
| 23 | + /// Optional tag |
| 24 | + Optional, |
33 | 25 | } |
34 | 26 |
|
35 | | -/// Decode `CBOR` encoded `UUID`. |
36 | | -pub(crate) fn decode_cbor_uuid(val: &coset::cbor::Value) -> Result<uuid::Uuid, CborUuidError> { |
37 | | - let Some((UUID_CBOR_TAG, coset::cbor::Value::Bytes(bytes))) = val.as_tag() else { |
38 | | - return Err(CborUuidError::InvalidCborType); |
| 27 | +/// Validate UUID CBOR Tag. |
| 28 | +fn validate_uuid_tag(tag: u64) -> Result<(), minicbor::decode::Error> { |
| 29 | + if UUID_CBOR_TAG != tag { |
| 30 | + return Err(minicbor::decode::Error::message(format!( |
| 31 | + "tag value must be: {UUID_CBOR_TAG}, provided: {tag}" |
| 32 | + ))); |
| 33 | + } |
| 34 | + Ok(()) |
| 35 | +} |
| 36 | + |
| 37 | +/// Decode from `CBOR` into `UUID` |
| 38 | +fn decode_cbor_uuid( |
| 39 | + d: &mut minicbor::Decoder<'_>, ctx: &mut CborContext, |
| 40 | +) -> Result<uuid::Uuid, minicbor::decode::Error> { |
| 41 | + let bytes = match ctx { |
| 42 | + CborContext::Untagged => d.bytes()?, |
| 43 | + CborContext::Tagged => { |
| 44 | + let tag = d.tag()?; |
| 45 | + validate_uuid_tag(tag.as_u64())?; |
| 46 | + d.bytes()? |
| 47 | + }, |
| 48 | + CborContext::Optional => { |
| 49 | + let pos = d.position(); |
| 50 | + if let Ok(tag) = d.tag() { |
| 51 | + validate_uuid_tag(tag.as_u64())?; |
| 52 | + d.bytes()? |
| 53 | + } else { |
| 54 | + d.set_position(pos); |
| 55 | + d.bytes()? |
| 56 | + } |
| 57 | + }, |
39 | 58 | }; |
40 | | - let uuid = uuid::Uuid::from_bytes( |
41 | | - bytes |
42 | | - .clone() |
43 | | - .try_into() |
44 | | - .map_err(|_| CborUuidError::InvalidByteSize)?, |
45 | | - ); |
| 59 | + let decoded: [u8; 16] = bytes.try_into().map_err(|_| { |
| 60 | + minicbor::decode::Error::message("Invalid CBOR encoded UUID type: invalid bytes size") |
| 61 | + })?; |
| 62 | + let uuid = uuid::Uuid::from_bytes(decoded); |
46 | 63 | Ok(uuid) |
47 | 64 | } |
| 65 | + |
| 66 | +/// Encode `UUID` into `CBOR` |
| 67 | +fn encode_cbor_uuid<W: minicbor::encode::Write>( |
| 68 | + uuid: uuid::Uuid, e: &mut minicbor::Encoder<W>, ctx: &mut CborContext, |
| 69 | +) -> Result<(), minicbor::encode::Error<W::Error>> { |
| 70 | + if let CborContext::Tagged = ctx { |
| 71 | + e.tag(Tag::new(UUID_CBOR_TAG))?; |
| 72 | + } |
| 73 | + e.bytes(uuid.as_bytes())?; |
| 74 | + Ok(()) |
| 75 | +} |
| 76 | + |
| 77 | +#[cfg(test)] |
| 78 | +mod tests { |
| 79 | + |
| 80 | + use super::{V4, V7}; |
| 81 | + use crate::uuid::CborContext; |
| 82 | + |
| 83 | + #[test] |
| 84 | + fn test_cbor_uuid_v4_roundtrip() { |
| 85 | + let uuid: V4 = uuid::Uuid::new_v4().into(); |
| 86 | + let mut bytes = Vec::new(); |
| 87 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap(); |
| 88 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Untagged).unwrap(); |
| 89 | + assert_eq!(uuid, decoded); |
| 90 | + } |
| 91 | + |
| 92 | + #[test] |
| 93 | + fn test_cbor_uuid_v7_roundtrip() { |
| 94 | + let uuid: V7 = uuid::Uuid::now_v7().into(); |
| 95 | + let mut bytes = Vec::new(); |
| 96 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap(); |
| 97 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Untagged).unwrap(); |
| 98 | + assert_eq!(uuid, decoded); |
| 99 | + } |
| 100 | + |
| 101 | + #[test] |
| 102 | + fn test_tagged_cbor_uuid_v4_roundtrip() { |
| 103 | + let uuid: V4 = uuid::Uuid::new_v4().into(); |
| 104 | + let mut bytes = Vec::new(); |
| 105 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap(); |
| 106 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Tagged).unwrap(); |
| 107 | + assert_eq!(uuid, decoded); |
| 108 | + } |
| 109 | + |
| 110 | + #[test] |
| 111 | + fn test_tagged_cbor_uuid_v7_roundtrip() { |
| 112 | + let uuid: V7 = uuid::Uuid::now_v7().into(); |
| 113 | + let mut bytes = Vec::new(); |
| 114 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap(); |
| 115 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Tagged).unwrap(); |
| 116 | + assert_eq!(uuid, decoded); |
| 117 | + } |
| 118 | + |
| 119 | + #[test] |
| 120 | + fn test_optional_cbor_uuid_v4_roundtrip() { |
| 121 | + let uuid: V4 = uuid::Uuid::new_v4().into(); |
| 122 | + |
| 123 | + let mut bytes = Vec::new(); |
| 124 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap(); |
| 125 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Optional).unwrap(); |
| 126 | + assert_eq!(uuid, decoded); |
| 127 | + |
| 128 | + let mut bytes = Vec::new(); |
| 129 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap(); |
| 130 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Optional).unwrap(); |
| 131 | + assert_eq!(uuid, decoded); |
| 132 | + } |
| 133 | + |
| 134 | + #[test] |
| 135 | + fn test_optional_cbor_uuid_v7_roundtrip() { |
| 136 | + let uuid: V7 = uuid::Uuid::now_v7().into(); |
| 137 | + |
| 138 | + let mut bytes = Vec::new(); |
| 139 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap(); |
| 140 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Optional).unwrap(); |
| 141 | + assert_eq!(uuid, decoded); |
| 142 | + |
| 143 | + let mut bytes = Vec::new(); |
| 144 | + minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap(); |
| 145 | + let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Optional).unwrap(); |
| 146 | + assert_eq!(uuid, decoded); |
| 147 | + } |
| 148 | +} |
0 commit comments