aboutsummaryrefslogtreecommitdiff
path: root/src/value/canonical.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/value/canonical.rs')
-rw-r--r--src/value/canonical.rs124
1 files changed, 124 insertions, 0 deletions
diff --git a/src/value/canonical.rs b/src/value/canonical.rs
new file mode 100644
index 0000000..f1196f4
--- /dev/null
+++ b/src/value/canonical.rs
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::value::Value;
+use alloc::vec::Vec;
+use core::cmp::Ordering;
+use serde::{de, ser};
+
+/// Manually serialize values to compare them.
+fn serialized_canonical_cmp(v1: &Value, v2: &Value) -> Ordering {
+ // There is an optimization to be done here, but it would take a lot more code
+ // and using mixing keys, Arrays or Maps as CanonicalValue is probably not the
+ // best use of this type as it is meant mainly to be used as keys.
+
+ let mut bytes1 = Vec::new();
+ let _ = crate::ser::into_writer(v1, &mut bytes1);
+ let mut bytes2 = Vec::new();
+ let _ = crate::ser::into_writer(v2, &mut bytes2);
+
+ match bytes1.len().cmp(&bytes2.len()) {
+ Ordering::Equal => bytes1.cmp(&bytes2),
+ x => x,
+ }
+}
+
+/// Compares two values uses canonical comparison, as defined in both
+/// RFC 7049 Section 3.9 (regarding key sorting) and RFC 8949 4.2.3 (as errata).
+///
+/// In short, the comparison follow the following rules:
+/// - If two keys have different lengths, the shorter one sorts earlier;
+/// - If two keys have the same length, the one with the lower value in
+/// (byte-wise) lexical order sorts earlier.
+///
+/// This specific comparison allows Maps and sorting that respect these two rules.
+pub fn cmp_value(v1: &Value, v2: &Value) -> Ordering {
+ use Value::*;
+
+ match (v1, v2) {
+ (Integer(i), Integer(o)) => {
+ // Because of the first rule above, two numbers might be in a different
+ // order than regular i128 comparison. For example, 10 < -1 in
+ // canonical ordering, since 10 serializes to `0x0a` and -1 to `0x20`,
+ // and -1 < -1000 because of their lengths.
+ i.canonical_cmp(o)
+ }
+ (Text(s), Text(o)) => match s.len().cmp(&o.len()) {
+ Ordering::Equal => s.cmp(o),
+ x => x,
+ },
+ (Bool(s), Bool(o)) => s.cmp(o),
+ (Null, Null) => Ordering::Equal,
+ (Tag(t, v), Tag(ot, ov)) => match Value::from(*t).partial_cmp(&Value::from(*ot)) {
+ Some(Ordering::Equal) | None => match v.partial_cmp(ov) {
+ Some(x) => x,
+ None => serialized_canonical_cmp(v1, v2),
+ },
+ Some(x) => x,
+ },
+ (_, _) => serialized_canonical_cmp(v1, v2),
+ }
+}
+
+/// A CBOR Value that impl Ord and Eq to allow sorting of values as defined in both
+/// RFC 7049 Section 3.9 (regarding key sorting) and RFC 8949 4.2.3 (as errata).
+///
+/// Since a regular [Value] can be
+#[derive(Clone, Debug)]
+pub struct CanonicalValue(Value);
+
+impl PartialEq for CanonicalValue {
+ fn eq(&self, other: &Self) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+
+impl Eq for CanonicalValue {}
+
+impl From<Value> for CanonicalValue {
+ fn from(v: Value) -> Self {
+ Self(v)
+ }
+}
+
+impl From<CanonicalValue> for Value {
+ fn from(v: CanonicalValue) -> Self {
+ v.0
+ }
+}
+
+impl ser::Serialize for CanonicalValue {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: ser::Serializer,
+ {
+ self.0.serialize(serializer)
+ }
+}
+
+impl<'de> de::Deserialize<'de> for CanonicalValue {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ Value::deserialize(deserializer).map(Into::into)
+ }
+
+ fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
+ where
+ D: de::Deserializer<'de>,
+ {
+ Value::deserialize_in_place(deserializer, &mut place.0)
+ }
+}
+
+impl Ord for CanonicalValue {
+ fn cmp(&self, other: &Self) -> Ordering {
+ cmp_value(&self.0, &other.0)
+ }
+}
+
+impl PartialOrd for CanonicalValue {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(cmp_value(&self.0, &other.0))
+ }
+}