|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// https://github.com/ldclabs/ciborium/blob/main/ciborium/src/value/canonical.rs |
| 3 | + |
| 4 | +use ciborium::value::Value; |
| 5 | +use core::cmp::Ordering; |
| 6 | +use serde::ser; |
| 7 | + |
| 8 | +/// Serializes an object as CBOR into a new Vec<u8> |
| 9 | +pub fn cbor_into_vec<T: ?Sized + ser::Serialize>(value: &T) -> Result<Vec<u8>, String> { |
| 10 | + let mut data = Vec::new(); |
| 11 | + ciborium::into_writer(&value, &mut data).map_err(|err| format!("{err:?}"))?; |
| 12 | + Ok(data) |
| 13 | +} |
| 14 | + |
| 15 | +/// Serializes an object as CBOR into a new Vec<u8> using RFC 8949 Deterministic Encoding. |
| 16 | +pub fn canonical_cbor_into_vec<T: ?Sized + ser::Serialize>(value: &T) -> Result<Vec<u8>, String> { |
| 17 | + let value = Value::serialized(value).map_err(|err| format!("{err:?}"))?; |
| 18 | + |
| 19 | + let value = canonical_value(value); |
| 20 | + let mut data = Vec::new(); |
| 21 | + ciborium::into_writer(&value, &mut data).map_err(|err| format!("{err:?}"))?; |
| 22 | + Ok(data) |
| 23 | +} |
| 24 | + |
| 25 | +/// Manually serialize values to compare them. |
| 26 | +fn serialized_canonical_cmp(v1: &Value, v2: &Value) -> Ordering { |
| 27 | + // There is an optimization to be done here, but it would take a lot more code |
| 28 | + // and using mixing keys, Arrays or Maps as CanonicalValue is probably not the |
| 29 | + // best use of this type as it is meant mainly to be used as keys. |
| 30 | + |
| 31 | + let mut bytes1 = Vec::new(); |
| 32 | + let _ = ciborium::into_writer(v1, &mut bytes1); |
| 33 | + let mut bytes2 = Vec::new(); |
| 34 | + let _ = ciborium::into_writer(v2, &mut bytes2); |
| 35 | + |
| 36 | + match bytes1.len().cmp(&bytes2.len()) { |
| 37 | + Ordering::Equal => bytes1.cmp(&bytes2), |
| 38 | + x => x, |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +fn cmp_value(v1: &Value, v2: &Value) -> Ordering { |
| 43 | + use Value::*; |
| 44 | + |
| 45 | + match (v1, v2) { |
| 46 | + (Integer(i), Integer(o)) => { |
| 47 | + // Because of the first rule above, two numbers might be in a different |
| 48 | + // order than regular i128 comparison. For example, 10 < -1 in |
| 49 | + // canonical ordering, since 10 serializes to `0x0a` and -1 to `0x20`, |
| 50 | + // and -1 < -1000 because of their lengths. |
| 51 | + i.canonical_cmp(o) |
| 52 | + } |
| 53 | + (Text(s), Text(o)) => match s.len().cmp(&o.len()) { |
| 54 | + Ordering::Equal => s.cmp(o), |
| 55 | + x => x, |
| 56 | + }, |
| 57 | + (Bool(s), Bool(o)) => s.cmp(o), |
| 58 | + (Null, Null) => Ordering::Equal, |
| 59 | + (Tag(t, v), Tag(ot, ov)) => match Value::from(*t).partial_cmp(&Value::from(*ot)) { |
| 60 | + Some(Ordering::Equal) | None => match v.partial_cmp(ov) { |
| 61 | + Some(x) => x, |
| 62 | + None => serialized_canonical_cmp(v1, v2), |
| 63 | + }, |
| 64 | + Some(x) => x, |
| 65 | + }, |
| 66 | + (_, _) => serialized_canonical_cmp(v1, v2), |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +fn canonical_value(value: Value) -> Value { |
| 71 | + match value { |
| 72 | + Value::Map(entries) => { |
| 73 | + let mut canonical_entries: Vec<(Value, Value)> = entries |
| 74 | + .into_iter() |
| 75 | + .map(|(k, v)| (canonical_value(k), canonical_value(v))) |
| 76 | + .collect(); |
| 77 | + |
| 78 | + // Sort entries based on the canonical comparison of their keys. |
| 79 | + // cmp_value (defined in this file) implements RFC 8949 key sorting. |
| 80 | + canonical_entries.sort_by(|(k1, _), (k2, _)| cmp_value(k1, k2)); |
| 81 | + |
| 82 | + Value::Map(canonical_entries) |
| 83 | + } |
| 84 | + Value::Array(elements) => { |
| 85 | + let canonical_elements: Vec<Value> = |
| 86 | + elements.into_iter().map(canonical_value).collect(); |
| 87 | + Value::Array(canonical_elements) |
| 88 | + } |
| 89 | + Value::Tag(tag, inner_value) => { |
| 90 | + // The tag itself is a u64; its representation is handled by the serializer. |
| 91 | + // The inner value must be in canonical form. |
| 92 | + Value::Tag(tag, Box::new(canonical_value(*inner_value))) |
| 93 | + } |
| 94 | + // Other Value variants (Integer, Bytes, Text, Bool, Null, Float) |
| 95 | + // are considered "canonical" in their structure. |
| 96 | + _ => value, |
| 97 | + } |
| 98 | +} |
0 commit comments