From beac77be2574acbd047408f250cf1e41c61c62aa Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 12 May 2025 16:28:30 +0200 Subject: [PATCH 01/20] der: clarify draft der: more clarify --- der/Cargo.toml | 3 + der/src/asn1/any.rs | 12 +- der/src/writer.rs | 47 ++++ der/src/writer/clarify.rs | 303 ++++++++++++++++++++++ der/src/writer/clarify/commentwriter.rs | 48 ++++ der/src/writer/clarify/hexdisplaylines.rs | 22 ++ der/src/writer/slice.rs | 6 +- 7 files changed, 436 insertions(+), 5 deletions(-) create mode 100644 der/src/writer/clarify.rs create mode 100644 der/src/writer/clarify/commentwriter.rs create mode 100644 der/src/writer/clarify/hexdisplaylines.rs diff --git a/der/Cargo.toml b/der/Cargo.toml index ea66f1739..068c14cb1 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -26,12 +26,14 @@ pem-rfc7468 = { version = "1.0.0-rc.3", optional = true, features = ["alloc"] } time = { version = "0.3.4", optional = true, default-features = false } zeroize = { version = "1.8", optional = true, default-features = false } heapless = { version = "0.8", optional = true, default-features = false } +tynm = { version = "0.2", optional = true, default-features = false } [dev-dependencies] hex-literal = "1" proptest = "1" [features] +default = ["clarify"] alloc = ["zeroize?/alloc"] std = ["alloc"] @@ -41,6 +43,7 @@ derive = ["dep:der_derive"] oid = ["dep:const-oid"] pem = ["dep:pem-rfc7468", "alloc", "zeroize"] real = [] +clarify = ["std", "pem", "dep:tynm"] [package.metadata.docs.rs] all-features = true diff --git a/der/src/asn1/any.rs b/der/src/asn1/any.rs index d1bbb05e8..d99da2e9a 100644 --- a/der/src/asn1/any.rs +++ b/der/src/asn1/any.rs @@ -56,6 +56,13 @@ impl<'a> AnyRef<'a> { pub fn value(self) -> &'a [u8] { self.value.as_slice() } + /// Returns [`Tag`] and [`Length`] of self. + pub fn header(&self) -> Header { + Header { + tag: self.tag, + length: self.value.len(), + } + } /// Attempt to decode this [`AnyRef`] type into the inner value. pub fn decode_as(self) -> Result>::Error> @@ -66,10 +73,7 @@ impl<'a> AnyRef<'a> { return Err(self.tag.unexpected_error(None).to_error().into()); } - let header = Header { - tag: self.tag, - length: self.value.len(), - }; + let header = self.header(); let mut decoder = SliceReader::new(self.value())?; let result = T::decode_value(&mut decoder, header)?; diff --git a/der/src/writer.rs b/der/src/writer.rs index 164b215f7..173566afb 100644 --- a/der/src/writer.rs +++ b/der/src/writer.rs @@ -1,5 +1,7 @@ //! Writer trait. +#[cfg(feature = "clarify")] +pub(crate) mod clarify; #[cfg(feature = "pem")] pub(crate) mod pem; pub(crate) mod slice; @@ -9,6 +11,9 @@ use crate::Result; #[cfg(feature = "std")] use std::io; +#[cfg(feature = "clarify")] +use crate::Tag; + /// Writer trait which outputs encoded DER. pub trait Writer { /// Write the given DER-encoded bytes as output. @@ -18,6 +23,48 @@ pub trait Writer { fn write_byte(&mut self, byte: u8) -> Result<()> { self.write(&[byte]) } + + #[cfg(feature = "clarify")] + /// Should return true for clarify writers + fn is_clarify(&self) -> bool { + false + } + + #[cfg(feature = "clarify")] + /// Called when starting next TLV value + fn clarify_start_value_type(&mut self) { + // can be overrided + } + + #[cfg(feature = "clarify")] + /// Called when ending next TLV value + fn clarify_end_value_type(&mut self) { + // can be overrided + } + + #[cfg(feature = "clarify")] + /// Called when starting next TLV tag + fn clarify_start_tag(&mut self, _tag: &Tag) { + // can be overrided + } + + #[cfg(feature = "clarify")] + /// Called when ending next TLV tag + fn clarify_end_tag(&mut self, _tag: &Tag) { + // can be overrided + } + + #[cfg(feature = "clarify")] + /// Called when writing field with name + fn clarify_field_name(&mut self, _field_name: &str) { + // can be overrided + } + + #[cfg(feature = "clarify")] + // Called when writing choice, e.g. enum name: "DnsName" + fn clarify_choice(&mut self, _choice_name: &[u8]) { + // can be overrided + } } #[cfg(feature = "std")] diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs new file mode 100644 index 000000000..a776ec6f3 --- /dev/null +++ b/der/src/writer/clarify.rs @@ -0,0 +1,303 @@ +pub(crate) mod commentwriter; +pub(crate) mod hexdisplaylines; + +use crate::std::io::Write; +use crate::{Encode, SliceWriter}; +use crate::{EncodeValue, ErrorKind, Header, Length, Result, Tag, TagMode, TagNumber, Tagged}; +use commentwriter::{CommentWriter, JavaCommentWriter, XmlCommentWriter}; +use core::cell::RefCell; +use core::ops::DerefMut; +use hexdisplaylines::HexDisplayLines; +use std::borrow::Cow; +use std::string::String; +use std::{boxed::Box, rc::Rc, vec::Vec}; + +use super::Writer; + +static INDENT_STR: &str = + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + +/// [`Writer`] which encodes DER as hex with comments. +pub struct ClarifySliceWriter<'a> { + writer: SliceWriter<'a>, + + // Buffer into which debug HEX and comments are written + debug_ref: Rc>>, + + /// Used for debug indentation + depth: Vec, + + indent_enabled: bool, + comment_writer: Box, +} + +/// Returned by .finish() +pub struct FinishOutputs<'a> { + pub raw: Result<&'a [u8]>, + //pub debug_ref: Vec, +} + +impl<'a> ClarifySliceWriter<'a> { + /// Create a new encoder with the given byte slice as a backing buffer. + pub fn new(bytes: &'a mut [u8], debug_ref: Rc>>, comment_xml: bool) -> Self { + Self { + writer: SliceWriter::new(bytes), + debug_ref, + depth: Vec::new(), + indent_enabled: true, + comment_writer: if comment_xml { + Box::new(XmlCommentWriter::default()) + } else { + Box::new(JavaCommentWriter::default()) + }, + } + } + + // /// Encode a value which impls the [`Encode`] trait. + // pub fn encode(&mut self, encodable: &T) -> Result<()> { + // self.writer.encode(encodable) + // } + + // /// Return an error with the given [`ErrorKind`], annotating it with + // /// context about where the error occurred. + // pub fn error(&mut self, kind: ErrorKind) -> Result { + // self.writer.error(kind) + // } + + // /// Did the decoding operation fail due to an error? + // pub fn is_failed(&self) -> bool { + // self.writer.is_failed() + // } + + // /// Finish encoding to the buffer, returning a slice containing the data + // /// written to the buffer. + // pub fn finish_internal(&self) -> Result<&'a [u8]> { + // self.writer.finish() + // } + + /// Finish encoding to the buffer, returning a slice containing the data + /// written to the buffer. + pub fn finish(self) -> FinishOutputs<'a> { + FinishOutputs { + raw: self.writer.finish(), + //debug_buf: self.debug.expect("debug buf not taken"), + } + } + + // /// Encode a `CONTEXT-SPECIFIC` field with the provided tag number and mode. + // pub fn context_specific( + // &mut self, + // tag_number: TagNumber, + // tag_mode: TagMode, + // value: &T, + // ) -> Result<()> + // where + // T: EncodeValue + Tagged, + // { + // self.writer.context_specific(tag_number, tag_mode, value) + // } + + // /// Encode an ASN.1 `SEQUENCE` of the given length. + // /// + // /// Spawns a nested slice writer which is expected to be exactly the + // /// specified length upon completion. + // pub fn sequence(&mut self, length: Length, f: F) -> Result<()> + // where + // F: FnOnce(&mut DebugSliceWriter<'_>) -> Result<()>, + // { + // Header::new(Tag::Sequence, length).and_then(|header| header.encode(self))?; + + // let debug_ref = self.debug_ref.clone(); + // let mut nested_encoder = DebugSliceWriter::new(self.reserve(length)?, debug_ref, true); + // f(&mut nested_encoder)?; + + // let nresult: FinishOutputs<'_> = nested_encoder.finish(); + // if nresult.raw?.len() == usize::try_from(length)? { + // Ok(()) + // } else { + // self.error(ErrorKind::Length { tag: Tag::Sequence }) + // } + // } + /// Returns indentation, for example "\n\t" for depth == 1 + pub fn indent_str(&self) -> &'static str { + let ilen = self.depth.len() * 1; + let ilen = ilen.min(INDENT_STR.len()); + &INDENT_STR[..ilen] + } + + /// Writes indentation to debug output, for example "\n\t" for depth == 1 + pub fn write_clarify_indent(&mut self) { + let indent = self.indent_str(); + let mut debugbuf = self.debug_ref.borrow_mut(); + { + self.comment_writer + .before_new_line(&mut debugbuf.deref_mut()); + write!(debugbuf, "\n{}", indent).unwrap(); + } + } + + /// Writes hex bytes to debug output, for example "30 04 " + pub fn write_clarify_hex(&mut self, slice: &[u8]) { + let indent = self.indent_str(); + let mut debugbuf = self.debug_ref.borrow_mut(); + { + write!(debugbuf, "{}", HexDisplayLines(&slice, indent)).unwrap(); + } + } + + /// Writes string to debug output, for example a comment "// SEQUENCE" + pub fn write_clarify_str(&mut self, s: &str) { + let mut debugbuf = self.debug_ref.borrow_mut(); + { + write!(debugbuf, "{}", s).unwrap(); + } + } + /// Writes string to debug output, for example a comment: `// SEQUENCE: name` + pub fn write_clarify_type_str(&mut self, start_end: &str, type_name: &str) { + //let mut debugbuf = self.debug_ref.borrow_mut(); + + let comment = format!("{}: {} ", start_end, type_name); + self.comment_writer.comment(&comment); + } + + /// Writes string to debug output, for example a comment: `// "abc"` + pub fn write_clarify_value_quote(&mut self, type_name: &str, value: &[u8]) { + //let mut debugbuf = self.debug_ref.borrow_mut(); + + let contains_control = value.iter().any(|&c| c == 0x7F || (c < 0x20 && c != b'\n')); + if value.len() > 2 && !contains_control { + let type_name = strip_transparent_types(type_name); + let comment = format!("{} {:?} ", type_name, String::from_utf8_lossy(value)); + self.comment_writer.comment(&comment); + } + } + + /// Writes int to debug output, for example a comment: `// integer: 16dec` + pub fn write_clarify_int(&mut self, value: i64) { + if value >= 10 || value < 0 { + let comment = format!("integer: {value}dec "); + self.comment_writer.comment(&comment); + } + } + + /// Reserve a portion of the internal buffer, updating the internal cursor + /// position and returning a mutable slice. + fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { + self.writer.reserve(len) + } + + fn clarify_start_value_type_str(&mut self, type_name: &str) { + self.indent_enabled = true; + self.depth.push(u32::from(self.writer.position())); + + let type_name = strip_transparent_types(type_name); + self.write_clarify_type_str("type", &type_name); + } + + fn clarify_end_value_type_str(&mut self, type_name: &str) { + let current = u32::from(self.writer.position()); + let last_pos = self.depth.pop().unwrap_or(current); + let diff = current - last_pos; + + if diff > 16 { + let type_name = strip_transparent_types(type_name); + self.write_clarify_indent(); + self.write_clarify_type_str("end", type_name.as_ref()); + } + } +} + +impl<'a> Writer for ClarifySliceWriter<'a> { + fn write(&mut self, slice: &[u8]) -> Result<()> { + self.reserve(slice.len())?.copy_from_slice(slice); + + if self.indent_enabled { + self.write_clarify_indent(); + } + + self.write_clarify_hex(slice); + Ok(()) + } + + fn clarify_start_value_type(&mut self) { + self.clarify_start_value_type_str(&tynm::type_name::()); + } + fn clarify_end_value_type(&mut self) { + self.clarify_end_value_type_str(&tynm::type_name::()); + } + + /// for better tag-length pretty-printing inline + fn clarify_end_tag(&mut self, _tag: &Tag) { + // just to print a single length byte without indent + self.indent_enabled = false; + } + + // fn debug_set_indent_enabled(&mut self, enabled: bool) { + // if !enabled { + // // Write tabs before we switch to in-line mode + // self.write_debug_indent(); + // } + // self.indent_enabled = enabled; + // } + + fn clarify_field_name(&mut self, field_name: &str) { + self.write_clarify_indent(); + self.write_clarify_type_str("field", field_name); + } + + // fn clarify_end_length(&mut self, tag: Option<&Tag>, length: Length) { + // self.indent_enabled = true; + // if let Some(tag) = tag { + // self.write_debug_type_str("tag", &format!("{}", tag)); + // } + // if u32::from(length) >= 10 { + // self.write_debug_type_str("len", &format!("{}", length)); + // } + // } + + // fn clarify_value_quote(&mut self, _type_name: &str, tag_name: &[u8]) { + // //self.write_debug_value_quote(type_name, tag_name); + // self.write_debug_value_quote("", tag_name); + // } + + // fn debug_int(&mut self, value: i64) { + // self.write_debug_int(value); + // } + + fn clarify_choice(&mut self, choice_name: &[u8]) { + self.write_clarify_indent(); + if let Ok(choice_name) = std::str::from_utf8(choice_name) { + self.write_clarify_type_str("CHOICE", choice_name); + } + } + + fn is_clarify(&self) -> bool { + true + } +} + +fn strip_transparent_types(type_name: &str) -> Cow<'_, str> { + // EncodeValueRef is commonly used and it is completely transparent + let type_name = if let Some(stripped) = type_name.strip_prefix("EncodeValueRef<") { + let stripped = stripped.strip_suffix(">").unwrap_or(stripped); + stripped + } else { + type_name + }; + + let type_name = if let Some(stripped) = type_name.strip_prefix("ApplicationRef<") { + let stripped = stripped.strip_suffix(">").unwrap_or(stripped); + stripped + } else { + type_name + }; + + let type_name = if let Some(stripped) = type_name.strip_prefix("ContextSpecificRef<") { + let stripped = stripped.strip_suffix(">").unwrap_or(stripped); + stripped + } else { + type_name + }; + + Cow::Borrowed(type_name) +} diff --git a/der/src/writer/clarify/commentwriter.rs b/der/src/writer/clarify/commentwriter.rs new file mode 100644 index 000000000..a873762a7 --- /dev/null +++ b/der/src/writer/clarify/commentwriter.rs @@ -0,0 +1,48 @@ +use std::{io::Write, vec::Vec}; + +pub trait CommentWriter { + fn comment(&mut self, s: &str); + + fn before_new_line(&mut self, w: &mut dyn Write); +} + +#[derive(Default)] +pub struct JavaCommentWriter { + pub buf: Vec, +} + +impl CommentWriter for JavaCommentWriter { + fn comment(&mut self, s: &str) { + self.buf.extend_from_slice(s.as_bytes()); + } + + fn before_new_line(&mut self, w: &mut dyn Write) { + if self.buf.len() == 0 { + return; + } + let _ = w.write_all(b"// "); + let _ = w.write_all(&self.buf); + self.buf.clear(); + } +} + +#[derive(Default)] +pub struct XmlCommentWriter { + pub buf: Vec, +} + +impl CommentWriter for XmlCommentWriter { + fn comment(&mut self, s: &str) { + self.buf.extend_from_slice(s.as_bytes()); + } + + fn before_new_line(&mut self, w: &mut dyn Write) { + if self.buf.len() == 0 { + return; + } + let _ = w.write_all(b""); + self.buf.clear(); + } +} diff --git a/der/src/writer/clarify/hexdisplaylines.rs b/der/src/writer/clarify/hexdisplaylines.rs new file mode 100644 index 000000000..b055d86e9 --- /dev/null +++ b/der/src/writer/clarify/hexdisplaylines.rs @@ -0,0 +1,22 @@ +use std::fmt; + +/// (bytes, indent) +pub struct HexDisplayLines<'a, 'i>(pub &'a [u8], pub &'i str); + +impl fmt::Display for HexDisplayLines<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut first = true; + for chunk in self.0.chunks(16) { + if !first { + write!(f, "\n{}", self.1)?; + } else { + first = false; + } + + for byte in chunk { + write!(f, "{:02X} ", byte)?; + } + } + Ok(()) + } +} diff --git a/der/src/writer/slice.rs b/der/src/writer/slice.rs index 43dfcf1bb..8ae2eee45 100644 --- a/der/src/writer/slice.rs +++ b/der/src/writer/slice.rs @@ -106,7 +106,7 @@ impl<'a> SliceWriter<'a> { /// Reserve a portion of the internal buffer, updating the internal cursor /// position and returning a mutable slice. - fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { + pub(crate) fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { if self.is_failed() { return Err(ErrorKind::Failed.at(self.position)); } @@ -124,6 +124,10 @@ impl<'a> SliceWriter<'a> { self.position = end; Ok(slice) } + + pub(crate) fn position(&self) -> Length { + self.position + } } impl Writer for SliceWriter<'_> { From 631458b4f76af27105a19e2085dfd73ff91830a1 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:37:39 +0200 Subject: [PATCH 02/20] der: clean warnings --- der/src/writer/clarify.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index a776ec6f3..139599d17 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -1,9 +1,9 @@ pub(crate) mod commentwriter; pub(crate) mod hexdisplaylines; +use crate::SliceWriter; use crate::std::io::Write; -use crate::{Encode, SliceWriter}; -use crate::{EncodeValue, ErrorKind, Header, Length, Result, Tag, TagMode, TagNumber, Tagged}; +use crate::{Length, Result, Tag}; use commentwriter::{CommentWriter, JavaCommentWriter, XmlCommentWriter}; use core::cell::RefCell; use core::ops::DerefMut; From d34d3f94c41a48a92722f40827b9db735ef12180 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:42:43 +0200 Subject: [PATCH 03/20] der: clarify feature in Cargo.toml --- der/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/der/Cargo.toml b/der/Cargo.toml index 068c14cb1..58744f362 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -33,7 +33,7 @@ hex-literal = "1" proptest = "1" [features] -default = ["clarify"] +default = [] alloc = ["zeroize?/alloc"] std = ["alloc"] @@ -43,7 +43,7 @@ derive = ["dep:der_derive"] oid = ["dep:const-oid"] pem = ["dep:pem-rfc7468", "alloc", "zeroize"] real = [] -clarify = ["std", "pem", "dep:tynm"] +clarify = ["std", "pem", "dep:tynm", "derive"] [package.metadata.docs.rs] all-features = true From 6a5e9b9575c259d7bcd0f76cd0a53a65969db078 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:46:20 +0200 Subject: [PATCH 04/20] der: add fn SliceWriter.position --- der/src/writer/slice.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/der/src/writer/slice.rs b/der/src/writer/slice.rs index 8ae2eee45..acf6d8104 100644 --- a/der/src/writer/slice.rs +++ b/der/src/writer/slice.rs @@ -125,7 +125,8 @@ impl<'a> SliceWriter<'a> { Ok(slice) } - pub(crate) fn position(&self) -> Length { + /// Returns current position in the buffer. + pub fn position(&self) -> Length { self.position } } From 4c4796595c045521f04b115aafd77ae38b16a0d2 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:57:41 +0200 Subject: [PATCH 05/20] der: simplify Writer trait --- der/Cargo.toml | 2 +- der/src/writer.rs | 47 ++------------ der/src/writer/clarify.rs | 125 ++++++++++++++++++++++++-------------- 3 files changed, 84 insertions(+), 90 deletions(-) diff --git a/der/Cargo.toml b/der/Cargo.toml index 58744f362..57d949a95 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -33,7 +33,7 @@ hex-literal = "1" proptest = "1" [features] -default = [] +default = ["clarify"] alloc = ["zeroize?/alloc"] std = ["alloc"] diff --git a/der/src/writer.rs b/der/src/writer.rs index 173566afb..e59bb4ec4 100644 --- a/der/src/writer.rs +++ b/der/src/writer.rs @@ -1,7 +1,7 @@ //! Writer trait. #[cfg(feature = "clarify")] -pub(crate) mod clarify; +pub mod clarify; #[cfg(feature = "pem")] pub(crate) mod pem; pub(crate) mod slice; @@ -11,9 +11,6 @@ use crate::Result; #[cfg(feature = "std")] use std::io; -#[cfg(feature = "clarify")] -use crate::Tag; - /// Writer trait which outputs encoded DER. pub trait Writer { /// Write the given DER-encoded bytes as output. @@ -25,45 +22,9 @@ pub trait Writer { } #[cfg(feature = "clarify")] - /// Should return true for clarify writers - fn is_clarify(&self) -> bool { - false - } - - #[cfg(feature = "clarify")] - /// Called when starting next TLV value - fn clarify_start_value_type(&mut self) { - // can be overrided - } - - #[cfg(feature = "clarify")] - /// Called when ending next TLV value - fn clarify_end_value_type(&mut self) { - // can be overrided - } - - #[cfg(feature = "clarify")] - /// Called when starting next TLV tag - fn clarify_start_tag(&mut self, _tag: &Tag) { - // can be overrided - } - - #[cfg(feature = "clarify")] - /// Called when ending next TLV tag - fn clarify_end_tag(&mut self, _tag: &Tag) { - // can be overrided - } - - #[cfg(feature = "clarify")] - /// Called when writing field with name - fn clarify_field_name(&mut self, _field_name: &str) { - // can be overrided - } - - #[cfg(feature = "clarify")] - // Called when writing choice, e.g. enum name: "DnsName" - fn clarify_choice(&mut self, _choice_name: &[u8]) { - // can be overrided + /// Should return Some(clarifier) for clarify writers + fn clarifier(&mut self) -> Option<&mut clarify::Clarifier> { + None } } diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index 139599d17..715aa7f7c 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -21,11 +21,21 @@ static INDENT_STR: &str = pub struct ClarifySliceWriter<'a> { writer: SliceWriter<'a>, + clarifier: Clarifier, +} + +/// Clarifier that creates HEX with comments +pub struct Clarifier { // Buffer into which debug HEX and comments are written debug_ref: Rc>>, + // Position in the buffer is used to track how long is the current sub-message + last_position: u32, + /// Used for debug indentation - depth: Vec, + /// + /// Pushes writer positions on the stack + depth: Vec>, indent_enabled: bool, comment_writer: Box, @@ -37,13 +47,14 @@ pub struct FinishOutputs<'a> { //pub debug_ref: Vec, } -impl<'a> ClarifySliceWriter<'a> { - /// Create a new encoder with the given byte slice as a backing buffer. - pub fn new(bytes: &'a mut [u8], debug_ref: Rc>>, comment_xml: bool) -> Self { +impl Clarifier { + pub fn new(debug_ref: Rc>>, comment_xml: bool) -> Self { Self { - writer: SliceWriter::new(bytes), debug_ref, + + last_position: 0, depth: Vec::new(), + indent_enabled: true, comment_writer: if comment_xml { Box::new(XmlCommentWriter::default()) @@ -52,6 +63,16 @@ impl<'a> ClarifySliceWriter<'a> { }, } } +} + +impl<'a> ClarifySliceWriter<'a> { + /// Create a new encoder with the given byte slice as a backing buffer. + pub fn new(bytes: &'a mut [u8], debug_ref: Rc>>, comment_xml: bool) -> Self { + Self { + writer: SliceWriter::new(bytes), + clarifier: Clarifier::new(debug_ref, comment_xml), + } + } // /// Encode a value which impls the [`Encode`] trait. // pub fn encode(&mut self, encodable: &T) -> Result<()> { @@ -118,6 +139,15 @@ impl<'a> ClarifySliceWriter<'a> { // self.error(ErrorKind::Length { tag: Tag::Sequence }) // } // } + + /// Reserve a portion of the internal buffer, updating the internal cursor + /// position and returning a mutable slice. + fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { + self.writer.reserve(len) + } +} + +impl Clarifier { /// Returns indentation, for example "\n\t" for depth == 1 pub fn indent_str(&self) -> &'static str { let ilen = self.depth.len() * 1; @@ -125,6 +155,12 @@ impl<'a> ClarifySliceWriter<'a> { &INDENT_STR[..ilen] } + pub fn write_clarify_indent_if_enabled(&mut self) { + if self.indent_enabled { + self.write_clarify_indent(); + } + } + /// Writes indentation to debug output, for example "\n\t" for depth == 1 pub fn write_clarify_indent(&mut self) { let indent = self.indent_str(); @@ -180,54 +216,36 @@ impl<'a> ClarifySliceWriter<'a> { } } - /// Reserve a portion of the internal buffer, updating the internal cursor - /// position and returning a mutable slice. - fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { - self.writer.reserve(len) - } - - fn clarify_start_value_type_str(&mut self, type_name: &str) { + /// input: u32::from(self.writer.position()) + pub fn clarify_start_value_type_str(&mut self, writer_pos: Option, type_name: &str) { self.indent_enabled = true; - self.depth.push(u32::from(self.writer.position())); + self.depth.push(writer_pos); let type_name = strip_transparent_types(type_name); self.write_clarify_type_str("type", &type_name); } - fn clarify_end_value_type_str(&mut self, type_name: &str) { - let current = u32::from(self.writer.position()); - let last_pos = self.depth.pop().unwrap_or(current); - let diff = current - last_pos; - - if diff > 16 { - let type_name = strip_transparent_types(type_name); - self.write_clarify_indent(); - self.write_clarify_type_str("end", type_name.as_ref()); - } - } -} - -impl<'a> Writer for ClarifySliceWriter<'a> { - fn write(&mut self, slice: &[u8]) -> Result<()> { - self.reserve(slice.len())?.copy_from_slice(slice); - - if self.indent_enabled { - self.write_clarify_indent(); + fn clarify_end_value_type_str(&mut self, writer_pos: Option, type_name: &str) { + let last_pos = self.depth.pop().unwrap_or(writer_pos); + + match (writer_pos, last_pos) { + (Some(writer_pos), Some(last_pos)) => { + let diff = writer_pos - last_pos; + if diff < 16 { + // ignore short runs + return; + } + } + _ => {} } - self.write_clarify_hex(slice); - Ok(()) - } - - fn clarify_start_value_type(&mut self) { - self.clarify_start_value_type_str(&tynm::type_name::()); - } - fn clarify_end_value_type(&mut self) { - self.clarify_end_value_type_str(&tynm::type_name::()); + let type_name = strip_transparent_types(type_name); + self.write_clarify_indent(); + self.write_clarify_type_str("end", type_name.as_ref()); } /// for better tag-length pretty-printing inline - fn clarify_end_tag(&mut self, _tag: &Tag) { + pub fn clarify_end_tag(&mut self, _tag: &Tag) { // just to print a single length byte without indent self.indent_enabled = false; } @@ -240,11 +258,18 @@ impl<'a> Writer for ClarifySliceWriter<'a> { // self.indent_enabled = enabled; // } - fn clarify_field_name(&mut self, field_name: &str) { + pub fn clarify_field_name(&mut self, field_name: &str) { self.write_clarify_indent(); self.write_clarify_type_str("field", field_name); } + pub fn clarify_start_value_type(&mut self) { + self.clarify_start_value_type_str(Some(self.last_position), &tynm::type_name::()); + } + pub fn clarify_end_value_type(&mut self) { + self.clarify_end_value_type_str(Some(self.last_position), &tynm::type_name::()); + } + // fn clarify_end_length(&mut self, tag: Option<&Tag>, length: Length) { // self.indent_enabled = true; // if let Some(tag) = tag { @@ -264,15 +289,23 @@ impl<'a> Writer for ClarifySliceWriter<'a> { // self.write_debug_int(value); // } - fn clarify_choice(&mut self, choice_name: &[u8]) { + pub fn clarify_choice(&mut self, choice_name: &[u8]) { self.write_clarify_indent(); if let Ok(choice_name) = std::str::from_utf8(choice_name) { self.write_clarify_type_str("CHOICE", choice_name); } } +} + +impl<'a> Writer for ClarifySliceWriter<'a> { + fn write(&mut self, slice: &[u8]) -> Result<()> { + self.reserve(slice.len())?.copy_from_slice(slice); + self.clarifier.last_position += slice.len() as u32; - fn is_clarify(&self) -> bool { - true + self.clarifier.write_clarify_indent_if_enabled(); + self.clarifier.write_clarify_hex(slice); + + Ok(()) } } From 314de0c31cd5e8005983024ceb17075ffd61e0e7 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:33:21 +0200 Subject: [PATCH 06/20] der: more progress on clarify writer --- der/src/lib.rs | 3 + der/src/writer/clarify.rs | 173 +++++++++++++++++++++++++++----------- 2 files changed, 128 insertions(+), 48 deletions(-) diff --git a/der/src/lib.rs b/der/src/lib.rs index 22842078d..9ca110db5 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -399,6 +399,9 @@ pub use { pem_rfc7468 as pem, }; +#[cfg(feature = "clarify")] +pub use writer::clarify::ClarifySliceWriter; + #[cfg(feature = "time")] pub use time; diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index 715aa7f7c..c3067753b 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -1,19 +1,53 @@ pub(crate) mod commentwriter; pub(crate) mod hexdisplaylines; -use crate::SliceWriter; use crate::std::io::Write; +use crate::{Encode, Error, SliceWriter}; use crate::{Length, Result, Tag}; use commentwriter::{CommentWriter, JavaCommentWriter, XmlCommentWriter}; -use core::cell::RefCell; -use core::ops::DerefMut; use hexdisplaylines::HexDisplayLines; use std::borrow::Cow; +use std::println; use std::string::String; -use std::{boxed::Box, rc::Rc, vec::Vec}; +use std::{boxed::Box, vec::Vec}; use super::Writer; +pub trait EncodeClarifyExt: Encode { + /// Encode this type as pretty-printed hex DER, with comments. + fn to_der_clarify(&self, flavor: ClarifyFlavor) -> Result { + let outputs = self.to_der_clarify_ignorant(flavor); + // Propagate encode and finish errors + outputs.raw?; + Ok(String::from_utf8(outputs.clarify_buf).expect("clarified output to be utf-8")) + } + + /// Encode this type as pretty-printed hex DER, with comments. + /// Ignores any errors that occur during [`Encode::encode`]. + fn to_der_clarify_ignorant(&self, flavor: ClarifyFlavor) -> ClarifyOutputs<'static> { + let len = match self.encoded_len() { + Ok(len) => len, + Err(err) => return ClarifyOutputs::from_err(err), + }; + + let mut buf = Vec::with_capacity(u32::from(len) as usize); + let mut writer = ClarifySliceWriter::new(&mut buf, Vec::new(), flavor); + let result = self.encode(&mut writer); + + let outputs = writer.finish(); + let outputs = ClarifyOutputs { + // prioritize Encode::encode errors + raw: result.and(outputs.raw), + // but use buffer from finish() (even if encode failed) + clarify_buf: outputs.clarify_buf, + }; + + outputs.into_owned() + } +} + +impl EncodeClarifyExt for T where T: Encode {} + static INDENT_STR: &str = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; @@ -27,7 +61,7 @@ pub struct ClarifySliceWriter<'a> { /// Clarifier that creates HEX with comments pub struct Clarifier { // Buffer into which debug HEX and comments are written - debug_ref: Rc>>, + clarify_buf: Vec, // Position in the buffer is used to track how long is the current sub-message last_position: u32, @@ -42,24 +76,54 @@ pub struct Clarifier { } /// Returned by .finish() -pub struct FinishOutputs<'a> { - pub raw: Result<&'a [u8]>, - //pub debug_ref: Vec, +pub struct ClarifyOutputs<'a> { + pub raw: Result>, + pub clarify_buf: Vec, +} + +impl<'a> ClarifyOutputs<'a> { + pub fn from_err(err: Error) -> ClarifyOutputs<'static> { + ClarifyOutputs { + raw: Err(err), + clarify_buf: Vec::new(), + } + } + + pub fn into_owned(self) -> ClarifyOutputs<'static> { + ClarifyOutputs { + raw: self.raw.map(|raw| Cow::Owned(raw.into_owned())), + clarify_buf: self.clarify_buf, + } + } + // pub fn and(result: Result>) { + // ClarifyOutputs { + // // prioritize Encode::encode errors + // raw: result.and(outputs.raw), + // clarify_buf: outputs.clarify_buf, + // }; + // } +} + +#[derive(Copy, Clone, Debug)] +pub enum ClarifyFlavor { + XmlComments, + JavaComments, + RustHex, } impl Clarifier { - pub fn new(debug_ref: Rc>>, comment_xml: bool) -> Self { + pub fn new(clarify_buf: Vec, flavor: ClarifyFlavor) -> Self { Self { - debug_ref, + clarify_buf, last_position: 0, depth: Vec::new(), indent_enabled: true, - comment_writer: if comment_xml { - Box::new(XmlCommentWriter::default()) - } else { - Box::new(JavaCommentWriter::default()) + comment_writer: match flavor { + ClarifyFlavor::XmlComments => Box::new(XmlCommentWriter::default()), + ClarifyFlavor::JavaComments => Box::new(JavaCommentWriter::default()), + ClarifyFlavor::RustHex => todo!(), }, } } @@ -67,10 +131,10 @@ impl Clarifier { impl<'a> ClarifySliceWriter<'a> { /// Create a new encoder with the given byte slice as a backing buffer. - pub fn new(bytes: &'a mut [u8], debug_ref: Rc>>, comment_xml: bool) -> Self { + pub fn new(bytes: &'a mut [u8], clarify_buf: Vec, flavor: ClarifyFlavor) -> Self { Self { writer: SliceWriter::new(bytes), - clarifier: Clarifier::new(debug_ref, comment_xml), + clarifier: Clarifier::new(clarify_buf, flavor), } } @@ -98,10 +162,10 @@ impl<'a> ClarifySliceWriter<'a> { /// Finish encoding to the buffer, returning a slice containing the data /// written to the buffer. - pub fn finish(self) -> FinishOutputs<'a> { - FinishOutputs { - raw: self.writer.finish(), - //debug_buf: self.debug.expect("debug buf not taken"), + pub fn finish(self) -> ClarifyOutputs<'a> { + ClarifyOutputs { + raw: self.writer.finish().map(|raw| Cow::Borrowed(raw)), + clarify_buf: self.clarifier.clarify_buf, } } @@ -164,28 +228,24 @@ impl Clarifier { /// Writes indentation to debug output, for example "\n\t" for depth == 1 pub fn write_clarify_indent(&mut self) { let indent = self.indent_str(); - let mut debugbuf = self.debug_ref.borrow_mut(); { - self.comment_writer - .before_new_line(&mut debugbuf.deref_mut()); - write!(debugbuf, "\n{}", indent).unwrap(); + self.comment_writer.before_new_line(&mut self.clarify_buf); + write!(&mut self.clarify_buf, "\n{}", indent).unwrap(); } } /// Writes hex bytes to debug output, for example "30 04 " pub fn write_clarify_hex(&mut self, slice: &[u8]) { let indent = self.indent_str(); - let mut debugbuf = self.debug_ref.borrow_mut(); { - write!(debugbuf, "{}", HexDisplayLines(&slice, indent)).unwrap(); + write!(&mut self.clarify_buf, "{}", HexDisplayLines(&slice, indent)).unwrap(); } } /// Writes string to debug output, for example a comment "// SEQUENCE" pub fn write_clarify_str(&mut self, s: &str) { - let mut debugbuf = self.debug_ref.borrow_mut(); { - write!(debugbuf, "{}", s).unwrap(); + write!(&mut self.clarify_buf, "{}", s).unwrap(); } } /// Writes string to debug output, for example a comment: `// SEQUENCE: name` @@ -299,6 +359,7 @@ impl Clarifier { impl<'a> Writer for ClarifySliceWriter<'a> { fn write(&mut self, slice: &[u8]) -> Result<()> { + println!("writing {slice:?}"); self.reserve(slice.len())?.copy_from_slice(slice); self.clarifier.last_position += slice.len() as u32; @@ -309,28 +370,44 @@ impl<'a> Writer for ClarifySliceWriter<'a> { } } -fn strip_transparent_types(type_name: &str) -> Cow<'_, str> { - // EncodeValueRef is commonly used and it is completely transparent - let type_name = if let Some(stripped) = type_name.strip_prefix("EncodeValueRef<") { - let stripped = stripped.strip_suffix(">").unwrap_or(stripped); - stripped - } else { - type_name - }; +/// Strips wrappers, such as `EncodeValueRef`, which is commonly used and is completely transparent +fn strip_transparent_types(mut type_name: &str) -> Cow<'_, str> { + let prefixes = [ + "EncodeValueRef<", + "ApplicationRef<", + "ContextSpecificRef<", + "PrivateRef<", + ]; + + for prefix in prefixes { + type_name = if let Some(stripped) = type_name.strip_prefix(prefix) { + let stripped = stripped.strip_suffix(">").unwrap_or(stripped); + stripped + } else { + type_name + }; + } - let type_name = if let Some(stripped) = type_name.strip_prefix("ApplicationRef<") { - let stripped = stripped.strip_suffix(">").unwrap_or(stripped); - stripped - } else { - type_name - }; + Cow::Borrowed(type_name) +} + +#[cfg(test)] +pub mod test { + use std::{println, vec::Vec}; - let type_name = if let Some(stripped) = type_name.strip_prefix("ContextSpecificRef<") { - let stripped = stripped.strip_suffix(">").unwrap_or(stripped); - stripped - } else { - type_name + use crate::{ + asn1::OctetString, + writer::clarify::{ClarifyFlavor, EncodeClarifyExt}, }; - Cow::Borrowed(type_name) + #[test] + fn clarify_simple_octetstring() { + let obj = OctetString::new(&[0xAA, 0xBB, 0xCC]).unwrap(); + + let clarified = obj + .to_der_clarify(ClarifyFlavor::XmlComments) + .expect("encoded DER"); + + println!("clarified: {clarified}"); + } } From bd6fcd920b39e1a9997409c280baeadc7dd537c4 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:58:38 +0200 Subject: [PATCH 07/20] der: clarify works for OctetString --- der/Cargo.toml | 2 +- der/src/encode.rs | 19 +++- der/src/header.rs | 19 +++- der/src/lib.rs | 2 +- der/src/writer/clarify.rs | 127 +++++++++++++--------- der/src/writer/clarify/commentwriter.rs | 35 +++++- der/src/writer/clarify/hexdisplaylines.rs | 1 - der/tests/clarify.rs | 93 ++++++++++++++++ 8 files changed, 239 insertions(+), 59 deletions(-) create mode 100644 der/tests/clarify.rs diff --git a/der/Cargo.toml b/der/Cargo.toml index 57d949a95..43c1c3dfe 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -43,7 +43,7 @@ derive = ["dep:der_derive"] oid = ["dep:const-oid"] pem = ["dep:pem-rfc7468", "alloc", "zeroize"] real = [] -clarify = ["std", "pem", "dep:tynm", "derive"] +clarify = ["std", "pem", "dep:tynm", "derive", "oid"] [package.metadata.docs.rs] all-features = true diff --git a/der/src/encode.rs b/der/src/encode.rs index 578f7c9c3..0331f0856 100644 --- a/der/src/encode.rs +++ b/der/src/encode.rs @@ -81,7 +81,24 @@ where /// Encode this value as ASN.1 DER using the provided [`Writer`]. fn encode(&self, writer: &mut impl Writer) -> Result<()> { self.header()?.encode(writer)?; - self.encode_value(writer) + clarify_start_value_type::(writer); + let result = self.encode_value(writer); + clarify_end_value_type::(writer); + result + } +} + +fn clarify_start_value_type(writer: &mut impl Writer) { + #[cfg(feature = "clarify")] + if let Some(clarifier) = writer.clarifier() { + clarifier.clarify_start_value_type::(); + } +} + +fn clarify_end_value_type(writer: &mut impl Writer) { + #[cfg(feature = "clarify")] + if let Some(clarifier) = writer.clarifier() { + clarifier.clarify_end_value_type::(); } } diff --git a/der/src/header.rs b/der/src/header.rs index 58cbe40a7..235c86b4d 100644 --- a/der/src/header.rs +++ b/der/src/header.rs @@ -54,8 +54,25 @@ impl Encode for Header { } fn encode(&self, writer: &mut impl Writer) -> Result<()> { + clarify_start_tag(writer, &self.tag); self.tag.encode(writer)?; - self.length.encode(writer) + let result = self.length.encode(writer); + clarify_end_length(writer, &self.tag, self.length); + result + } +} + +fn clarify_start_tag(writer: &mut impl Writer, tag: &Tag) { + #[cfg(feature = "clarify")] + if let Some(clarifier) = writer.clarifier() { + clarifier.clarify_header_start_tag(&tag); + } +} + +fn clarify_end_length(writer: &mut impl Writer, tag: &Tag, length: Length) { + #[cfg(feature = "clarify")] + if let Some(clarifier) = writer.clarifier() { + clarifier.clarify_header_end_length(Some(&tag), length); } } diff --git a/der/src/lib.rs b/der/src/lib.rs index 9ca110db5..76ebfd9a7 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -400,7 +400,7 @@ pub use { }; #[cfg(feature = "clarify")] -pub use writer::clarify::ClarifySliceWriter; +pub use writer::clarify::{Clarifier, ClarifyFlavor, ClarifySliceWriter, EncodeClarifyExt}; #[cfg(feature = "time")] pub use time; diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index c3067753b..628aa9a4b 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -2,21 +2,22 @@ pub(crate) mod commentwriter; pub(crate) mod hexdisplaylines; use crate::std::io::Write; +use crate::writer::clarify::commentwriter::RustHexWriter; use crate::{Encode, Error, SliceWriter}; use crate::{Length, Result, Tag}; use commentwriter::{CommentWriter, JavaCommentWriter, XmlCommentWriter}; use hexdisplaylines::HexDisplayLines; use std::borrow::Cow; -use std::println; use std::string::String; use std::{boxed::Box, vec::Vec}; use super::Writer; +/// Extension trait, auto-implemented on [`Encode`] pub trait EncodeClarifyExt: Encode { /// Encode this type as pretty-printed hex DER, with comments. fn to_der_clarify(&self, flavor: ClarifyFlavor) -> Result { - let outputs = self.to_der_clarify_ignorant(flavor); + let outputs = self.to_der_clarify_err_ignorant(flavor); // Propagate encode and finish errors outputs.raw?; Ok(String::from_utf8(outputs.clarify_buf).expect("clarified output to be utf-8")) @@ -24,13 +25,15 @@ pub trait EncodeClarifyExt: Encode { /// Encode this type as pretty-printed hex DER, with comments. /// Ignores any errors that occur during [`Encode::encode`]. - fn to_der_clarify_ignorant(&self, flavor: ClarifyFlavor) -> ClarifyOutputs<'static> { + fn to_der_clarify_err_ignorant(&self, flavor: ClarifyFlavor) -> ClarifyOutputs<'static> { let len = match self.encoded_len() { Ok(len) => len, Err(err) => return ClarifyOutputs::from_err(err), }; - let mut buf = Vec::with_capacity(u32::from(len) as usize); + let mut buf: Vec = Vec::new(); + buf.resize(u32::from(len) as usize, 0u8); + let mut writer = ClarifySliceWriter::new(&mut buf, Vec::new(), flavor); let result = self.encode(&mut writer); @@ -104,14 +107,19 @@ impl<'a> ClarifyOutputs<'a> { // } } +/// Determines how comments will look like #[derive(Copy, Clone, Debug)] pub enum ClarifyFlavor { + /// `01 02 ` XmlComments, + /// `01 02 // comment` JavaComments, + /// `"01 02" // comment` RustHex, } impl Clarifier { + /// Creates new Clarifier with buffer, that accumulates comments and hex bytes. pub fn new(clarify_buf: Vec, flavor: ClarifyFlavor) -> Self { Self { clarify_buf, @@ -123,7 +131,7 @@ impl Clarifier { comment_writer: match flavor { ClarifyFlavor::XmlComments => Box::new(XmlCommentWriter::default()), ClarifyFlavor::JavaComments => Box::new(JavaCommentWriter::default()), - ClarifyFlavor::RustHex => todo!(), + ClarifyFlavor::RustHex => Box::new(RustHexWriter::default()), }, } } @@ -162,7 +170,9 @@ impl<'a> ClarifySliceWriter<'a> { /// Finish encoding to the buffer, returning a slice containing the data /// written to the buffer. - pub fn finish(self) -> ClarifyOutputs<'a> { + pub fn finish(mut self) -> ClarifyOutputs<'a> { + self.clarifier.flush_line(); + ClarifyOutputs { raw: self.writer.finish().map(|raw| Cow::Borrowed(raw)), clarify_buf: self.clarifier.clarify_buf, @@ -219,34 +229,48 @@ impl Clarifier { &INDENT_STR[..ilen] } + /// Writes indent if it is currently enabled pub fn write_clarify_indent_if_enabled(&mut self) { if self.indent_enabled { self.write_clarify_indent(); } } + fn flush_line(&mut self) { + // if current line ends in space + if self + .clarify_buf + .last() + .map(|last| *last == b' ') + .unwrap_or_default() + { + // remove space + self.clarify_buf.pop(); + } + // write comment after hex + self.comment_writer.before_new_line(&mut self.clarify_buf); + } + /// Writes indentation to debug output, for example "\n\t" for depth == 1 pub fn write_clarify_indent(&mut self) { + self.flush_line(); + let indent = self.indent_str(); - { - self.comment_writer.before_new_line(&mut self.clarify_buf); - write!(&mut self.clarify_buf, "\n{}", indent).unwrap(); - } + write!(&mut self.clarify_buf, "\n{}", indent).ok(); + + // write e.g. '"' before hex + self.comment_writer.start_new_line(&mut self.clarify_buf); } /// Writes hex bytes to debug output, for example "30 04 " pub fn write_clarify_hex(&mut self, slice: &[u8]) { let indent = self.indent_str(); - { - write!(&mut self.clarify_buf, "{}", HexDisplayLines(&slice, indent)).unwrap(); - } + write!(&mut self.clarify_buf, "{}", HexDisplayLines(&slice, indent)).ok(); } /// Writes string to debug output, for example a comment "// SEQUENCE" pub fn write_clarify_str(&mut self, s: &str) { - { - write!(&mut self.clarify_buf, "{}", s).unwrap(); - } + write!(&mut self.clarify_buf, "{}", s).unwrap(); } /// Writes string to debug output, for example a comment: `// SEQUENCE: name` pub fn write_clarify_type_str(&mut self, start_end: &str, type_name: &str) { @@ -291,7 +315,7 @@ impl Clarifier { match (writer_pos, last_pos) { (Some(writer_pos), Some(last_pos)) => { let diff = writer_pos - last_pos; - if diff < 16 { + if diff < 15 { // ignore short runs return; } @@ -304,9 +328,16 @@ impl Clarifier { self.write_clarify_type_str("end", type_name.as_ref()); } + // /// for better tag-length pretty-printing inline + // pub fn clarify_end_tag(&mut self, _tag: &Tag) { + // // just to print a single length byte without indent + // self.indent_enabled = false; + // } + /// for better tag-length pretty-printing inline - pub fn clarify_end_tag(&mut self, _tag: &Tag) { - // just to print a single length byte without indent + pub fn clarify_header_start_tag(&mut self, _tag: &Tag) { + self.write_clarify_indent(); + // just to print header bytes without indent self.indent_enabled = false; } @@ -318,27 +349,36 @@ impl Clarifier { // self.indent_enabled = enabled; // } + /// Writes field name, i.e. field: `public_key` + /// + /// when used on Sequence field: + /// ```text + /// public_key: Option<&'a [u8]> + /// ``` pub fn clarify_field_name(&mut self, field_name: &str) { self.write_clarify_indent(); self.write_clarify_type_str("field", field_name); } - pub fn clarify_start_value_type(&mut self) { + /// Writes e.g. `// type: OctetString` + pub fn clarify_start_value_type(&mut self) { self.clarify_start_value_type_str(Some(self.last_position), &tynm::type_name::()); } - pub fn clarify_end_value_type(&mut self) { + /// Writes e.g. `// end: OctetString` + pub fn clarify_end_value_type(&mut self) { self.clarify_end_value_type_str(Some(self.last_position), &tynm::type_name::()); } - // fn clarify_end_length(&mut self, tag: Option<&Tag>, length: Length) { - // self.indent_enabled = true; - // if let Some(tag) = tag { - // self.write_debug_type_str("tag", &format!("{}", tag)); - // } - // if u32::from(length) >= 10 { - // self.write_debug_type_str("len", &format!("{}", length)); - // } - // } + /// Writes e.g. `// tag: OCTET STRING len: 17` + pub fn clarify_header_end_length(&mut self, tag: Option<&Tag>, length: Length) { + self.indent_enabled = true; + if let Some(tag) = tag { + self.write_clarify_type_str("tag", &format!("{}", tag)); + } + if u32::from(length) >= 10 { + self.write_clarify_type_str("len", &format!("{}", length)); + } + } // fn clarify_value_quote(&mut self, _type_name: &str, tag_name: &[u8]) { // //self.write_debug_value_quote(type_name, tag_name); @@ -349,6 +389,7 @@ impl Clarifier { // self.write_debug_int(value); // } + /// Writes pretty-printed `CHOICE name` pub fn clarify_choice(&mut self, choice_name: &[u8]) { self.write_clarify_indent(); if let Ok(choice_name) = std::str::from_utf8(choice_name) { @@ -359,7 +400,6 @@ impl Clarifier { impl<'a> Writer for ClarifySliceWriter<'a> { fn write(&mut self, slice: &[u8]) -> Result<()> { - println!("writing {slice:?}"); self.reserve(slice.len())?.copy_from_slice(slice); self.clarifier.last_position += slice.len() as u32; @@ -368,6 +408,10 @@ impl<'a> Writer for ClarifySliceWriter<'a> { Ok(()) } + + fn clarifier(&mut self) -> Option<&mut Clarifier> { + Some(&mut self.clarifier) + } } /// Strips wrappers, such as `EncodeValueRef`, which is commonly used and is completely transparent @@ -390,24 +434,3 @@ fn strip_transparent_types(mut type_name: &str) -> Cow<'_, str> { Cow::Borrowed(type_name) } - -#[cfg(test)] -pub mod test { - use std::{println, vec::Vec}; - - use crate::{ - asn1::OctetString, - writer::clarify::{ClarifyFlavor, EncodeClarifyExt}, - }; - - #[test] - fn clarify_simple_octetstring() { - let obj = OctetString::new(&[0xAA, 0xBB, 0xCC]).unwrap(); - - let clarified = obj - .to_der_clarify(ClarifyFlavor::XmlComments) - .expect("encoded DER"); - - println!("clarified: {clarified}"); - } -} diff --git a/der/src/writer/clarify/commentwriter.rs b/der/src/writer/clarify/commentwriter.rs index a873762a7..e477f9faa 100644 --- a/der/src/writer/clarify/commentwriter.rs +++ b/der/src/writer/clarify/commentwriter.rs @@ -4,6 +4,8 @@ pub trait CommentWriter { fn comment(&mut self, s: &str); fn before_new_line(&mut self, w: &mut dyn Write); + + fn start_new_line(&mut self, _w: &mut dyn Write) {} } #[derive(Default)] @@ -20,7 +22,7 @@ impl CommentWriter for JavaCommentWriter { if self.buf.len() == 0 { return; } - let _ = w.write_all(b"// "); + let _ = w.write_all(b" // "); let _ = w.write_all(&self.buf); self.buf.clear(); } @@ -40,9 +42,38 @@ impl CommentWriter for XmlCommentWriter { if self.buf.len() == 0 { return; } - let _ = w.write_all(b""); self.buf.clear(); } } + +#[derive(Default)] +pub struct RustHexWriter { + pub started_newline: bool, + pub buf: Vec, +} + +impl CommentWriter for RustHexWriter { + fn comment(&mut self, s: &str) { + self.buf.extend_from_slice(s.as_bytes()); + } + + fn before_new_line(&mut self, w: &mut dyn Write) { + if self.started_newline { + w.write_all(b"\"").ok(); + } + if self.buf.len() == 0 { + return; + } + w.write_all(b" // ").ok(); + w.write_all(&self.buf).ok(); + self.buf.clear(); + self.started_newline = false; + } + fn start_new_line(&mut self, w: &mut dyn Write) { + self.started_newline = true; + w.write_all(b"\"").ok(); + } +} diff --git a/der/src/writer/clarify/hexdisplaylines.rs b/der/src/writer/clarify/hexdisplaylines.rs index b055d86e9..3fb7fa2b0 100644 --- a/der/src/writer/clarify/hexdisplaylines.rs +++ b/der/src/writer/clarify/hexdisplaylines.rs @@ -12,7 +12,6 @@ impl fmt::Display for HexDisplayLines<'_, '_> { } else { first = false; } - for byte in chunk { write!(f, "{:02X} ", byte)?; } diff --git a/der/tests/clarify.rs b/der/tests/clarify.rs new file mode 100644 index 000000000..39b7c95bd --- /dev/null +++ b/der/tests/clarify.rs @@ -0,0 +1,93 @@ +//! Tests for clarify pretty-printing support. +#![cfg(all(feature = "derive", feature = "alloc", feature = "clarify"))] + +pub mod sequence { + use std::println; + + use const_oid::ObjectIdentifier; + use der::{ + AnyRef, ClarifyFlavor, EncodeClarifyExt, Sequence, ValueOrd, + asn1::{OctetString, SetOf}, + }; + use hex_literal::hex; + + /// X.509 `AlgorithmIdentifier` + #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] + pub struct AlgorithmIdentifier<'a> { + pub algorithm: ObjectIdentifier, + pub parameters: Option>, + } + + /// PKCS#8v2 `OneAsymmetricKey` + #[derive(Sequence)] + pub struct OneAsymmetricKey<'a> { + pub version: u8, + pub private_key_algorithm: AlgorithmIdentifier<'a>, + #[asn1(type = "OCTET STRING")] + pub private_key: &'a [u8], + #[asn1(context_specific = "0", extensible = "true", optional = "true")] + pub attributes: Option, 1>>, + #[asn1( + context_specific = "1", + extensible = "true", + optional = "true", + type = "BIT STRING" + )] + pub public_key: Option<&'a [u8]>, + } + + #[test] + fn clarify_simple_octetstring_javacomments() { + let obj = OctetString::new(&[0xAA, 0xBB, 0xCC]).unwrap(); + + let clarified = obj + .to_der_clarify(ClarifyFlavor::JavaComments) + .expect("encoded DER"); + + assert_eq!( + clarified, + "\n04 03 // tag: OCTET STRING type: OctetString \n\tAA BB CC" + ) + } + + #[test] + fn clarify_simple_octetstring_rusthex() { + let obj = OctetString::new(&[0xAA, 0xBB, 0xCC]).unwrap(); + + let clarified = obj + .to_der_clarify(ClarifyFlavor::RustHex) + .expect("encoded DER"); + + assert_eq!( + clarified, + "\n\"04 03\" // tag: OCTET STRING type: OctetString \n\t\"AA BB CC\"" + ) + } + + #[test] + fn clarify_simple_octetstring_long_rusthex() { + let obj = OctetString::new(&[ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0x01, + ]) + .unwrap(); + + let clarified = obj + .to_der_clarify(ClarifyFlavor::RustHex) + .expect("encoded DER"); + + println!("clarified: {clarified}"); + assert_eq!( + clarified, + "\n\"04 11\" // tag: OCTET STRING len: 17 type: OctetString \n\t\"00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n\t01\"\n\"\" // end: OctetString " + ); + + // use-case example: + hex!( + "04 11" // tag: OCTET STRING len: 17 type: OctetString + "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF + 01" + "" // end: OctetString + ); + } +} From 46e54d9ffaff82725d6ee6e544a40b675e85fe80 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:45:03 +0200 Subject: [PATCH 08/20] der: fix merge --- der/src/asn1/any.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/der/src/asn1/any.rs b/der/src/asn1/any.rs index d656407eb..620934127 100644 --- a/der/src/asn1/any.rs +++ b/der/src/asn1/any.rs @@ -56,13 +56,6 @@ impl<'a> AnyRef<'a> { pub fn value(self) -> &'a [u8] { self.value.as_slice() } - /// Returns [`Tag`] and [`Length`] of self. - pub fn header(&self) -> Header { - Header { - tag: self.tag, - length: self.value.len(), - } - } /// Returns [`Tag`] and [`Length`] of self. pub fn header(&self) -> Header { @@ -94,7 +87,7 @@ impl<'a> AnyRef<'a> { let mut decoder = SliceReader::new_with_encoding_rules(self.value(), encoding)?; let result = T::decode_value(&mut decoder, self.header())?; - + decoder.finish()?; Ok(result) } From f73c805eb44a1fca3ddd088a5c6a74d284883def Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:53:40 +0200 Subject: [PATCH 09/20] der: test clarify inception + features default = [] --- der/Cargo.toml | 2 +- der/tests/clarify.rs | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/der/Cargo.toml b/der/Cargo.toml index 1cb2397c9..546907af1 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -33,7 +33,7 @@ hex-literal = "1" proptest = "1" [features] -default = ["clarify"] +default = [] alloc = ["zeroize?/alloc"] std = ["alloc"] diff --git a/der/tests/clarify.rs b/der/tests/clarify.rs index 39b7c95bd..ca4c23a7d 100644 --- a/der/tests/clarify.rs +++ b/der/tests/clarify.rs @@ -6,7 +6,7 @@ pub mod sequence { use const_oid::ObjectIdentifier; use der::{ - AnyRef, ClarifyFlavor, EncodeClarifyExt, Sequence, ValueOrd, + AnyRef, ClarifyFlavor, Decode, EncodeClarifyExt, Sequence, ValueOrd, asn1::{OctetString, SetOf}, }; use hex_literal::hex; @@ -38,7 +38,7 @@ pub mod sequence { #[test] fn clarify_simple_octetstring_javacomments() { - let obj = OctetString::new(&[0xAA, 0xBB, 0xCC]).unwrap(); + let obj = OctetString::new(hex!("AA BB CC")).unwrap(); let clarified = obj .to_der_clarify(ClarifyFlavor::JavaComments) @@ -52,7 +52,7 @@ pub mod sequence { #[test] fn clarify_simple_octetstring_rusthex() { - let obj = OctetString::new(&[0xAA, 0xBB, 0xCC]).unwrap(); + let obj = OctetString::new(hex!("AA BB CC")).unwrap(); let clarified = obj .to_der_clarify(ClarifyFlavor::RustHex) @@ -66,10 +66,12 @@ pub mod sequence { #[test] fn clarify_simple_octetstring_long_rusthex() { - let obj = OctetString::new(&[ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, - 0xEE, 0xFF, 0x01, - ]) + let obj = OctetString::from_der(&hex!( + "04 11" // tag: OCTET STRING len: 17 type: OctetString + "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF + 01" + "" // end: OctetString + )) .unwrap(); let clarified = obj @@ -81,13 +83,5 @@ pub mod sequence { clarified, "\n\"04 11\" // tag: OCTET STRING len: 17 type: OctetString \n\t\"00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n\t01\"\n\"\" // end: OctetString " ); - - // use-case example: - hex!( - "04 11" // tag: OCTET STRING len: 17 type: OctetString - "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF - 01" - "" // end: OctetString - ); } } From f6c912d82f1a7cee41b4a55d428b732fb6f9a986 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:28:30 +0200 Subject: [PATCH 10/20] der: clarify: add OneAsymmetricKey example --- der/src/writer/clarify.rs | 19 +++++-- der/src/writer/clarify/commentwriter.rs | 10 ++++ der/src/writer/clarify/hexdisplaylines.rs | 16 ++++-- der/tests/clarify.rs | 68 ++++++++++++++++++++++- 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index 628aa9a4b..3135a7256 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -263,9 +263,18 @@ impl Clarifier { } /// Writes hex bytes to debug output, for example "30 04 " - pub fn write_clarify_hex(&mut self, slice: &[u8]) { + pub fn write_clarify_hex(&mut self, bytes: &[u8]) { let indent = self.indent_str(); - write!(&mut self.clarify_buf, "{}", HexDisplayLines(&slice, indent)).ok(); + write!( + &mut self.clarify_buf, + "{}", + HexDisplayLines { + bytes, + indent, + space: self.comment_writer.needs_newline_space() + } + ) + .ok(); } /// Writes string to debug output, for example a comment "// SEQUENCE" @@ -418,9 +427,9 @@ impl<'a> Writer for ClarifySliceWriter<'a> { fn strip_transparent_types(mut type_name: &str) -> Cow<'_, str> { let prefixes = [ "EncodeValueRef<", - "ApplicationRef<", - "ContextSpecificRef<", - "PrivateRef<", + // "ApplicationRef<", + // "ContextSpecificRef<", + // "PrivateRef<", ]; for prefix in prefixes { diff --git a/der/src/writer/clarify/commentwriter.rs b/der/src/writer/clarify/commentwriter.rs index e477f9faa..91b22add3 100644 --- a/der/src/writer/clarify/commentwriter.rs +++ b/der/src/writer/clarify/commentwriter.rs @@ -6,6 +6,10 @@ pub trait CommentWriter { fn before_new_line(&mut self, w: &mut dyn Write); fn start_new_line(&mut self, _w: &mut dyn Write) {} + + fn needs_newline_space(&self) -> bool { + false + } } #[derive(Default)] @@ -72,8 +76,14 @@ impl CommentWriter for RustHexWriter { self.buf.clear(); self.started_newline = false; } + fn start_new_line(&mut self, w: &mut dyn Write) { self.started_newline = true; w.write_all(b"\"").ok(); } + + fn needs_newline_space(&self) -> bool { + // because '"' is in first line, next lines need to be moved by 1 character + true + } } diff --git a/der/src/writer/clarify/hexdisplaylines.rs b/der/src/writer/clarify/hexdisplaylines.rs index 3fb7fa2b0..fe94671c6 100644 --- a/der/src/writer/clarify/hexdisplaylines.rs +++ b/der/src/writer/clarify/hexdisplaylines.rs @@ -1,14 +1,22 @@ +use core::fmt::Write; use std::fmt; -/// (bytes, indent) -pub struct HexDisplayLines<'a, 'i>(pub &'a [u8], pub &'i str); +/// (bytes, indent, nl_ident) +pub struct HexDisplayLines<'a, 'i> { + pub bytes: &'a [u8], + pub indent: &'i str, + pub space: bool, +} impl fmt::Display for HexDisplayLines<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut first = true; - for chunk in self.0.chunks(16) { + for chunk in self.bytes.chunks(16) { if !first { - write!(f, "\n{}", self.1)?; + write!(f, "\n{}", self.indent)?; + if self.space { + f.write_char(' ').ok(); + } } else { first = false; } diff --git a/der/tests/clarify.rs b/der/tests/clarify.rs index ca4c23a7d..23a0614bf 100644 --- a/der/tests/clarify.rs +++ b/der/tests/clarify.rs @@ -2,11 +2,11 @@ #![cfg(all(feature = "derive", feature = "alloc", feature = "clarify"))] pub mod sequence { - use std::println; + use std::{println, str::FromStr}; use const_oid::ObjectIdentifier; use der::{ - AnyRef, ClarifyFlavor, Decode, EncodeClarifyExt, Sequence, ValueOrd, + AnyRef, ClarifyFlavor, Decode, EncodeClarifyExt, Sequence, TagNumber, ValueOrd, asn1::{OctetString, SetOf}, }; use hex_literal::hex; @@ -84,4 +84,68 @@ pub mod sequence { "\n\"04 11\" // tag: OCTET STRING len: 17 type: OctetString \n\t\"00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF \n\t01\"\n\"\" // end: OctetString " ); } + #[test] + fn clarify_one_assymetric_key_rusthex() { + let obj = OneAsymmetricKey { + version: 1, + private_key_algorithm: AlgorithmIdentifier { + algorithm: ObjectIdentifier::from_str("1.2.3.4.5.6.7.8").expect("valid oid"), + parameters: Some( + AnyRef::new( + der::Tag::ContextSpecific { + constructed: true, + number: TagNumber(0), + }, + &[0xAA, 0xBB], + ) + .expect("valid length"), + ), + }, + private_key: &[ + 0x00, 0x11, 0x22, 0x33, 0x00, 0x11, 0x22, 0x33, 0x00, 0x11, 0x22, 0x33, 0x00, 0x11, + 0x22, 0x33, 0x00, 0x11, 0x22, 0x33, 0x00, 0x11, 0x22, 0x33, 0x00, 0x11, 0x22, 0x33, + ], + attributes: None, + public_key: Some(&[ + 0x44, 0x55, 0x66, 0x77, 0x44, 0x55, 0x66, 0x77, 0x44, 0x55, 0x66, 0x77, 0x44, 0x55, + 0x66, 0x77, 0x44, 0x55, 0x66, 0x77, 0x44, 0x55, 0x66, 0x77, 0x44, 0x55, 0x66, 0x77, + ]), + }; + let clarified = obj + .to_der_clarify(ClarifyFlavor::RustHex) + .expect("encoded DER"); + + //println!("clarified: {clarified}"); + + assert!(clarified.contains("type: OneAsymmetricKey")); + assert!(clarified.contains("tag: CONTEXT-SPECIFIC [0] (constructed)")); + assert!(clarified.contains("tag: CONTEXT-SPECIFIC [1] (constructed)")); + assert!(clarified.contains("type: AlgorithmIdentifier")); + assert!(clarified.contains("tag: OBJECT IDENTIFIER")); + assert!(clarified.contains("type: ObjectIdentifier")); + assert!(clarified.contains("end: OneAsymmetricKey")); + + hex!( + "30 51" // tag: SEQUENCE len: 81 type: OneAsymmetricKey + "02 01" // tag: INTEGER type: u8 + "01" + "30 0D" // tag: SEQUENCE len: 13 type: AlgorithmIdentifier + "06 07" // tag: OBJECT IDENTIFIER type: ObjectIdentifier + "2A 03 04 05 06 07 08" + "A0 02" // tag: CONTEXT-SPECIFIC [0] (constructed) type: AnyRef + "AA BB" + "04 1C" // tag: OCTET STRING len: 28 type: OctetStringRef + "00 11 22 33 00 11 22 33 00 11 22 33 00 11 22 33 + 00 11 22 33 00 11 22 33 00 11 22 33" + "" // end: OctetStringRef + "A1 1F" // tag: CONTEXT-SPECIFIC [1] (constructed) len: 31 type: ContextSpecificRef + "03 1D" // tag: BIT STRING len: 29 type: BitStringRef + "00" + "44 55 66 77 44 55 66 77 44 55 66 77 44 55 66 77 + 44 55 66 77 44 55 66 77 44 55 66 77" + "" // end: BitStringRef + "" // end: ContextSpecificRef + "" // end: OneAsymmetricKey + ); + } } From 21d1b177860ee2d96f4aea689a678c984b7a94b5 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:30:55 +0200 Subject: [PATCH 11/20] der: clarify #[allow(unused_variables)] --- der/src/encode.rs | 2 ++ der/src/header.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/der/src/encode.rs b/der/src/encode.rs index 0331f0856..ecc66fafb 100644 --- a/der/src/encode.rs +++ b/der/src/encode.rs @@ -88,6 +88,7 @@ where } } +#[allow(unused_variables)] fn clarify_start_value_type(writer: &mut impl Writer) { #[cfg(feature = "clarify")] if let Some(clarifier) = writer.clarifier() { @@ -95,6 +96,7 @@ fn clarify_start_value_type(writer: &mut impl Writer) { } } +#[allow(unused_variables)] fn clarify_end_value_type(writer: &mut impl Writer) { #[cfg(feature = "clarify")] if let Some(clarifier) = writer.clarifier() { diff --git a/der/src/header.rs b/der/src/header.rs index 507ef5b75..bcf8c2fc8 100644 --- a/der/src/header.rs +++ b/der/src/header.rs @@ -69,6 +69,7 @@ impl Encode for Header { } } +#[allow(unused_variables)] fn clarify_start_tag(writer: &mut impl Writer, tag: &Tag) { #[cfg(feature = "clarify")] if let Some(clarifier) = writer.clarifier() { @@ -76,6 +77,7 @@ fn clarify_start_tag(writer: &mut impl Writer, tag: &Tag) { } } +#[allow(unused_variables)] fn clarify_end_length(writer: &mut impl Writer, tag: &Tag, length: Length) { #[cfg(feature = "clarify")] if let Some(clarifier) = writer.clarifier() { From 24ad8287ed14063cbd039fe381ba778380bfdc08 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:41:56 +0200 Subject: [PATCH 12/20] der: clarify: cleanup + clippy --- der/src/header.rs | 4 +- der/src/writer/clarify.rs | 97 +++++------------------ der/src/writer/clarify/commentwriter.rs | 6 +- der/src/writer/clarify/hexdisplaylines.rs | 2 +- der/tests/clarify.rs | 2 + 5 files changed, 27 insertions(+), 84 deletions(-) diff --git a/der/src/header.rs b/der/src/header.rs index bcf8c2fc8..5a6a03117 100644 --- a/der/src/header.rs +++ b/der/src/header.rs @@ -73,7 +73,7 @@ impl Encode for Header { fn clarify_start_tag(writer: &mut impl Writer, tag: &Tag) { #[cfg(feature = "clarify")] if let Some(clarifier) = writer.clarifier() { - clarifier.clarify_header_start_tag(&tag); + clarifier.clarify_header_start_tag(tag); } } @@ -81,7 +81,7 @@ fn clarify_start_tag(writer: &mut impl Writer, tag: &Tag) { fn clarify_end_length(writer: &mut impl Writer, tag: &Tag, length: Length) { #[cfg(feature = "clarify")] if let Some(clarifier) = writer.clarifier() { - clarifier.clarify_header_end_length(Some(&tag), length); + clarifier.clarify_header_end_length(Some(tag), length); } } diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index 3135a7256..b883212cf 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -31,8 +31,7 @@ pub trait EncodeClarifyExt: Encode { Err(err) => return ClarifyOutputs::from_err(err), }; - let mut buf: Vec = Vec::new(); - buf.resize(u32::from(len) as usize, 0u8); + let mut buf = vec![0u8; u32::from(len) as usize]; let mut writer = ClarifySliceWriter::new(&mut buf, Vec::new(), flavor); let result = self.encode(&mut writer); @@ -146,74 +145,17 @@ impl<'a> ClarifySliceWriter<'a> { } } - // /// Encode a value which impls the [`Encode`] trait. - // pub fn encode(&mut self, encodable: &T) -> Result<()> { - // self.writer.encode(encodable) - // } - - // /// Return an error with the given [`ErrorKind`], annotating it with - // /// context about where the error occurred. - // pub fn error(&mut self, kind: ErrorKind) -> Result { - // self.writer.error(kind) - // } - - // /// Did the decoding operation fail due to an error? - // pub fn is_failed(&self) -> bool { - // self.writer.is_failed() - // } - - // /// Finish encoding to the buffer, returning a slice containing the data - // /// written to the buffer. - // pub fn finish_internal(&self) -> Result<&'a [u8]> { - // self.writer.finish() - // } - /// Finish encoding to the buffer, returning a slice containing the data /// written to the buffer. pub fn finish(mut self) -> ClarifyOutputs<'a> { self.clarifier.flush_line(); ClarifyOutputs { - raw: self.writer.finish().map(|raw| Cow::Borrowed(raw)), + raw: self.writer.finish().map(Cow::Borrowed), clarify_buf: self.clarifier.clarify_buf, } } - // /// Encode a `CONTEXT-SPECIFIC` field with the provided tag number and mode. - // pub fn context_specific( - // &mut self, - // tag_number: TagNumber, - // tag_mode: TagMode, - // value: &T, - // ) -> Result<()> - // where - // T: EncodeValue + Tagged, - // { - // self.writer.context_specific(tag_number, tag_mode, value) - // } - - // /// Encode an ASN.1 `SEQUENCE` of the given length. - // /// - // /// Spawns a nested slice writer which is expected to be exactly the - // /// specified length upon completion. - // pub fn sequence(&mut self, length: Length, f: F) -> Result<()> - // where - // F: FnOnce(&mut DebugSliceWriter<'_>) -> Result<()>, - // { - // Header::new(Tag::Sequence, length).and_then(|header| header.encode(self))?; - - // let debug_ref = self.debug_ref.clone(); - // let mut nested_encoder = DebugSliceWriter::new(self.reserve(length)?, debug_ref, true); - // f(&mut nested_encoder)?; - - // let nresult: FinishOutputs<'_> = nested_encoder.finish(); - // if nresult.raw?.len() == usize::try_from(length)? { - // Ok(()) - // } else { - // self.error(ErrorKind::Length { tag: Tag::Sequence }) - // } - // } - /// Reserve a portion of the internal buffer, updating the internal cursor /// position and returning a mutable slice. fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { @@ -224,7 +166,7 @@ impl<'a> ClarifySliceWriter<'a> { impl Clarifier { /// Returns indentation, for example "\n\t" for depth == 1 pub fn indent_str(&self) -> &'static str { - let ilen = self.depth.len() * 1; + let ilen = self.depth.len(); let ilen = ilen.min(INDENT_STR.len()); &INDENT_STR[..ilen] } @@ -256,7 +198,7 @@ impl Clarifier { self.flush_line(); let indent = self.indent_str(); - write!(&mut self.clarify_buf, "\n{}", indent).ok(); + write!(&mut self.clarify_buf, "\n{indent}").ok(); // write e.g. '"' before hex self.comment_writer.start_new_line(&mut self.clarify_buf); @@ -279,13 +221,13 @@ impl Clarifier { /// Writes string to debug output, for example a comment "// SEQUENCE" pub fn write_clarify_str(&mut self, s: &str) { - write!(&mut self.clarify_buf, "{}", s).unwrap(); + write!(&mut self.clarify_buf, "{s}").ok(); } /// Writes string to debug output, for example a comment: `// SEQUENCE: name` pub fn write_clarify_type_str(&mut self, start_end: &str, type_name: &str) { //let mut debugbuf = self.debug_ref.borrow_mut(); - let comment = format!("{}: {} ", start_end, type_name); + let comment = format!("{start_end}: {type_name} "); self.comment_writer.comment(&comment); } @@ -303,13 +245,15 @@ impl Clarifier { /// Writes int to debug output, for example a comment: `// integer: 16dec` pub fn write_clarify_int(&mut self, value: i64) { - if value >= 10 || value < 0 { + if !(0..10).contains(&value) { let comment = format!("integer: {value}dec "); self.comment_writer.comment(&comment); } } - /// input: u32::from(self.writer.position()) + /// Writes e.g. `type: OctetString` + /// + /// Expected `writer_pos` input: `u32::from(self.writer.position())` pub fn clarify_start_value_type_str(&mut self, writer_pos: Option, type_name: &str) { self.indent_enabled = true; self.depth.push(writer_pos); @@ -321,15 +265,12 @@ impl Clarifier { fn clarify_end_value_type_str(&mut self, writer_pos: Option, type_name: &str) { let last_pos = self.depth.pop().unwrap_or(writer_pos); - match (writer_pos, last_pos) { - (Some(writer_pos), Some(last_pos)) => { - let diff = writer_pos - last_pos; - if diff < 15 { - // ignore short runs - return; - } + if let (Some(writer_pos), Some(last_pos)) = (writer_pos, last_pos) { + let diff = writer_pos - last_pos; + if diff < 16 { + // ignore short runs + return; } - _ => {} } let type_name = strip_transparent_types(type_name); @@ -382,10 +323,10 @@ impl Clarifier { pub fn clarify_header_end_length(&mut self, tag: Option<&Tag>, length: Length) { self.indent_enabled = true; if let Some(tag) = tag { - self.write_clarify_type_str("tag", &format!("{}", tag)); + self.write_clarify_type_str("tag", &format!("{tag}")); } if u32::from(length) >= 10 { - self.write_clarify_type_str("len", &format!("{}", length)); + self.write_clarify_type_str("len", &format!("{length}")); } } @@ -408,6 +349,7 @@ impl Clarifier { } impl<'a> Writer for ClarifySliceWriter<'a> { + #[allow(clippy::cast_possible_truncation)] fn write(&mut self, slice: &[u8]) -> Result<()> { self.reserve(slice.len())?.copy_from_slice(slice); self.clarifier.last_position += slice.len() as u32; @@ -434,8 +376,7 @@ fn strip_transparent_types(mut type_name: &str) -> Cow<'_, str> { for prefix in prefixes { type_name = if let Some(stripped) = type_name.strip_prefix(prefix) { - let stripped = stripped.strip_suffix(">").unwrap_or(stripped); - stripped + stripped.strip_suffix(">").unwrap_or(stripped) } else { type_name }; diff --git a/der/src/writer/clarify/commentwriter.rs b/der/src/writer/clarify/commentwriter.rs index 91b22add3..a422ca613 100644 --- a/der/src/writer/clarify/commentwriter.rs +++ b/der/src/writer/clarify/commentwriter.rs @@ -23,7 +23,7 @@ impl CommentWriter for JavaCommentWriter { } fn before_new_line(&mut self, w: &mut dyn Write) { - if self.buf.len() == 0 { + if self.buf.is_empty() { return; } let _ = w.write_all(b" // "); @@ -43,7 +43,7 @@ impl CommentWriter for XmlCommentWriter { } fn before_new_line(&mut self, w: &mut dyn Write) { - if self.buf.len() == 0 { + if self.buf.is_empty() { return; } let _ = w.write_all(b" ` XmlComments, /// `01 02 // comment` + #[default] JavaComments, /// `"01 02" // comment` RustHex, @@ -118,7 +143,7 @@ pub enum ClarifyFlavor { impl Clarifier { /// Creates new Clarifier with buffer, that accumulates comments and hex bytes. - pub fn new(clarify_buf: Vec, flavor: ClarifyFlavor) -> Self { + pub fn new(clarify_buf: Vec, options: ClarifyOptions) -> Self { Self { clarify_buf, @@ -126,7 +151,10 @@ impl Clarifier { depth: Vec::new(), indent_enabled: true, - comment_writer: match flavor { + + print_types: options.print_types, + + comment_writer: match options.flavor { ClarifyFlavor::XmlComments => Box::new(XmlCommentWriter::default()), ClarifyFlavor::JavaComments => Box::new(JavaCommentWriter::default()), ClarifyFlavor::RustHex => Box::new(RustHexWriter::default()), @@ -137,10 +165,10 @@ impl Clarifier { impl<'a> ClarifySliceWriter<'a> { /// Create a new encoder with the given byte slice as a backing buffer. - pub fn new(bytes: &'a mut [u8], clarify_buf: Vec, flavor: ClarifyFlavor) -> Self { + pub fn new(bytes: &'a mut [u8], clarify_buf: Vec, options: ClarifyOptions) -> Self { Self { writer: SliceWriter::new(bytes), - clarifier: Clarifier::new(clarify_buf, flavor), + clarifier: Clarifier::new(clarify_buf, options), } } @@ -254,8 +282,10 @@ impl Clarifier { self.indent_enabled = true; self.depth.push(writer_pos); - let type_name = strip_transparent_types(type_name); - self.write_clarify_type_str("type", &type_name); + if self.print_types { + let type_name = strip_transparent_types(type_name); + self.write_clarify_type_str("type", &type_name); + } } fn clarify_end_value_type_str(&mut self, writer_pos: Option, type_name: &str) { From 3494760dbd2c3456d2129e42f7f408f5d90efdc7 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:47:15 +0200 Subject: [PATCH 19/20] der: clarify: add option to disable e.g. `end: OctetStringRef` --- der/src/writer/clarify.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/der/src/writer/clarify.rs b/der/src/writer/clarify.rs index 727869fd9..c730965e7 100644 --- a/der/src/writer/clarify.rs +++ b/der/src/writer/clarify.rs @@ -299,9 +299,11 @@ impl Clarifier { } } - let type_name = strip_transparent_types(type_name); - self.write_clarify_indent(); - self.write_clarify_type_str("end", type_name.as_ref()); + if self.print_types { + let type_name = strip_transparent_types(type_name); + self.write_clarify_indent(); + self.write_clarify_type_str("end", type_name.as_ref()); + } } /// for better tag-length pretty-printing inline From 79ef9d2bea1cb978f4a3092d27eec7d6d01b6d65 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:46:58 +0200 Subject: [PATCH 20/20] der: organize Cargo.toml --- der/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/der/Cargo.toml b/der/Cargo.toml index 546907af1..57bff1449 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -33,7 +33,6 @@ hex-literal = "1" proptest = "1" [features] -default = [] alloc = ["zeroize?/alloc"] std = ["alloc"] @@ -43,7 +42,7 @@ derive = ["dep:der_derive"] oid = ["dep:const-oid"] pem = ["dep:pem-rfc7468", "alloc", "zeroize"] real = [] -clarify = ["std", "pem", "dep:tynm", "derive", "oid"] +clarify = ["dep:tynm", "std", "pem", "derive", "oid"] [package.metadata.docs.rs] all-features = true