From aae1db20d7a70ad13cf622e2c882a8161b58f299 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 7 Nov 2025 15:26:51 -0700 Subject: [PATCH] der: impl `DecodeValue`/`EncodeValue`/`Tagged` for `Cow` Also includes a POC that the traits work with `OctetStringRef` --- der/src/asn1/octet_string.rs | 19 +++++++++++++++++++ der/src/decode.rs | 18 +++++++++++++++++- der/src/encode.rs | 21 ++++++++++++++++++++- der/src/tag.rs | 12 ++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/der/src/asn1/octet_string.rs b/der/src/asn1/octet_string.rs index 10159ce06..e080a4d11 100644 --- a/der/src/asn1/octet_string.rs +++ b/der/src/asn1/octet_string.rs @@ -383,6 +383,9 @@ mod tests { }; use hex_literal::hex; + #[cfg(feature = "alloc")] + use {crate::Encode, alloc::borrow::Cow}; + #[test] fn octet_string_decode() { // PrintableString "hi" @@ -405,6 +408,22 @@ mod tests { assert_eq!(AsRef::::as_ref(&res), "hi"); } + #[cfg(feature = "alloc")] + #[test] + fn cow_octet_string_decode_and_encode() { + // PrintableString "hi" + const EXAMPLE: &[u8] = &hex!( + "040c" // primitive definite length OCTET STRING + "48656c6c6f2c20776f726c64" // "Hello, world" + ); + + let decoded = Cow::::from_der(EXAMPLE).unwrap(); + assert_eq!(decoded.as_bytes(), b"Hello, world"); + + let encoded = decoded.to_der().unwrap(); + assert_eq!(EXAMPLE, encoded); + } + #[test] #[cfg(all(feature = "alloc", feature = "ber"))] fn decode_ber_primitive_definite() { diff --git a/der/src/decode.rs b/der/src/decode.rs index 700c3145a..051e84a90 100644 --- a/der/src/decode.rs +++ b/der/src/decode.rs @@ -11,7 +11,10 @@ use crate::{PemReader, pem::PemLabel}; use crate::{ErrorKind, Length, Tag}; #[cfg(feature = "alloc")] -use alloc::boxed::Box; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, +}; #[cfg(feature = "ber")] use crate::EncodingRules; @@ -207,3 +210,16 @@ where Ok(Box::new(T::decode_value(reader, header)?)) } } + +#[cfg(feature = "alloc")] +impl<'a, T> DecodeValue<'a> for Cow<'a, T> +where + T: ToOwned + ?Sized, + &'a T: DecodeValue<'a>, +{ + type Error = <&'a T as DecodeValue<'a>>::Error; + + fn decode_value>(reader: &mut R, header: Header) -> Result { + Ok(Cow::Borrowed(<&'a T>::decode_value(reader, header)?)) + } +} diff --git a/der/src/encode.rs b/der/src/encode.rs index 72827b240..8a1328bf3 100644 --- a/der/src/encode.rs +++ b/der/src/encode.rs @@ -4,7 +4,11 @@ use crate::{Header, Length, Result, SliceWriter, Tagged, Writer}; use core::marker::PhantomData; #[cfg(feature = "alloc")] -use {alloc::boxed::Box, alloc::vec::Vec}; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + vec::Vec, +}; #[cfg(feature = "pem")] use { @@ -228,6 +232,21 @@ where } } +#[cfg(feature = "alloc")] +impl EncodeValue for Cow<'_, T> +where + T: ToOwned + ?Sized, + for<'a> &'a T: EncodeValue, +{ + fn value_len(&self) -> Result { + self.as_ref().value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { + self.as_ref().encode_value(writer) + } +} + /// Encodes value only (without tag + length) to a slice. pub(crate) fn encode_value_to_slice<'a, T>(buf: &'a mut [u8], value: &T) -> Result<&'a [u8]> where diff --git a/der/src/tag.rs b/der/src/tag.rs index 34644892e..74b18815e 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -10,6 +10,9 @@ pub use self::{class::Class, mode::TagMode, number::TagNumber}; use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Length, Reader, Result, Writer}; use core::{cmp::Ordering, fmt}; +#[cfg(feature = "alloc")] +use alloc::borrow::{Cow, ToOwned}; + /// Indicator bit for constructed form encoding (i.e. vs primitive form) const CONSTRUCTED_FLAG: u8 = 0b100000; @@ -30,6 +33,15 @@ pub trait FixedTag { const TAG: Tag; } +#[cfg(feature = "alloc")] +impl<'a, T> FixedTag for Cow<'a, T> +where + T: ToOwned + ?Sized, + &'a T: FixedTag, +{ + const TAG: Tag = <&'a T>::TAG; +} + /// Types which have an ASN.1 [`Tag`]. /// /// ## Example