From ca05ac26cb6f244d2eac91d86137eef1980d96d5 Mon Sep 17 00:00:00 2001 From: boxdot Date: Wed, 23 Sep 2026 10:44:46 +0200 Subject: [PATCH 1/2] fix: order extension name by RFC 8949 4.2.1 --- src/content_container.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/content_container.rs b/src/content_container.rs index cecc9ef..7d28962 100644 --- a/src/content_container.rs +++ b/src/content_container.rs @@ -6,7 +6,7 @@ use minicbor::{bytes::ByteVec, data::Type}; #[cfg(feature = "serde")] use serde::{de::DeserializeOwned, Serialize}; use sha2::{Digest, Sha256}; -use std::{collections::BTreeMap, convert::Infallible, fmt}; +use std::{cmp::Ordering, collections::BTreeMap, convert::Infallible, fmt}; use crate::{ cbor, @@ -229,12 +229,32 @@ impl MimiContent { } } -#[derive(PartialEq, Eq, Debug, Clone, PartialOrd, Ord)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum ExtensionName { Text(String), Number(i64), } +// Ordering per RFC 8949 4.2.1 +impl Ord for ExtensionName { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + use ExtensionName::*; + match (self, other) { + (Number(a), Number(b)) if *a >= 0 && *b >= 0 => a.cmp(b), + (Number(a), Number(b)) => a.cmp(b).reverse(), + (Text(a), Text(b)) => a.len().cmp(&b.len()).then(a.cmp(b)), + (Number(_), Text(_)) => Ordering::Less, + (Text(_), Number(_)) => Ordering::Greater, + } + } +} + +impl PartialOrd for ExtensionName { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + impl minicbor::Encode for ExtensionName { fn encode( &self, From 9b0e28f8b3aebe2a0eb30ff6461e514a53d3b239 Mon Sep 17 00:00:00 2001 From: boxdot Date: Wed, 23 Sep 2026 10:49:01 +0200 Subject: [PATCH 2/2] add test --- src/content_container.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/content_container.rs b/src/content_container.rs index 7d28962..5062dab 100644 --- a/src/content_container.rs +++ b/src/content_container.rs @@ -237,7 +237,7 @@ pub enum ExtensionName { // Ordering per RFC 8949 4.2.1 impl Ord for ExtensionName { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> Ordering { use ExtensionName::*; match (self, other) { (Number(a), Number(b)) if *a >= 0 && *b >= 0 => a.cmp(b), @@ -1624,6 +1624,39 @@ mod tests { buf } + /// RFC 8949 4.2.1 orders keys bytewise on their encodings. + #[test] + fn extension_name_order_is_bytewise_on_encoding() { + let names = [ + ExtensionName::Number(0), + ExtensionName::Number(1), + ExtensionName::Number(23), + ExtensionName::Number(24), + ExtensionName::Number(255), + ExtensionName::Number(256), + ExtensionName::Number(i64::MAX), + ExtensionName::Number(-1), + ExtensionName::Number(-2), + ExtensionName::Number(-24), + ExtensionName::Number(-25), + ExtensionName::Number(i64::MIN), + ExtensionName::Text("".to_owned()), + ExtensionName::Text("z".to_owned()), + ExtensionName::Text("aa".to_owned()), + ExtensionName::Text("a".repeat(23)), + ExtensionName::Text("a".repeat(24)), + ]; + + let mut by_ord = names.to_vec(); + by_ord.reverse(); + by_ord.sort(); + + let mut by_encoding = names.to_vec(); + by_encoding.sort_by_key(|name| encode_value(name)); + + assert_eq!(by_ord, by_encoding); + } + #[cfg(feature = "serde")] #[test] fn extensions_max_depth() {