Skip to content

Commit f373cae

Browse files
committed
feat: add canonical_into_vec, canonical_into_writer, canonical_value
Signed-off-by: 0xZensh <txr1883@gmail.com>
1 parent 66554fc commit f373cae

File tree

3 files changed

+161
-4
lines changed

3 files changed

+161
-4
lines changed

ciborium/src/value/canonical.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// SPDX-License-Identifier: Apache-2.0
22

3-
use crate::value::Value;
4-
use alloc::vec::Vec;
3+
use alloc::{boxed::Box, string::ToString, vec::Vec};
4+
use ciborium_io::Write;
55
use core::cmp::Ordering;
66
use serde::{de, ser};
77

8+
use crate::value::Value;
9+
810
/// Manually serialize values to compare them.
911
fn serialized_canonical_cmp(v1: &Value, v2: &Value) -> Ordering {
1012
// There is an optimization to be done here, but it would take a lot more code
@@ -122,3 +124,63 @@ impl PartialOrd for CanonicalValue {
122124
Some(self.cmp(other))
123125
}
124126
}
127+
128+
/// Recursively convert a Value to its canonical form as defined in RFC 8949 "core deterministic encoding requirements".
129+
pub fn canonical_value(value: Value) -> Value {
130+
match value {
131+
Value::Map(entries) => {
132+
let mut canonical_entries: Vec<(Value, Value)> = entries
133+
.into_iter()
134+
.map(|(k, v)| (canonical_value(k), canonical_value(v)))
135+
.collect();
136+
137+
// Sort entries based on the canonical comparison of their keys.
138+
// cmp_value (defined in this file) implements RFC 8949 key sorting.
139+
canonical_entries.sort_by(|(k1, _), (k2, _)| cmp_value(k1, k2));
140+
141+
Value::Map(canonical_entries)
142+
}
143+
Value::Array(elements) => {
144+
let canonical_elements: Vec<Value> =
145+
elements.into_iter().map(canonical_value).collect();
146+
Value::Array(canonical_elements)
147+
}
148+
Value::Tag(tag, inner_value) => {
149+
// The tag itself is a u64; its representation is handled by the serializer.
150+
// The inner value must be in canonical form.
151+
Value::Tag(tag, Box::new(canonical_value(*inner_value)))
152+
}
153+
// Other Value variants (Integer, Bytes, Text, Bool, Null, Float)
154+
// are considered "canonical" in their structure.
155+
_ => value,
156+
}
157+
}
158+
159+
/// Serializes an object as CBOR into a writer using RFC 8949 Deterministic Encoding.
160+
#[inline]
161+
pub fn canonical_into_writer<T: ?Sized + ser::Serialize, W: Write>(
162+
value: &T,
163+
writer: W,
164+
) -> Result<(), crate::ser::Error<W::Error>>
165+
where
166+
W::Error: core::fmt::Debug,
167+
{
168+
let value =
169+
Value::serialized(value).map_err(|err| crate::ser::Error::Value(err.to_string()))?;
170+
171+
let cvalue = canonical_value(value);
172+
crate::into_writer(&cvalue, writer)
173+
}
174+
175+
/// Serializes an object as CBOR into a new Vec<u8> using RFC 8949 Deterministic Encoding.
176+
#[cfg(feature = "std")]
177+
#[inline]
178+
pub fn canonical_into_vec<T: ?Sized + ser::Serialize>(
179+
value: &T,
180+
) -> Result<Vec<u8>, crate::ser::Error<<Vec<u8> as ciborium_io::Write>::Error>> {
181+
let value =
182+
Value::serialized(value).map_err(|err| crate::ser::Error::Value(err.to_string()))?;
183+
184+
let cvalue = canonical_value(value);
185+
crate::into_vec(&cvalue)
186+
}

ciborium/src/value/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ mod de;
99
mod error;
1010
mod ser;
1111

12-
pub use canonical::CanonicalValue;
12+
pub use canonical::{canonical_into_writer, canonical_value, CanonicalValue};
1313
pub use error::Error;
1414
pub use integer::Integer;
1515

16+
#[cfg(feature = "std")]
17+
pub use canonical::canonical_into_vec;
18+
1619
use alloc::{boxed::Box, string::String, vec::Vec};
1720

1821
/// A representation of a dynamic CBOR value that can handled dynamically

ciborium/tests/canonical.rs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ extern crate std;
44

55
use ciborium::cbor;
66
use ciborium::tag::Required;
7-
use ciborium::value::CanonicalValue;
7+
use ciborium::value::{canonical_into_writer, canonical_value, CanonicalValue, Value};
88
use rand::prelude::*;
9+
use serde::{Deserialize, Serialize};
910
use std::collections::BTreeMap;
1011

1112
macro_rules! cval {
@@ -109,3 +110,94 @@ fn tagged_option() {
109110
let output = ciborium::de::from_reader(&bytes[..]).unwrap();
110111
assert_eq!(opt, output);
111112
}
113+
114+
#[test]
115+
fn canonical_value_example() {
116+
let map = Value::Map(vec![
117+
(val!(false), val!(2)),
118+
(val!([-1]), val!(5)),
119+
(val!(-1), val!(1)),
120+
(val!(10), val!(0)),
121+
(val!(100), val!(3)),
122+
(val!([100]), val!(7)),
123+
(val!("z"), val!(4)),
124+
(val!("aa"), val!(6)),
125+
]);
126+
127+
let mut bytes = Vec::new();
128+
canonical_into_writer(&map, &mut bytes).unwrap();
129+
assert_eq!(
130+
hex::encode(&bytes),
131+
"a80a002001f402186403617a048120056261610681186407"
132+
);
133+
134+
let canonical = canonical_value(map);
135+
let bytes = ciborium::ser::into_vec(&canonical).unwrap();
136+
137+
assert_eq!(
138+
hex::encode(&bytes),
139+
"a80a002001f402186403617a048120056261610681186407"
140+
);
141+
}
142+
143+
#[test]
144+
fn canonical_value_nested_structures() {
145+
// Create nested structure with unsorted maps
146+
let nested = Value::Array(vec![
147+
Value::Map(vec![(val!("b"), val!(2)), (val!("a"), val!(1))]),
148+
Value::Tag(
149+
1,
150+
Box::new(Value::Map(vec![
151+
(val!(100), val!("high")),
152+
(val!(10), val!("low")),
153+
])),
154+
),
155+
]);
156+
157+
let canonical = canonical_value(nested);
158+
159+
if let Value::Array(elements) = canonical {
160+
// Check first map is sorted
161+
if let Value::Map(entries) = &elements[0] {
162+
assert_eq!(entries[0].0, val!("a"));
163+
assert_eq!(entries[1].0, val!("b"));
164+
}
165+
166+
// Check tagged map is sorted
167+
if let Value::Tag(_, inner) = &elements[1] {
168+
if let Value::Map(entries) = inner.as_ref() {
169+
assert_eq!(entries[0].0, val!(10));
170+
assert_eq!(entries[1].0, val!(100));
171+
}
172+
}
173+
} else {
174+
panic!("Expected Array value");
175+
}
176+
}
177+
178+
#[test]
179+
fn canonical_value_struct() {
180+
#[derive(Clone, Debug, Deserialize, Serialize)]
181+
struct T1 {
182+
a: u32,
183+
b: u32,
184+
c: u32,
185+
}
186+
187+
#[derive(Clone, Debug, Deserialize, Serialize)]
188+
struct T2 {
189+
c: u32,
190+
b: u32,
191+
a: u32,
192+
}
193+
194+
let t1 = T1 { a: 1, b: 2, c: 3 };
195+
let t2 = T2 { c: 3, b: 2, a: 1 };
196+
197+
let mut bytes1 = Vec::new();
198+
canonical_into_writer(&t1, &mut bytes1).unwrap();
199+
200+
let mut bytes2 = Vec::new();
201+
canonical_into_writer(&t2, &mut bytes2).unwrap();
202+
assert_eq!(bytes1, bytes2);
203+
}

0 commit comments

Comments
 (0)