summaryrefslogtreecommitdiff
path: root/src/asn1_types
diff options
context:
space:
mode:
Diffstat (limited to 'src/asn1_types')
-rw-r--r--src/asn1_types/any.rs429
-rw-r--r--src/asn1_types/bitstring.rs157
-rw-r--r--src/asn1_types/boolean.rs147
-rw-r--r--src/asn1_types/choice.rs21
-rw-r--r--src/asn1_types/embedded_pdv.rs125
-rw-r--r--src/asn1_types/end_of_content.rs55
-rw-r--r--src/asn1_types/enumerated.rs72
-rw-r--r--src/asn1_types/generalizedtime.rs303
-rw-r--r--src/asn1_types/integer.rs712
-rw-r--r--src/asn1_types/mod.rs26
-rw-r--r--src/asn1_types/null.rs99
-rw-r--r--src/asn1_types/object_descriptor.rs17
-rw-r--r--src/asn1_types/octetstring.rs157
-rw-r--r--src/asn1_types/oid.rs517
-rw-r--r--src/asn1_types/optional.rs87
-rw-r--r--src/asn1_types/real.rs462
-rw-r--r--src/asn1_types/real/f32.rs27
-rw-r--r--src/asn1_types/real/f64.rs27
-rw-r--r--src/asn1_types/sequence.rs398
-rw-r--r--src/asn1_types/sequence/iterator.rs106
-rw-r--r--src/asn1_types/sequence/sequence_of.rs150
-rw-r--r--src/asn1_types/sequence/vec.rs138
-rw-r--r--src/asn1_types/set.rs387
-rw-r--r--src/asn1_types/set/btreeset.rs124
-rw-r--r--src/asn1_types/set/hashset.rs125
-rw-r--r--src/asn1_types/set/iterator.rs22
-rw-r--r--src/asn1_types/set/set_of.rs150
-rw-r--r--src/asn1_types/strings.rs171
-rw-r--r--src/asn1_types/strings/bmpstring.rs132
-rw-r--r--src/asn1_types/strings/generalstring.rs14
-rw-r--r--src/asn1_types/strings/graphicstring.rs14
-rw-r--r--src/asn1_types/strings/ia5string.rs14
-rw-r--r--src/asn1_types/strings/numericstring.rs18
-rw-r--r--src/asn1_types/strings/printablestring.rs35
-rw-r--r--src/asn1_types/strings/str.rs67
-rw-r--r--src/asn1_types/strings/string.rs64
-rw-r--r--src/asn1_types/strings/teletexstring.rs18
-rw-r--r--src/asn1_types/strings/universalstring.rs137
-rw-r--r--src/asn1_types/strings/utf8string.rs13
-rw-r--r--src/asn1_types/strings/videotexstring.rs19
-rw-r--r--src/asn1_types/strings/visiblestring.rs18
-rw-r--r--src/asn1_types/tagged.rs128
-rw-r--r--src/asn1_types/tagged/application.rs42
-rw-r--r--src/asn1_types/tagged/builder.rs104
-rw-r--r--src/asn1_types/tagged/explicit.rs262
-rw-r--r--src/asn1_types/tagged/helpers.rs103
-rw-r--r--src/asn1_types/tagged/implicit.rs287
-rw-r--r--src/asn1_types/tagged/optional.rs240
-rw-r--r--src/asn1_types/tagged/parser.rs78
-rw-r--r--src/asn1_types/tagged/private.rs42
-rw-r--r--src/asn1_types/utctime.rs222
51 files changed, 7282 insertions, 0 deletions
diff --git a/src/asn1_types/any.rs b/src/asn1_types/any.rs
new file mode 100644
index 0000000..8628d7d
--- /dev/null
+++ b/src/asn1_types/any.rs
@@ -0,0 +1,429 @@
+use crate::ber::*;
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::string::String;
+use core::convert::{TryFrom, TryInto};
+
+/// The `Any` object is not strictly an ASN.1 type, but holds a generic description of any object
+/// that could be encoded.
+///
+/// It contains a header, and either a reference to or owned data for the object content.
+///
+/// Note: this type is only provided in **borrowed** version (*i.e.* it cannot own the inner data).
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Any<'a> {
+ /// The object header
+ pub header: Header<'a>,
+ /// The object contents
+ pub data: &'a [u8],
+}
+
+impl<'a> Any<'a> {
+ /// Create a new `Any` from BER/DER header and content
+ #[inline]
+ pub const fn new(header: Header<'a>, data: &'a [u8]) -> Self {
+ Any { header, data }
+ }
+
+ /// Create a new `Any` from a tag, and BER/DER content
+ #[inline]
+ pub const fn from_tag_and_data(tag: Tag, data: &'a [u8]) -> Self {
+ let constructed = matches!(tag, Tag::Sequence | Tag::Set);
+ Any {
+ header: Header {
+ tag,
+ constructed,
+ class: Class::Universal,
+ length: Length::Definite(data.len()),
+ raw_tag: None,
+ },
+ data,
+ }
+ }
+
+ /// Return the `Class` of this object
+ #[inline]
+ pub const fn class(&self) -> Class {
+ self.header.class
+ }
+
+ /// Update the class of the current object
+ #[inline]
+ pub fn with_class(self, class: Class) -> Self {
+ Any {
+ header: self.header.with_class(class),
+ ..self
+ }
+ }
+
+ /// Return the `Tag` of this object
+ #[inline]
+ pub const fn tag(&self) -> Tag {
+ self.header.tag
+ }
+
+ /// Update the tag of the current object
+ #[inline]
+ pub fn with_tag(self, tag: Tag) -> Self {
+ Any {
+ header: self.header.with_tag(tag),
+ data: self.data,
+ }
+ }
+
+ /// Get the bytes representation of the *content*
+ #[inline]
+ pub fn as_bytes(&'a self) -> &'a [u8] {
+ self.data
+ }
+
+ #[inline]
+ pub fn parse_ber<T>(&'a self) -> ParseResult<'a, T>
+ where
+ T: FromBer<'a>,
+ {
+ T::from_ber(self.data)
+ }
+
+ /// Parse a BER value and apply the provided parsing function to content
+ ///
+ /// After parsing, the sequence object and header are discarded.
+ pub fn from_ber_and_then<F, T, E>(
+ class: Class,
+ tag: u32,
+ bytes: &'a [u8],
+ op: F,
+ ) -> ParseResult<'a, T, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+ E: From<Error>,
+ {
+ let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+ any.tag()
+ .assert_eq(Tag(tag))
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ any.class()
+ .assert_eq(class)
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ let (_, res) = op(any.data)?;
+ Ok((rem, res))
+ }
+
+ /// Parse a DER value and apply the provided parsing function to content
+ ///
+ /// After parsing, the sequence object and header are discarded.
+ pub fn from_der_and_then<F, T, E>(
+ class: Class,
+ tag: u32,
+ bytes: &'a [u8],
+ op: F,
+ ) -> ParseResult<'a, T, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+ E: From<Error>,
+ {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ any.tag()
+ .assert_eq(Tag(tag))
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ any.class()
+ .assert_eq(class)
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ let (_, res) = op(any.data)?;
+ Ok((rem, res))
+ }
+
+ #[inline]
+ pub fn parse_der<T>(&'a self) -> ParseResult<'a, T>
+ where
+ T: FromDer<'a>,
+ {
+ T::from_der(self.data)
+ }
+
+ /// Get the content following a BER header
+ #[inline]
+ pub fn parse_ber_content<'i>(i: &'i [u8], header: &'_ Header) -> ParseResult<'i, &'i [u8]> {
+ header.parse_ber_content(i)
+ }
+
+ /// Get the content following a DER header
+ #[inline]
+ pub fn parse_der_content<'i>(i: &'i [u8], header: &'_ Header) -> ParseResult<'i, &'i [u8]> {
+ header.assert_definite()?;
+ DerParser::get_object_content(i, header, 8)
+ }
+}
+
+macro_rules! impl_any_into {
+ (IMPL $sname:expr, $fn_name:ident => $ty:ty, $asn1:expr) => {
+ #[doc = "Attempt to convert object to `"]
+ #[doc = $sname]
+ #[doc = "` (ASN.1 type: `"]
+ #[doc = $asn1]
+ #[doc = "`)."]
+ pub fn $fn_name(self) -> Result<$ty> {
+ self.try_into()
+ }
+ };
+ ($fn_name:ident => $ty:ty, $asn1:expr) => {
+ impl_any_into! {
+ IMPL stringify!($ty), $fn_name => $ty, $asn1
+ }
+ };
+}
+
+macro_rules! impl_any_as {
+ (IMPL $sname:expr, $fn_name:ident => $ty:ty, $asn1:expr) => {
+ #[doc = "Attempt to create ASN.1 type `"]
+ #[doc = $asn1]
+ #[doc = "` from this object."]
+ #[inline]
+ pub fn $fn_name(&self) -> Result<$ty> {
+ TryFrom::try_from(self)
+ }
+ };
+ ($fn_name:ident => $ty:ty, $asn1:expr) => {
+ impl_any_as! {
+ IMPL stringify!($ty), $fn_name => $ty, $asn1
+ }
+ };
+}
+
+impl<'a> Any<'a> {
+ impl_any_into!(bitstring => BitString<'a>, "BIT STRING");
+ impl_any_into!(bmpstring => BmpString<'a>, "BmpString");
+ impl_any_into!(bool => bool, "BOOLEAN");
+ impl_any_into!(boolean => Boolean, "BOOLEAN");
+ impl_any_into!(embedded_pdv => EmbeddedPdv<'a>, "EMBEDDED PDV");
+ impl_any_into!(enumerated => Enumerated, "ENUMERATED");
+ impl_any_into!(generalizedtime => GeneralizedTime, "GeneralizedTime");
+ impl_any_into!(generalstring => GeneralString<'a>, "GeneralString");
+ impl_any_into!(graphicstring => GraphicString<'a>, "GraphicString");
+ impl_any_into!(i8 => i8, "INTEGER");
+ impl_any_into!(i16 => i16, "INTEGER");
+ impl_any_into!(i32 => i32, "INTEGER");
+ impl_any_into!(i64 => i64, "INTEGER");
+ impl_any_into!(i128 => i128, "INTEGER");
+ impl_any_into!(ia5string => Ia5String<'a>, "IA5String");
+ impl_any_into!(integer => Integer<'a>, "INTEGER");
+ impl_any_into!(null => Null, "NULL");
+ impl_any_into!(numericstring => NumericString<'a>, "NumericString");
+ impl_any_into!(objectdescriptor => ObjectDescriptor<'a>, "ObjectDescriptor");
+ impl_any_into!(octetstring => OctetString<'a>, "OCTET STRING");
+ impl_any_into!(oid => Oid<'a>, "OBJECT IDENTIFIER");
+ /// Attempt to convert object to `Oid` (ASN.1 type: `RELATIVE-OID`).
+ pub fn relative_oid(self) -> Result<Oid<'a>> {
+ self.header.assert_tag(Tag::RelativeOid)?;
+ let asn1 = Cow::Borrowed(self.data);
+ Ok(Oid::new_relative(asn1))
+ }
+ impl_any_into!(printablestring => PrintableString<'a>, "PrintableString");
+ // XXX REAL
+ impl_any_into!(sequence => Sequence<'a>, "SEQUENCE");
+ impl_any_into!(set => Set<'a>, "SET");
+ impl_any_into!(str => &'a str, "UTF8String");
+ impl_any_into!(string => String, "UTF8String");
+ impl_any_into!(teletexstring => TeletexString<'a>, "TeletexString");
+ impl_any_into!(u8 => u8, "INTEGER");
+ impl_any_into!(u16 => u16, "INTEGER");
+ impl_any_into!(u32 => u32, "INTEGER");
+ impl_any_into!(u64 => u64, "INTEGER");
+ impl_any_into!(u128 => u128, "INTEGER");
+ impl_any_into!(universalstring => UniversalString<'a>, "UniversalString");
+ impl_any_into!(utctime => UtcTime, "UTCTime");
+ impl_any_into!(utf8string => Utf8String<'a>, "UTF8String");
+ impl_any_into!(videotexstring => VideotexString<'a>, "VideotexString");
+ impl_any_into!(visiblestring => VisibleString<'a>, "VisibleString");
+
+ impl_any_as!(as_bitstring => BitString, "BITSTRING");
+ impl_any_as!(as_bool => bool, "BOOLEAN");
+ impl_any_as!(as_boolean => Boolean, "BOOLEAN");
+ impl_any_as!(as_embedded_pdv => EmbeddedPdv, "EMBEDDED PDV");
+ impl_any_as!(as_endofcontent => EndOfContent, "END OF CONTENT (not a real ASN.1 type)");
+ impl_any_as!(as_enumerated => Enumerated, "ENUMERATED");
+ impl_any_as!(as_generalizedtime => GeneralizedTime, "GeneralizedTime");
+ impl_any_as!(as_generalstring => GeneralizedTime, "GeneralString");
+ impl_any_as!(as_graphicstring => GraphicString, "GraphicString");
+ impl_any_as!(as_i8 => i8, "INTEGER");
+ impl_any_as!(as_i16 => i16, "INTEGER");
+ impl_any_as!(as_i32 => i32, "INTEGER");
+ impl_any_as!(as_i64 => i64, "INTEGER");
+ impl_any_as!(as_i128 => i128, "INTEGER");
+ impl_any_as!(as_ia5string => Ia5String, "IA5String");
+ impl_any_as!(as_integer => Integer, "INTEGER");
+ impl_any_as!(as_null => Null, "NULL");
+ impl_any_as!(as_numericstring => NumericString, "NumericString");
+ impl_any_as!(as_objectdescriptor => ObjectDescriptor, "OBJECT IDENTIFIER");
+ impl_any_as!(as_octetstring => OctetString, "OCTET STRING");
+ impl_any_as!(as_oid => Oid, "OBJECT IDENTIFIER");
+ /// Attempt to create ASN.1 type `RELATIVE-OID` from this object.
+ pub fn as_relative_oid(&self) -> Result<Oid<'a>> {
+ self.header.assert_tag(Tag::RelativeOid)?;
+ let asn1 = Cow::Borrowed(self.data);
+ Ok(Oid::new_relative(asn1))
+ }
+ impl_any_as!(as_printablestring => PrintableString, "PrintableString");
+ impl_any_as!(as_sequence => Sequence, "SEQUENCE");
+ impl_any_as!(as_set => Set, "SET");
+ impl_any_as!(as_str => &str, "UTF8String");
+ impl_any_as!(as_string => String, "UTF8String");
+ impl_any_as!(as_teletexstring => TeletexString, "TeletexString");
+ impl_any_as!(as_u8 => u8, "INTEGER");
+ impl_any_as!(as_u16 => u16, "INTEGER");
+ impl_any_as!(as_u32 => u32, "INTEGER");
+ impl_any_as!(as_u64 => u64, "INTEGER");
+ impl_any_as!(as_u128 => u128, "INTEGER");
+ impl_any_as!(as_universalstring => UniversalString, "UniversalString");
+ impl_any_as!(as_utctime => UtcTime, "UTCTime");
+ impl_any_as!(as_utf8string => Utf8String, "UTF8String");
+ impl_any_as!(as_videotexstring => VideotexString, "VideotexString");
+ impl_any_as!(as_visiblestring => VisibleString, "VisibleString");
+
+ /// Attempt to create an `Option<T>` from this object.
+ pub fn as_optional<'b, T>(&'b self) -> Result<Option<T>>
+ where
+ T: TryFrom<&'b Any<'a>, Error = Error>,
+ 'a: 'b,
+ {
+ match TryFrom::try_from(self) {
+ Ok(t) => Ok(Some(t)),
+ Err(Error::UnexpectedTag { .. }) => Ok(None),
+ Err(e) => Err(e),
+ }
+ }
+
+ /// Attempt to create a tagged value (EXPLICIT) from this object.
+ pub fn as_tagged_explicit<T, E, const CLASS: u8, const TAG: u32>(
+ &self,
+ ) -> Result<TaggedValue<T, E, Explicit, CLASS, TAG>, E>
+ where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+ {
+ TryFrom::try_from(self)
+ }
+
+ /// Attempt to create a tagged value (IMPLICIT) from this object.
+ pub fn as_tagged_implicit<T, E, const CLASS: u8, const TAG: u32>(
+ &self,
+ ) -> Result<TaggedValue<T, E, Implicit, CLASS, TAG>, E>
+ where
+ T: TryFrom<Any<'a>, Error = E>,
+ T: Tagged,
+ E: From<Error>,
+ {
+ TryFrom::try_from(self)
+ }
+}
+
+impl<'a> FromBer<'a> for Any<'a> {
+ fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+ let (i, header) = Header::from_ber(bytes)?;
+ let (i, data) = BerParser::get_object_content(i, &header, MAX_RECURSION)?;
+ Ok((i, Any { header, data }))
+ }
+}
+
+impl<'a> FromDer<'a> for Any<'a> {
+ fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
+ let (i, header) = Header::from_der(bytes)?;
+ // X.690 section 10.1: The definite form of length encoding shall be used
+ header.length.assert_definite()?;
+ let (i, data) = DerParser::get_object_content(i, &header, MAX_RECURSION)?;
+ Ok((i, Any { header, data }))
+ }
+}
+
+impl CheckDerConstraints for Any<'_> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length().assert_definite()?;
+ // if len < 128, must use short form (10.1: minimum number of octets)
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for Any<'_> {}
+
+impl DynTagged for Any<'_> {
+ fn tag(&self) -> Tag {
+ self.tag()
+ }
+}
+
+// impl<'a> ToStatic for Any<'a> {
+// type Owned = Any<'static>;
+
+// fn to_static(&self) -> Self::Owned {
+// Any {
+// header: self.header.to_static(),
+// data: Cow::Owned(self.data.to_vec()),
+// }
+// }
+// }
+
+#[cfg(feature = "std")]
+impl ToDer for Any<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let hdr_len = self.header.to_der_len()?;
+ Ok(hdr_len + self.data.len())
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ // create fake header to have correct length
+ let header = Header::new(
+ self.header.class,
+ self.header.constructed,
+ self.header.tag,
+ Length::Definite(self.data.len()),
+ );
+ let sz = header.write_der_header(writer)?;
+ Ok(sz)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(self.data).map_err(Into::into)
+ }
+
+ /// Similar to using `to_der`, but uses header without computing length value
+ fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let sz = self.header.write_der_header(writer)?;
+ let sz = sz + writer.write(self.data)?;
+ Ok(sz)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::*;
+ use hex_literal::hex;
+
+ #[test]
+ fn methods_any() {
+ let header = Header::new_simple(Tag::Integer);
+ let any = Any::new(header, &[])
+ .with_class(Class::ContextSpecific)
+ .with_tag(Tag(0));
+ assert_eq!(any.as_bytes(), &[]);
+
+ let input = &hex! {"80 03 02 01 01"};
+ let (_, any) = Any::from_ber(input).expect("parsing failed");
+
+ let (_, r) = any.parse_ber::<Integer>().expect("parse_ber failed");
+ assert_eq!(r.as_u32(), Ok(1));
+ let (_, r) = any.parse_der::<Integer>().expect("parse_der failed");
+ assert_eq!(r.as_u32(), Ok(1));
+
+ let header = &any.header;
+ let (_, content) = Any::parse_ber_content(&input[2..], header).unwrap();
+ assert_eq!(content.len(), 3);
+ let (_, content) = Any::parse_der_content(&input[2..], header).unwrap();
+ assert_eq!(content.len(), 3);
+
+ let (_, any) = Any::from_der(&input[2..]).unwrap();
+ Any::check_constraints(&any).unwrap();
+ assert_eq!(<Any as DynTagged>::tag(&any), any.tag());
+ let int = any.integer().unwrap();
+ assert_eq!(int.as_u16(), Ok(1));
+ }
+}
diff --git a/src/asn1_types/bitstring.rs b/src/asn1_types/bitstring.rs
new file mode 100644
index 0000000..fad03cf
--- /dev/null
+++ b/src/asn1_types/bitstring.rs
@@ -0,0 +1,157 @@
+use crate::*;
+use alloc::borrow::Cow;
+#[cfg(feature = "bits")]
+use bitvec::{order::Msb0, slice::BitSlice};
+use core::convert::TryFrom;
+
+/// ASN.1 `BITSTRING` type
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct BitString<'a> {
+ pub unused_bits: u8,
+ pub data: Cow<'a, [u8]>,
+}
+
+impl<'a> BitString<'a> {
+ // Length must be >= 1 (first byte is number of ignored bits)
+ pub const fn new(unused_bits: u8, s: &'a [u8]) -> Self {
+ BitString {
+ unused_bits,
+ data: Cow::Borrowed(s),
+ }
+ }
+
+ /// Test if bit `bitnum` is set
+ pub fn is_set(&self, bitnum: usize) -> bool {
+ let byte_pos = bitnum / 8;
+ if byte_pos >= self.data.len() {
+ return false;
+ }
+ let b = 7 - (bitnum % 8);
+ (self.data[byte_pos] & (1 << b)) != 0
+ }
+
+ /// Constructs a shared `&BitSlice` reference over the object data.
+ #[cfg(feature = "bits")]
+ pub fn as_bitslice(&self) -> Option<&BitSlice<u8, Msb0>> {
+ BitSlice::<_, Msb0>::try_from_slice(&self.data).ok()
+ }
+}
+
+impl<'a> AsRef<[u8]> for BitString<'a> {
+ fn as_ref(&self) -> &[u8] {
+ &self.data
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for BitString<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<BitString<'a>> {
+ TryFrom::try_from(&any)
+ }
+}
+
+// non-consuming version
+impl<'a, 'b> TryFrom<&'b Any<'a>> for BitString<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<BitString<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+ if any.data.is_empty() {
+ return Err(Error::InvalidLength);
+ }
+ let s = any.data;
+ let (unused_bits, data) = (s[0], Cow::Borrowed(&s[1..]));
+ Ok(BitString { unused_bits, data })
+ }
+}
+
+impl<'a> CheckDerConstraints for BitString<'a> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ // X.690 section 10.2
+ any.header.assert_primitive()?;
+ // Check that padding bits are all 0 (X.690 section 11.2.1)
+ match any.data.len() {
+ 0 => Err(Error::InvalidLength),
+ 1 => {
+ // X.690 section 11.2.2 Note 2
+ if any.data[0] == 0 {
+ Ok(())
+ } else {
+ Err(Error::InvalidLength)
+ }
+ }
+ len => {
+ let unused_bits = any.data[0];
+ let last_byte = any.data[len - 1];
+ if last_byte.trailing_zeros() < unused_bits as u32 {
+ return Err(Error::DerConstraintFailed(DerConstraint::UnusedBitsNotZero));
+ }
+
+ Ok(())
+ }
+ }
+ }
+}
+
+impl DerAutoDerive for BitString<'_> {}
+
+impl<'a> Tagged for BitString<'a> {
+ const TAG: Tag = Tag::BitString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for BitString<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.data.len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + 1 (unused bits) + len
+ Ok(3 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + 1 (unused bits) + len
+ let n = Length::Definite(sz + 1).to_der_len()?;
+ Ok(2 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(1 + self.data.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let sz = writer.write(&[self.unused_bits])?;
+ let sz = sz + writer.write(&self.data)?;
+ Ok(sz)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::BitString;
+
+ #[test]
+ fn test_bitstring_is_set() {
+ let obj = BitString::new(0, &[0x0f, 0x00, 0x40]);
+ assert!(!obj.is_set(0));
+ assert!(obj.is_set(7));
+ assert!(!obj.is_set(9));
+ assert!(obj.is_set(17));
+ }
+
+ #[cfg(feature = "bits")]
+ #[test]
+ fn test_bitstring_to_bitvec() {
+ let obj = BitString::new(0, &[0x0f, 0x00, 0x40]);
+ let bv = obj.as_bitslice().expect("could not get bitslice");
+ assert_eq!(bv.get(0).as_deref(), Some(&false));
+ assert_eq!(bv.get(7).as_deref(), Some(&true));
+ assert_eq!(bv.get(9).as_deref(), Some(&false));
+ assert_eq!(bv.get(17).as_deref(), Some(&true));
+ }
+}
diff --git a/src/asn1_types/boolean.rs b/src/asn1_types/boolean.rs
new file mode 100644
index 0000000..ed620e4
--- /dev/null
+++ b/src/asn1_types/boolean.rs
@@ -0,0 +1,147 @@
+use crate::*;
+use core::convert::TryFrom;
+
+/// ASN.1 `BOOLEAN` type
+///
+/// BER objects consider any non-zero value as `true`, and `0` as `false`.
+///
+/// DER objects must use value `0x0` (`false`) or `0xff` (`true`).
+#[derive(Debug, PartialEq, Eq)]
+pub struct Boolean {
+ pub value: u8,
+}
+
+impl Boolean {
+ /// `BOOLEAN` object for value `false`
+ pub const FALSE: Boolean = Boolean::new(0);
+ /// `BOOLEAN` object for value `true`
+ pub const TRUE: Boolean = Boolean::new(0xff);
+
+ /// Create a new `Boolean` from the provided logical value.
+ #[inline]
+ pub const fn new(value: u8) -> Self {
+ Boolean { value }
+ }
+
+ /// Return the `bool` value from this object.
+ #[inline]
+ pub const fn bool(&self) -> bool {
+ self.value != 0
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Boolean {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Boolean> {
+ TryFrom::try_from(&any)
+ }
+}
+
+// non-consuming version
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Boolean {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Boolean> {
+ any.tag().assert_eq(Self::TAG)?;
+ // X.690 section 8.2.1:
+ // The encoding of a boolean value shall be primitive. The contents octets shall consist of a single octet
+ if any.header.length != Length::Definite(1) {
+ return Err(Error::InvalidLength);
+ }
+ let value = any.data[0];
+ Ok(Boolean { value })
+ }
+}
+
+impl CheckDerConstraints for Boolean {
+ fn check_constraints(any: &Any) -> Result<()> {
+ let c = any.data[0];
+ // X.690 section 11.1
+ if !(c == 0 || c == 0xff) {
+ return Err(Error::DerConstraintFailed(DerConstraint::InvalidBoolean));
+ }
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for Boolean {}
+
+impl Tagged for Boolean {
+ const TAG: Tag = Tag::Boolean;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Boolean {
+ fn to_der_len(&self) -> Result<usize> {
+ // 3 = 1 (tag) + 1 (length) + 1 (value)
+ Ok(3)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&[Self::TAG.0 as u8, 0x01]).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let b = if self.value != 0 { 0xff } else { 0x00 };
+ writer.write(&[b]).map_err(Into::into)
+ }
+
+ /// Similar to using `to_der`, but uses header without computing length value
+ fn write_der_raw(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let sz = writer.write(&[Self::TAG.0 as u8, 0x01, self.value])?;
+ Ok(sz)
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for bool {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<bool> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for bool {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<bool> {
+ any.tag().assert_eq(Self::TAG)?;
+ let b = Boolean::try_from(any)?;
+ Ok(b.bool())
+ }
+}
+
+impl CheckDerConstraints for bool {
+ fn check_constraints(any: &Any) -> Result<()> {
+ let c = any.data[0];
+ // X.690 section 11.1
+ if !(c == 0 || c == 0xff) {
+ return Err(Error::DerConstraintFailed(DerConstraint::InvalidBoolean));
+ }
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for bool {}
+
+impl Tagged for bool {
+ const TAG: Tag = Tag::Boolean;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for bool {
+ fn to_der_len(&self) -> Result<usize> {
+ // 3 = 1 (tag) + 1 (length) + 1 (value)
+ Ok(3)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&[Self::TAG.0 as u8, 0x01]).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let b = if *self { 0xff } else { 0x00 };
+ writer.write(&[b]).map_err(Into::into)
+ }
+}
diff --git a/src/asn1_types/choice.rs b/src/asn1_types/choice.rs
new file mode 100644
index 0000000..4bc01cd
--- /dev/null
+++ b/src/asn1_types/choice.rs
@@ -0,0 +1,21 @@
+use crate::{FromBer, FromDer, Tag, Tagged};
+
+pub trait Choice {
+ /// Is the provided [`Tag`] decodable as a variant of this `CHOICE`?
+ fn can_decode(tag: Tag) -> bool;
+}
+
+/// This blanket impl allows any [`Tagged`] type to function as a [`Choice`]
+/// with a single alternative.
+impl<T> Choice for T
+where
+ T: Tagged,
+{
+ fn can_decode(tag: Tag) -> bool {
+ T::TAG == tag
+ }
+}
+
+pub trait BerChoice<'a>: Choice + FromBer<'a> {}
+
+pub trait DerChoice<'a>: Choice + FromDer<'a> {}
diff --git a/src/asn1_types/embedded_pdv.rs b/src/asn1_types/embedded_pdv.rs
new file mode 100644
index 0000000..9452856
--- /dev/null
+++ b/src/asn1_types/embedded_pdv.rs
@@ -0,0 +1,125 @@
+use crate::*;
+use core::convert::TryFrom;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct EmbeddedPdv<'a> {
+ pub identification: PdvIdentification<'a>,
+ pub data_value_descriptor: Option<ObjectDescriptor<'a>>,
+ pub data_value: &'a [u8],
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum PdvIdentification<'a> {
+ Syntaxes {
+ s_abstract: Oid<'a>,
+ s_transfer: Oid<'a>,
+ },
+ Syntax(Oid<'a>),
+ PresentationContextId(Integer<'a>),
+ ContextNegotiation {
+ presentation_context_id: Integer<'a>,
+ presentation_syntax: Oid<'a>,
+ },
+ TransferSyntax(Oid<'a>),
+ Fixed,
+}
+
+impl<'a> TryFrom<Any<'a>> for EmbeddedPdv<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for EmbeddedPdv<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Self> {
+ let data = any.data;
+ // AUTOMATIC TAGS means all values will be tagged (IMPLICIT)
+ // [0] -> identification
+ let (rem, seq0) =
+ TaggedParser::<Explicit, Any>::parse_ber(Class::ContextSpecific, Tag(0), data)?;
+ let inner = seq0.inner;
+ let identification = match inner.tag() {
+ Tag(0) => {
+ // syntaxes SEQUENCE {
+ // abstract OBJECT IDENTIFIER,
+ // transfer OBJECT IDENTIFIER
+ // },
+ // AUTOMATIC tags -> implicit! Hopefully, Oid does not check tag value!
+ let (rem, s_abstract) = Oid::from_ber(inner.data)?;
+ let (_, s_transfer) = Oid::from_ber(rem)?;
+ PdvIdentification::Syntaxes {
+ s_abstract,
+ s_transfer,
+ }
+ }
+ Tag(1) => {
+ // syntax OBJECT IDENTIFIER
+ let oid = Oid::new(inner.data.into());
+ PdvIdentification::Syntax(oid)
+ }
+ Tag(2) => {
+ // presentation-context-id INTEGER
+ let i = Integer::new(inner.data);
+ PdvIdentification::PresentationContextId(i)
+ }
+ Tag(3) => {
+ // context-negotiation SEQUENCE {
+ // presentation-context-id INTEGER,
+ // transfer-syntax OBJECT IDENTIFIER
+ // },
+ // AUTOMATIC tags -> implicit!
+ let (rem, any) = Any::from_ber(inner.data)?;
+ let presentation_context_id = Integer::new(any.data);
+ let (_, presentation_syntax) = Oid::from_ber(rem)?;
+ PdvIdentification::ContextNegotiation {
+ presentation_context_id,
+ presentation_syntax,
+ }
+ }
+ Tag(4) => {
+ // transfer-syntax OBJECT IDENTIFIER
+ let oid = Oid::new(inner.data.into());
+ PdvIdentification::TransferSyntax(oid)
+ }
+ Tag(5) => {
+ // fixed NULL
+ PdvIdentification::Fixed
+ }
+ _ => {
+ return Err(inner
+ .tag()
+ .invalid_value("Invalid identification tag in EMBEDDED PDV"))
+ }
+ };
+ // [1] -> data-value-descriptor ObjectDescriptor OPTIONAL
+ // *BUT* WITH COMPONENTS data-value-descriptor ABSENT
+ // XXX this should be parse_ber?
+ // let (rem, data_value_descriptor) =
+ // TaggedOptional::from(1).parse_der(rem, |_, inner| ObjectDescriptor::from_ber(inner))?;
+ let (rem, data_value_descriptor) = (rem, None);
+ // [2] -> data-value OCTET STRING
+ let (_, data_value) =
+ TaggedParser::<Implicit, &[u8]>::parse_ber(Class::ContextSpecific, Tag(2), rem)?;
+ let data_value = data_value.inner;
+ let obj = EmbeddedPdv {
+ identification,
+ data_value_descriptor,
+ data_value,
+ };
+ Ok(obj)
+ }
+}
+
+impl CheckDerConstraints for EmbeddedPdv<'_> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length().assert_definite()?;
+ any.header.assert_constructed()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for EmbeddedPdv<'_> {}
diff --git a/src/asn1_types/end_of_content.rs b/src/asn1_types/end_of_content.rs
new file mode 100644
index 0000000..4a9d291
--- /dev/null
+++ b/src/asn1_types/end_of_content.rs
@@ -0,0 +1,55 @@
+use crate::{Any, Error, Result, Tag, Tagged};
+use core::convert::TryFrom;
+
+/// End-of-contents octets
+///
+/// `EndOfContent` is not a BER type, but represents a marked to indicate the end of contents
+/// of an object, when the length is `Indefinite` (see X.690 section 8.1.5).
+///
+/// This type cannot exist in DER, and so provides no `FromDer`/`ToDer` implementation.
+#[derive(Debug)]
+pub struct EndOfContent {}
+
+impl EndOfContent {
+ pub const fn new() -> Self {
+ EndOfContent {}
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for EndOfContent {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<EndOfContent> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for EndOfContent {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<EndOfContent> {
+ any.tag().assert_eq(Self::TAG)?;
+ if !any.header.length.is_null() {
+ return Err(Error::InvalidLength);
+ }
+ Ok(EndOfContent {})
+ }
+}
+
+impl Tagged for EndOfContent {
+ const TAG: Tag = Tag::EndOfContent;
+}
+
+// impl ToDer for EndOfContent {
+// fn to_der_len(&self) -> Result<usize> {
+// Ok(2)
+// }
+
+// fn write_der_header(&self, writer: &mut dyn std::io::Write) -> crate::SerializeResult<usize> {
+// writer.write(&[Self::TAG.0 as u8, 0x00]).map_err(Into::into)
+// }
+
+// fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> crate::SerializeResult<usize> {
+// Ok(0)
+// }
+// }
diff --git a/src/asn1_types/enumerated.rs b/src/asn1_types/enumerated.rs
new file mode 100644
index 0000000..5f843a8
--- /dev/null
+++ b/src/asn1_types/enumerated.rs
@@ -0,0 +1,72 @@
+use crate::ber::bytes_to_u64;
+use crate::*;
+use core::convert::TryFrom;
+
+/// ASN.1 `ENUMERATED` type
+///
+/// # Limitations
+///
+/// Supported values are limited to 0 .. 2^32
+#[derive(Debug, PartialEq, Eq)]
+pub struct Enumerated(pub u32);
+
+impl Enumerated {
+ pub const fn new(value: u32) -> Self {
+ Enumerated(value)
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Enumerated {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Enumerated> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Enumerated {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Enumerated> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ let res_u64 = bytes_to_u64(any.data)?;
+ if res_u64 > (<u32>::MAX as u64) {
+ return Err(Error::IntegerTooLarge);
+ }
+ let value = res_u64 as u32;
+ Ok(Enumerated(value))
+ }
+}
+
+impl CheckDerConstraints for Enumerated {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length.assert_definite()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for Enumerated {}
+
+impl Tagged for Enumerated {
+ const TAG: Tag = Tag::Enumerated;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Enumerated {
+ fn to_der_len(&self) -> Result<usize> {
+ Integer::from(self.0).to_der_len()
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let i = Integer::from(self.0);
+ let len = i.data.len();
+ let header = Header::new(Class::Universal, false, Self::TAG, Length::Definite(len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(self.0);
+ int.write_der_content(writer).map_err(Into::into)
+ }
+}
diff --git a/src/asn1_types/generalizedtime.rs b/src/asn1_types/generalizedtime.rs
new file mode 100644
index 0000000..6e039d8
--- /dev/null
+++ b/src/asn1_types/generalizedtime.rs
@@ -0,0 +1,303 @@
+use crate::datetime::decode_decimal;
+use crate::*;
+use alloc::format;
+use alloc::string::String;
+use core::convert::TryFrom;
+use core::fmt;
+#[cfg(feature = "datetime")]
+use time::OffsetDateTime;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct GeneralizedTime(pub ASN1DateTime);
+
+impl GeneralizedTime {
+ pub const fn new(datetime: ASN1DateTime) -> Self {
+ GeneralizedTime(datetime)
+ }
+
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ // X.680 section 42 defines a GeneralizedTime as a VisibleString restricted to:
+ //
+ // a) a string representing the calendar date, as specified in ISO 8601, with a four-digit representation of the
+ // year, a two-digit representation of the month and a two-digit representation of the day, without use of
+ // separators, followed by a string representing the time of day, as specified in ISO 8601, without separators
+ // other than decimal comma or decimal period (as provided for in ISO 8601), and with no terminating Z (as
+ // provided for in ISO 8601); or
+ // b) the characters in a) above followed by an upper-case letter Z ; or
+ // c) he characters in a) above followed by a string representing a local time differential, as specified in
+ // ISO 8601, without separators.
+ let (year, month, day, hour, minute, rem) = match bytes {
+ [year1, year2, year3, year4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] =>
+ {
+ let year_hi = decode_decimal(Self::TAG, *year1, *year2)?;
+ let year_lo = decode_decimal(Self::TAG, *year3, *year4)?;
+ let year = (year_hi as u32) * 100 + (year_lo as u32);
+ let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
+ let day = decode_decimal(Self::TAG, *day1, *day2)?;
+ let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
+ let minute = decode_decimal(Self::TAG, *min1, *min2)?;
+ (year, month, day, hour, minute, rem)
+ }
+ _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
+ };
+ if rem.is_empty() {
+ return Err(Self::TAG.invalid_value("malformed time string"));
+ }
+ // check for seconds
+ let (second, rem) = match rem {
+ [sec1, sec2, rem @ ..] => {
+ let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
+ (second, rem)
+ }
+ _ => (0, rem),
+ };
+ if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
+ // eprintln!("GeneralizedTime: time checks failed");
+ // eprintln!(" month:{}", month);
+ // eprintln!(" day:{}", day);
+ // eprintln!(" hour:{}", hour);
+ // eprintln!(" minute:{}", minute);
+ // eprintln!(" second:{}", second);
+ return Err(Self::TAG.invalid_value("time components with invalid values"));
+ }
+ if rem.is_empty() {
+ // case a): no fractional seconds part, and no terminating Z
+ return Ok(GeneralizedTime(ASN1DateTime::new(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ None,
+ ASN1TimeZone::Undefined,
+ )));
+ }
+ // check for fractional seconds
+ let (millisecond, rem) = match rem {
+ [b'.' | b',', rem @ ..] => {
+ let mut fsecond = 0;
+ let mut rem = rem;
+ let mut digits = 0;
+ for idx in 0..=4 {
+ if rem.is_empty() {
+ if idx == 0 {
+ // dot or comma, but no following digit
+ return Err(Self::TAG.invalid_value(
+ "malformed time string (dot or comma but no digits)",
+ ));
+ }
+ digits = idx;
+ break;
+ }
+ if idx == 4 {
+ return Err(
+ Self::TAG.invalid_value("malformed time string (invalid milliseconds)")
+ );
+ }
+ match rem[0] {
+ b'0'..=b'9' => {
+ // cannot overflow, max 4 digits will be read
+ fsecond = fsecond * 10 + (rem[0] - b'0') as u16;
+ }
+ b'Z' | b'+' | b'-' => {
+ digits = idx;
+ break;
+ }
+ _ => {
+ return Err(Self::TAG.invalid_value(
+ "malformed time string (invalid milliseconds/timezone)",
+ ))
+ }
+ }
+ rem = &rem[1..];
+ }
+ // fix fractional seconds depending on the number of digits
+ // for ex, date "xxxx.3" means 3000 milliseconds, not 3
+ let fsecond = match digits {
+ 1 => fsecond * 100,
+ 2 => fsecond * 10,
+ _ => fsecond,
+ };
+ (Some(fsecond), rem)
+ }
+ _ => (None, rem),
+ };
+ // check timezone
+ if rem.is_empty() {
+ // case a): fractional seconds part, and no terminating Z
+ return Ok(GeneralizedTime(ASN1DateTime::new(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ millisecond,
+ ASN1TimeZone::Undefined,
+ )));
+ }
+ let tz = match rem {
+ [b'Z'] => ASN1TimeZone::Z,
+ [b'+', h1, h2, m1, m2] => {
+ let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+ let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+ ASN1TimeZone::Offset(hh as i8, mm as i8)
+ }
+ [b'-', h1, h2, m1, m2] => {
+ let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+ let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+ ASN1TimeZone::Offset(-(hh as i8), mm as i8)
+ }
+ _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
+ };
+ Ok(GeneralizedTime(ASN1DateTime::new(
+ year,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ millisecond,
+ tz,
+ )))
+ }
+
+ /// Return a ISO 8601 combined date and time with time zone.
+ #[cfg(feature = "datetime")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+ pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
+ self.0.to_datetime()
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for GeneralizedTime {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<GeneralizedTime> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for GeneralizedTime {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<GeneralizedTime> {
+ any.tag().assert_eq(Self::TAG)?;
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_visible(b: &u8) -> bool {
+ 0x20 <= *b && *b <= 0x7f
+ }
+ if !any.data.iter().all(is_visible) {
+ return Err(Error::StringInvalidCharset);
+ }
+
+ GeneralizedTime::from_bytes(any.data)
+ }
+}
+
+impl fmt::Display for GeneralizedTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let dt = &self.0;
+ let fsec = match self.0.millisecond {
+ Some(v) => format!(".{}", v),
+ None => String::new(),
+ };
+ match dt.tz {
+ ASN1TimeZone::Undefined => write!(
+ f,
+ "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}",
+ dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
+ ),
+ ASN1TimeZone::Z => write!(
+ f,
+ "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}Z",
+ dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec
+ ),
+ ASN1TimeZone::Offset(hh, mm) => {
+ let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
+ write!(
+ f,
+ "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{}{:02}{:02}",
+ dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, fsec, s, hh, mm
+ )
+ }
+ }
+ }
+}
+
+impl CheckDerConstraints for GeneralizedTime {
+ fn check_constraints(any: &Any) -> Result<()> {
+ // X.690 section 11.7.1: The encoding shall terminate with a "Z"
+ if any.data.last() != Some(&b'Z') {
+ return Err(Error::DerConstraintFailed(DerConstraint::MissingTimeZone));
+ }
+ // X.690 section 11.7.2: The seconds element shall always be present.
+ // XXX
+ // X.690 section 11.7.4: The decimal point element, if present, shall be the point option "."
+ if any.data.iter().any(|&b| b == b',') {
+ return Err(Error::DerConstraintFailed(DerConstraint::MissingSeconds));
+ }
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for GeneralizedTime {}
+
+impl Tagged for GeneralizedTime {
+ const TAG: Tag = Tag::GeneralizedTime;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for GeneralizedTime {
+ fn to_der_len(&self) -> Result<usize> {
+ // data:
+ // - 8 bytes for YYYYMMDD
+ // - 6 for hhmmss in DER (X.690 section 11.7.2)
+ // - (variable) the fractional part, without trailing zeros, with a point "."
+ // - 1 for the character Z in DER (X.690 section 11.7.1)
+ // data length: 15 + fractional part
+ //
+ // thus, length will always be on 1 byte (short length) and
+ // class+structure+tag also on 1
+ //
+ // total: = 1 (class+constructed+tag) + 1 (length) + 15 + fractional
+ let num_digits = match self.0.millisecond {
+ None => 0,
+ Some(v) => 1 + v.to_string().len(),
+ };
+ Ok(2 + 15 + num_digits)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ // see above for length value
+ let num_digits = match self.0.millisecond {
+ None => 0,
+ Some(v) => 1 + v.to_string().len() as u8,
+ };
+ writer
+ .write(&[Self::TAG.0 as u8, 15 + num_digits])
+ .map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let fractional = match self.0.millisecond {
+ None => "".to_string(),
+ Some(v) => format!(".{}", v),
+ };
+ let num_digits = fractional.len();
+ write!(
+ writer,
+ "{:04}{:02}{:02}{:02}{:02}{:02}{}Z",
+ self.0.year,
+ self.0.month,
+ self.0.day,
+ self.0.hour,
+ self.0.minute,
+ self.0.second,
+ fractional,
+ )?;
+ // write_fmt returns (), see above for length value
+ Ok(15 + num_digits)
+ }
+}
diff --git a/src/asn1_types/integer.rs b/src/asn1_types/integer.rs
new file mode 100644
index 0000000..59f846a
--- /dev/null
+++ b/src/asn1_types/integer.rs
@@ -0,0 +1,712 @@
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::vec;
+use core::convert::{TryFrom, TryInto};
+
+#[cfg(feature = "bigint")]
+#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
+pub use num_bigint::{BigInt, BigUint, Sign};
+
+/// Decode an unsigned integer into a big endian byte slice with all leading
+/// zeroes removed (if positive) and extra 0xff remove (if negative)
+fn trim_slice<'a>(any: &'a Any<'_>) -> Result<&'a [u8]> {
+ let bytes = any.data;
+
+ if bytes.is_empty() || (bytes[0] != 0x00 && bytes[0] != 0xff) {
+ return Ok(bytes);
+ }
+
+ match bytes.iter().position(|&b| b != 0) {
+ // first byte is not 0
+ Some(0) => (),
+ // all bytes are 0
+ None => return Ok(&bytes[bytes.len() - 1..]),
+ Some(first) => return Ok(&bytes[first..]),
+ }
+
+ // same for negative integers : skip byte 0->n if byte 0->n = 0xff AND byte n+1 >= 0x80
+ match bytes.windows(2).position(|s| match s {
+ &[a, b] => !(a == 0xff && b >= 0x80),
+ _ => true,
+ }) {
+ // first byte is not 0xff
+ Some(0) => (),
+ // all bytes are 0xff
+ None => return Ok(&bytes[bytes.len() - 1..]),
+ Some(first) => return Ok(&bytes[first..]),
+ }
+
+ Ok(bytes)
+}
+
+/// Decode an unsigned integer into a byte array of the requested size
+/// containing a big endian integer.
+fn decode_array_uint<const N: usize>(any: &Any<'_>) -> Result<[u8; N]> {
+ if is_highest_bit_set(any.data) {
+ return Err(Error::IntegerNegative);
+ }
+ let input = trim_slice(any)?;
+
+ if input.len() > N {
+ return Err(Error::IntegerTooLarge);
+ }
+
+ // Input has leading zeroes removed, so we need to add them back
+ let mut output = [0u8; N];
+ assert!(input.len() <= N);
+ output[N.saturating_sub(input.len())..].copy_from_slice(input);
+ Ok(output)
+}
+
+/// Decode an unsigned integer of the specified size.
+///
+/// Returns a byte array of the requested size containing a big endian integer.
+fn decode_array_int<const N: usize>(any: &Any<'_>) -> Result<[u8; N]> {
+ if any.data.len() > N {
+ return Err(Error::IntegerTooLarge);
+ }
+
+ // any.tag().assert_eq(Tag::Integer)?;
+ let mut output = [0xFFu8; N];
+ let offset = N.saturating_sub(any.as_bytes().len());
+ output[offset..].copy_from_slice(any.as_bytes());
+ Ok(output)
+}
+
+/// Is the highest bit of the first byte in the slice 1? (if present)
+#[inline]
+fn is_highest_bit_set(bytes: &[u8]) -> bool {
+ bytes
+ .first()
+ .map(|byte| byte & 0b10000000 != 0)
+ .unwrap_or(false)
+}
+
+macro_rules! impl_int {
+ ($uint:ty => $int:ty) => {
+ impl<'a> TryFrom<Any<'a>> for $int {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ TryFrom::try_from(&any)
+ }
+ }
+
+ impl<'a, 'b> TryFrom<&'b Any<'a>> for $int {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ let uint = if is_highest_bit_set(any.as_bytes()) {
+ <$uint>::from_be_bytes(decode_array_int(&any)?)
+ } else {
+ // read as uint, but check if the value will fit in a signed integer
+ let u = <$uint>::from_be_bytes(decode_array_uint(&any)?);
+ if u > <$int>::MAX as $uint {
+ return Err(Error::IntegerTooLarge);
+ }
+ u
+ };
+ Ok(uint as $int)
+ }
+ }
+
+ impl CheckDerConstraints for $int {
+ fn check_constraints(any: &Any) -> Result<()> {
+ check_der_int_constraints(any)
+ }
+ }
+
+ impl DerAutoDerive for $int {}
+
+ impl Tagged for $int {
+ const TAG: Tag = Tag::Integer;
+ }
+
+ #[cfg(feature = "std")]
+ impl ToDer for $int {
+ fn to_der_len(&self) -> Result<usize> {
+ let int = Integer::from(*self);
+ int.to_der_len()
+ }
+
+ fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(*self);
+ int.write_der(writer)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(*self);
+ int.write_der_header(writer)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(*self);
+ int.write_der_content(writer)
+ }
+ }
+ };
+}
+
+macro_rules! impl_uint {
+ ($ty:ty) => {
+ impl<'a> TryFrom<Any<'a>> for $ty {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ TryFrom::try_from(&any)
+ }
+ }
+ impl<'a, 'b> TryFrom<&'b Any<'a>> for $ty {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ let result = Self::from_be_bytes(decode_array_uint(any)?);
+ Ok(result)
+ }
+ }
+ impl CheckDerConstraints for $ty {
+ fn check_constraints(any: &Any) -> Result<()> {
+ check_der_int_constraints(any)
+ }
+ }
+
+ impl DerAutoDerive for $ty {}
+
+ impl Tagged for $ty {
+ const TAG: Tag = Tag::Integer;
+ }
+
+ #[cfg(feature = "std")]
+ impl ToDer for $ty {
+ fn to_der_len(&self) -> Result<usize> {
+ let int = Integer::from(*self);
+ int.to_der_len()
+ }
+
+ fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(*self);
+ int.write_der(writer)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(*self);
+ int.write_der_header(writer)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let int = Integer::from(*self);
+ int.write_der_content(writer)
+ }
+ }
+ };
+}
+
+impl_uint!(u8);
+impl_uint!(u16);
+impl_uint!(u32);
+impl_uint!(u64);
+impl_uint!(u128);
+impl_int!(u8 => i8);
+impl_int!(u16 => i16);
+impl_int!(u32 => i32);
+impl_int!(u64 => i64);
+impl_int!(u128 => i128);
+
+/// ASN.1 `INTEGER` type
+///
+/// Generic representation for integer types.
+/// BER/DER integers can be of any size, so it is not possible to store them as simple integers (they
+/// are stored as raw bytes).
+///
+/// The internal representation can be obtained using `.as_ref()`.
+///
+/// # Note
+///
+/// Methods from/to BER and DER encodings are also implemented for primitive types
+/// (`u8`, `u16` to `u128`, and `i8` to `i128`).
+/// In most cases, it is easier to use these types directly.
+///
+/// # Examples
+///
+/// Creating an `Integer`
+///
+/// ```
+/// use asn1_rs::Integer;
+///
+/// // unsigned
+/// let i = Integer::from(4);
+/// assert_eq!(i.as_ref(), &[4]);
+/// // signed
+/// let j = Integer::from(-2);
+/// assert_eq!(j.as_ref(), &[0xfe]);
+/// ```
+///
+/// Converting an `Integer` to a primitive type (using the `TryInto` trait)
+///
+/// ```
+/// use asn1_rs::{Error, Integer};
+/// use std::convert::TryInto;
+///
+/// let i = Integer::new(&[0x12, 0x34, 0x56, 0x78]);
+/// // converts to an u32
+/// let n: u32 = i.try_into().unwrap();
+///
+/// // Same, but converting to an u16: will fail, value cannot fit into an u16
+/// let i = Integer::new(&[0x12, 0x34, 0x56, 0x78]);
+/// assert_eq!(i.try_into() as Result<u16, _>, Err(Error::IntegerTooLarge));
+/// ```
+///
+/// Encoding an `Integer` to DER
+///
+/// ```
+/// use asn1_rs::{Integer, ToDer};
+///
+/// let i = Integer::from(4);
+/// let v = i.to_der_vec().unwrap();
+/// assert_eq!(&v, &[2, 1, 4]);
+///
+/// // same, with primitive types
+/// let v = 4.to_der_vec().unwrap();
+/// assert_eq!(&v, &[2, 1, 4]);
+/// ```
+#[derive(Debug, Eq, PartialEq)]
+pub struct Integer<'a> {
+ pub(crate) data: Cow<'a, [u8]>,
+}
+
+impl<'a> Integer<'a> {
+ /// Creates a new `Integer` containing the given value (borrowed).
+ #[inline]
+ pub const fn new(s: &'a [u8]) -> Self {
+ Integer {
+ data: Cow::Borrowed(s),
+ }
+ }
+
+ /// Creates a borrowed `Any` for this object
+ #[inline]
+ pub fn any(&'a self) -> Any<'a> {
+ Any::from_tag_and_data(Self::TAG, &self.data)
+ }
+
+ /// Returns a `BigInt` built from this `Integer` value.
+ #[cfg(feature = "bigint")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
+ pub fn as_bigint(&self) -> BigInt {
+ BigInt::from_signed_bytes_be(&self.data)
+ }
+
+ /// Returns a `BigUint` built from this `Integer` value.
+ #[cfg(feature = "bigint")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))]
+ pub fn as_biguint(&self) -> Result<BigUint> {
+ if is_highest_bit_set(&self.data) {
+ Err(Error::IntegerNegative)
+ } else {
+ Ok(BigUint::from_bytes_be(&self.data))
+ }
+ }
+
+ /// Build an `Integer` from a constant array of bytes representation of an integer.
+ pub fn from_const_array<const N: usize>(b: [u8; N]) -> Self {
+ // if high bit set -> add leading 0 to ensure unsigned
+ if is_highest_bit_set(&b) {
+ let mut bytes = vec![0];
+ bytes.extend_from_slice(&b);
+
+ Integer {
+ data: Cow::Owned(bytes),
+ }
+ }
+ // otherwise -> remove 0 unless next has high bit set
+ else {
+ let mut idx = 0;
+
+ while idx < b.len() - 1 {
+ if b[idx] == 0 && b[idx + 1] < 0x80 {
+ idx += 1;
+ continue;
+ }
+ break;
+ }
+
+ Integer {
+ data: Cow::Owned(b[idx..].to_vec()),
+ }
+ }
+ }
+
+ fn from_const_array_negative<const N: usize>(b: [u8; N]) -> Self {
+ let mut idx = 0;
+
+ // Skip leading FF unless next has high bit clear
+ while idx < b.len() - 1 {
+ if b[idx] == 0xFF && b[idx + 1] >= 0x80 {
+ idx += 1;
+ continue;
+ }
+ break;
+ }
+
+ if idx == b.len() {
+ Integer {
+ data: Cow::Borrowed(&[0]),
+ }
+ } else {
+ Integer {
+ data: Cow::Owned(b[idx..].to_vec()),
+ }
+ }
+ }
+}
+
+macro_rules! impl_from_to {
+ ($ty:ty, $sty:expr, $from:ident, $to:ident) => {
+ impl From<$ty> for Integer<'_> {
+ fn from(i: $ty) -> Self {
+ Self::$from(i)
+ }
+ }
+
+ impl TryFrom<Integer<'_>> for $ty {
+ type Error = Error;
+
+ fn try_from(value: Integer<'_>) -> Result<Self> {
+ value.$to()
+ }
+ }
+
+ impl Integer<'_> {
+ #[doc = "Attempts to convert an `Integer` to a `"]
+ #[doc = $sty]
+ #[doc = "`."]
+ #[doc = ""]
+ #[doc = "This function returns an `IntegerTooLarge` error if the integer will not fit into the output type."]
+ pub fn $to(&self) -> Result<$ty> {
+ self.any().try_into()
+ }
+ }
+ };
+ (IMPL SIGNED $ty:ty, $sty:expr, $from:ident, $to:ident) => {
+ impl_from_to!($ty, $sty, $from, $to);
+
+ impl Integer<'_> {
+ #[doc = "Converts a `"]
+ #[doc = $sty]
+ #[doc = "` to an `Integer`"]
+ #[doc = ""]
+ #[doc = "Note: this function allocates data."]
+ pub fn $from(i: $ty) -> Self {
+ let b = i.to_be_bytes();
+ if i >= 0 {
+ Self::from_const_array(b)
+ } else {
+ Self::from_const_array_negative(b)
+ }
+ }
+ }
+ };
+ (IMPL UNSIGNED $ty:ty, $sty:expr, $from:ident, $to:ident) => {
+ impl_from_to!($ty, $sty, $from, $to);
+
+ impl Integer<'_> {
+ #[doc = "Converts a `"]
+ #[doc = $sty]
+ #[doc = "` to an `Integer`"]
+ #[doc = ""]
+ #[doc = "Note: this function allocates data."]
+ pub fn $from(i: $ty) -> Self {
+ Self::from_const_array(i.to_be_bytes())
+ }
+ }
+ };
+ (SIGNED $ty:ty, $from:ident, $to:ident) => {
+ impl_from_to!(IMPL SIGNED $ty, stringify!($ty), $from, $to);
+ };
+ (UNSIGNED $ty:ty, $from:ident, $to:ident) => {
+ impl_from_to!(IMPL UNSIGNED $ty, stringify!($ty), $from, $to);
+ };
+}
+
+impl_from_to!(SIGNED i8, from_i8, as_i8);
+impl_from_to!(SIGNED i16, from_i16, as_i16);
+impl_from_to!(SIGNED i32, from_i32, as_i32);
+impl_from_to!(SIGNED i64, from_i64, as_i64);
+impl_from_to!(SIGNED i128, from_i128, as_i128);
+
+impl_from_to!(UNSIGNED u8, from_u8, as_u8);
+impl_from_to!(UNSIGNED u16, from_u16, as_u16);
+impl_from_to!(UNSIGNED u32, from_u32, as_u32);
+impl_from_to!(UNSIGNED u64, from_u64, as_u64);
+impl_from_to!(UNSIGNED u128, from_u128, as_u128);
+
+impl<'a> AsRef<[u8]> for Integer<'a> {
+ fn as_ref(&self) -> &[u8] {
+ &self.data
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Integer<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Integer<'a>> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Integer<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Integer<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+ Ok(Integer {
+ data: Cow::Borrowed(any.data),
+ })
+ }
+}
+
+impl<'a> CheckDerConstraints for Integer<'a> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ check_der_int_constraints(any)
+ }
+}
+
+fn check_der_int_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ any.header.length.assert_definite()?;
+ match any.as_bytes() {
+ [] => Err(Error::DerConstraintFailed(DerConstraint::IntegerEmpty)),
+ [0] => Ok(()),
+ // leading zeroes
+ [0, byte, ..] if *byte < 0x80 => Err(Error::DerConstraintFailed(
+ DerConstraint::IntegerLeadingZeroes,
+ )),
+ // negative integer with non-minimal encoding
+ [0xff, byte, ..] if *byte >= 0x80 => {
+ Err(Error::DerConstraintFailed(DerConstraint::IntegerLeadingFF))
+ }
+ _ => Ok(()),
+ }
+}
+
+impl DerAutoDerive for Integer<'_> {}
+
+impl<'a> Tagged for Integer<'a> {
+ const TAG: Tag = Tag::Integer;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Integer<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.data.len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // hmm, a very long integer. anyway:
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.data.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&self.data).map_err(Into::into)
+ }
+}
+
+/// Helper macro to declare integers at compile-time
+///
+/// [`Integer`] stores the encoded representation of the integer, so declaring
+/// an integer requires to either use a runtime function or provide the encoded value.
+/// This macro simplifies this task by encoding the value.
+/// It can be used the following ways:
+///
+/// - `int!(1234)`: Create a const expression for the corresponding `Integer<'static>`
+/// - `int!(raw 1234)`: Return the DER encoded form as a byte array (hex-encoded, big-endian
+/// representation from the integer, with leading zeroes removed).
+///
+/// # Examples
+///
+/// ```rust
+/// use asn1_rs::{int, Integer};
+///
+/// const INT0: Integer = int!(1234);
+/// ```
+#[macro_export]
+macro_rules! int {
+ (raw $item:expr) => {
+ $crate::exports::asn1_rs_impl::encode_int!($item)
+ };
+ (rel $item:expr) => {
+ $crate::exports::asn1_rs_impl::encode_int!(rel $item)
+ };
+ ($item:expr) => {
+ $crate::Integer::new(
+ &$crate::int!(raw $item),
+ )
+ };
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{Any, FromDer, Header, Tag, ToDer};
+ use std::convert::TryInto;
+
+ // Vectors from Section 5.7 of:
+ // https://luca.ntop.org/Teaching/Appunti/asn1.html
+ pub(crate) const I0_BYTES: &[u8] = &[0x02, 0x01, 0x00];
+ pub(crate) const I127_BYTES: &[u8] = &[0x02, 0x01, 0x7F];
+ pub(crate) const I128_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0x80];
+ pub(crate) const I256_BYTES: &[u8] = &[0x02, 0x02, 0x01, 0x00];
+ pub(crate) const INEG128_BYTES: &[u8] = &[0x02, 0x01, 0x80];
+ pub(crate) const INEG129_BYTES: &[u8] = &[0x02, 0x02, 0xFF, 0x7F];
+
+ // Additional vectors
+ pub(crate) const I255_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0xFF];
+ pub(crate) const I32767_BYTES: &[u8] = &[0x02, 0x02, 0x7F, 0xFF];
+ pub(crate) const I65535_BYTES: &[u8] = &[0x02, 0x03, 0x00, 0xFF, 0xFF];
+ pub(crate) const INEG32768_BYTES: &[u8] = &[0x02, 0x02, 0x80, 0x00];
+
+ #[test]
+ fn decode_i8() {
+ assert_eq!(0, i8::from_der(I0_BYTES).unwrap().1);
+ assert_eq!(127, i8::from_der(I127_BYTES).unwrap().1);
+ assert_eq!(-128, i8::from_der(INEG128_BYTES).unwrap().1);
+ }
+
+ #[test]
+ fn encode_i8() {
+ assert_eq!(0i8.to_der_vec().unwrap(), I0_BYTES);
+ assert_eq!(127i8.to_der_vec().unwrap(), I127_BYTES);
+ assert_eq!((-128i8).to_der_vec().unwrap(), INEG128_BYTES);
+ }
+
+ #[test]
+ fn decode_i16() {
+ assert_eq!(0, i16::from_der(I0_BYTES).unwrap().1);
+ assert_eq!(127, i16::from_der(I127_BYTES).unwrap().1);
+ assert_eq!(128, i16::from_der(I128_BYTES).unwrap().1);
+ assert_eq!(255, i16::from_der(I255_BYTES).unwrap().1);
+ assert_eq!(256, i16::from_der(I256_BYTES).unwrap().1);
+ assert_eq!(32767, i16::from_der(I32767_BYTES).unwrap().1);
+ assert_eq!(-128, i16::from_der(INEG128_BYTES).unwrap().1);
+ assert_eq!(-129, i16::from_der(INEG129_BYTES).unwrap().1);
+ assert_eq!(-32768, i16::from_der(INEG32768_BYTES).unwrap().1);
+ }
+
+ #[test]
+ fn encode_i16() {
+ assert_eq!(0i16.to_der_vec().unwrap(), I0_BYTES);
+ assert_eq!(127i16.to_der_vec().unwrap(), I127_BYTES);
+ assert_eq!(128i16.to_der_vec().unwrap(), I128_BYTES);
+ assert_eq!(255i16.to_der_vec().unwrap(), I255_BYTES);
+ assert_eq!(256i16.to_der_vec().unwrap(), I256_BYTES);
+ assert_eq!(32767i16.to_der_vec().unwrap(), I32767_BYTES);
+ assert_eq!((-128i16).to_der_vec().unwrap(), INEG128_BYTES);
+ assert_eq!((-129i16).to_der_vec().unwrap(), INEG129_BYTES);
+ assert_eq!((-32768i16).to_der_vec().unwrap(), INEG32768_BYTES);
+ }
+
+ #[test]
+ fn decode_u8() {
+ assert_eq!(0, u8::from_der(I0_BYTES).unwrap().1);
+ assert_eq!(127, u8::from_der(I127_BYTES).unwrap().1);
+ assert_eq!(255, u8::from_der(I255_BYTES).unwrap().1);
+ }
+
+ #[test]
+ fn encode_u8() {
+ assert_eq!(0u8.to_der_vec().unwrap(), I0_BYTES);
+ assert_eq!(127u8.to_der_vec().unwrap(), I127_BYTES);
+ assert_eq!(255u8.to_der_vec().unwrap(), I255_BYTES);
+ }
+
+ #[test]
+ fn decode_u16() {
+ assert_eq!(0, u16::from_der(I0_BYTES).unwrap().1);
+ assert_eq!(127, u16::from_der(I127_BYTES).unwrap().1);
+ assert_eq!(255, u16::from_der(I255_BYTES).unwrap().1);
+ assert_eq!(256, u16::from_der(I256_BYTES).unwrap().1);
+ assert_eq!(32767, u16::from_der(I32767_BYTES).unwrap().1);
+ assert_eq!(65535, u16::from_der(I65535_BYTES).unwrap().1);
+ }
+
+ #[test]
+ fn encode_u16() {
+ assert_eq!(0u16.to_der_vec().unwrap(), I0_BYTES);
+ assert_eq!(127u16.to_der_vec().unwrap(), I127_BYTES);
+ assert_eq!(255u16.to_der_vec().unwrap(), I255_BYTES);
+ assert_eq!(256u16.to_der_vec().unwrap(), I256_BYTES);
+ assert_eq!(32767u16.to_der_vec().unwrap(), I32767_BYTES);
+ assert_eq!(65535u16.to_der_vec().unwrap(), I65535_BYTES);
+ }
+
+ /// Integers must be encoded with a minimum number of octets
+ #[test]
+ fn reject_non_canonical() {
+ assert!(i8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+ assert!(i16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+ assert!(u8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+ assert!(u16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err());
+ }
+
+ #[test]
+ fn declare_int() {
+ let int = super::int!(1234);
+ assert_eq!(int.try_into(), Ok(1234));
+ }
+
+ #[test]
+ fn trim_slice() {
+ use super::trim_slice;
+ let h = Header::new_simple(Tag(0));
+ // no zero nor ff - nothing to remove
+ let input: &[u8] = &[0x7f, 0xff, 0x00, 0x02];
+ assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+ //
+ // 0x00
+ //
+ // empty - nothing to remove
+ let input: &[u8] = &[];
+ assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+ // one zero - nothing to remove
+ let input: &[u8] = &[0];
+ assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+ // all zeroes - keep only one
+ let input: &[u8] = &[0, 0, 0];
+ assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+ // some zeroes - keep only the non-zero part
+ let input: &[u8] = &[0, 0, 1];
+ assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+ //
+ // 0xff
+ //
+ // one ff - nothing to remove
+ let input: &[u8] = &[0xff];
+ assert_eq!(Ok(input), trim_slice(&Any::new(h.clone(), input)));
+ // all ff - keep only one
+ let input: &[u8] = &[0xff, 0xff, 0xff];
+ assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+ // some ff - keep only the non-zero part
+ let input: &[u8] = &[0xff, 0xff, 1];
+ assert_eq!(Ok(&input[1..]), trim_slice(&Any::new(h.clone(), input)));
+ // some ff and a MSB 1 - keep only the non-zero part
+ let input: &[u8] = &[0xff, 0xff, 0x80, 1];
+ assert_eq!(Ok(&input[2..]), trim_slice(&Any::new(h.clone(), input)));
+ }
+}
diff --git a/src/asn1_types/mod.rs b/src/asn1_types/mod.rs
new file mode 100644
index 0000000..33e50d6
--- /dev/null
+++ b/src/asn1_types/mod.rs
@@ -0,0 +1,26 @@
+mod any;
+mod bitstring;
+mod boolean;
+mod choice;
+mod embedded_pdv;
+mod end_of_content;
+mod enumerated;
+mod generalizedtime;
+mod integer;
+mod null;
+mod object_descriptor;
+mod octetstring;
+mod oid;
+mod optional;
+mod real;
+mod sequence;
+mod set;
+mod strings;
+mod tagged;
+mod utctime;
+
+pub use {
+ any::*, bitstring::*, boolean::*, choice::*, embedded_pdv::*, end_of_content::*, enumerated::*,
+ generalizedtime::*, integer::*, null::*, object_descriptor::*, octetstring::*, oid::*,
+ optional::*, real::*, sequence::*, set::*, strings::*, tagged::*, utctime::*,
+};
diff --git a/src/asn1_types/null.rs b/src/asn1_types/null.rs
new file mode 100644
index 0000000..ba9f0f5
--- /dev/null
+++ b/src/asn1_types/null.rs
@@ -0,0 +1,99 @@
+use crate::*;
+use core::convert::TryFrom;
+
+/// ASN.1 `NULL` type
+#[derive(Debug, PartialEq, Eq)]
+pub struct Null {}
+
+impl Null {
+ pub const fn new() -> Self {
+ Null {}
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Null {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Null> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Null {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Null> {
+ any.tag().assert_eq(Self::TAG)?;
+ if !any.header.length.is_null() {
+ return Err(Error::InvalidLength);
+ }
+ Ok(Null {})
+ }
+}
+
+impl CheckDerConstraints for Null {
+ fn check_constraints(_any: &Any) -> Result<()> {
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for Null {}
+
+impl Tagged for Null {
+ const TAG: Tag = Tag::Null;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Null {
+ fn to_der_len(&self) -> Result<usize> {
+ Ok(2)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&[0x05, 0x00]).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ Ok(0)
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for () {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<()> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ if !any.header.length.is_null() {
+ return Err(Error::InvalidLength);
+ }
+ Ok(())
+ }
+}
+
+impl CheckDerConstraints for () {
+ fn check_constraints(_any: &Any) -> Result<()> {
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for () {}
+
+impl Tagged for () {
+ const TAG: Tag = Tag::Null;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for () {
+ fn to_der_len(&self) -> Result<usize> {
+ Ok(2)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&[0x05, 0x00]).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, _writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ Ok(0)
+ }
+}
diff --git a/src/asn1_types/object_descriptor.rs b/src/asn1_types/object_descriptor.rs
new file mode 100644
index 0000000..db78870
--- /dev/null
+++ b/src/asn1_types/object_descriptor.rs
@@ -0,0 +1,17 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+// X.680 section 44.3
+// ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString
+
+asn1_string!(ObjectDescriptor);
+
+impl<'a> TestValidCharset for ObjectDescriptor<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ if !i.iter().all(u8::is_ascii) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/octetstring.rs b/src/asn1_types/octetstring.rs
new file mode 100644
index 0000000..b4b71e5
--- /dev/null
+++ b/src/asn1_types/octetstring.rs
@@ -0,0 +1,157 @@
+use crate::*;
+use alloc::borrow::Cow;
+use core::convert::TryFrom;
+
+/// ASN.1 `OCTETSTRING` type
+#[derive(Debug, PartialEq, Eq)]
+pub struct OctetString<'a> {
+ data: Cow<'a, [u8]>,
+}
+
+impl<'a> OctetString<'a> {
+ pub const fn new(s: &'a [u8]) -> Self {
+ OctetString {
+ data: Cow::Borrowed(s),
+ }
+ }
+
+ /// Get the bytes representation of the *content*
+ pub fn as_cow(&'a self) -> &Cow<'a, [u8]> {
+ &self.data
+ }
+
+ /// Get the bytes representation of the *content*
+ pub fn into_cow(self) -> Cow<'a, [u8]> {
+ self.data
+ }
+}
+
+impl<'a> AsRef<[u8]> for OctetString<'a> {
+ fn as_ref(&self) -> &[u8] {
+ &self.data
+ }
+}
+
+impl<'a> From<&'a [u8]> for OctetString<'a> {
+ fn from(b: &'a [u8]) -> Self {
+ OctetString {
+ data: Cow::Borrowed(b),
+ }
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for OctetString<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<OctetString<'a>> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for OctetString<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<OctetString<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+ Ok(OctetString {
+ data: Cow::Borrowed(any.data),
+ })
+ }
+}
+
+impl<'a> CheckDerConstraints for OctetString<'a> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ // X.690 section 10.2
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for OctetString<'_> {}
+
+impl<'a> Tagged for OctetString<'a> {
+ const TAG: Tag = Tag::OctetString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for OctetString<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.data.len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.data.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&self.data).map_err(Into::into)
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for &'a [u8] {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<&'a [u8]> {
+ any.tag().assert_eq(Self::TAG)?;
+ let s = OctetString::try_from(any)?;
+ match s.data {
+ Cow::Borrowed(s) => Ok(s),
+ Cow::Owned(_) => Err(Error::LifetimeError),
+ }
+ }
+}
+
+impl<'a> CheckDerConstraints for &'a [u8] {
+ fn check_constraints(any: &Any) -> Result<()> {
+ // X.690 section 10.2
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for &'_ [u8] {}
+
+impl<'a> Tagged for &'a [u8] {
+ const TAG: Tag = Tag::OctetString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for &'_ [u8] {
+ fn to_der_len(&self) -> Result<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.len()),
+ );
+ Ok(header.to_der_len()? + self.len())
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(self).map_err(Into::into)
+ }
+}
diff --git a/src/asn1_types/oid.rs b/src/asn1_types/oid.rs
new file mode 100644
index 0000000..a6629b4
--- /dev/null
+++ b/src/asn1_types/oid.rs
@@ -0,0 +1,517 @@
+use crate::*;
+use alloc::borrow::Cow;
+#[cfg(not(feature = "std"))]
+use alloc::format;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+use core::{
+ convert::TryFrom, fmt, iter::FusedIterator, marker::PhantomData, ops::Shl, str::FromStr,
+};
+
+#[cfg(feature = "bigint")]
+use num_bigint::BigUint;
+use num_traits::Num;
+
+/// An error for OID parsing functions.
+#[derive(Debug)]
+pub enum OidParseError {
+ TooShort,
+ /// Signalizes that the first or second component is too large.
+ /// The first must be within the range 0 to 6 (inclusive).
+ /// The second component must be less than 40.
+ FirstComponentsTooLarge,
+ ParseIntError,
+}
+
+/// Object ID (OID) representation which can be relative or non-relative.
+/// An example for an OID in string representation is `"1.2.840.113549.1.1.5"`.
+///
+/// For non-relative OIDs restrictions apply to the first two components.
+///
+/// This library contains a procedural macro `oid` which can be used to
+/// create oids. For example `oid!(1.2.44.233)` or `oid!(rel 44.233)`
+/// for relative oids. See the [module documentation](index.html) for more information.
+#[derive(Hash, PartialEq, Eq, Clone)]
+
+pub struct Oid<'a> {
+ asn1: Cow<'a, [u8]>,
+ relative: bool,
+}
+
+impl<'a> TryFrom<Any<'a>> for Oid<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Oid<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Self> {
+ // check that any.data.last().unwrap() >> 7 == 0u8
+ let asn1 = Cow::Borrowed(any.data);
+ Ok(Oid::new(asn1))
+ }
+}
+
+impl<'a> CheckDerConstraints for Oid<'a> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ any.header.length.assert_definite()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for Oid<'_> {}
+
+impl<'a> Tagged for Oid<'a> {
+ const TAG: Tag = Tag::Oid;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Oid<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ // OID/REL-OID tag will not change header size, so we don't care here
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.asn1.len()),
+ );
+ Ok(header.to_der_len()? + self.asn1.len())
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let tag = if self.relative {
+ Tag::RelativeOid
+ } else {
+ Tag::Oid
+ };
+ let header = Header::new(
+ Class::Universal,
+ false,
+ tag,
+ Length::Definite(self.asn1.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&self.asn1).map_err(Into::into)
+ }
+}
+
+fn encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_ {
+ ids.iter().flat_map(|id| {
+ let bit_count = 64 - id.leading_zeros();
+ let octets_needed = ((bit_count + 6) / 7).max(1);
+ (0..octets_needed).map(move |i| {
+ let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 };
+ ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag
+ })
+ })
+}
+
+impl<'a> Oid<'a> {
+ /// Create an OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
+ /// for other ways to create oids.
+ pub const fn new(asn1: Cow<'a, [u8]>) -> Oid {
+ Oid {
+ asn1,
+ relative: false,
+ }
+ }
+
+ /// Create a relative OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
+ /// for other ways to create relative oids.
+ pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid {
+ Oid {
+ asn1,
+ relative: true,
+ }
+ }
+
+ /// Build an OID from an array of object identifier components.
+ /// This method allocates memory on the heap.
+ pub fn from(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> {
+ if s.len() < 2 {
+ if s.len() == 1 && s[0] == 0 {
+ return Ok(Oid {
+ asn1: Cow::Borrowed(&[0]),
+ relative: false,
+ });
+ }
+ return Err(OidParseError::TooShort);
+ }
+ if s[0] >= 7 || s[1] >= 40 {
+ return Err(OidParseError::FirstComponentsTooLarge);
+ }
+ let asn1_encoded: Vec<u8> = [(s[0] * 40 + s[1]) as u8]
+ .iter()
+ .copied()
+ .chain(encode_relative(&s[2..]))
+ .collect();
+ Ok(Oid {
+ asn1: Cow::from(asn1_encoded),
+ relative: false,
+ })
+ }
+
+ /// Build a relative OID from an array of object identifier components.
+ pub fn from_relative(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> {
+ if s.is_empty() {
+ return Err(OidParseError::TooShort);
+ }
+ let asn1_encoded: Vec<u8> = encode_relative(s).collect();
+ Ok(Oid {
+ asn1: Cow::from(asn1_encoded),
+ relative: true,
+ })
+ }
+
+ /// Create a deep copy of the oid.
+ ///
+ /// This method allocates data on the heap. The returned oid
+ /// can be used without keeping the ASN.1 representation around.
+ ///
+ /// Cloning the returned oid does again allocate data.
+ pub fn to_owned(&self) -> Oid<'static> {
+ Oid {
+ asn1: Cow::from(self.asn1.to_vec()),
+ relative: self.relative,
+ }
+ }
+
+ /// Get the encoded oid without the header.
+ #[inline]
+ pub fn as_bytes(&self) -> &[u8] {
+ self.asn1.as_ref()
+ }
+
+ /// Get the encoded oid without the header.
+ #[deprecated(since = "0.2.0", note = "Use `as_bytes` instead")]
+ #[inline]
+ pub fn bytes(&self) -> &[u8] {
+ self.as_bytes()
+ }
+
+ /// Get the bytes representation of the encoded oid
+ pub fn into_cow(self) -> Cow<'a, [u8]> {
+ self.asn1
+ }
+
+ /// Convert the OID to a string representation.
+ /// The string contains the IDs separated by dots, for ex: "1.2.840.113549.1.1.5"
+ #[cfg(feature = "bigint")]
+ pub fn to_id_string(&self) -> String {
+ let ints: Vec<String> = self.iter_bigint().map(|i| i.to_string()).collect();
+ ints.join(".")
+ }
+
+ #[cfg(not(feature = "bigint"))]
+ /// Convert the OID to a string representation.
+ ///
+ /// If every arc fits into a u64 a string like "1.2.840.113549.1.1.5"
+ /// is returned, otherwise a hex representation.
+ ///
+ /// See also the "bigint" feature of this crate.
+ pub fn to_id_string(&self) -> String {
+ if let Some(arcs) = self.iter() {
+ let ints: Vec<String> = arcs.map(|i| i.to_string()).collect();
+ ints.join(".")
+ } else {
+ let mut ret = String::with_capacity(self.asn1.len() * 3);
+ for (i, o) in self.asn1.iter().enumerate() {
+ ret.push_str(&format!("{:02x}", o));
+ if i + 1 != self.asn1.len() {
+ ret.push(' ');
+ }
+ }
+ ret
+ }
+ }
+
+ /// Return an iterator over the sub-identifiers (arcs).
+ #[cfg(feature = "bigint")]
+ pub fn iter_bigint(
+ &'_ self,
+ ) -> impl Iterator<Item = BigUint> + FusedIterator + ExactSizeIterator + '_ {
+ SubIdentifierIterator {
+ oid: self,
+ pos: 0,
+ first: false,
+ n: PhantomData,
+ }
+ }
+
+ /// Return an iterator over the sub-identifiers (arcs).
+ /// Returns `None` if at least one arc does not fit into `u64`.
+ pub fn iter(
+ &'_ self,
+ ) -> Option<impl Iterator<Item = u64> + FusedIterator + ExactSizeIterator + '_> {
+ // Check that every arc fits into u64
+ let bytes = if self.relative {
+ &self.asn1
+ } else if self.asn1.is_empty() {
+ &[]
+ } else {
+ &self.asn1[1..]
+ };
+ let max_bits = bytes
+ .iter()
+ .fold((0usize, 0usize), |(max, cur), c| {
+ let is_end = (c >> 7) == 0u8;
+ if is_end {
+ (max.max(cur + 7), 0)
+ } else {
+ (max, cur + 7)
+ }
+ })
+ .0;
+ if max_bits > 64 {
+ return None;
+ }
+
+ Some(SubIdentifierIterator {
+ oid: self,
+ pos: 0,
+ first: false,
+ n: PhantomData,
+ })
+ }
+
+ pub fn from_ber_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+ let (rem, any) = Any::from_ber(bytes)?;
+ any.header.assert_primitive()?;
+ any.header.assert_tag(Tag::RelativeOid)?;
+ let asn1 = Cow::Borrowed(any.data);
+ Ok((rem, Oid::new_relative(asn1)))
+ }
+
+ pub fn from_der_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> {
+ let (rem, any) = Any::from_der(bytes)?;
+ any.header.assert_tag(Tag::RelativeOid)?;
+ Self::check_constraints(&any)?;
+ let asn1 = Cow::Borrowed(any.data);
+ Ok((rem, Oid::new_relative(asn1)))
+ }
+
+ /// Returns true if `needle` is a prefix of the OID.
+ pub fn starts_with(&self, needle: &Oid) -> bool {
+ self.asn1.len() >= needle.asn1.len() && self.asn1.starts_with(needle.as_bytes())
+ }
+}
+
+trait Repr: Num + Shl<usize, Output = Self> + From<u8> {}
+impl<N> Repr for N where N: Num + Shl<usize, Output = N> + From<u8> {}
+
+struct SubIdentifierIterator<'a, N: Repr> {
+ oid: &'a Oid<'a>,
+ pos: usize,
+ first: bool,
+ n: PhantomData<&'a N>,
+}
+
+impl<'a, N: Repr> Iterator for SubIdentifierIterator<'a, N> {
+ type Item = N;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ use num_traits::identities::Zero;
+
+ if self.pos == self.oid.asn1.len() {
+ return None;
+ }
+ if !self.oid.relative {
+ if !self.first {
+ debug_assert!(self.pos == 0);
+ self.first = true;
+ return Some((self.oid.asn1[0] / 40).into());
+ } else if self.pos == 0 {
+ self.pos += 1;
+ if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 {
+ return None;
+ }
+ return Some((self.oid.asn1[0] % 40).into());
+ }
+ }
+ // decode objet sub-identifier according to the asn.1 standard
+ let mut res = <N as Zero>::zero();
+ for o in self.oid.asn1[self.pos..].iter() {
+ self.pos += 1;
+ res = (res << 7) + (o & 0b111_1111).into();
+ let flag = o >> 7;
+ if flag == 0u8 {
+ break;
+ }
+ }
+ Some(res)
+ }
+}
+
+impl<'a, N: Repr> FusedIterator for SubIdentifierIterator<'a, N> {}
+
+impl<'a, N: Repr> ExactSizeIterator for SubIdentifierIterator<'a, N> {
+ fn len(&self) -> usize {
+ if self.oid.relative {
+ self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count()
+ } else if self.oid.asn1.len() == 0 {
+ 0
+ } else if self.oid.asn1.len() == 1 {
+ if self.oid.asn1[0] == 0 {
+ 1
+ } else {
+ 2
+ }
+ } else {
+ 2 + self.oid.asn1[2..]
+ .iter()
+ .filter(|o| (*o >> 7) == 0u8)
+ .count()
+ }
+ }
+
+ #[cfg(feature = "exact_size_is_empty")]
+ fn is_empty(&self) -> bool {
+ self.oid.asn1.is_empty()
+ }
+}
+
+impl<'a> fmt::Display for Oid<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.relative {
+ f.write_str("rel. ")?;
+ }
+ f.write_str(&self.to_id_string())
+ }
+}
+
+impl<'a> fmt::Debug for Oid<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str("OID(")?;
+ <Oid as fmt::Display>::fmt(self, f)?;
+ f.write_str(")")
+ }
+}
+
+impl<'a> FromStr for Oid<'a> {
+ type Err = OidParseError;
+
+ fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
+ let v: core::result::Result<Vec<_>, _> = s.split('.').map(|c| c.parse::<u64>()).collect();
+ v.map_err(|_| OidParseError::ParseIntError)
+ .and_then(|v| Oid::from(&v))
+ }
+}
+
+/// Helper macro to declare integers at compile-time
+///
+/// Since the DER encoded oids are not very readable we provide a
+/// procedural macro `oid!`. The macro can be used the following ways:
+///
+/// - `oid!(1.4.42.23)`: Create a const expression for the corresponding `Oid<'static>`
+/// - `oid!(rel 42.23)`: Create a const expression for the corresponding relative `Oid<'static>`
+/// - `oid!(raw 1.4.42.23)`/`oid!(raw rel 42.23)`: Obtain the DER encoded form as a byte array.
+///
+/// # Comparing oids
+///
+/// Comparing a parsed oid to a static oid is probably the most common
+/// thing done with oids in your code. The `oid!` macro can be used in expression positions for
+/// this purpose. For example
+/// ```
+/// use asn1_rs::{oid, Oid};
+///
+/// # let some_oid: Oid<'static> = oid!(1.2.456);
+/// const SOME_STATIC_OID: Oid<'static> = oid!(1.2.456);
+/// assert_eq!(some_oid, SOME_STATIC_OID)
+/// ```
+/// To get a relative Oid use `oid!(rel 1.2)`.
+///
+/// Because of limitations for procedural macros ([rust issue](https://github.com/rust-lang/rust/issues/54727))
+/// and constants used in patterns ([rust issue](https://github.com/rust-lang/rust/issues/31434))
+/// the `oid` macro can not directly be used in patterns, also not through constants.
+/// You can do this, though:
+/// ```
+/// # use asn1_rs::{oid, Oid};
+/// # let some_oid: Oid<'static> = oid!(1.2.456);
+/// const SOME_OID: Oid<'static> = oid!(1.2.456);
+/// if some_oid == SOME_OID || some_oid == oid!(1.2.456) {
+/// println!("match");
+/// }
+///
+/// // Alternatively, compare the DER encoded form directly:
+/// const SOME_OID_RAW: &[u8] = &oid!(raw 1.2.456);
+/// match some_oid.as_bytes() {
+/// SOME_OID_RAW => println!("match"),
+/// _ => panic!("no match"),
+/// }
+/// ```
+/// *Attention*, be aware that the latter version might not handle the case of a relative oid correctly. An
+/// extra check might be necessary.
+#[macro_export]
+macro_rules! oid {
+ (raw $items:expr) => {
+ $crate::exports::asn1_rs_impl::encode_oid!($items)
+ };
+ (rel $items:expr) => {
+ $crate::Oid::new_relative($crate::exports::borrow::Cow::Borrowed(
+ &$crate::exports::asn1_rs_impl::encode_oid!(rel $items),
+ ))
+ };
+ ($items:expr) => {
+ $crate::Oid::new($crate::exports::borrow::Cow::Borrowed(
+ &$crate::oid!(raw $items),
+ ))
+ };
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{FromDer, Oid, ToDer};
+ use hex_literal::hex;
+
+ #[test]
+ fn declare_oid() {
+ let oid = super::oid! {1.2.840.113549.1};
+ assert_eq!(oid.to_string(), "1.2.840.113549.1");
+ }
+
+ const OID_RSA_ENCRYPTION: &[u8] = &oid! {raw 1.2.840.113549.1.1.1};
+ const OID_EC_PUBLIC_KEY: &[u8] = &oid! {raw 1.2.840.10045.2.1};
+ #[allow(clippy::match_like_matches_macro)]
+ fn compare_oid(oid: &Oid) -> bool {
+ match oid.as_bytes() {
+ OID_RSA_ENCRYPTION => true,
+ OID_EC_PUBLIC_KEY => true,
+ _ => false,
+ }
+ }
+
+ #[test]
+ fn test_compare_oid() {
+ let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap();
+ assert_eq!(oid, oid! {1.2.840.113549.1.1.1});
+ let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap();
+ assert!(compare_oid(&oid));
+ }
+
+ #[test]
+ fn oid_to_der() {
+ let oid = super::oid! {1.2.840.113549.1};
+ assert_eq!(oid.to_der_len(), Ok(9));
+ let v = oid.to_der_vec().expect("could not serialize");
+ assert_eq!(&v, &hex! {"06 07 2a 86 48 86 f7 0d 01"});
+ let (_, oid2) = Oid::from_der(&v).expect("could not re-parse");
+ assert_eq!(&oid, &oid2);
+ }
+
+ #[test]
+ fn oid_starts_with() {
+ const OID_RSA_ENCRYPTION: Oid = oid! {1.2.840.113549.1.1.1};
+ const OID_EC_PUBLIC_KEY: Oid = oid! {1.2.840.10045.2.1};
+ let oid = super::oid! {1.2.840.113549.1};
+ assert!(OID_RSA_ENCRYPTION.starts_with(&oid));
+ assert!(!OID_EC_PUBLIC_KEY.starts_with(&oid));
+ }
+}
diff --git a/src/asn1_types/optional.rs b/src/asn1_types/optional.rs
new file mode 100644
index 0000000..a8027ab
--- /dev/null
+++ b/src/asn1_types/optional.rs
@@ -0,0 +1,87 @@
+use crate::*;
+
+// note: we cannot implement `TryFrom<Any<'a>> with generic errors for Option<T>`,
+// because this conflicts with generic `T` implementation in
+// `src/traits.rs`, since `T` always satisfies `T: Into<Option<T>>`
+//
+// for the same reason, we cannot use a generic error type here
+impl<'a, T> FromBer<'a> for Option<T>
+where
+ T: FromBer<'a>,
+{
+ fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+ if bytes.is_empty() {
+ return Ok((bytes, None));
+ }
+ match T::from_ber(bytes) {
+ Ok((rem, t)) => Ok((rem, Some(t))),
+ Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)),
+ Err(e) => Err(e),
+ }
+ }
+}
+
+impl<'a, T> FromDer<'a> for Option<T>
+where
+ T: FromDer<'a>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
+ if bytes.is_empty() {
+ return Ok((bytes, None));
+ }
+ match T::from_der(bytes) {
+ Ok((rem, t)) => Ok((rem, Some(t))),
+ Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)),
+ Err(e) => Err(e),
+ }
+ }
+}
+
+impl<T> CheckDerConstraints for Option<T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ T::check_constraints(any)
+ }
+}
+
+impl<T> DynTagged for Option<T>
+where
+ T: DynTagged,
+{
+ fn tag(&self) -> Tag {
+ if self.is_some() {
+ self.tag()
+ } else {
+ Tag(0)
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for Option<T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ match self {
+ None => Ok(0),
+ Some(t) => t.to_der_len(),
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ match self {
+ None => Ok(0),
+ Some(t) => t.write_der_header(writer),
+ }
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ match self {
+ None => Ok(0),
+ Some(t) => t.write_der_content(writer),
+ }
+ }
+}
diff --git a/src/asn1_types/real.rs b/src/asn1_types/real.rs
new file mode 100644
index 0000000..db72a0d
--- /dev/null
+++ b/src/asn1_types/real.rs
@@ -0,0 +1,462 @@
+use crate::*;
+use alloc::format;
+use core::convert::TryFrom;
+use nom::Needed;
+
+mod f32;
+mod f64;
+pub use self::f32::*;
+pub use self::f64::*;
+
+/// ASN.1 `REAL` type
+///
+/// # Limitations
+///
+/// When encoding binary values, only base 2 is supported
+#[derive(Debug, PartialEq)]
+pub enum Real {
+ /// Non-special values
+ Binary {
+ mantissa: f64,
+ base: u32,
+ exponent: i32,
+ enc_base: u8,
+ },
+ /// Infinity (∞).
+ Infinity,
+ /// Negative infinity (−∞).
+ NegInfinity,
+ /// Zero
+ Zero,
+}
+
+impl Real {
+ /// Create a new `REAL` from the `f64` value.
+ pub fn new(f: f64) -> Self {
+ if f.is_infinite() {
+ if f.is_sign_positive() {
+ Self::Infinity
+ } else {
+ Self::NegInfinity
+ }
+ } else if f.abs() == 0.0 {
+ Self::Zero
+ } else {
+ let mut e = 0;
+ let mut f = f;
+ while f.fract() != 0.0 {
+ f *= 10.0_f64;
+ e -= 1;
+ }
+ Real::Binary {
+ mantissa: f,
+ base: 10,
+ exponent: e,
+ enc_base: 10,
+ }
+ .normalize_base10()
+ }
+ }
+
+ pub const fn with_enc_base(self, enc_base: u8) -> Self {
+ match self {
+ Real::Binary {
+ mantissa,
+ base,
+ exponent,
+ ..
+ } => Real::Binary {
+ mantissa,
+ base,
+ exponent,
+ enc_base,
+ },
+ e => e,
+ }
+ }
+
+ fn normalize_base10(self) -> Self {
+ match self {
+ Real::Binary {
+ mantissa,
+ base: 10,
+ exponent,
+ enc_base: _enc_base,
+ } => {
+ let mut m = mantissa;
+ let mut e = exponent;
+ while m.abs() > f64::EPSILON && m.rem_euclid(10.0).abs() < f64::EPSILON {
+ m /= 10.0;
+ e += 1;
+ }
+ Real::Binary {
+ mantissa: m,
+ base: 10,
+ exponent: e,
+ enc_base: _enc_base,
+ }
+ }
+ _ => self,
+ }
+ }
+
+ /// Create a new binary `REAL`
+ #[inline]
+ pub const fn binary(mantissa: f64, base: u32, exponent: i32) -> Self {
+ Self::Binary {
+ mantissa,
+ base,
+ exponent,
+ enc_base: 2,
+ }
+ }
+
+ /// Returns `true` if this value is positive infinity or negative infinity, and
+ /// `false` otherwise.
+ #[inline]
+ pub fn is_infinite(&self) -> bool {
+ matches!(self, Real::Infinity | Real::NegInfinity)
+ }
+
+ /// Returns `true` if this number is not infinite.
+ #[inline]
+ pub fn is_finite(&self) -> bool {
+ matches!(self, Real::Zero | Real::Binary { .. })
+ }
+
+ /// Returns the 'f64' value of this `REAL`.
+ ///
+ /// Returned value is a float, and may be infinite.
+ pub fn f64(&self) -> f64 {
+ match self {
+ Real::Binary {
+ mantissa,
+ base,
+ exponent,
+ ..
+ } => {
+ let f = mantissa;
+ let exp = (*base as f64).powi(*exponent);
+ f * exp
+ }
+ Real::Zero => 0.0_f64,
+ Real::Infinity => f64::INFINITY,
+ Real::NegInfinity => f64::NEG_INFINITY,
+ }
+ }
+
+ /// Returns the 'f32' value of this `REAL`.
+ ///
+ /// This functions casts the result of [`Real::f64`] to a `f32`, and loses precision.
+ pub fn f32(&self) -> f32 {
+ self.f64() as f32
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Real {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ let data = &any.data;
+ if data.is_empty() {
+ return Ok(Real::Zero);
+ }
+ // code inspired from pyasn1
+ let first = data[0];
+ let rem = &data[1..];
+ if first & 0x80 != 0 {
+ // binary encoding (X.690 section 8.5.6)
+ let rem = rem;
+ // format of exponent
+ let (n, rem) = match first & 0x03 {
+ 4 => {
+ let (b, rem) = rem
+ .split_first()
+ .ok_or_else(|| Error::Incomplete(Needed::new(1)))?;
+ (*b as usize, rem)
+ }
+ b => (b as usize + 1, rem),
+ };
+ if n >= rem.len() {
+ return Err(any.tag().invalid_value("Invalid float value(exponent)"));
+ }
+ // n cannot be 0 (see the +1 above)
+ let (eo, rem) = rem.split_at(n);
+ // so 'eo' cannot be empty
+ let mut e = if eo[0] & 0x80 != 0 { -1 } else { 0 };
+ // safety check: 'eo' length must be <= container type for 'e'
+ if eo.len() > 4 {
+ return Err(any.tag().invalid_value("Exponent too large (REAL)"));
+ }
+ for b in eo {
+ e = (e << 8) | (*b as i32);
+ }
+ // base bits
+ let b = (first >> 4) & 0x03;
+ let _enc_base = match b {
+ 0 => 2,
+ 1 => 8,
+ 2 => 16,
+ _ => return Err(any.tag().invalid_value("Illegal REAL encoding base")),
+ };
+ let e = match b {
+ // base 2
+ 0 => e,
+ // base 8
+ 1 => e * 3,
+ // base 16
+ 2 => e * 4,
+ _ => return Err(any.tag().invalid_value("Illegal REAL base")),
+ };
+ if rem.len() > 8 {
+ return Err(any.tag().invalid_value("Mantissa too large (REAL)"));
+ }
+ let mut p = 0;
+ for b in rem {
+ p = (p << 8) | (*b as i64);
+ }
+ // sign bit
+ let p = if first & 0x40 != 0 { -p } else { p };
+ // scale bits
+ let sf = (first >> 2) & 0x03;
+ let p = match sf {
+ 0 => p as f64,
+ sf => {
+ // 2^sf: cannot overflow, sf is between 0 and 3
+ let scale = 2_f64.powi(sf as _);
+ (p as f64) * scale
+ }
+ };
+ Ok(Real::Binary {
+ mantissa: p,
+ base: 2,
+ exponent: e,
+ enc_base: _enc_base,
+ })
+ } else if first & 0x40 != 0 {
+ // special real value (X.690 section 8.5.8)
+ // there shall be only one contents octet,
+ if any.header.length != Length::Definite(1) {
+ return Err(Error::InvalidLength);
+ }
+ // with values as follows
+ match first {
+ 0x40 => Ok(Real::Infinity),
+ 0x41 => Ok(Real::NegInfinity),
+ _ => Err(any.tag().invalid_value("Invalid float special value")),
+ }
+ } else {
+ // decimal encoding (X.690 section 8.5.7)
+ let s = alloc::str::from_utf8(rem)?;
+ match first & 0x03 {
+ 0x1 => {
+ // NR1
+ match s.parse::<u32>() {
+ Err(_) => Err(any.tag().invalid_value("Invalid float string encoding")),
+ Ok(v) => Ok(Real::new(v.into())),
+ }
+ }
+ 0x2 /* NR2 */ | 0x3 /* NR3 */=> {
+ match s.parse::<f64>() {
+ Err(_) => Err(any.tag().invalid_value("Invalid float string encoding")),
+ Ok(v) => Ok(Real::new(v)),
+ }
+ }
+ c => {
+ Err(any.tag().invalid_value(&format!("Invalid NR ({})", c)))
+ }
+ }
+ }
+ }
+}
+
+impl CheckDerConstraints for Real {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ any.header.length.assert_definite()?;
+ // XXX more checks
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for Real {}
+
+impl Tagged for Real {
+ const TAG: Tag = Tag::RealType;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Real {
+ fn to_der_len(&self) -> Result<usize> {
+ match self {
+ Real::Zero => Ok(0),
+ Real::Infinity | Real::NegInfinity => Ok(1),
+ Real::Binary { .. } => {
+ let mut sink = std::io::sink();
+ let n = self
+ .write_der_content(&mut sink)
+ .map_err(|_| Self::TAG.invalid_value("Serialization of REAL failed"))?;
+ Ok(n)
+ }
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.to_der_len()?),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ match self {
+ Real::Zero => Ok(0),
+ Real::Infinity => writer.write(&[0x40]).map_err(Into::into),
+ Real::NegInfinity => writer.write(&[0x41]).map_err(Into::into),
+ Real::Binary {
+ mantissa,
+ base,
+ exponent,
+ enc_base: _enc_base,
+ } => {
+ if *base == 10 {
+ // using character form
+ let sign = if *exponent == 0 { "+" } else { "" };
+ let s = format!("\x03{}E{}{}", mantissa, sign, exponent);
+ return writer.write(s.as_bytes()).map_err(Into::into);
+ }
+ if *base != 2 {
+ return Err(Self::TAG.invalid_value("Invalid base for REAL").into());
+ }
+ let mut first: u8 = 0x80;
+ // choose encoding base
+ let enc_base = *_enc_base;
+ let (ms, mut m, enc_base, mut e) =
+ drop_floating_point(*mantissa, enc_base, *exponent);
+ assert!(m != 0);
+ if ms < 0 {
+ first |= 0x40
+ };
+ // exponent & mantissa normalization
+ match enc_base {
+ 2 => {
+ while m & 0x1 == 0 {
+ m >>= 1;
+ e += 1;
+ }
+ }
+ 8 => {
+ while m & 0x7 == 0 {
+ m >>= 3;
+ e += 1;
+ }
+ first |= 0x10;
+ }
+ _ /* 16 */ => {
+ while m & 0xf == 0 {
+ m >>= 4;
+ e += 1;
+ }
+ first |= 0x20;
+ }
+ }
+ // scale factor
+ // XXX in DER, sf is always 0 (11.3.1)
+ let mut sf = 0;
+ while m & 0x1 == 0 && sf < 4 {
+ m >>= 1;
+ sf += 1;
+ }
+ first |= sf << 2;
+ // exponent length and bytes
+ let len_e = match e.abs() {
+ 0..=0xff => 1,
+ 0x100..=0xffff => 2,
+ 0x1_0000..=0xff_ffff => 3,
+ // e is an `i32` so it can't be longer than 4 bytes
+ // use 4, so `first` is ORed with 3
+ _ => 4,
+ };
+ first |= (len_e - 1) & 0x3;
+ // write first byte
+ let mut n = writer.write(&[first])?;
+ // write exponent
+ // special case: number of bytes from exponent is > 3 and cannot fit in 2 bits
+ #[allow(clippy::identity_op)]
+ if len_e == 4 {
+ let b = len_e & 0xff;
+ n += writer.write(&[b])?;
+ }
+ // we only need to write e.len() bytes
+ let bytes = e.to_be_bytes();
+ n += writer.write(&bytes[(4 - len_e) as usize..])?;
+ // write mantissa
+ let bytes = m.to_be_bytes();
+ let mut idx = 0;
+ for &b in bytes.iter() {
+ if b != 0 {
+ break;
+ }
+ idx += 1;
+ }
+ n += writer.write(&bytes[idx..])?;
+ Ok(n)
+ }
+ }
+ }
+}
+
+impl From<f32> for Real {
+ fn from(f: f32) -> Self {
+ Real::new(f.into())
+ }
+}
+
+impl From<f64> for Real {
+ fn from(f: f64) -> Self {
+ Real::new(f)
+ }
+}
+
+impl From<Real> for f32 {
+ fn from(r: Real) -> Self {
+ r.f32()
+ }
+}
+
+impl From<Real> for f64 {
+ fn from(r: Real) -> Self {
+ r.f64()
+ }
+}
+
+#[cfg(feature = "std")]
+fn drop_floating_point(m: f64, b: u8, e: i32) -> (i8, u64, u8, i32) {
+ let ms = if m.is_sign_positive() { 1 } else { -1 };
+ let es = if e.is_positive() { 1 } else { -1 };
+ let mut m = m.abs();
+ let mut e = e;
+ //
+ if b == 8 {
+ m *= 2_f64.powi((e.abs() / 3) * es);
+ e = (e.abs() / 3) * es;
+ } else if b == 16 {
+ m *= 2_f64.powi((e.abs() / 4) * es);
+ e = (e.abs() / 4) * es;
+ }
+ //
+ while m.abs() > f64::EPSILON {
+ if m.fract() != 0.0 {
+ m *= b as f64;
+ e -= 1;
+ } else {
+ break;
+ }
+ }
+ (ms, m as u64, b, e)
+}
diff --git a/src/asn1_types/real/f32.rs b/src/asn1_types/real/f32.rs
new file mode 100644
index 0000000..cb41dbf
--- /dev/null
+++ b/src/asn1_types/real/f32.rs
@@ -0,0 +1,27 @@
+use crate::{Any, CheckDerConstraints, DerAutoDerive, Error, Real, Result, Tag, Tagged};
+use core::convert::{TryFrom, TryInto};
+
+impl<'a> TryFrom<Any<'a>> for f32 {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<f32> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ let real: Real = any.try_into()?;
+ Ok(real.f32())
+ }
+}
+
+impl CheckDerConstraints for f32 {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ any.header.length.assert_definite()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for f32 {}
+
+impl Tagged for f32 {
+ const TAG: Tag = Tag::RealType;
+}
diff --git a/src/asn1_types/real/f64.rs b/src/asn1_types/real/f64.rs
new file mode 100644
index 0000000..4986d9b
--- /dev/null
+++ b/src/asn1_types/real/f64.rs
@@ -0,0 +1,27 @@
+use crate::{Any, CheckDerConstraints, DerAutoDerive, Error, Real, Result, Tag, Tagged};
+use core::convert::{TryFrom, TryInto};
+
+impl<'a> TryFrom<Any<'a>> for f64 {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<f64> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_primitive()?;
+ let real: Real = any.try_into()?;
+ Ok(real.f64())
+ }
+}
+
+impl CheckDerConstraints for f64 {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ any.header.length.assert_definite()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for f64 {}
+
+impl Tagged for f64 {
+ const TAG: Tag = Tag::RealType;
+}
diff --git a/src/asn1_types/sequence.rs b/src/asn1_types/sequence.rs
new file mode 100644
index 0000000..3a65ec0
--- /dev/null
+++ b/src/asn1_types/sequence.rs
@@ -0,0 +1,398 @@
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+
+mod iterator;
+mod sequence_of;
+mod vec;
+
+pub use iterator::*;
+pub use sequence_of::*;
+pub use vec::*;
+
+/// The `SEQUENCE` object is an ordered list of heteregeneous types.
+///
+/// Sequences can usually be of 2 types:
+/// - a list of different objects (`SEQUENCE`, usually parsed as a `struct`)
+/// - a list of similar objects (`SEQUENCE OF`, usually parsed as a `Vec<T>`)
+///
+/// The current object covers the former. For the latter, see the [`SequenceOf`] documentation.
+///
+/// The `Sequence` object contains the (*unparsed*) encoded representation of its content. It provides
+/// methods to parse and iterate contained objects, or convert the sequence to other types.
+///
+/// # Building a Sequence
+///
+/// To build a DER sequence:
+/// - if the sequence is composed of objects of the same type, the [`Sequence::from_iter_to_der`] method can be used
+/// - otherwise, the [`ToDer`] trait can be used to create content incrementally
+///
+/// ```
+/// use asn1_rs::{Integer, Sequence, SerializeResult, ToDer};
+///
+/// fn build_seq<'a>() -> SerializeResult<Sequence<'a>> {
+/// let mut v = Vec::new();
+/// // add an Integer object (construct type):
+/// let i = Integer::from_u32(4);
+/// let _ = i.write_der(&mut v)?;
+/// // some primitive objects also implement `ToDer`. A string will be mapped as `Utf8String`:
+/// let _ = "abcd".write_der(&mut v)?;
+/// // return the sequence built from the DER content
+/// Ok(Sequence::new(v.into()))
+/// }
+///
+/// let seq = build_seq().unwrap();
+///
+/// ```
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Error, Sequence};
+///
+/// // build sequence
+/// let it = [2, 3, 4].iter();
+/// let seq = Sequence::from_iter_to_der(it).unwrap();
+///
+/// // `seq` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in seq.der_iter::<u32, Error>() {
+/// // item has type `Result<u32>`, since parsing the serialized bytes could fail
+/// sum += item.expect("parsing list item failed");
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+///
+/// Note: the above example encodes a `SEQUENCE OF INTEGER` object, the [`SequenceOf`] object could
+/// be used to provide a simpler API.
+///
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Sequence<'a> {
+ /// Serialized DER representation of the sequence content
+ pub content: Cow<'a, [u8]>,
+}
+
+impl<'a> Sequence<'a> {
+ /// Build a sequence, given the provided content
+ pub const fn new(content: Cow<'a, [u8]>) -> Self {
+ Sequence { content }
+ }
+
+ /// Consume the sequence and return the content
+ #[inline]
+ pub fn into_content(self) -> Cow<'a, [u8]> {
+ self.content
+ }
+
+ /// Apply the parsing function to the sequence content, consuming the sequence
+ ///
+ /// Note: this function expects the caller to take ownership of content.
+ /// In some cases, handling the lifetime of objects is not easy (when keeping only references on
+ /// data). Other methods are provided (depending on the use case):
+ /// - [`Sequence::parse`] takes a reference on the sequence data, but does not consume it,
+ /// - [`Sequence::from_der_and_then`] does the parsing of the sequence and applying the function
+ /// in one step, ensuring there are only references (and dropping the temporary sequence).
+ pub fn and_then<U, F, E>(self, op: F) -> ParseResult<'a, U, E>
+ where
+ F: FnOnce(Cow<'a, [u8]>) -> ParseResult<U, E>,
+ {
+ op(self.content)
+ }
+
+ /// Same as [`Sequence::from_der_and_then`], but using BER encoding (no constraints).
+ pub fn from_ber_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+ E: From<Error>,
+ {
+ let (rem, seq) = Sequence::from_ber(bytes).map_err(Err::convert)?;
+ let data = match seq.content {
+ Cow::Borrowed(b) => b,
+ // Since 'any' is built from 'bytes', it is borrowed by construction
+ Cow::Owned(_) => unreachable!(),
+ };
+ let (_, res) = op(data)?;
+ Ok((rem, res))
+ }
+
+ /// Parse a DER sequence and apply the provided parsing function to content
+ ///
+ /// After parsing, the sequence object and header are discarded.
+ ///
+ /// ```
+ /// use asn1_rs::{FromDer, ParseResult, Sequence};
+ ///
+ /// // Parse a SEQUENCE {
+ /// // a INTEGER (0..255),
+ /// // b INTEGER (0..4294967296)
+ /// // }
+ /// // and return only `(a,b)
+ /// fn parser(i: &[u8]) -> ParseResult<(u8, u32)> {
+ /// Sequence::from_der_and_then(i, |i| {
+ /// let (i, a) = u8::from_der(i)?;
+ /// let (i, b) = u32::from_der(i)?;
+ /// Ok((i, (a, b)))
+ /// }
+ /// )
+ /// }
+ /// ```
+ pub fn from_der_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+ E: From<Error>,
+ {
+ let (rem, seq) = Sequence::from_der(bytes).map_err(Err::convert)?;
+ let data = match seq.content {
+ Cow::Borrowed(b) => b,
+ // Since 'any' is built from 'bytes', it is borrowed by construction
+ Cow::Owned(_) => unreachable!(),
+ };
+ let (_, res) = op(data)?;
+ Ok((rem, res))
+ }
+
+ /// Apply the parsing function to the sequence content (non-consuming version)
+ pub fn parse<F, T, E>(&'a self, mut f: F) -> ParseResult<'a, T, E>
+ where
+ F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+ {
+ let input: &[u8] = &self.content;
+ f(input)
+ }
+
+ /// Apply the parsing function to the sequence content (consuming version)
+ ///
+ /// Note: to parse and apply a parsing function in one step, use the
+ /// [`Sequence::from_der_and_then`] method.
+ ///
+ /// # Limitations
+ ///
+ /// This function fails if the sequence contains `Owned` data, because the parsing function
+ /// takes a reference on data (which is dropped).
+ pub fn parse_into<F, T, E>(self, mut f: F) -> ParseResult<'a, T, E>
+ where
+ F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+ E: From<Error>,
+ {
+ match self.content {
+ Cow::Borrowed(b) => f(b),
+ _ => Err(nom::Err::Error(Error::LifetimeError.into())),
+ }
+ }
+
+ /// Return an iterator over the sequence content, attempting to decode objects as BER
+ ///
+ /// This method can be used when all objects from the sequence have the same type.
+ pub fn ber_iter<T, E>(&'a self) -> SequenceIterator<'a, T, BerParser, E>
+ where
+ T: FromBer<'a, E>,
+ {
+ SequenceIterator::new(&self.content)
+ }
+
+ /// Return an iterator over the sequence content, attempting to decode objects as DER
+ ///
+ /// This method can be used when all objects from the sequence have the same type.
+ pub fn der_iter<T, E>(&'a self) -> SequenceIterator<'a, T, DerParser, E>
+ where
+ T: FromDer<'a, E>,
+ {
+ SequenceIterator::new(&self.content)
+ }
+
+ /// Attempt to parse the sequence as a `SEQUENCE OF` items (BER), and return the parsed items as a `Vec`.
+ pub fn ber_sequence_of<T, E>(&'a self) -> Result<Vec<T>, E>
+ where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+ {
+ self.ber_iter().collect()
+ }
+
+ /// Attempt to parse the sequence as a `SEQUENCE OF` items (DER), and return the parsed items as a `Vec`.
+ pub fn der_sequence_of<T, E>(&'a self) -> Result<Vec<T>, E>
+ where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+ {
+ self.der_iter().collect()
+ }
+
+ /// Attempt to parse the sequence as a `SEQUENCE OF` items (BER) (consuming input),
+ /// and return the parsed items as a `Vec`.
+ ///
+ /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+ pub fn into_ber_sequence_of<T, U, E>(self) -> Result<Vec<T>, E>
+ where
+ for<'b> T: FromBer<'b, E>,
+ E: From<Error>,
+ T: ToStatic<Owned = T>,
+ {
+ match self.content {
+ Cow::Borrowed(bytes) => SequenceIterator::<T, BerParser, E>::new(bytes).collect(),
+ Cow::Owned(data) => {
+ let v1 = SequenceIterator::<T, BerParser, E>::new(&data)
+ .collect::<Result<Vec<T>, E>>()?;
+ let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+ Ok(v2)
+ }
+ }
+ }
+
+ /// Attempt to parse the sequence as a `SEQUENCE OF` items (DER) (consuming input),
+ /// and return the parsed items as a `Vec`.
+ ///
+ /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+ pub fn into_der_sequence_of<T, U, E>(self) -> Result<Vec<T>, E>
+ where
+ for<'b> T: FromDer<'b, E>,
+ E: From<Error>,
+ T: ToStatic<Owned = T>,
+ {
+ match self.content {
+ Cow::Borrowed(bytes) => SequenceIterator::<T, DerParser, E>::new(bytes).collect(),
+ Cow::Owned(data) => {
+ let v1 = SequenceIterator::<T, DerParser, E>::new(&data)
+ .collect::<Result<Vec<T>, E>>()?;
+ let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+ Ok(v2)
+ }
+ }
+ }
+
+ pub fn into_der_sequence_of_ref<T, E>(self) -> Result<Vec<T>, E>
+ where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+ {
+ match self.content {
+ Cow::Borrowed(bytes) => SequenceIterator::<T, DerParser, E>::new(bytes).collect(),
+ Cow::Owned(_) => Err(Error::LifetimeError.into()),
+ }
+ }
+}
+
+impl<'a> ToStatic for Sequence<'a> {
+ type Owned = Sequence<'static>;
+
+ fn to_static(&self) -> Self::Owned {
+ Sequence {
+ content: Cow::Owned(self.content.to_vec()),
+ }
+ }
+}
+
+impl<T, U> ToStatic for Vec<T>
+where
+ T: ToStatic<Owned = U>,
+ U: 'static,
+{
+ type Owned = Vec<U>;
+
+ fn to_static(&self) -> Self::Owned {
+ self.iter().map(|t| t.to_static()).collect()
+ }
+}
+
+impl<'a> AsRef<[u8]> for Sequence<'a> {
+ fn as_ref(&self) -> &[u8] {
+ &self.content
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Sequence<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Sequence<'a>> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Sequence<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Sequence<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ Ok(Sequence {
+ content: Cow::Borrowed(any.data),
+ })
+ }
+}
+
+impl<'a> CheckDerConstraints for Sequence<'a> {
+ fn check_constraints(_any: &Any) -> Result<()> {
+ // TODO: iterate on ANY objects and check constraints? -> this will not be exhaustive
+ // test, for ex INTEGER encoding will not be checked
+ Ok(())
+ }
+}
+
+impl<'a> DerAutoDerive for Sequence<'a> {}
+
+impl<'a> Tagged for Sequence<'a> {
+ const TAG: Tag = Tag::Sequence;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Sequence<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.content.len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ true,
+ Self::TAG,
+ Length::Definite(self.content.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&self.content).map_err(Into::into)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Sequence<'a> {
+ /// Attempt to create a `Sequence` from an iterator over serializable objects (to DER)
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use asn1_rs::Sequence;
+ ///
+ /// // build sequence
+ /// let it = [2, 3, 4].iter();
+ /// let seq = Sequence::from_iter_to_der(it).unwrap();
+ /// ```
+ pub fn from_iter_to_der<T, IT>(it: IT) -> SerializeResult<Self>
+ where
+ IT: Iterator<Item = T>,
+ T: ToDer,
+ T: Tagged,
+ {
+ let mut v = Vec::new();
+ for item in it {
+ let item_v = <T as ToDer>::to_der_vec(&item)?;
+ v.extend_from_slice(&item_v);
+ }
+ Ok(Sequence {
+ content: Cow::Owned(v),
+ })
+ }
+}
diff --git a/src/asn1_types/sequence/iterator.rs b/src/asn1_types/sequence/iterator.rs
new file mode 100644
index 0000000..0ff5fc9
--- /dev/null
+++ b/src/asn1_types/sequence/iterator.rs
@@ -0,0 +1,106 @@
+use crate::{ASN1Parser, BerParser, DerParser, Error, FromBer, FromDer};
+use core::marker::PhantomData;
+
+/// An Iterator over binary data, parsing elements of type `T`
+///
+/// This helps parsing `SEQUENCE OF` items of type `T`. The type of parser
+/// (BER/DER) is specified using the generic parameter `F` of this struct.
+///
+/// Note: the iterator must start on the sequence *contents*, not the sequence itself.
+///
+/// # Examples
+///
+/// ```rust
+/// use asn1_rs::{DerParser, Integer, SequenceIterator};
+///
+/// let data = &[0x30, 0x6, 0x2, 0x1, 0x1, 0x2, 0x1, 0x2];
+/// for (idx, item) in SequenceIterator::<Integer, DerParser>::new(&data[2..]).enumerate() {
+/// let item = item.unwrap(); // parsing could have failed
+/// let i = item.as_u32().unwrap(); // integer can be negative, or too large to fit into u32
+/// assert_eq!(i as usize, idx + 1);
+/// }
+/// ```
+#[derive(Debug)]
+pub struct SequenceIterator<'a, T, F, E = Error>
+where
+ F: ASN1Parser,
+{
+ data: &'a [u8],
+ has_error: bool,
+ _t: PhantomData<T>,
+ _f: PhantomData<F>,
+ _e: PhantomData<E>,
+}
+
+impl<'a, T, F, E> SequenceIterator<'a, T, F, E>
+where
+ F: ASN1Parser,
+{
+ pub fn new(data: &'a [u8]) -> Self {
+ SequenceIterator {
+ data,
+ has_error: false,
+ _t: PhantomData,
+ _f: PhantomData,
+ _e: PhantomData,
+ }
+ }
+}
+
+impl<'a, T, E> Iterator for SequenceIterator<'a, T, BerParser, E>
+where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+{
+ type Item = Result<T, E>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.has_error || self.data.is_empty() {
+ return None;
+ }
+ match T::from_ber(self.data) {
+ Ok((rem, obj)) => {
+ self.data = rem;
+ Some(Ok(obj))
+ }
+ Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
+ self.has_error = true;
+ Some(Err(e))
+ }
+
+ Err(nom::Err::Incomplete(n)) => {
+ self.has_error = true;
+ Some(Err(Error::Incomplete(n).into()))
+ }
+ }
+ }
+}
+
+impl<'a, T, E> Iterator for SequenceIterator<'a, T, DerParser, E>
+where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+{
+ type Item = Result<T, E>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.has_error || self.data.is_empty() {
+ return None;
+ }
+ match T::from_der(self.data) {
+ Ok((rem, obj)) => {
+ self.data = rem;
+ Some(Ok(obj))
+ }
+ Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => {
+ self.has_error = true;
+ Some(Err(e))
+ }
+
+ Err(nom::Err::Incomplete(n)) => {
+ self.has_error = true;
+ Some(Err(Error::Incomplete(n).into()))
+ }
+ }
+ }
+}
diff --git a/src/asn1_types/sequence/sequence_of.rs b/src/asn1_types/sequence/sequence_of.rs
new file mode 100644
index 0000000..239e72f
--- /dev/null
+++ b/src/asn1_types/sequence/sequence_of.rs
@@ -0,0 +1,150 @@
+use crate::*;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::iter::FromIterator;
+
+/// The `SEQUENCE OF` object is an ordered list of homogeneous types.
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::SequenceOf;
+/// use std::iter::FromIterator;
+///
+/// // build set
+/// let it = [2, 3, 4].iter();
+/// let seq = SequenceOf::from_iter(it);
+///
+/// // `seq` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in seq.iter() {
+/// // item has type `Result<u32>`, since parsing the serialized bytes could fail
+/// sum += *item;
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+#[derive(Debug)]
+pub struct SequenceOf<T> {
+ pub(crate) items: Vec<T>,
+}
+
+impl<T> SequenceOf<T> {
+ /// Builds a `SEQUENCE OF` from the provided content
+ #[inline]
+ pub const fn new(items: Vec<T>) -> Self {
+ SequenceOf { items }
+ }
+
+ /// Returns the length of this `SEQUENCE` (the number of items).
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.items.len()
+ }
+
+ /// Returns `true` if this `SEQUENCE` is empty.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.items.is_empty()
+ }
+
+ /// Returns an iterator over the items of the `SEQUENCE`.
+ #[inline]
+ pub fn iter(&self) -> impl Iterator<Item = &T> {
+ self.items.iter()
+ }
+}
+
+impl<T> AsRef<[T]> for SequenceOf<T> {
+ fn as_ref(&self) -> &[T] {
+ &self.items
+ }
+}
+
+impl<'a, T> IntoIterator for &'a SequenceOf<T> {
+ type Item = &'a T;
+ type IntoIter = core::slice::Iter<'a, T>;
+
+ fn into_iter(self) -> core::slice::Iter<'a, T> {
+ self.items.iter()
+ }
+}
+
+impl<'a, T> IntoIterator for &'a mut SequenceOf<T> {
+ type Item = &'a mut T;
+ type IntoIter = core::slice::IterMut<'a, T>;
+
+ fn into_iter(self) -> core::slice::IterMut<'a, T> {
+ self.items.iter_mut()
+ }
+}
+
+impl<T> From<SequenceOf<T>> for Vec<T> {
+ fn from(set: SequenceOf<T>) -> Self {
+ set.items
+ }
+}
+
+impl<T> FromIterator<T> for SequenceOf<T> {
+ fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
+ let items = iter.into_iter().collect();
+ SequenceOf::new(items)
+ }
+}
+
+impl<'a, T> TryFrom<Any<'a>> for SequenceOf<T>
+where
+ T: FromBer<'a>,
+{
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ if !any.header.is_constructed() {
+ return Err(Error::ConstructExpected);
+ }
+ let items = SequenceIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+ Ok(SequenceOf::new(items))
+ }
+}
+
+impl<T> CheckDerConstraints for SequenceOf<T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ for item in SequenceIterator::<Any, DerParser>::new(any.data) {
+ let item = item?;
+ T::check_constraints(&item)?;
+ }
+ Ok(())
+ }
+}
+
+impl<T> DerAutoDerive for SequenceOf<T> {}
+
+impl<T> Tagged for SequenceOf<T> {
+ const TAG: Tag = Tag::Sequence;
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for SequenceOf<T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ self.items.to_der_len()
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.items.write_der_header(writer)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.items.write_der_content(writer)
+ }
+}
diff --git a/src/asn1_types/sequence/vec.rs b/src/asn1_types/sequence/vec.rs
new file mode 100644
index 0000000..d8beead
--- /dev/null
+++ b/src/asn1_types/sequence/vec.rs
@@ -0,0 +1,138 @@
+use crate::*;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+
+// // XXX this compiles but requires bound TryFrom :/
+// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T>
+// where
+// T: TryFrom<&'b Any<'a>>,
+// for<'e> <T as TryFrom<&'b Any<'a>>>::Error: From<Error>,
+// T: FromBer<'a, <T as TryFrom<&'b Any<'a>>>::Error>,
+// // T: FromBer<'a, E>,
+// // E: From<Error>,
+// {
+// type Error = <T as TryFrom<&'b Any<'a>>>::Error;
+
+// fn try_from(any: &'b Any<'a>) -> Result<Vec<T>, Self::Error> {
+// any.tag().assert_eq(Self::TAG)?;
+// any.header.assert_constructed()?;
+// let v = SequenceIterator::<T, BerParser, Self::Error>::new(any.data)
+// .collect::<Result<Vec<T>, Self::Error>>()?;
+// Ok(v)
+// }
+// }
+
+// // XXX this compiles but requires bound TryFrom :/
+// impl<'a, 'b, T> TryFrom<&'b Any<'a>> for Vec<T>
+// where
+// T: TryFrom<&'b Any<'a>>,
+// <T as TryFrom<&'b Any<'a>>>::Error: From<Error>,
+// T: FromBer<'a, <T as TryFrom<&'b Any<'a>>>::Error>,
+// // T: FromBer<'a, E>,
+// // E: From<Error>,
+// {
+// type Error = <T as TryFrom<&'b Any<'a>>>::Error;
+
+// fn try_from(any: &'b Any<'a>) -> Result<Vec<T>, Self::Error> {
+// any.tag().assert_eq(Self::TAG)?;
+// any.header.assert_constructed()?;
+// let v = SequenceIterator::<T, BerParser, Self::Error>::new(any.data)
+// .collect::<Result<Vec<T>, Self::Error>>()?;
+// Ok(v)
+// }
+// }
+
+impl<'a, T> TryFrom<Any<'a>> for Vec<T>
+where
+ T: FromBer<'a>,
+{
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+ Ok(items)
+ }
+}
+
+impl<T> CheckDerConstraints for Vec<T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ for item in SequenceIterator::<Any, DerParser>::new(any.data) {
+ let item = item?;
+ <T as CheckDerConstraints>::check_constraints(&item)?;
+ }
+ Ok(())
+ }
+}
+
+impl<T> Tagged for Vec<T> {
+ const TAG: Tag = Tag::Sequence;
+}
+
+// impl<'a, T> FromBer<'a> for Vec<T>
+// where
+// T: FromBer<'a>,
+// {
+// fn from_ber(bytes: &'a [u8]) -> ParseResult<Self> {
+// let (rem, any) = Any::from_ber(bytes)?;
+// any.header.assert_tag(Self::TAG)?;
+// let v = SequenceIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+// Ok((rem, v))
+// }
+// }
+
+/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints`
+impl<'a, T, E> FromDer<'a, E> for Vec<T>
+where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ any.header
+ .assert_tag(Self::TAG)
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ let v = SequenceIterator::<T, DerParser, E>::new(any.data)
+ .collect::<Result<Vec<T>, E>>()
+ .map_err(nom::Err::Error)?;
+ Ok((rem, v))
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for Vec<T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ let mut len = 0;
+ for t in self.iter() {
+ len += t.to_der_len()?;
+ }
+ let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+ Ok(header.to_der_len()? + len)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut len = 0;
+ for t in self.iter() {
+ len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?;
+ }
+ let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut sz = 0;
+ for t in self.iter() {
+ sz += t.write_der(writer)?;
+ }
+ Ok(sz)
+ }
+}
diff --git a/src/asn1_types/set.rs b/src/asn1_types/set.rs
new file mode 100644
index 0000000..ef693d2
--- /dev/null
+++ b/src/asn1_types/set.rs
@@ -0,0 +1,387 @@
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+
+mod btreeset;
+mod hashset;
+mod iterator;
+mod set_of;
+
+pub use btreeset::*;
+#[cfg(feature = "std")]
+pub use hashset::*;
+pub use iterator::*;
+pub use set_of::*;
+
+/// The `SET` object is an unordered list of heteregeneous types.
+///
+/// Sets can usually be of 2 types:
+/// - a list of different objects (`SET`, usually parsed as a `struct`)
+/// - a list of similar objects (`SET OF`, usually parsed as a `BTreeSet<T>` or `HashSet<T>`)
+///
+/// The current object covers the former. For the latter, see the [`SetOf`] documentation.
+///
+/// The `Set` object contains the (*unparsed*) encoded representation of its content. It provides
+/// methods to parse and iterate contained objects, or convert the sequence to other types.
+///
+/// # Building a Set
+///
+/// To build a DER set:
+/// - if the set is composed of objects of the same type, the [`Set::from_iter_to_der`] method can be used
+/// - otherwise, the [`ToDer`] trait can be used to create content incrementally
+///
+/// ```
+/// use asn1_rs::{Integer, Set, SerializeResult, ToDer};
+///
+/// fn build_set<'a>() -> SerializeResult<Set<'a>> {
+/// let mut v = Vec::new();
+/// // add an Integer object (construct type):
+/// let i = Integer::from_u32(4);
+/// let _ = i.write_der(&mut v)?;
+/// // some primitive objects also implement `ToDer`. A string will be mapped as `Utf8String`:
+/// let _ = "abcd".write_der(&mut v)?;
+/// // return the set built from the DER content
+/// Ok(Set::new(v.into()))
+/// }
+///
+/// let seq = build_set().unwrap();
+///
+/// ```
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Error, Set};
+///
+/// // build set
+/// let it = [2, 3, 4].iter();
+/// let set = Set::from_iter_to_der(it).unwrap();
+///
+/// // `set` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in set.der_iter::<u32, Error>() {
+/// // item has type `Result<u32>`, since parsing the serialized bytes could fail
+/// sum += item.expect("parsing list item failed");
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+///
+/// Note: the above example encodes a `SET OF INTEGER` object, the [`SetOf`] object could
+/// be used to provide a simpler API.
+///
+#[derive(Clone, Debug)]
+pub struct Set<'a> {
+ /// Serialized DER representation of the set content
+ pub content: Cow<'a, [u8]>,
+}
+
+impl<'a> Set<'a> {
+ /// Build a set, given the provided content
+ pub const fn new(content: Cow<'a, [u8]>) -> Self {
+ Set { content }
+ }
+
+ /// Consume the set and return the content
+ #[inline]
+ pub fn into_content(self) -> Cow<'a, [u8]> {
+ self.content
+ }
+
+ /// Apply the parsing function to the set content, consuming the set
+ ///
+ /// Note: this function expects the caller to take ownership of content.
+ /// In some cases, handling the lifetime of objects is not easy (when keeping only references on
+ /// data). Other methods are provided (depending on the use case):
+ /// - [`Set::parse`] takes a reference on the set data, but does not consume it,
+ /// - [`Set::from_der_and_then`] does the parsing of the set and applying the function
+ /// in one step, ensuring there are only references (and dropping the temporary set).
+ pub fn and_then<U, F, E>(self, op: F) -> ParseResult<'a, U, E>
+ where
+ F: FnOnce(Cow<'a, [u8]>) -> ParseResult<U, E>,
+ {
+ op(self.content)
+ }
+
+ /// Same as [`Set::from_der_and_then`], but using BER encoding (no constraints).
+ pub fn from_ber_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+ E: From<Error>,
+ {
+ let (rem, seq) = Set::from_ber(bytes).map_err(Err::convert)?;
+ let data = match seq.content {
+ Cow::Borrowed(b) => b,
+ // Since 'any' is built from 'bytes', it is borrowed by construction
+ Cow::Owned(_) => unreachable!(),
+ };
+ let (_, res) = op(data)?;
+ Ok((rem, res))
+ }
+
+ /// Parse a DER set and apply the provided parsing function to content
+ ///
+ /// After parsing, the set object and header are discarded.
+ ///
+ /// ```
+ /// use asn1_rs::{FromDer, ParseResult, Set};
+ ///
+ /// // Parse a SET {
+ /// // a INTEGER (0..255),
+ /// // b INTEGER (0..4294967296)
+ /// // }
+ /// // and return only `(a,b)
+ /// fn parser(i: &[u8]) -> ParseResult<(u8, u32)> {
+ /// Set::from_der_and_then(i, |i| {
+ /// let (i, a) = u8::from_der(i)?;
+ /// let (i, b) = u32::from_der(i)?;
+ /// Ok((i, (a, b)))
+ /// }
+ /// )
+ /// }
+ /// ```
+ pub fn from_der_and_then<U, F, E>(bytes: &'a [u8], op: F) -> ParseResult<'a, U, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<U, E>,
+ E: From<Error>,
+ {
+ let (rem, seq) = Set::from_der(bytes).map_err(Err::convert)?;
+ let data = match seq.content {
+ Cow::Borrowed(b) => b,
+ // Since 'any' is built from 'bytes', it is borrowed by construction
+ Cow::Owned(_) => unreachable!(),
+ };
+ let (_, res) = op(data)?;
+ Ok((rem, res))
+ }
+
+ /// Apply the parsing function to the set content (non-consuming version)
+ pub fn parse<F, T, E>(&'a self, mut f: F) -> ParseResult<'a, T, E>
+ where
+ F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+ {
+ let input: &[u8] = &self.content;
+ f(input)
+ }
+
+ /// Apply the parsing function to the set content (consuming version)
+ ///
+ /// Note: to parse and apply a parsing function in one step, use the
+ /// [`Set::from_der_and_then`] method.
+ ///
+ /// # Limitations
+ ///
+ /// This function fails if the set contains `Owned` data, because the parsing function
+ /// takes a reference on data (which is dropped).
+ pub fn parse_into<F, T, E>(self, mut f: F) -> ParseResult<'a, T, E>
+ where
+ F: FnMut(&'a [u8]) -> ParseResult<'a, T, E>,
+ E: From<Error>,
+ {
+ match self.content {
+ Cow::Borrowed(b) => f(b),
+ _ => Err(nom::Err::Error(Error::LifetimeError.into())),
+ }
+ }
+
+ /// Return an iterator over the set content, attempting to decode objects as BER
+ ///
+ /// This method can be used when all objects from the set have the same type.
+ pub fn ber_iter<T, E>(&'a self) -> SetIterator<'a, T, BerParser, E>
+ where
+ T: FromBer<'a, E>,
+ {
+ SetIterator::new(&self.content)
+ }
+
+ /// Return an iterator over the set content, attempting to decode objects as DER
+ ///
+ /// This method can be used when all objects from the set have the same type.
+ pub fn der_iter<T, E>(&'a self) -> SetIterator<'a, T, DerParser, E>
+ where
+ T: FromDer<'a, E>,
+ {
+ SetIterator::new(&self.content)
+ }
+
+ /// Attempt to parse the set as a `SET OF` items (BER), and return the parsed items as a `Vec`.
+ pub fn ber_set_of<T, E>(&'a self) -> Result<Vec<T>, E>
+ where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+ {
+ self.ber_iter().collect()
+ }
+
+ /// Attempt to parse the set as a `SET OF` items (DER), and return the parsed items as a `Vec`.
+ pub fn der_set_of<T, E>(&'a self) -> Result<Vec<T>, E>
+ where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+ {
+ self.der_iter().collect()
+ }
+
+ /// Attempt to parse the set as a `SET OF` items (BER) (consuming input),
+ /// and return the parsed items as a `Vec`.
+ ///
+ /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+ pub fn into_ber_set_of<T, E>(self) -> Result<Vec<T>, E>
+ where
+ for<'b> T: FromBer<'b, E>,
+ E: From<Error>,
+ T: ToStatic<Owned = T>,
+ {
+ match self.content {
+ Cow::Borrowed(bytes) => SetIterator::<T, BerParser, E>::new(bytes).collect(),
+ Cow::Owned(data) => {
+ let v1 =
+ SetIterator::<T, BerParser, E>::new(&data).collect::<Result<Vec<T>, E>>()?;
+ let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+ Ok(v2)
+ }
+ }
+ }
+
+ /// Attempt to parse the set as a `SET OF` items (DER) (consuming input),
+ /// and return the parsed items as a `Vec`.
+ ///
+ /// Note: if `Self` is an `Owned` object, the data will be duplicated (causing allocations) into separate objects.
+ pub fn into_der_set_of<T, E>(self) -> Result<Vec<T>, E>
+ where
+ for<'b> T: FromDer<'b, E>,
+ E: From<Error>,
+ T: ToStatic<Owned = T>,
+ {
+ match self.content {
+ Cow::Borrowed(bytes) => SetIterator::<T, DerParser, E>::new(bytes).collect(),
+ Cow::Owned(data) => {
+ let v1 =
+ SetIterator::<T, DerParser, E>::new(&data).collect::<Result<Vec<T>, E>>()?;
+ let v2 = v1.iter().map(|t| t.to_static()).collect::<Vec<_>>();
+ Ok(v2)
+ }
+ }
+ }
+
+ pub fn into_der_set_of_ref<T, E>(self) -> Result<Vec<T>, E>
+ where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+ {
+ match self.content {
+ Cow::Borrowed(bytes) => SetIterator::<T, DerParser, E>::new(bytes).collect(),
+ Cow::Owned(_) => Err(Error::LifetimeError.into()),
+ }
+ }
+}
+
+impl<'a> ToStatic for Set<'a> {
+ type Owned = Set<'static>;
+
+ fn to_static(&self) -> Self::Owned {
+ Set {
+ content: Cow::Owned(self.content.to_vec()),
+ }
+ }
+}
+
+impl<'a> AsRef<[u8]> for Set<'a> {
+ fn as_ref(&self) -> &[u8] {
+ &self.content
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for Set<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Set<'a>> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for Set<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Set<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ Ok(Set {
+ content: Cow::Borrowed(any.data),
+ })
+ }
+}
+
+impl<'a> CheckDerConstraints for Set<'a> {
+ fn check_constraints(_any: &Any) -> Result<()> {
+ Ok(())
+ }
+}
+
+impl<'a> DerAutoDerive for Set<'a> {}
+
+impl<'a> Tagged for Set<'a> {
+ const TAG: Tag = Tag::Set;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for Set<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.content.len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ true,
+ Self::TAG,
+ Length::Definite(self.content.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(&self.content).map_err(Into::into)
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a> Set<'a> {
+ /// Attempt to create a `Set` from an iterator over serializable objects (to DER)
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use asn1_rs::Set;
+ ///
+ /// // build set
+ /// let it = [2, 3, 4].iter();
+ /// let seq = Set::from_iter_to_der(it).unwrap();
+ /// ```
+ pub fn from_iter_to_der<T, IT>(it: IT) -> SerializeResult<Self>
+ where
+ IT: Iterator<Item = T>,
+ T: ToDer,
+ T: Tagged,
+ {
+ let mut v = Vec::new();
+ for item in it {
+ let item_v = <T as ToDer>::to_der_vec(&item)?;
+ v.extend_from_slice(&item_v);
+ }
+ Ok(Set {
+ content: Cow::Owned(v),
+ })
+ }
+}
diff --git a/src/asn1_types/set/btreeset.rs b/src/asn1_types/set/btreeset.rs
new file mode 100644
index 0000000..a70f9f9
--- /dev/null
+++ b/src/asn1_types/set/btreeset.rs
@@ -0,0 +1,124 @@
+use crate::*;
+use alloc::collections::BTreeSet;
+use core::convert::TryFrom;
+
+impl<T> Tagged for BTreeSet<T> {
+ const TAG: Tag = Tag::Set;
+}
+
+impl<'a, T> TryFrom<Any<'a>> for BTreeSet<T>
+where
+ T: FromBer<'a>,
+ T: Ord,
+{
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<BTreeSet<T>>>()?;
+ Ok(items)
+ }
+}
+
+impl<T> CheckDerConstraints for BTreeSet<T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ for item in SetIterator::<Any, DerParser>::new(any.data) {
+ let item = item?;
+ T::check_constraints(&item)?;
+ }
+ Ok(())
+ }
+}
+
+/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints`
+impl<'a, T, E> FromDer<'a, E> for BTreeSet<T>
+where
+ T: FromDer<'a, E>,
+ T: Ord,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ any.tag()
+ .assert_eq(Self::TAG)
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ any.header
+ .assert_constructed()
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ let items = SetIterator::<T, DerParser, E>::new(any.data)
+ .collect::<Result<BTreeSet<T>, E>>()
+ .map_err(nom::Err::Error)?;
+ Ok((rem, items))
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for BTreeSet<T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ let mut len = 0;
+ for t in self.iter() {
+ len += t.to_der_len()?;
+ }
+ let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+ Ok(header.to_der_len()? + len)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut len = 0;
+ for t in self.iter() {
+ len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?;
+ }
+ let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut sz = 0;
+ for t in self.iter() {
+ sz += t.write_der(writer)?;
+ }
+ Ok(sz)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::*;
+ use core::convert::TryFrom;
+ use hex_literal::hex;
+ use std::collections::BTreeSet;
+
+ #[test]
+ fn ber_btreeset() {
+ let input = &hex! {"31 06 02 01 00 02 01 01"};
+ let (_, any) = Any::from_ber(input).expect("parsing hashset failed");
+ <BTreeSet<u32>>::check_constraints(&any).unwrap();
+
+ let h = <BTreeSet<u32>>::try_from(any).unwrap();
+
+ assert_eq!(h.len(), 2);
+ }
+
+ #[test]
+ fn der_btreeset() {
+ let input = &hex! {"31 06 02 01 00 02 01 01"};
+ let r: IResult<_, _, Error> = BTreeSet::<u32>::from_der(input);
+ let (_, h) = r.expect("parsing hashset failed");
+
+ assert_eq!(h.len(), 2);
+
+ assert_eq!(h.to_der_len(), Ok(8));
+ let v = h.to_der_vec().expect("could not serialize");
+ let (_, h2) = SetOf::<u32>::from_der(&v).unwrap();
+ assert!(h.iter().eq(h2.iter()));
+ }
+}
diff --git a/src/asn1_types/set/hashset.rs b/src/asn1_types/set/hashset.rs
new file mode 100644
index 0000000..d505301
--- /dev/null
+++ b/src/asn1_types/set/hashset.rs
@@ -0,0 +1,125 @@
+#![cfg(feature = "std")]
+use crate::*;
+use std::collections::HashSet;
+use std::convert::TryFrom;
+use std::hash::Hash;
+
+impl<T> Tagged for HashSet<T> {
+ const TAG: Tag = Tag::Set;
+}
+
+impl<'a, T> TryFrom<Any<'a>> for HashSet<T>
+where
+ T: FromBer<'a>,
+ T: Hash + Eq,
+{
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<HashSet<T>>>()?;
+ Ok(items)
+ }
+}
+
+impl<T> CheckDerConstraints for HashSet<T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ for item in SetIterator::<Any, DerParser>::new(any.data) {
+ let item = item?;
+ T::check_constraints(&item)?;
+ }
+ Ok(())
+ }
+}
+
+/// manual impl of FromDer, so we do not need to require `TryFrom<Any> + CheckDerConstraints`
+impl<'a, T, E> FromDer<'a, E> for HashSet<T>
+where
+ T: FromDer<'a, E>,
+ T: Hash + Eq,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ any.tag()
+ .assert_eq(Self::TAG)
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ any.header
+ .assert_constructed()
+ .map_err(|e| nom::Err::Error(e.into()))?;
+ let items = SetIterator::<T, DerParser, E>::new(any.data)
+ .collect::<Result<HashSet<T>, E>>()
+ .map_err(nom::Err::Error)?;
+ Ok((rem, items))
+ }
+}
+
+impl<T> ToDer for HashSet<T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ let mut len = 0;
+ for t in self.iter() {
+ len += t.to_der_len()?;
+ }
+ let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+ Ok(header.to_der_len()? + len)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut len = 0;
+ for t in self.iter() {
+ len += t.to_der_len().map_err(|_| SerializeError::InvalidLength)?;
+ }
+ let header = Header::new(Class::Universal, true, Self::TAG, Length::Definite(len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut sz = 0;
+ for t in self.iter() {
+ sz += t.write_der(writer)?;
+ }
+ Ok(sz)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::*;
+ use core::convert::TryFrom;
+ use hex_literal::hex;
+ use std::collections::HashSet;
+
+ #[test]
+ fn ber_hashset() {
+ let input = &hex! {"31 06 02 01 00 02 01 01"};
+ let (_, any) = Any::from_ber(input).expect("parsing hashset failed");
+ <HashSet<u32>>::check_constraints(&any).unwrap();
+
+ let h = <HashSet<u32>>::try_from(any).unwrap();
+
+ assert_eq!(h.len(), 2);
+ }
+
+ #[test]
+ fn der_hashset() {
+ let input = &hex! {"31 06 02 01 00 02 01 01"};
+ let r: IResult<_, _, Error> = HashSet::<u32>::from_der(input);
+ let (_, h) = r.expect("parsing hashset failed");
+
+ assert_eq!(h.len(), 2);
+
+ assert_eq!(h.to_der_len(), Ok(8));
+ let v = h.to_der_vec().expect("could not serialize");
+ let (_, h2) = SetOf::<u32>::from_der(&v).unwrap();
+ assert!(h.iter().eq(h2.iter()));
+ }
+}
diff --git a/src/asn1_types/set/iterator.rs b/src/asn1_types/set/iterator.rs
new file mode 100644
index 0000000..eb46075
--- /dev/null
+++ b/src/asn1_types/set/iterator.rs
@@ -0,0 +1,22 @@
+pub use crate::{Error, SequenceIterator};
+
+/// An Iterator over binary data, parsing elements of type `T`
+///
+/// This helps parsing `SET OF` items of type `T`. The type of parser
+/// (BER/DER) is specified using the generic parameter `F` of this struct.
+///
+/// Note: the iterator must start on the set *contents*, not the set itself.
+///
+/// # Examples
+///
+/// ```rust
+/// use asn1_rs::{DerParser, Integer, SetIterator};
+///
+/// let data = &[0x30, 0x6, 0x2, 0x1, 0x1, 0x2, 0x1, 0x2];
+/// for (idx, item) in SetIterator::<Integer, DerParser>::new(&data[2..]).enumerate() {
+/// let item = item.unwrap(); // parsing could have failed
+/// let i = item.as_u32().unwrap(); // integer can be negative, or too large to fit into u32
+/// assert_eq!(i as usize, idx + 1);
+/// }
+/// ```
+pub type SetIterator<'a, T, F, E = Error> = SequenceIterator<'a, T, F, E>;
diff --git a/src/asn1_types/set/set_of.rs b/src/asn1_types/set/set_of.rs
new file mode 100644
index 0000000..af6c0a7
--- /dev/null
+++ b/src/asn1_types/set/set_of.rs
@@ -0,0 +1,150 @@
+use crate::*;
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::iter::FromIterator;
+
+/// The `SET OF` object is an unordered list of homogeneous types.
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::SetOf;
+/// use std::iter::FromIterator;
+///
+/// // build set
+/// let it = [2, 3, 4].iter();
+/// let set = SetOf::from_iter(it);
+///
+/// // `set` now contains the serialized DER representation of the array
+///
+/// // iterate objects
+/// let mut sum = 0;
+/// for item in set.iter() {
+/// // item has type `Result<u32>`, since parsing the serialized bytes could fail
+/// sum += *item;
+/// }
+/// assert_eq!(sum, 9);
+///
+/// ```
+#[derive(Debug)]
+pub struct SetOf<T> {
+ items: Vec<T>,
+}
+
+impl<T> SetOf<T> {
+ /// Builds a `SET OF` from the provided content
+ #[inline]
+ pub const fn new(items: Vec<T>) -> Self {
+ SetOf { items }
+ }
+
+ /// Returns the length of this `SET` (the number of items).
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.items.len()
+ }
+
+ /// Returns `true` if this `SET` is empty.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.items.is_empty()
+ }
+
+ /// Returns an iterator over the items of the `SET`.
+ #[inline]
+ pub fn iter(&self) -> impl Iterator<Item = &T> {
+ self.items.iter()
+ }
+}
+
+impl<T> AsRef<[T]> for SetOf<T> {
+ fn as_ref(&self) -> &[T] {
+ &self.items
+ }
+}
+
+impl<'a, T> IntoIterator for &'a SetOf<T> {
+ type Item = &'a T;
+ type IntoIter = core::slice::Iter<'a, T>;
+
+ fn into_iter(self) -> core::slice::Iter<'a, T> {
+ self.items.iter()
+ }
+}
+
+impl<'a, T> IntoIterator for &'a mut SetOf<T> {
+ type Item = &'a mut T;
+ type IntoIter = core::slice::IterMut<'a, T>;
+
+ fn into_iter(self) -> core::slice::IterMut<'a, T> {
+ self.items.iter_mut()
+ }
+}
+
+impl<T> From<SetOf<T>> for Vec<T> {
+ fn from(set: SetOf<T>) -> Self {
+ set.items
+ }
+}
+
+impl<T> FromIterator<T> for SetOf<T> {
+ fn from_iter<IT: IntoIterator<Item = T>>(iter: IT) -> Self {
+ let items = iter.into_iter().collect();
+ SetOf::new(items)
+ }
+}
+
+impl<'a, T> TryFrom<Any<'a>> for SetOf<T>
+where
+ T: FromBer<'a>,
+{
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<Self> {
+ any.tag().assert_eq(Self::TAG)?;
+ if !any.header.is_constructed() {
+ return Err(Error::ConstructExpected);
+ }
+ let items = SetIterator::<T, BerParser>::new(any.data).collect::<Result<Vec<T>>>()?;
+ Ok(SetOf::new(items))
+ }
+}
+
+impl<T> CheckDerConstraints for SetOf<T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.tag().assert_eq(Self::TAG)?;
+ any.header.assert_constructed()?;
+ for item in SetIterator::<Any, DerParser>::new(any.data) {
+ let item = item?;
+ T::check_constraints(&item)?;
+ }
+ Ok(())
+ }
+}
+
+impl<T> DerAutoDerive for SetOf<T> {}
+
+impl<T> Tagged for SetOf<T> {
+ const TAG: Tag = Tag::Set;
+}
+
+#[cfg(feature = "std")]
+impl<T> ToDer for SetOf<T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ self.items.to_der_len()
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.items.write_der_header(writer)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.items.write_der_content(writer)
+ }
+}
diff --git a/src/asn1_types/strings.rs b/src/asn1_types/strings.rs
new file mode 100644
index 0000000..66a4c19
--- /dev/null
+++ b/src/asn1_types/strings.rs
@@ -0,0 +1,171 @@
+mod bmpstring;
+mod generalstring;
+mod graphicstring;
+mod ia5string;
+mod numericstring;
+mod printablestring;
+mod str;
+mod string;
+mod teletexstring;
+mod universalstring;
+mod utf8string;
+mod videotexstring;
+mod visiblestring;
+
+pub use self::str::*;
+pub use bmpstring::*;
+pub use generalstring::*;
+pub use graphicstring::*;
+pub use ia5string::*;
+pub use numericstring::*;
+pub use printablestring::*;
+pub use string::*;
+pub use teletexstring::*;
+pub use universalstring::*;
+pub use utf8string::*;
+pub use videotexstring::*;
+pub use visiblestring::*;
+
+/// Base trait for BER string objects and character set validation
+///
+/// This trait is implemented by several types, and is used to determine if some bytes
+/// would be valid for the given type.
+///
+/// # Example
+///
+/// ```rust
+/// use asn1_rs::{PrintableString, TestValidCharset, VisibleString};
+///
+/// let bytes: &[u8] = b"abcd*4";
+/// let res = PrintableString::test_valid_charset(bytes);
+/// assert!(res.is_err());
+/// let res = VisibleString::test_valid_charset(bytes);
+/// assert!(res.is_ok());
+/// ```
+pub trait TestValidCharset {
+ /// Check character set for this object type.
+ fn test_valid_charset(i: &[u8]) -> crate::Result<()>;
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! asn1_string {
+ (IMPL $name:ident, $sname:expr) => {
+ #[doc="ASN.1 restricted character string type (`"]
+ #[doc = $sname]
+ #[doc = "`)"]
+ #[derive(Debug, PartialEq, Eq)]
+ pub struct $name<'a> {
+ pub(crate) data: alloc::borrow::Cow<'a, str>,
+ }
+
+ impl<'a> $name<'a> {
+ pub const fn new(s: &'a str) -> Self {
+ $name {
+ data: alloc::borrow::Cow::Borrowed(s),
+ }
+ }
+
+ pub fn string(&self) -> String {
+ use alloc::string::ToString;
+ self.data.to_string()
+ }
+ }
+
+ impl<'a> AsRef<str> for $name<'a> {
+ fn as_ref(&self) -> &str {
+ &self.data
+ }
+ }
+
+ impl<'a> From<&'a str> for $name<'a> {
+ fn from(s: &'a str) -> Self {
+ Self::new(s)
+ }
+ }
+
+ impl From<String> for $name<'_> {
+ fn from(s: String) -> Self {
+ Self {
+ data: alloc::borrow::Cow::Owned(s),
+ }
+ }
+ }
+
+ impl<'a> core::convert::TryFrom<$crate::Any<'a>> for $name<'a> {
+ type Error = $crate::Error;
+
+ fn try_from(any: $crate::Any<'a>) -> $crate::Result<$name<'a>> {
+ use core::convert::TryFrom;
+ TryFrom::try_from(&any)
+ }
+ }
+
+ impl<'a, 'b> core::convert::TryFrom<&'b $crate::Any<'a>> for $name<'a> {
+ type Error = $crate::Error;
+
+ fn try_from(any: &'b $crate::Any<'a>) -> $crate::Result<$name<'a>> {
+ use $crate::traits::Tagged;
+ use alloc::borrow::Cow;
+ any.tag().assert_eq(Self::TAG)?;
+ <$name>::test_valid_charset(any.data)?;
+
+ let s = alloc::str::from_utf8(any.data)?;
+ let data = Cow::Borrowed(s);
+ Ok($name { data })
+ }
+ }
+
+ impl<'a> $crate::CheckDerConstraints for $name<'a> {
+ fn check_constraints(any: &$crate::Any) -> $crate::Result<()> {
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+ }
+
+ impl $crate::DerAutoDerive for $name<'_> {}
+
+ impl<'a> $crate::Tagged for $name<'a> {
+ const TAG: $crate::Tag = $crate::Tag::$name;
+ }
+
+ #[cfg(feature = "std")]
+ impl $crate::ToDer for $name<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.data.as_bytes().len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = $crate::Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(
+ &self,
+ writer: &mut dyn std::io::Write,
+ ) -> $crate::SerializeResult<usize> {
+ use $crate::Tagged;
+ let header = $crate::Header::new(
+ $crate::Class::Universal,
+ false,
+ Self::TAG,
+ $crate::Length::Definite(self.data.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(
+ &self,
+ writer: &mut dyn std::io::Write,
+ ) -> $crate::SerializeResult<usize> {
+ writer.write(self.data.as_bytes()).map_err(Into::into)
+ }
+ }
+ };
+ ($name:ident) => {
+ asn1_string!(IMPL $name, stringify!($name));
+ };
+}
diff --git a/src/asn1_types/strings/bmpstring.rs b/src/asn1_types/strings/bmpstring.rs
new file mode 100644
index 0000000..490c68c
--- /dev/null
+++ b/src/asn1_types/strings/bmpstring.rs
@@ -0,0 +1,132 @@
+// do not use the `asn1_string` macro, since types are not the same
+// X.680 section 37.15
+
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+
+/// ASN.1 `BMPSTRING` type
+///
+/// Note: parsing a `BmpString` allocates memory since the UTF-16 to UTF-8 conversion requires a memory allocation.
+/// (see `String::from_utf16` method).
+#[derive(Debug, PartialEq, Eq)]
+pub struct BmpString<'a> {
+ pub(crate) data: Cow<'a, str>,
+}
+
+impl<'a> BmpString<'a> {
+ pub const fn new(s: &'a str) -> Self {
+ BmpString {
+ data: Cow::Borrowed(s),
+ }
+ }
+
+ pub fn string(&self) -> String {
+ self.data.to_string()
+ }
+}
+
+impl<'a> AsRef<str> for BmpString<'a> {
+ fn as_ref(&self) -> &str {
+ &self.data
+ }
+}
+
+impl<'a> From<&'a str> for BmpString<'a> {
+ fn from(s: &'a str) -> Self {
+ Self::new(s)
+ }
+}
+
+impl From<String> for BmpString<'_> {
+ fn from(s: String) -> Self {
+ Self {
+ data: alloc::borrow::Cow::Owned(s),
+ }
+ }
+}
+
+impl<'a> core::convert::TryFrom<Any<'a>> for BmpString<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<BmpString<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+
+ // read slice as big-endian UTF-16 string
+ let v = &any
+ .data
+ .chunks(2)
+ .map(|s| match s {
+ [a, b] => ((*a as u16) << 8) | (*b as u16),
+ [a] => *a as u16,
+ _ => unreachable!(),
+ })
+ .collect::<Vec<_>>();
+
+ let s = String::from_utf16(v)?;
+ let data = Cow::Owned(s);
+
+ Ok(BmpString { data })
+ }
+}
+
+impl<'a> CheckDerConstraints for BmpString<'a> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for BmpString<'_> {}
+
+impl<'a> Tagged for BmpString<'a> {
+ const TAG: Tag = Tag::BmpString;
+}
+
+impl<'a> TestValidCharset for BmpString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ if i.len() % 2 != 0 {
+ return Err(Error::StringInvalidCharset);
+ }
+ let iter = i.chunks(2).map(|s| ((s[0] as u16) << 8) | (s[1] as u16));
+ for c in char::decode_utf16(iter) {
+ if c.is_err() {
+ return Err(Error::StringInvalidCharset);
+ }
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl ToDer for BmpString<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ // compute the UTF-16 length
+ let sz = self.data.encode_utf16().count() * 2;
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ // compute the UTF-16 length
+ let l = self.data.encode_utf16().count() * 2;
+ let header = Header::new(Class::Universal, false, Self::TAG, Length::Definite(l));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut v = Vec::new();
+ for u in self.data.encode_utf16() {
+ v.push((u >> 8) as u8);
+ v.push((u & 0xff) as u8);
+ }
+ writer.write(&v).map_err(Into::into)
+ }
+}
diff --git a/src/asn1_types/strings/generalstring.rs b/src/asn1_types/strings/generalstring.rs
new file mode 100644
index 0000000..f455d6a
--- /dev/null
+++ b/src/asn1_types/strings/generalstring.rs
@@ -0,0 +1,14 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(GeneralString);
+
+impl<'a> TestValidCharset for GeneralString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ if !i.iter().all(u8::is_ascii) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/graphicstring.rs b/src/asn1_types/strings/graphicstring.rs
new file mode 100644
index 0000000..3d84040
--- /dev/null
+++ b/src/asn1_types/strings/graphicstring.rs
@@ -0,0 +1,14 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(GraphicString);
+
+impl<'a> TestValidCharset for GraphicString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ if !i.iter().all(u8::is_ascii) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/ia5string.rs b/src/asn1_types/strings/ia5string.rs
new file mode 100644
index 0000000..4b37465
--- /dev/null
+++ b/src/asn1_types/strings/ia5string.rs
@@ -0,0 +1,14 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(Ia5String);
+
+impl<'a> TestValidCharset for Ia5String<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ if !i.iter().all(u8::is_ascii) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/numericstring.rs b/src/asn1_types/strings/numericstring.rs
new file mode 100644
index 0000000..dbe4ff1
--- /dev/null
+++ b/src/asn1_types/strings/numericstring.rs
@@ -0,0 +1,18 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(NumericString);
+
+impl<'a> TestValidCharset for NumericString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_numeric(b: &u8) -> bool {
+ matches!(*b, b'0'..=b'9' | b' ')
+ }
+ if !i.iter().all(is_numeric) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/printablestring.rs b/src/asn1_types/strings/printablestring.rs
new file mode 100644
index 0000000..5cd51fa
--- /dev/null
+++ b/src/asn1_types/strings/printablestring.rs
@@ -0,0 +1,35 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(PrintableString);
+
+impl<'a> TestValidCharset for PrintableString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ // Argument must be a reference, because of the .iter().all(F) call below
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_printable(b: &u8) -> bool {
+ matches!(*b,
+ b'a'..=b'z'
+ | b'A'..=b'Z'
+ | b'0'..=b'9'
+ | b' '
+ | b'\''
+ | b'('
+ | b')'
+ | b'+'
+ | b','
+ | b'-'
+ | b'.'
+ | b'/'
+ | b':'
+ | b'='
+ | b'?')
+ }
+
+ if !i.iter().all(is_printable) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/str.rs b/src/asn1_types/strings/str.rs
new file mode 100644
index 0000000..a90e8e3
--- /dev/null
+++ b/src/asn1_types/strings/str.rs
@@ -0,0 +1,67 @@
+use crate::*;
+use alloc::borrow::Cow;
+use core::convert::TryFrom;
+
+impl<'a> TryFrom<Any<'a>> for &'a str {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<&'a str> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for &'a str {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<&'a str> {
+ any.tag().assert_eq(Self::TAG)?;
+ let s = Utf8String::try_from(any)?;
+ match s.data {
+ Cow::Borrowed(s) => Ok(s),
+ Cow::Owned(_) => Err(Error::LifetimeError),
+ }
+ }
+}
+
+impl<'a> CheckDerConstraints for &'a str {
+ fn check_constraints(any: &Any) -> Result<()> {
+ // X.690 section 10.2
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for &'_ str {}
+
+impl<'a> Tagged for &'a str {
+ const TAG: Tag = Tag::Utf8String;
+}
+
+#[cfg(feature = "std")]
+impl<'a> ToDer for &'a str {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.as_bytes().len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.as_bytes().len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(self.as_bytes()).map_err(Into::into)
+ }
+}
diff --git a/src/asn1_types/strings/string.rs b/src/asn1_types/strings/string.rs
new file mode 100644
index 0000000..81f6ba8
--- /dev/null
+++ b/src/asn1_types/strings/string.rs
@@ -0,0 +1,64 @@
+use crate::*;
+use alloc::string::String;
+use core::convert::TryFrom;
+
+impl<'a> TryFrom<Any<'a>> for String {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<String> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for String {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<String> {
+ any.tag().assert_eq(Self::TAG)?;
+ let s = Utf8String::try_from(any)?;
+ Ok(s.data.into_owned())
+ }
+}
+
+impl CheckDerConstraints for String {
+ fn check_constraints(any: &Any) -> Result<()> {
+ // X.690 section 10.2
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for String {}
+
+impl Tagged for String {
+ const TAG: Tag = Tag::Utf8String;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for String {
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.as_bytes().len();
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.len()),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ writer.write(self.as_ref()).map_err(Into::into)
+ }
+}
diff --git a/src/asn1_types/strings/teletexstring.rs b/src/asn1_types/strings/teletexstring.rs
new file mode 100644
index 0000000..2f58804
--- /dev/null
+++ b/src/asn1_types/strings/teletexstring.rs
@@ -0,0 +1,18 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(TeletexString);
+
+impl<'a> TestValidCharset for TeletexString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_visible(b: &u8) -> bool {
+ 0x20 <= *b && *b <= 0x7f
+ }
+ if !i.iter().all(is_visible) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/universalstring.rs b/src/asn1_types/strings/universalstring.rs
new file mode 100644
index 0000000..9006362
--- /dev/null
+++ b/src/asn1_types/strings/universalstring.rs
@@ -0,0 +1,137 @@
+// do not use the `asn1_string` macro, since types are not the same
+// X.680 section 37.6 and X.690 section 8.21.7
+
+use crate::*;
+use alloc::borrow::Cow;
+use alloc::string::{String, ToString};
+use alloc::vec::Vec;
+use core::convert::TryFrom;
+use core::iter::FromIterator;
+
+/// ASN.1 `UniversalString` type
+///
+/// Note: parsing a `UniversalString` allocates memory since the UCS-4 to UTF-8 conversion requires a memory allocation.
+#[derive(Debug, PartialEq, Eq)]
+pub struct UniversalString<'a> {
+ pub(crate) data: Cow<'a, str>,
+}
+
+impl<'a> UniversalString<'a> {
+ pub const fn new(s: &'a str) -> Self {
+ UniversalString {
+ data: Cow::Borrowed(s),
+ }
+ }
+
+ pub fn string(&self) -> String {
+ self.data.to_string()
+ }
+}
+
+impl<'a> AsRef<str> for UniversalString<'a> {
+ fn as_ref(&self) -> &str {
+ &self.data
+ }
+}
+
+impl<'a> From<&'a str> for UniversalString<'a> {
+ fn from(s: &'a str) -> Self {
+ Self::new(s)
+ }
+}
+
+impl From<String> for UniversalString<'_> {
+ fn from(s: String) -> Self {
+ Self {
+ data: alloc::borrow::Cow::Owned(s),
+ }
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for UniversalString<'a> {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<UniversalString<'a>> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for UniversalString<'a> {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<UniversalString<'a>> {
+ any.tag().assert_eq(Self::TAG)?;
+
+ if any.data.len() % 4 != 0 {
+ return Err(Error::StringInvalidCharset);
+ }
+
+ // read slice as big-endian UCS-4 string
+ let v = &any
+ .data
+ .chunks(4)
+ .map(|s| match s {
+ [a, b, c, d] => {
+ let u32_val = ((*a as u32) << 24)
+ | ((*b as u32) << 16)
+ | ((*c as u32) << 8)
+ | (*d as u32);
+ char::from_u32(u32_val)
+ }
+ _ => unreachable!(),
+ })
+ .collect::<Option<Vec<_>>>()
+ .ok_or(Error::StringInvalidCharset)?;
+
+ let s = String::from_iter(v);
+ let data = Cow::Owned(s);
+
+ Ok(UniversalString { data })
+ }
+}
+
+impl<'a> CheckDerConstraints for UniversalString<'a> {
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.assert_primitive()?;
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for UniversalString<'_> {}
+
+impl<'a> Tagged for UniversalString<'a> {
+ const TAG: Tag = Tag::UniversalString;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for UniversalString<'_> {
+ fn to_der_len(&self) -> Result<usize> {
+ // UCS-4: 4 bytes per character
+ let sz = self.data.as_bytes().len() * 4;
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let header = Header::new(
+ Class::Universal,
+ false,
+ Self::TAG,
+ Length::Definite(self.data.as_bytes().len() * 4),
+ );
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.data
+ .chars()
+ .try_for_each(|c| writer.write(&(c as u32).to_be_bytes()[..]).map(|_| ()))?;
+ Ok(self.data.as_bytes().len() * 4)
+ }
+}
diff --git a/src/asn1_types/strings/utf8string.rs b/src/asn1_types/strings/utf8string.rs
new file mode 100644
index 0000000..0bf87d4
--- /dev/null
+++ b/src/asn1_types/strings/utf8string.rs
@@ -0,0 +1,13 @@
+use crate::asn1_string;
+use crate::Result;
+use crate::TestValidCharset;
+use alloc::string::String;
+
+asn1_string!(Utf8String);
+
+impl<'a> TestValidCharset for Utf8String<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ let _ = core::str::from_utf8(i)?;
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/videotexstring.rs b/src/asn1_types/strings/videotexstring.rs
new file mode 100644
index 0000000..51c5a46
--- /dev/null
+++ b/src/asn1_types/strings/videotexstring.rs
@@ -0,0 +1,19 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(VideotexString);
+
+impl<'a> TestValidCharset for VideotexString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_visible(b: &u8) -> bool {
+ // XXX
+ 0x20 <= *b && *b <= 0x7f
+ }
+ if !i.iter().all(is_visible) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/strings/visiblestring.rs b/src/asn1_types/strings/visiblestring.rs
new file mode 100644
index 0000000..2b141dc
--- /dev/null
+++ b/src/asn1_types/strings/visiblestring.rs
@@ -0,0 +1,18 @@
+use crate::{asn1_string, TestValidCharset};
+use crate::{Error, Result};
+use alloc::string::String;
+
+asn1_string!(VisibleString);
+
+impl<'a> TestValidCharset for VisibleString<'a> {
+ fn test_valid_charset(i: &[u8]) -> Result<()> {
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_visible(b: &u8) -> bool {
+ 0x20 <= *b && *b <= 0x7f
+ }
+ if !i.iter().all(is_visible) {
+ return Err(Error::StringInvalidCharset);
+ }
+ Ok(())
+ }
+}
diff --git a/src/asn1_types/tagged.rs b/src/asn1_types/tagged.rs
new file mode 100644
index 0000000..c1652d4
--- /dev/null
+++ b/src/asn1_types/tagged.rs
@@ -0,0 +1,128 @@
+use crate::{Class, Error, Tag, Tagged};
+use core::marker::PhantomData;
+
+mod application;
+mod builder;
+mod explicit;
+mod helpers;
+mod implicit;
+mod optional;
+mod parser;
+mod private;
+
+pub use application::*;
+pub use builder::*;
+pub use explicit::*;
+pub use helpers::*;
+pub use implicit::*;
+pub use optional::*;
+pub use parser::*;
+pub use private::*;
+
+pub(crate) const CONTEXT_SPECIFIC: u8 = Class::ContextSpecific as u8;
+
+/// A type parameter for `IMPLICIT` tagged values.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Implicit {}
+
+/// A type parameter for `EXPLICIT` tagged values.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Explicit {}
+
+/// A type parameter for tagged values either [`Explicit`] or [`Implicit`].
+pub trait TagKind {}
+
+impl TagKind for Implicit {}
+impl TagKind for Explicit {}
+
+/// Helper object for creating `FromBer`/`FromDer` types for TAGGED OPTIONAL types
+///
+/// When parsing `ContextSpecific` (the most common class), see [`TaggedExplicit`] and
+/// [`TaggedImplicit`] alias types.
+///
+/// # Notes
+///
+/// `CLASS` must be between 0 and 4. See [`Class`] for possible values for the `CLASS` parameter.
+/// Constants from this class can be used, but they must be wrapped in braces due to
+/// [Rust syntax for generics](https://doc.rust-lang.org/reference/items/generics.html)
+/// (see example below).
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Class, Error, Explicit, FromBer, Integer, TaggedValue};
+///
+/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) =
+/// TaggedValue::<Integer, Error, Explicit, {Class::APPLICATION}, 0>::from_ber(bytes)
+/// .unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+#[derive(Debug, PartialEq, Eq)]
+pub struct TaggedValue<T, E, TagKind, const CLASS: u8, const TAG: u32> {
+ pub(crate) inner: T,
+
+ tag_kind: PhantomData<TagKind>,
+ _e: PhantomData<E>,
+}
+
+impl<T, E, TagKind, const CLASS: u8, const TAG: u32> TaggedValue<T, E, TagKind, CLASS, TAG> {
+ /// Consumes the `TaggedParser`, returning the wrapped value.
+ #[inline]
+ pub fn into_inner(self) -> T {
+ self.inner
+ }
+
+ /// Return the (outer) tag of this object
+ pub const fn tag(&self) -> Tag {
+ Self::TAG
+ }
+
+ /// Return the (outer) class of this object
+ #[inline]
+ pub const fn class(&self) -> u8 {
+ CLASS
+ }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> TaggedValue<T, E, Explicit, CLASS, TAG> {
+ /// Constructs a new `EXPLICIT TaggedParser` with the provided value
+ #[inline]
+ pub const fn explicit(inner: T) -> Self {
+ TaggedValue {
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ }
+ }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> TaggedValue<T, E, Implicit, CLASS, TAG> {
+ /// Constructs a new `IMPLICIT TaggedParser` with the provided value
+ #[inline]
+ pub const fn implicit(inner: T) -> Self {
+ TaggedValue {
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ }
+ }
+}
+
+impl<T, E, TagKind, const CLASS: u8, const TAG: u32> AsRef<T>
+ for TaggedValue<T, E, TagKind, CLASS, TAG>
+{
+ fn as_ref(&self) -> &T {
+ &self.inner
+ }
+}
+
+impl<T, E, TagKind, const CLASS: u8, const TAG: u32> Tagged
+ for TaggedValue<T, E, TagKind, CLASS, TAG>
+{
+ const TAG: Tag = Tag(TAG);
+}
diff --git a/src/asn1_types/tagged/application.rs b/src/asn1_types/tagged/application.rs
new file mode 100644
index 0000000..ddd2b07
--- /dev/null
+++ b/src/asn1_types/tagged/application.rs
@@ -0,0 +1,42 @@
+use crate::{Class, Explicit, Implicit, TaggedValue};
+
+/// A helper object to parse `[APPLICATION n] EXPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse explicit application-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{ApplicationExplicit, Error, FromBer, Integer, TaggedValue};
+///
+/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = ApplicationExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+pub type ApplicationExplicit<T, E, const TAG: u32> =
+ TaggedValue<T, E, Explicit, { Class::APPLICATION }, TAG>;
+
+/// A helper object to parse `[APPLICATION n] IMPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse explicit application-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] IMPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{ApplicationImplicit, Error, FromBer, Integer, TaggedValue};
+///
+/// let bytes = &[0x60, 0x1, 0x2];
+///
+/// let (_, tagged) = ApplicationImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2_u8)));
+/// ```
+pub type ApplicationImplicit<T, E, const TAG: u32> =
+ TaggedValue<T, E, Implicit, { Class::APPLICATION }, TAG>;
diff --git a/src/asn1_types/tagged/builder.rs b/src/asn1_types/tagged/builder.rs
new file mode 100644
index 0000000..093b9d9
--- /dev/null
+++ b/src/asn1_types/tagged/builder.rs
@@ -0,0 +1,104 @@
+use super::{Error, Explicit, Implicit, TaggedParser};
+use crate::{Class, FromBer, FromDer, ParseResult, Tag};
+use core::marker::PhantomData;
+
+/// A builder for parsing tagged values (`IMPLICIT` or `EXPLICIT`)
+///
+/// # Examples
+///
+/// ```
+/// use asn1_rs::{Class, Tag, TaggedParserBuilder};
+///
+/// let parser = TaggedParserBuilder::explicit()
+/// .with_class(Class::ContextSpecific)
+/// .with_tag(Tag(0))
+/// .der_parser::<u32>();
+///
+/// let input = &[0xa0, 0x03, 0x02, 0x01, 0x02];
+/// let (rem, tagged) = parser(input).expect("parsing failed");
+///
+/// assert!(rem.is_empty());
+/// assert_eq!(tagged.tag(), Tag(0));
+/// assert_eq!(tagged.as_ref(), &2);
+/// ```
+#[derive(Clone, Copy, Debug)]
+pub struct TaggedParserBuilder<TagKind, E = Error> {
+ class: Class,
+ tag: Tag,
+ tag_kind: PhantomData<TagKind>,
+ _e: PhantomData<E>,
+}
+
+impl<TagKind, E> TaggedParserBuilder<TagKind, E> {
+ /// Create a default `TaggedParserBuilder` builder
+ ///
+ /// `TagKind` must be specified as either [`Explicit`] or [`Implicit`]
+ ///
+ /// ```
+ /// use asn1_rs::{Explicit, TaggedParserBuilder};
+ ///
+ /// let builder = TaggedParserBuilder::<Explicit>::new();
+ /// ```
+ pub const fn new() -> Self {
+ TaggedParserBuilder {
+ class: Class::Universal,
+ tag: Tag(0),
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ }
+ }
+
+ /// Set the expected `Class` for the builder
+ pub const fn with_class(self, class: Class) -> Self {
+ Self { class, ..self }
+ }
+
+ /// Set the expected `Tag` for the builder
+ pub const fn with_tag(self, tag: Tag) -> Self {
+ Self { tag, ..self }
+ }
+}
+
+impl<E> TaggedParserBuilder<Explicit, E> {
+ /// Create a `TagParser` builder for `EXPLICIT` tagged values
+ pub const fn explicit() -> Self {
+ TaggedParserBuilder::new()
+ }
+}
+
+impl<E> TaggedParserBuilder<Implicit, E> {
+ /// Create a `TagParser` builder for `IMPLICIT` tagged values
+ pub const fn implicit() -> Self {
+ TaggedParserBuilder::new()
+ }
+}
+
+impl<TagKind, E> TaggedParserBuilder<TagKind, E> {
+ /// Create the BER parser from the builder parameters
+ ///
+ /// This method will consume the builder and return a parser (to be used as a function).
+ pub fn ber_parser<'a, T>(
+ self,
+ ) -> impl Fn(&'a [u8]) -> ParseResult<'a, TaggedParser<'a, TagKind, T, E>, E>
+ where
+ TaggedParser<'a, TagKind, T, E>: FromBer<'a, E>,
+ E: From<Error>,
+ {
+ move |bytes: &[u8]| TaggedParser::<TagKind, T, E>::parse_ber(self.class, self.tag, bytes)
+ }
+}
+
+impl<TagKind, E> TaggedParserBuilder<TagKind, E> {
+ /// Create the DER parser from the builder parameters
+ ///
+ /// This method will consume the builder and return a parser (to be used as a function).
+ pub fn der_parser<'a, T>(
+ self,
+ ) -> impl Fn(&'a [u8]) -> ParseResult<'a, TaggedParser<'a, TagKind, T, E>, E>
+ where
+ TaggedParser<'a, TagKind, T, E>: FromDer<'a, E>,
+ E: From<Error>,
+ {
+ move |bytes: &[u8]| TaggedParser::<TagKind, T, E>::parse_der(self.class, self.tag, bytes)
+ }
+}
diff --git a/src/asn1_types/tagged/explicit.rs b/src/asn1_types/tagged/explicit.rs
new file mode 100644
index 0000000..7fdbcce
--- /dev/null
+++ b/src/asn1_types/tagged/explicit.rs
@@ -0,0 +1,262 @@
+use crate::*;
+use core::convert::TryFrom;
+use core::marker::PhantomData;
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> TryFrom<Any<'a>>
+ for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+{
+ type Error = E;
+
+ fn try_from(any: Any<'a>) -> Result<Self, E> {
+ Self::try_from(&any)
+ }
+}
+
+impl<'a, 'b, T, E, const CLASS: u8, const TAG: u32> TryFrom<&'b Any<'a>>
+ for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+{
+ type Error = E;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Self, E> {
+ any.tag().assert_eq(Tag(TAG))?;
+ any.header.assert_constructed()?;
+ if any.class() as u8 != CLASS {
+ let class = Class::try_from(CLASS).ok();
+ return Err(Error::unexpected_class(class, any.class()).into());
+ }
+ let (_, inner) = match T::from_ber(any.data) {
+ Ok((rem, res)) => (rem, res),
+ Err(Err::Error(e)) | Err(Err::Failure(e)) => return Err(e),
+ Err(Err::Incomplete(n)) => return Err(Error::Incomplete(n).into()),
+ };
+ Ok(TaggedValue::explicit(inner))
+ }
+}
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> FromDer<'a, E>
+ for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ any.tag()
+ .assert_eq(Tag(TAG))
+ .map_err(|e| Err::Error(e.into()))?;
+ any.header
+ .assert_constructed()
+ .map_err(|e| Err::Error(e.into()))?;
+ if any.class() as u8 != CLASS {
+ let class = Class::try_from(CLASS).ok();
+ return Err(Err::Error(
+ Error::unexpected_class(class, any.class()).into(),
+ ));
+ }
+ let (_, inner) = T::from_der(any.data)?;
+ Ok((rem, TaggedValue::explicit(inner)))
+ }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> CheckDerConstraints
+ for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length.assert_definite()?;
+ let (_, inner) = Any::from_ber(any.data)?;
+ T::check_constraints(&inner)?;
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T, E, const CLASS: u8, const TAG: u32> ToDer for TaggedValue<T, E, Explicit, CLASS, TAG>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.inner.to_der_len()?;
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let inner_len = self.inner.to_der_len()?;
+ let class =
+ Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?;
+ let header = Header::new(class, true, self.tag(), Length::Definite(inner_len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.inner.write_der(writer)
+ }
+}
+
+/// A helper object to parse `[ n ] EXPLICIT T`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// See [`TaggedValue`] or [`TaggedParser`] for more generic implementations if needed.
+///
+/// # Examples
+///
+/// To parse a `[0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, TaggedExplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = TaggedExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+pub type TaggedExplicit<T, E, const TAG: u32> = TaggedValue<T, E, Explicit, CONTEXT_SPECIFIC, TAG>;
+
+// implementations for TaggedParser
+
+impl<'a, T, E> TaggedParser<'a, Explicit, T, E> {
+ pub const fn new_explicit(class: Class, tag: u32, inner: T) -> Self {
+ Self {
+ header: Header::new(class, true, Tag(tag), Length::Definite(0)),
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ }
+ }
+
+ /// Parse a BER tagged value and apply the provided parsing function to content
+ ///
+ /// After parsing, the sequence object and header are discarded.
+ ///
+ /// Note: this function is provided for `Explicit`, but there is not difference between
+ /// explicit or implicit tags. The `op` function is responsible of handling the content.
+ #[inline]
+ pub fn from_ber_and_then<F>(
+ class: Class,
+ tag: u32,
+ bytes: &'a [u8],
+ op: F,
+ ) -> ParseResult<'a, T, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+ E: From<Error>,
+ {
+ Any::from_ber_and_then(class, tag, bytes, op)
+ }
+
+ /// Parse a DER tagged value and apply the provided parsing function to content
+ ///
+ /// After parsing, the sequence object and header are discarded.
+ ///
+ /// Note: this function is provided for `Explicit`, but there is not difference between
+ /// explicit or implicit tags. The `op` function is responsible of handling the content.
+ #[inline]
+ pub fn from_der_and_then<F>(
+ class: Class,
+ tag: u32,
+ bytes: &'a [u8],
+ op: F,
+ ) -> ParseResult<'a, T, E>
+ where
+ F: FnOnce(&'a [u8]) -> ParseResult<T, E>,
+ E: From<Error>,
+ {
+ Any::from_der_and_then(class, tag, bytes, op)
+ }
+}
+
+impl<'a, T, E> FromBer<'a, E> for TaggedParser<'a, Explicit, T, E>
+where
+ T: FromBer<'a, E>,
+ E: From<Error>,
+{
+ fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+ let header = any.header;
+ let (_, inner) = T::from_ber(any.data)?;
+ let tagged = TaggedParser {
+ header,
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ };
+ Ok((rem, tagged))
+ }
+}
+
+impl<'a, T, E> FromDer<'a, E> for TaggedParser<'a, Explicit, T, E>
+where
+ T: FromDer<'a, E>,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ let header = any.header;
+ let (_, inner) = T::from_der(any.data)?;
+ let tagged = TaggedParser {
+ header,
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ };
+ Ok((rem, tagged))
+ }
+}
+
+impl<'a, T> CheckDerConstraints for TaggedParser<'a, Explicit, T>
+where
+ T: CheckDerConstraints,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length.assert_definite()?;
+ let (_, inner_any) = Any::from_der(any.data)?;
+ T::check_constraints(&inner_any)?;
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a, T> ToDer for TaggedParser<'a, Explicit, T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ let sz = self.inner.to_der_len()?;
+ if sz < 127 {
+ // 1 (class+tag) + 1 (length) + len
+ Ok(2 + sz)
+ } else {
+ // 1 (class+tag) + n (length) + len
+ let n = Length::Definite(sz).to_der_len()?;
+ Ok(1 + n + sz)
+ }
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let inner_len = self.inner.to_der_len()?;
+ let header = Header::new(self.class(), true, self.tag(), Length::Definite(inner_len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.inner.write_der(writer)
+ }
+}
diff --git a/src/asn1_types/tagged/helpers.rs b/src/asn1_types/tagged/helpers.rs
new file mode 100644
index 0000000..dfb1018
--- /dev/null
+++ b/src/asn1_types/tagged/helpers.rs
@@ -0,0 +1,103 @@
+use super::{Explicit, Implicit, TaggedParser};
+use crate::{Any, Error, FromDer, Header, ParseResult, Tag, Tagged};
+use nom::error::ParseError;
+use nom::{Err, IResult};
+
+// helper functions for parsing tagged objects
+
+pub fn parse_der_tagged_explicit<'a, IntoTag, T, E>(
+ tag: IntoTag,
+) -> impl FnMut(&'a [u8]) -> ParseResult<TaggedParser<'a, Explicit, T, E>, E>
+where
+ IntoTag: Into<Tag>,
+ TaggedParser<'a, Explicit, T, E>: FromDer<'a, E>,
+ E: From<Error>,
+{
+ let tag = tag.into();
+ move |i| {
+ let (rem, tagged) = TaggedParser::from_der(i)?;
+ tagged.assert_tag(tag).map_err(|e| Err::Error(e.into()))?;
+ Ok((rem, tagged))
+ }
+}
+
+pub fn parse_der_tagged_explicit_g<'a, IntoTag, T, F, E>(
+ tag: IntoTag,
+ f: F,
+) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E>
+where
+ F: Fn(&'a [u8], Header<'a>) -> IResult<&'a [u8], T, E>,
+ E: ParseError<&'a [u8]> + From<Error>,
+ IntoTag: Into<Tag>,
+{
+ let tag = tag.into();
+ parse_der_container(tag, move |any: Any<'a>| {
+ any.header
+ .assert_tag(tag)
+ .map_err(|e| nom::Err::convert(e.into()))?;
+ f(any.data, any.header)
+ })
+}
+
+pub fn parse_der_tagged_implicit<'a, IntoTag, T, E>(
+ tag: IntoTag,
+) -> impl FnMut(&'a [u8]) -> ParseResult<TaggedParser<'a, Implicit, T, E>, E>
+where
+ IntoTag: Into<Tag>,
+ // T: TryFrom<Any<'a>, Error = Error> + Tagged,
+ TaggedParser<'a, Implicit, T, E>: FromDer<'a, E>,
+ E: From<Error>,
+{
+ let tag = tag.into();
+ move |i| {
+ let (rem, tagged) = TaggedParser::from_der(i)?;
+ tagged
+ .assert_tag(tag)
+ .map_err(|e| nom::Err::convert(e.into()))?;
+ Ok((rem, tagged))
+ }
+}
+
+pub fn parse_der_tagged_implicit_g<'a, IntoTag, T, F, E>(
+ tag: IntoTag,
+ f: F,
+) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E>
+where
+ F: Fn(&'a [u8], Tag, Header<'a>) -> IResult<&'a [u8], T, E>,
+ E: ParseError<&'a [u8]> + From<Error>,
+ IntoTag: Into<Tag>,
+ T: Tagged,
+{
+ let tag = tag.into();
+ parse_der_container(tag, move |any: Any<'a>| {
+ // verify tag of external header
+ any.header
+ .assert_tag(tag)
+ .map_err(|e| nom::Err::convert(e.into()))?;
+ // build a fake header with the expected tag
+ let Any { header, data } = any;
+ let header = Header {
+ tag: T::TAG,
+ ..header.clone()
+ };
+ f(data, tag, header)
+ })
+}
+
+fn parse_der_container<'a, T, F, E>(
+ tag: Tag,
+ f: F,
+) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], T, E>
+where
+ F: Fn(Any<'a>) -> IResult<&'a [u8], T, E>,
+ E: ParseError<&'a [u8]> + From<Error>,
+{
+ move |i: &[u8]| {
+ let (rem, any) = Any::from_der(i).map_err(nom::Err::convert)?;
+ any.header
+ .assert_tag(tag)
+ .map_err(|e| nom::Err::convert(e.into()))?;
+ let (_, output) = f(any)?;
+ Ok((rem, output))
+ }
+}
diff --git a/src/asn1_types/tagged/implicit.rs b/src/asn1_types/tagged/implicit.rs
new file mode 100644
index 0000000..53a921c
--- /dev/null
+++ b/src/asn1_types/tagged/implicit.rs
@@ -0,0 +1,287 @@
+use crate::*;
+use core::convert::TryFrom;
+use core::marker::PhantomData;
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> TryFrom<Any<'a>>
+ for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+ T: TryFrom<Any<'a>, Error = E>,
+ T: Tagged,
+ E: From<Error>,
+{
+ type Error = E;
+
+ fn try_from(any: Any<'a>) -> Result<Self, E> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b, E, T, const CLASS: u8, const TAG: u32> TryFrom<&'b Any<'a>>
+ for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+ T: TryFrom<Any<'a>, Error = E>,
+ T: Tagged,
+ E: From<Error>,
+{
+ type Error = E;
+
+ fn try_from(any: &'b Any<'a>) -> Result<Self, E> {
+ any.tag().assert_eq(Tag(TAG))?;
+ // XXX if input is empty, this function is not called
+
+ if any.class() as u8 != CLASS {
+ let class = Class::try_from(CLASS).ok();
+ return Err(Error::unexpected_class(class, any.class()).into());
+ }
+ let any = Any {
+ header: Header {
+ tag: T::TAG,
+ ..any.header.clone()
+ },
+ data: any.data,
+ };
+ match T::try_from(any) {
+ Ok(inner) => Ok(TaggedValue::implicit(inner)),
+ Err(e) => Err(e),
+ }
+ }
+}
+
+impl<'a, T, E, const CLASS: u8, const TAG: u32> FromDer<'a, E>
+ for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+ T: TryFrom<Any<'a>, Error = E>,
+ T: Tagged,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ any.tag()
+ .assert_eq(Tag(TAG))
+ .map_err(|e| Err::Error(e.into()))?;
+ if any.class() as u8 != CLASS {
+ let class = Class::try_from(CLASS).ok();
+ return Err(Err::Error(
+ Error::unexpected_class(class, any.class()).into(),
+ ));
+ }
+ let any = Any {
+ header: Header {
+ tag: T::TAG,
+ ..any.header.clone()
+ },
+ data: any.data,
+ };
+ match T::try_from(any) {
+ Ok(inner) => Ok((rem, TaggedValue::implicit(inner))),
+ Err(e) => Err(nom::Err::Error(e)),
+ }
+ }
+}
+
+impl<T, E, const CLASS: u8, const TAG: u32> CheckDerConstraints
+ for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+ T: CheckDerConstraints,
+ T: Tagged,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length.assert_definite()?;
+ let header = any.header.clone().with_tag(T::TAG);
+ let inner = Any::new(header, any.data);
+ T::check_constraints(&inner)?;
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl<T, E, const CLASS: u8, const TAG: u32> ToDer for TaggedValue<T, E, Implicit, CLASS, TAG>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ self.inner.to_der_len()
+ }
+
+ fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let class =
+ Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?;
+ let mut v = Vec::new();
+ let inner_len = self.inner.write_der_content(&mut v)?;
+ // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+ // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+ let constructed = matches!(TAG, 16 | 17);
+ let header = Header::new(class, constructed, self.tag(), Length::Definite(inner_len));
+ let sz = header.write_der_header(writer)?;
+ let sz = sz + writer.write(&v)?;
+ Ok(sz)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut sink = std::io::sink();
+ let class =
+ Class::try_from(CLASS).map_err(|_| SerializeError::InvalidClass { class: CLASS })?;
+ let inner_len = self.inner.write_der_content(&mut sink)?;
+ // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+ // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+ let constructed = matches!(TAG, 16 | 17);
+ let header = Header::new(class, constructed, self.tag(), Length::Definite(inner_len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.inner.write_der(writer)
+ }
+}
+
+/// A helper object to parse `[ n ] IMPLICIT T`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// See [`TaggedValue`] or [`TaggedParser`] for more generic implementations if needed.
+///
+/// # Examples
+///
+/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, TaggedImplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x1, 0x2];
+///
+/// let (_, tagged) = TaggedImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2)));
+/// ```
+pub type TaggedImplicit<T, E, const TAG: u32> = TaggedValue<T, E, Implicit, CONTEXT_SPECIFIC, TAG>;
+
+impl<'a, T, E> FromBer<'a, E> for TaggedParser<'a, Implicit, T, E>
+where
+ T: TryFrom<Any<'a>, Error = E>,
+ T: Tagged,
+ E: From<Error>,
+{
+ fn from_ber(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+ let Any { header, data } = any;
+ let any = Any {
+ header: Header {
+ tag: T::TAG,
+ ..header.clone()
+ },
+ data,
+ };
+ match T::try_from(any) {
+ Ok(t) => {
+ let tagged_value = TaggedParser {
+ header,
+ inner: t,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ };
+ Ok((rem, tagged_value))
+ }
+ Err(e) => Err(nom::Err::Error(e)),
+ }
+ }
+}
+
+// implementations for TaggedParser
+
+impl<'a, T, E> TaggedParser<'a, Implicit, T, E> {
+ pub const fn new_implicit(class: Class, constructed: bool, tag: u32, inner: T) -> Self {
+ Self {
+ header: Header::new(class, constructed, Tag(tag), Length::Definite(0)),
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ }
+ }
+}
+
+impl<'a, T, E> FromDer<'a, E> for TaggedParser<'a, Implicit, T, E>
+where
+ T: TryFrom<Any<'a>, Error = E>,
+ T: CheckDerConstraints,
+ T: Tagged,
+ E: From<Error>,
+{
+ fn from_der(bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ let Any { header, data } = any;
+ let any = Any {
+ header: Header {
+ tag: T::TAG,
+ ..header.clone()
+ },
+ data,
+ };
+ T::check_constraints(&any).map_err(|e| nom::Err::Error(e.into()))?;
+ match T::try_from(any) {
+ Ok(t) => {
+ let tagged_value = TaggedParser {
+ header,
+ inner: t,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ };
+ Ok((rem, tagged_value))
+ }
+ Err(e) => Err(nom::Err::Error(e)),
+ }
+ }
+}
+
+impl<'a, T> CheckDerConstraints for TaggedParser<'a, Implicit, T>
+where
+ T: CheckDerConstraints,
+ T: Tagged,
+{
+ fn check_constraints(any: &Any) -> Result<()> {
+ any.header.length.assert_definite()?;
+ let any = Any {
+ header: Header {
+ tag: T::TAG,
+ ..any.header.clone()
+ },
+ data: any.data,
+ };
+ T::check_constraints(&any)?;
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'a, T> ToDer for TaggedParser<'a, Implicit, T>
+where
+ T: ToDer,
+{
+ fn to_der_len(&self) -> Result<usize> {
+ self.inner.to_der_len()
+ }
+
+ fn write_der(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut v = Vec::new();
+ let inner_len = self.inner.write_der_content(&mut v)?;
+ // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+ // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+ let header = Header::new(self.class(), false, self.tag(), Length::Definite(inner_len));
+ let sz = header.write_der_header(writer)?;
+ let sz = sz + writer.write(&v)?;
+ Ok(sz)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ let mut sink = std::io::sink();
+ let inner_len = self.inner.write_der_content(&mut sink)?;
+ // XXX X.690 section 8.14.3: if implicing tagging was used [...]:
+ // XXX a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise
+ let header = Header::new(self.class(), false, self.tag(), Length::Definite(inner_len));
+ header.write_der_header(writer).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ self.inner.write_der_content(writer)
+ }
+}
diff --git a/src/asn1_types/tagged/optional.rs b/src/asn1_types/tagged/optional.rs
new file mode 100644
index 0000000..00504df
--- /dev/null
+++ b/src/asn1_types/tagged/optional.rs
@@ -0,0 +1,240 @@
+use super::{explicit::TaggedExplicit, implicit::TaggedImplicit};
+use crate::*;
+
+/// Helper object to parse TAGGED OPTIONAL types (explicit or implicit)
+///
+/// This object can be used similarly to a builder pattern, to specify the expected class and
+/// tag of the object to parse, and the content parsing function.
+///
+/// The content parsing function takes two arguments: the outer header, and the data.
+///
+/// It can be used for both EXPLICIT or IMPLICIT tagged objects by using parsing functions that
+/// expect a header (or not) in the contents.
+///
+/// The [`OptTaggedParser::from`] method is a shortcut to build an object with `ContextSpecific`
+/// class and the given tag. The [`OptTaggedParser::new`] method is more generic.
+///
+/// See also [`OptTaggedExplicit`] and [`OptTaggedImplicit`] for alternatives that implement [`FromBer`]/
+/// [`FromDer`].
+///
+/// # Examples
+///
+/// To parse a `[APPLICATION 0] EXPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Class, FromDer, Integer, Tag, OptTaggedParser};
+///
+/// let bytes = &[0x60, 0x03, 0x2, 0x1, 0x2];
+///
+/// let (_, tagged) = OptTaggedParser::new(Class::Application, Tag(0))
+/// .parse_der(bytes, |_, data| Integer::from_der(data))
+/// .unwrap();
+///
+/// assert_eq!(tagged, Some(Integer::from(2)));
+/// ```
+///
+/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, Integer, OptTaggedParser};
+///
+/// let bytes = &[0xa0, 0x1, 0x2];
+///
+/// let (_, tagged) = OptTaggedParser::from(0)
+/// .parse_der::<_, Error, _>(bytes, |_, data| Ok((&[], Integer::new(data))))
+/// .unwrap();
+///
+/// assert_eq!(tagged, Some(Integer::from(2)));
+/// ```
+#[derive(Debug)]
+pub struct OptTaggedParser {
+ /// The expected class for the object to parse
+ pub class: Class,
+ /// The expected tag for the object to parse
+ pub tag: Tag,
+}
+
+impl OptTaggedParser {
+ /// Build a new `OptTaggedParser` object.
+ ///
+ /// If using `Class::ContextSpecific`, using [`OptTaggedParser::from`] with either a `Tag` or `u32` is
+ /// a shorter way to build this object.
+ pub const fn new(class: Class, tag: Tag) -> Self {
+ OptTaggedParser { class, tag }
+ }
+
+ pub const fn universal(tag: u32) -> Self {
+ Self::new(Class::Universal, Tag(tag))
+ }
+
+ pub const fn tagged(tag: u32) -> Self {
+ Self::new(Class::ContextSpecific, Tag(tag))
+ }
+
+ pub const fn application(tag: u32) -> Self {
+ Self::new(Class::Application, Tag(tag))
+ }
+
+ pub const fn private(tag: u32) -> Self {
+ Self::new(Class::Private, Tag(tag))
+ }
+
+ /// Parse input as BER, and apply the provided function to parse object.
+ ///
+ /// Returns the remaining bytes, and `Some(T)` if expected tag was found, else `None`.
+ ///
+ /// This function returns an error if tag was found but has a different class, or if parsing fails.
+ ///
+ /// # Examples
+ ///
+ /// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object:
+ ///
+ /// ```rust
+ /// use asn1_rs::{FromBer, Integer, OptTaggedParser};
+ ///
+ /// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+ ///
+ /// let (_, tagged) = OptTaggedParser::from(0)
+ /// .parse_ber(bytes, |_, data| Integer::from_ber(data))
+ /// .unwrap();
+ ///
+ /// assert_eq!(tagged, Some(Integer::from(2)));
+ /// ```
+ pub fn parse_ber<'a, T, E, F>(&self, bytes: &'a [u8], f: F) -> ParseResult<'a, Option<T>, E>
+ where
+ F: Fn(Header, &'a [u8]) -> ParseResult<'a, T, E>,
+ E: From<Error>,
+ {
+ if bytes.is_empty() {
+ return Ok((bytes, None));
+ }
+ let (rem, any) = Any::from_ber(bytes).map_err(Err::convert)?;
+ if any.tag() != self.tag {
+ return Ok((bytes, None));
+ }
+ if any.class() != self.class {
+ return Err(Err::Error(
+ Error::unexpected_class(Some(self.class), any.class()).into(),
+ ));
+ }
+ let Any { header, data } = any;
+ let (_, res) = f(header, data)?;
+ Ok((rem, Some(res)))
+ }
+
+ /// Parse input as DER, and apply the provided function to parse object.
+ ///
+ /// Returns the remaining bytes, and `Some(T)` if expected tag was found, else `None`.
+ ///
+ /// This function returns an error if tag was found but has a different class, or if parsing fails.
+ ///
+ /// # Examples
+ ///
+ /// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object:
+ ///
+ /// ```rust
+ /// use asn1_rs::{FromDer, Integer, OptTaggedParser};
+ ///
+ /// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+ ///
+ /// let (_, tagged) = OptTaggedParser::from(0)
+ /// .parse_der(bytes, |_, data| Integer::from_der(data))
+ /// .unwrap();
+ ///
+ /// assert_eq!(tagged, Some(Integer::from(2)));
+ /// ```
+ pub fn parse_der<'a, T, E, F>(&self, bytes: &'a [u8], f: F) -> ParseResult<'a, Option<T>, E>
+ where
+ F: Fn(Header, &'a [u8]) -> ParseResult<'a, T, E>,
+ E: From<Error>,
+ {
+ if bytes.is_empty() {
+ return Ok((bytes, None));
+ }
+ let (rem, any) = Any::from_der(bytes).map_err(Err::convert)?;
+ if any.tag() != self.tag {
+ return Ok((bytes, None));
+ }
+ if any.class() != self.class {
+ return Err(Err::Error(
+ Error::unexpected_class(Some(self.class), any.class()).into(),
+ ));
+ }
+ let Any { header, data } = any;
+ let (_, res) = f(header, data)?;
+ Ok((rem, Some(res)))
+ }
+}
+
+impl From<Tag> for OptTaggedParser {
+ /// Build a `TaggedOptional` object with class `ContextSpecific` and given tag
+ #[inline]
+ fn from(tag: Tag) -> Self {
+ OptTaggedParser::new(Class::ContextSpecific, tag)
+ }
+}
+
+impl From<u32> for OptTaggedParser {
+ /// Build a `TaggedOptional` object with class `ContextSpecific` and given tag
+ #[inline]
+ fn from(tag: u32) -> Self {
+ OptTaggedParser::new(Class::ContextSpecific, Tag(tag))
+ }
+}
+
+/// A helper object to parse `[ n ] EXPLICIT T OPTIONAL`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// Use `Option<` [`TaggedValue`] `>` for a more generic implementation.
+///
+/// # Examples
+///
+/// To parse a `[0] EXPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, OptTaggedExplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, Some(TaggedValue::explicit(Integer::from(2))));
+///
+/// // If tagged object is not present or has different tag, parsing
+/// // also succeeds (returning None):
+/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 0>::from_ber(&[]).unwrap();
+/// assert_eq!(tagged, None);
+/// let (_, tagged) = OptTaggedExplicit::<Integer, Error, 1>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, None);
+/// ```
+pub type OptTaggedExplicit<T, E, const TAG: u32> = Option<TaggedExplicit<T, E, TAG>>;
+
+/// A helper object to parse `[ n ] IMPLICIT T OPTIONAL`
+///
+/// A helper object implementing [`FromBer`] and [`FromDer`], to parse tagged
+/// optional values.
+///
+/// This helper expects context-specific tags.
+/// Use `Option<` [`TaggedValue`] `>` for a more generic implementation.
+///
+/// # Examples
+///
+/// To parse a `[0] IMPLICIT INTEGER OPTIONAL` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, OptTaggedImplicit, TaggedValue};
+///
+/// let bytes = &[0xa0, 0x1, 0x2];
+///
+/// let (_, tagged) = OptTaggedImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, Some(TaggedValue::implicit(Integer::from(2))));
+///
+/// // If tagged object is not present or has different tag, parsing
+/// // also succeeds (returning None):
+/// let (_, tagged) = OptTaggedImplicit::<Integer, Error, 0>::from_ber(&[]).unwrap();
+/// assert_eq!(tagged, None);
+/// ```
+pub type OptTaggedImplicit<T, E, const TAG: u32> = Option<TaggedImplicit<T, E, TAG>>;
diff --git a/src/asn1_types/tagged/parser.rs b/src/asn1_types/tagged/parser.rs
new file mode 100644
index 0000000..243b081
--- /dev/null
+++ b/src/asn1_types/tagged/parser.rs
@@ -0,0 +1,78 @@
+use crate::*;
+use core::marker::PhantomData;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct TaggedParser<'a, TagKind, T, E = Error> {
+ pub header: Header<'a>,
+ pub inner: T,
+
+ pub(crate) tag_kind: PhantomData<TagKind>,
+ pub(crate) _e: PhantomData<E>,
+}
+
+impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E> {
+ pub const fn new(header: Header<'a>, inner: T) -> Self {
+ TaggedParser {
+ header,
+ inner,
+ tag_kind: PhantomData,
+ _e: PhantomData,
+ }
+ }
+
+ pub const fn assert_class(&self, class: Class) -> Result<()> {
+ self.header.assert_class(class)
+ }
+
+ pub const fn assert_tag(&self, tag: Tag) -> Result<()> {
+ self.header.assert_tag(tag)
+ }
+
+ #[inline]
+ pub const fn class(&self) -> Class {
+ self.header.class
+ }
+
+ #[inline]
+ pub const fn tag(&self) -> Tag {
+ self.header.tag
+ }
+}
+
+impl<'a, TagKind, T, E> AsRef<T> for TaggedParser<'a, TagKind, T, E> {
+ fn as_ref(&self) -> &T {
+ &self.inner
+ }
+}
+
+impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E>
+where
+ Self: FromBer<'a, E>,
+ E: From<Error>,
+{
+ pub fn parse_ber(class: Class, tag: Tag, bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, t) = TaggedParser::<TagKind, T, E>::from_ber(bytes)?;
+ t.assert_class(class).map_err(|e| Err::Error(e.into()))?;
+ t.assert_tag(tag).map_err(|e| Err::Error(e.into()))?;
+ Ok((rem, t))
+ }
+}
+
+impl<'a, TagKind, T, E> TaggedParser<'a, TagKind, T, E>
+where
+ Self: FromDer<'a, E>,
+ E: From<Error>,
+{
+ pub fn parse_der(class: Class, tag: Tag, bytes: &'a [u8]) -> ParseResult<'a, Self, E> {
+ let (rem, t) = TaggedParser::<TagKind, T, E>::from_der(bytes)?;
+ t.assert_class(class).map_err(|e| Err::Error(e.into()))?;
+ t.assert_tag(tag).map_err(|e| Err::Error(e.into()))?;
+ Ok((rem, t))
+ }
+}
+
+impl<'a, TagKind, T, E> DynTagged for TaggedParser<'a, TagKind, T, E> {
+ fn tag(&self) -> Tag {
+ self.tag()
+ }
+}
diff --git a/src/asn1_types/tagged/private.rs b/src/asn1_types/tagged/private.rs
new file mode 100644
index 0000000..5d8708a
--- /dev/null
+++ b/src/asn1_types/tagged/private.rs
@@ -0,0 +1,42 @@
+use crate::{Class, Explicit, Implicit, TaggedValue};
+
+/// A helper object to parse `[PRIVATE n] EXPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse explicit private-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[PRIVATE 0] EXPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, PrivateExplicit, TaggedValue};
+///
+/// let bytes = &[0xe0, 0x03, 0x2, 0x1, 0x2];
+///
+/// // If tagged object is present (and has expected tag), parsing succeeds:
+/// let (_, tagged) = PrivateExplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::explicit(Integer::from(2)));
+/// ```
+pub type PrivateExplicit<T, E, const TAG: u32> =
+ TaggedValue<T, E, Explicit, { Class::PRIVATE }, TAG>;
+
+/// A helper object to parse `[PRIVATE n] IMPLICIT T`
+///
+/// A helper object implementing [`FromBer`](crate::FromBer) and [`FromDer`](crate::FromDer), to
+/// parse implicit private-tagged values.
+///
+/// # Examples
+///
+/// To parse a `[PRIVATE 0] IMPLICIT INTEGER` object:
+///
+/// ```rust
+/// use asn1_rs::{Error, FromBer, Integer, PrivateImplicit, TaggedValue};
+///
+/// let bytes = &[0xe0, 0x1, 0x2];
+///
+/// let (_, tagged) = PrivateImplicit::<Integer, Error, 0>::from_ber(bytes).unwrap();
+/// assert_eq!(tagged, TaggedValue::implicit(Integer::from(2_u8)));
+/// ```
+pub type PrivateImplicit<T, E, const TAG: u32> =
+ TaggedValue<T, E, Implicit, { Class::PRIVATE }, TAG>;
diff --git a/src/asn1_types/utctime.rs b/src/asn1_types/utctime.rs
new file mode 100644
index 0000000..8604914
--- /dev/null
+++ b/src/asn1_types/utctime.rs
@@ -0,0 +1,222 @@
+use crate::datetime::decode_decimal;
+use crate::*;
+use core::convert::TryFrom;
+use core::fmt;
+#[cfg(feature = "datetime")]
+use time::OffsetDateTime;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct UtcTime(pub ASN1DateTime);
+
+impl UtcTime {
+ pub const fn new(datetime: ASN1DateTime) -> Self {
+ UtcTime(datetime)
+ }
+
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
+ // X.680 section 43 defines a UniversalTime as a VisibleString restricted to:
+ //
+ // a) the six digits YYMMDD where YY is the two low-order digits of the Christian year, MM is the month
+ // (counting January as 01), and DD is the day of the month (01 to 31); and
+ // b) either:
+ // 1) the four digits hhmm where hh is hour (00 to 23) and mm is minutes (00 to 59); or
+ // 2) the six digits hhmmss where hh and mm are as in 1) above, and ss is seconds (00 to 59); and
+ // c) either:
+ // 1) the character Z ; or
+ // 2) one of the characters + or - , followed by hhmm, where hh is hour and mm is minutes.
+ //
+ // XXX // RFC 5280 requires mandatory seconds and Z-normalized time zone
+ let (year, month, day, hour, minute, rem) = match bytes {
+ [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, rem @ ..] => {
+ let year = decode_decimal(Self::TAG, *year1, *year2)?;
+ let month = decode_decimal(Self::TAG, *mon1, *mon2)?;
+ let day = decode_decimal(Self::TAG, *day1, *day2)?;
+ let hour = decode_decimal(Self::TAG, *hour1, *hour2)?;
+ let minute = decode_decimal(Self::TAG, *min1, *min2)?;
+ (year, month, day, hour, minute, rem)
+ }
+ _ => return Err(Self::TAG.invalid_value("malformed time string (not yymmddhhmm)")),
+ };
+ if rem.is_empty() {
+ return Err(Self::TAG.invalid_value("malformed time string"));
+ }
+ // check for seconds
+ let (second, rem) = match rem {
+ [sec1, sec2, rem @ ..] => {
+ let second = decode_decimal(Self::TAG, *sec1, *sec2)?;
+ (second, rem)
+ }
+ _ => (0, rem),
+ };
+ if month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59 {
+ return Err(Self::TAG.invalid_value("time components with invalid values"));
+ }
+ if rem.is_empty() {
+ return Err(Self::TAG.invalid_value("malformed time string"));
+ }
+ let tz = match rem {
+ [b'Z'] => ASN1TimeZone::Z,
+ [b'+', h1, h2, m1, m2] => {
+ let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+ let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+ ASN1TimeZone::Offset(hh as i8, mm as i8)
+ }
+ [b'-', h1, h2, m1, m2] => {
+ let hh = decode_decimal(Self::TAG, *h1, *h2)?;
+ let mm = decode_decimal(Self::TAG, *m1, *m2)?;
+ ASN1TimeZone::Offset(-(hh as i8), mm as i8)
+ }
+ _ => return Err(Self::TAG.invalid_value("malformed time string: no time zone")),
+ };
+ Ok(UtcTime(ASN1DateTime::new(
+ year as u32,
+ month,
+ day,
+ hour,
+ minute,
+ second,
+ None,
+ tz,
+ )))
+ // match *bytes {
+ // [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
+ // let year = decode_decimal(Self::TAG, year1, year2)?;
+ // let month = decode_decimal(Self::TAG, mon1, mon2)?;
+ // let day = decode_decimal(Self::TAG, day1, day2)?;
+ // let hour = decode_decimal(Self::TAG, hour1, hour2)?;
+ // let minute = decode_decimal(Self::TAG, min1, min2)?;
+ // let second = decode_decimal(Self::TAG, sec1, sec2)?;
+
+ // // RFC 5280 rules for interpreting the year
+ // let year = if year >= 50 { year + 1900 } else { year + 2000 };
+
+ // Ok(UtcTime::new(year, month, day, hour, minute, second))
+ // }
+ // _ => Err(Error::InvalidValue),
+ // }
+ }
+
+ /// Return a ISO 8601 combined date and time with time zone.
+ #[cfg(feature = "datetime")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+ #[inline]
+ pub fn utc_datetime(&self) -> Result<OffsetDateTime> {
+ self.0.to_datetime()
+ }
+
+ /// Return an adjusted ISO 8601 combined date and time with time zone.
+ /// According to Universal time definition in X.680 we add 2000 years
+ /// from 0 to 49 year and 1900 otherwise.
+ #[cfg(feature = "datetime")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+ #[inline]
+ pub fn utc_adjusted_datetime(&self) -> Result<OffsetDateTime> {
+ self.0.to_datetime().and_then(|dt| {
+ let year = dt.year();
+ // We follow the Universal time definition in X.680 for interpreting
+ // the adjusted year
+ let year = if year >= 50 { year + 1900 } else { year + 2000 };
+ time::Date::from_calendar_date(year, dt.month(), dt.day())
+ .map(|d| dt.replace_date(d))
+ .map_err(|_e| Self::TAG.invalid_value("Invalid adjusted date"))
+ })
+ }
+
+ /// Returns the number of non-leap seconds since the midnight on January 1, 1970.
+ #[cfg(feature = "datetime")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "datetime")))]
+ pub fn timestamp(&self) -> Result<i64> {
+ let dt = self.0.to_datetime()?;
+ Ok(dt.unix_timestamp())
+ }
+}
+
+impl<'a> TryFrom<Any<'a>> for UtcTime {
+ type Error = Error;
+
+ fn try_from(any: Any<'a>) -> Result<UtcTime> {
+ TryFrom::try_from(&any)
+ }
+}
+
+impl<'a, 'b> TryFrom<&'b Any<'a>> for UtcTime {
+ type Error = Error;
+
+ fn try_from(any: &'b Any<'a>) -> Result<UtcTime> {
+ any.tag().assert_eq(Self::TAG)?;
+ #[allow(clippy::trivially_copy_pass_by_ref)]
+ fn is_visible(b: &u8) -> bool {
+ 0x20 <= *b && *b <= 0x7f
+ }
+ if !any.data.iter().all(is_visible) {
+ return Err(Error::StringInvalidCharset);
+ }
+
+ UtcTime::from_bytes(any.data)
+ }
+}
+
+impl fmt::Display for UtcTime {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let dt = &self.0;
+ match dt.tz {
+ ASN1TimeZone::Z | ASN1TimeZone::Undefined => write!(
+ f,
+ "{:04}-{:02}-{:02} {:02}:{:02}:{:02}Z",
+ dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
+ ),
+ ASN1TimeZone::Offset(hh, mm) => {
+ let (s, hh) = if hh > 0 { ('+', hh) } else { ('-', -hh) };
+ write!(
+ f,
+ "{:04}-{:02}-{:02} {:02}:{:02}:{:02}{}{:02}{:02}",
+ dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, s, hh, mm
+ )
+ }
+ }
+ }
+}
+
+impl CheckDerConstraints for UtcTime {
+ fn check_constraints(_any: &Any) -> Result<()> {
+ Ok(())
+ }
+}
+
+impl DerAutoDerive for UtcTime {}
+
+impl Tagged for UtcTime {
+ const TAG: Tag = Tag::UtcTime;
+}
+
+#[cfg(feature = "std")]
+impl ToDer for UtcTime {
+ fn to_der_len(&self) -> Result<usize> {
+ // data:
+ // - 6 bytes for YYMMDD
+ // - 6 for hhmmss in DER (X.690 section 11.8.2)
+ // - 1 for the character Z in DER (X.690 section 11.8.1)
+ // data length: 13
+ //
+ // thus, length will always be on 1 byte (short length) and
+ // class+structure+tag also on 1
+ //
+ // total: 15 = 1 (class+constructed+tag) + 1 (length) + 13
+ Ok(15)
+ }
+
+ fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ // see above for length value
+ writer.write(&[Self::TAG.0 as u8, 13]).map_err(Into::into)
+ }
+
+ fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
+ write!(
+ writer,
+ "{:02}{:02}{:02}{:02}{:02}{:02}Z",
+ self.0.year, self.0.month, self.0.day, self.0.hour, self.0.minute, self.0.second,
+ )?;
+ // write_fmt returns (), see above for length value
+ Ok(13)
+ }
+}