From a1e5b96b883292c101bff808256cfd1bc5263efb Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Thu, 27 Nov 2025 17:55:00 +0700 Subject: [PATCH 1/5] feat: initial context option --- rust/cbork-utils/src/array.rs | 16 +++++++++++++++- rust/cbork-utils/src/decode_context.rs | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/rust/cbork-utils/src/array.rs b/rust/cbork-utils/src/array.rs index 9eac6f166d..21c6a0ef0a 100644 --- a/rust/cbork-utils/src/array.rs +++ b/rust/cbork-utils/src/array.rs @@ -133,7 +133,7 @@ fn check_array_minimal_length( fn decode_array_elements( d: &mut minicbor::Decoder, length: u64, - _ctx: &mut DecodeCtx, + ctx: &mut DecodeCtx, ) -> Result>, minicbor::decode::Error> { let capacity = usize::try_from(length).map_err(|_| { minicbor::decode::Error::message("Array length too large for current platform") @@ -155,6 +155,20 @@ fn decode_array_elements( elements.push(element_bytes); } + // for deterministic decoding (4.2.3) + if matches!(ctx, DecodeCtx::ArrayDeterministic) { + elements.sort_by(|a, b| { + // 1. shorter first + match a.len().cmp(&b.len()) { + std::cmp::Ordering::Equal => { + // 2. lexical order + a.as_slice().cmp(b.as_slice()) + }, + other => other, + } + }); + } + Ok(elements) } diff --git a/rust/cbork-utils/src/decode_context.rs b/rust/cbork-utils/src/decode_context.rs index ff58faa66a..a1f542b72f 100644 --- a/rust/cbork-utils/src/decode_context.rs +++ b/rust/cbork-utils/src/decode_context.rs @@ -9,6 +9,11 @@ pub enum DecodeCtx { /// Decode a CBOR object applying deterministic decoding rules (RFC 8949 /// Section 4.2). Deterministic, + /// Decode a CBOR object applying deterministic decoding rules (RFC 8949 + /// Section 4.2). + /// Additional apply `RFC 8949 Section 4.2.3` for array entries, so it become + /// deterministically sorted. + ArrayDeterministic, /// Decode a CBOR object **NOT** applying deterministic decoding rules (RFC 8949 /// Section 4.2). /// @@ -48,6 +53,7 @@ impl DecodeCtx { ) -> Result<(), minicbor::decode::Error> { match self { Self::Deterministic => f(), + Self::ArrayDeterministic => f(), Self::NonDeterministic(None) => Ok(()), Self::NonDeterministic(Some(h)) => { if let Err(err) = f() { From 4a74cb6e41f8da2ff01cd0ac78887ff2b47d1ea8 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Thu, 27 Nov 2025 20:30:02 +0700 Subject: [PATCH 2/5] add test --- rust/cbork-utils/Cargo.toml | 2 +- rust/cbork-utils/src/array.rs | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/rust/cbork-utils/Cargo.toml b/rust/cbork-utils/Cargo.toml index 478435b5b7..42be8d4828 100644 --- a/rust/cbork-utils/Cargo.toml +++ b/rust/cbork-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cbork-utils" -version = "0.0.2" +version = "0.0.3" edition.workspace = true license.workspace = true authors.workspace = true diff --git a/rust/cbork-utils/src/array.rs b/rust/cbork-utils/src/array.rs index 21c6a0ef0a..438acf0cfe 100644 --- a/rust/cbork-utils/src/array.rs +++ b/rust/cbork-utils/src/array.rs @@ -155,7 +155,7 @@ fn decode_array_elements( elements.push(element_bytes); } - // for deterministic decoding (4.2.3) + // for deterministic array decoding (4.2.3) if matches!(ctx, DecodeCtx::ArrayDeterministic) { elements.sort_by(|a, b| { // 1. shorter first @@ -335,4 +335,25 @@ mod tests { // Even it's non-deterministic, this should fail, as we enforce for the defined length. assert!(Array::decode(&mut decoder.clone(), &mut DecodeCtx::non_deterministic()).is_err()); } + + #[test] + fn test_deterministic_array_decoding() { + let bytes = [ + 0x83, // array of length 3 + 0x41, 0x02, // element: [0x02] + 0x42, 0x01, 0x01, // element: [0x01, 0x01] + 0x41, 0x01, // element: [0x01] + ]; + + let mut decoder = Decoder::new(&bytes); + + let decoded = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic) + .expect("deterministic decoding should succeed"); + let decoded = Vec::from_iter(decoded.into_iter()); + + let expected: Vec> = + vec![vec![0x41, 0x01], vec![0x41, 0x02], vec![0x42, 0x01, 0x01]]; + + assert_eq!(decoded, expected); + } } From c8a5bc5ba69cc774db2dedd1a5b09ec7d26fe757 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Thu, 27 Nov 2025 20:58:01 +0700 Subject: [PATCH 3/5] chore: lintfix --- rust/cbork-utils/src/array.rs | 6 +++--- rust/cbork-utils/src/decode_context.rs | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rust/cbork-utils/src/array.rs b/rust/cbork-utils/src/array.rs index 438acf0cfe..78426fe7bc 100644 --- a/rust/cbork-utils/src/array.rs +++ b/rust/cbork-utils/src/array.rs @@ -347,13 +347,13 @@ mod tests { let mut decoder = Decoder::new(&bytes); - let decoded = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic) + let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic) .expect("deterministic decoding should succeed"); - let decoded = Vec::from_iter(decoded.into_iter()); + let result = Vec::from_iter(result); let expected: Vec> = vec![vec![0x41, 0x01], vec![0x41, 0x02], vec![0x42, 0x01, 0x01]]; - assert_eq!(decoded, expected); + assert_eq!(result, expected); } } diff --git a/rust/cbork-utils/src/decode_context.rs b/rust/cbork-utils/src/decode_context.rs index a1f542b72f..bf563c5eb7 100644 --- a/rust/cbork-utils/src/decode_context.rs +++ b/rust/cbork-utils/src/decode_context.rs @@ -52,8 +52,7 @@ impl DecodeCtx { f: impl FnOnce() -> Result<(), minicbor::decode::Error>, ) -> Result<(), minicbor::decode::Error> { match self { - Self::Deterministic => f(), - Self::ArrayDeterministic => f(), + Self::Deterministic | Self::ArrayDeterministic => f(), Self::NonDeterministic(None) => Ok(()), Self::NonDeterministic(Some(h)) => { if let Err(err) = f() { From 2bb05acf6b9c6f4648036e7a33bfd1e299204ecf Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Thu, 27 Nov 2025 22:43:12 +0700 Subject: [PATCH 4/5] refactor: validate fn --- rust/cbork-utils/src/array.rs | 62 ++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/rust/cbork-utils/src/array.rs b/rust/cbork-utils/src/array.rs index 78426fe7bc..485f52aef9 100644 --- a/rust/cbork-utils/src/array.rs +++ b/rust/cbork-utils/src/array.rs @@ -155,23 +155,43 @@ fn decode_array_elements( elements.push(element_bytes); } - // for deterministic array decoding (4.2.3) if matches!(ctx, DecodeCtx::ArrayDeterministic) { - elements.sort_by(|a, b| { - // 1. shorter first - match a.len().cmp(&b.len()) { - std::cmp::Ordering::Equal => { - // 2. lexical order - a.as_slice().cmp(b.as_slice()) - }, - other => other, - } - }); + ctx.try_check(|| validate_array_ordering(&elements))?; } Ok(elements) } +/// Validates array elements are properly ordered according to RFC 8949 Section 4.2.3. +/// +/// Ordering rules: +/// 1. If two items differ in length → shorter sorts first. +/// 2. If lengths are equal → compare byte-wise lexicographically. +/// +/// Returns Ok(()) if elements are correctly ordered. +/// Returns Err(...) if any adjacent pair violates ordering. +fn validate_array_ordering(elements: &[Vec]) -> Result<(), minicbor::decode::Error> { + for pair in elements.windows(2) { + let [prev, curr] = pair else { + // fails if the array has 0-1 element + return Ok(()); + }; + + let ord = match prev.len().cmp(&curr.len()) { + std::cmp::Ordering::Equal => prev.as_slice().cmp(curr.as_slice()), + other => other, + }; + + if ord == std::cmp::Ordering::Greater { + return Err(minicbor::decode::Error::message( + "Array elements are not ordered deterministically", + )); + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use minicbor::{Decode, Decoder}; @@ -338,22 +358,26 @@ mod tests { #[test] fn test_deterministic_array_decoding() { - let bytes = [ + let invalid_bytes = [ 0x83, // array of length 3 0x41, 0x02, // element: [0x02] 0x42, 0x01, 0x01, // element: [0x01, 0x01] 0x41, 0x01, // element: [0x01] ]; - let mut decoder = Decoder::new(&bytes); + let mut decoder = Decoder::new(&invalid_bytes); + let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic); + assert!(result.is_err()); - let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic) - .expect("deterministic decoding should succeed"); - let result = Vec::from_iter(result); + // should not affact to other decoding ctx + let mut decoder = Decoder::new(&invalid_bytes); + let result = Array::decode(&mut decoder, &mut DecodeCtx::Deterministic); + assert!(result.is_ok()); - let expected: Vec> = - vec![vec![0x41, 0x01], vec![0x41, 0x02], vec![0x42, 0x01, 0x01]]; + let valid_bytes = [0x83, 0x41, 0x01, 0x41, 0x02, 0x42, 0x01, 0x01]; - assert_eq!(result, expected); + let mut decoder = Decoder::new(&valid_bytes); + let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic); + assert!(result.is_ok()); } } From b8744b5e31ef656b5d08417f74ce4b80b91c61c6 Mon Sep 17 00:00:00 2001 From: Apisit Ritruengroj Date: Fri, 28 Nov 2025 15:13:36 +0700 Subject: [PATCH 5/5] fix: cspell --- rust/cbork-utils/src/array.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/cbork-utils/src/array.rs b/rust/cbork-utils/src/array.rs index 485f52aef9..62d26a0b17 100644 --- a/rust/cbork-utils/src/array.rs +++ b/rust/cbork-utils/src/array.rs @@ -172,13 +172,13 @@ fn decode_array_elements( /// Returns Err(...) if any adjacent pair violates ordering. fn validate_array_ordering(elements: &[Vec]) -> Result<(), minicbor::decode::Error> { for pair in elements.windows(2) { - let [prev, curr] = pair else { + let [prev, current] = pair else { // fails if the array has 0-1 element return Ok(()); }; - let ord = match prev.len().cmp(&curr.len()) { - std::cmp::Ordering::Equal => prev.as_slice().cmp(curr.as_slice()), + let ord = match prev.len().cmp(¤t.len()) { + std::cmp::Ordering::Equal => prev.as_slice().cmp(current.as_slice()), other => other, }; @@ -369,7 +369,7 @@ mod tests { let result = Array::decode(&mut decoder, &mut DecodeCtx::ArrayDeterministic); assert!(result.is_err()); - // should not affact to other decoding ctx + // should not affect to other decoding ctx let mut decoder = Decoder::new(&invalid_bytes); let result = Array::decode(&mut decoder, &mut DecodeCtx::Deterministic); assert!(result.is_ok());