| //! Context-specific field. |
| |
| use crate::{ |
| asn1::AnyRef, Choice, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, |
| Header, Length, Reader, Result, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, |
| }; |
| use core::cmp::Ordering; |
| |
| /// Context-specific field which wraps an owned inner value. |
| /// |
| /// This type decodes/encodes a field which is specific to a particular context |
| /// and is identified by a [`TagNumber`]. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] |
| pub struct ContextSpecific<T> { |
| /// Context-specific tag number sans the leading `0b10000000` class |
| /// identifier bit and `0b100000` constructed flag. |
| pub tag_number: TagNumber, |
| |
| /// Tag mode: `EXPLICIT` VS `IMPLICIT`. |
| pub tag_mode: TagMode, |
| |
| /// Value of the field. |
| pub value: T, |
| } |
| |
| impl<T> ContextSpecific<T> { |
| /// Attempt to decode an `EXPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the |
| /// provided [`TagNumber`]. |
| /// |
| /// This method has the following behavior which is designed to simplify |
| /// handling of extension fields, which are denoted in an ASN.1 schema |
| /// using the `...` ellipsis extension marker: |
| /// |
| /// - Skips over [`ContextSpecific`] fields with a tag number lower than |
| /// the current one, consuming and ignoring them. |
| /// - Returns `Ok(None)` if a [`ContextSpecific`] field with a higher tag |
| /// number is encountered. These fields are not consumed in this case, |
| /// allowing a field with a lower tag number to be omitted, then the |
| /// higher numbered field consumed as a follow-up. |
| /// - Returns `Ok(None)` if anything other than a [`ContextSpecific`] field |
| /// is encountered. |
| pub fn decode_explicit<'a, R: Reader<'a>>( |
| reader: &mut R, |
| tag_number: TagNumber, |
| ) -> Result<Option<Self>> |
| where |
| T: Decode<'a>, |
| { |
| Self::decode_with(reader, tag_number, |reader| Self::decode(reader)) |
| } |
| |
| /// Attempt to decode an `IMPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the |
| /// provided [`TagNumber`]. |
| /// |
| /// This method otherwise behaves the same as `decode_explicit`, |
| /// but should be used in cases where the particular fields are `IMPLICIT` |
| /// as opposed to `EXPLICIT`. |
| pub fn decode_implicit<'a, R: Reader<'a>>( |
| reader: &mut R, |
| tag_number: TagNumber, |
| ) -> Result<Option<Self>> |
| where |
| T: DecodeValue<'a> + Tagged, |
| { |
| Self::decode_with(reader, tag_number, |reader| { |
| let header = Header::decode(reader)?; |
| let value = T::decode_value(reader, header)?; |
| |
| if header.tag.is_constructed() != value.tag().is_constructed() { |
| return Err(header.tag.non_canonical_error()); |
| } |
| |
| Ok(Self { |
| tag_number, |
| tag_mode: TagMode::Implicit, |
| value, |
| }) |
| }) |
| } |
| |
| /// Attempt to decode a context-specific field with the given |
| /// helper callback. |
| fn decode_with<'a, F, R: Reader<'a>>( |
| reader: &mut R, |
| tag_number: TagNumber, |
| f: F, |
| ) -> Result<Option<Self>> |
| where |
| F: FnOnce(&mut R) -> Result<Self>, |
| { |
| while let Some(octet) = reader.peek_byte() { |
| let tag = Tag::try_from(octet)?; |
| |
| if !tag.is_context_specific() || (tag.number() > tag_number) { |
| break; |
| } else if tag.number() == tag_number { |
| return Some(f(reader)).transpose(); |
| } else { |
| AnyRef::decode(reader)?; |
| } |
| } |
| |
| Ok(None) |
| } |
| } |
| |
| impl<'a, T> Choice<'a> for ContextSpecific<T> |
| where |
| T: Decode<'a> + Tagged, |
| { |
| fn can_decode(tag: Tag) -> bool { |
| tag.is_context_specific() |
| } |
| } |
| |
| impl<'a, T> Decode<'a> for ContextSpecific<T> |
| where |
| T: Decode<'a>, |
| { |
| fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self> { |
| let header = Header::decode(reader)?; |
| |
| match header.tag { |
| Tag::ContextSpecific { |
| number, |
| constructed: true, |
| } => Ok(Self { |
| tag_number: number, |
| tag_mode: TagMode::default(), |
| value: reader.read_nested(header.length, |reader| T::decode(reader))?, |
| }), |
| tag => Err(tag.unexpected_error(None)), |
| } |
| } |
| } |
| |
| impl<T> EncodeValue for ContextSpecific<T> |
| where |
| T: EncodeValue + Tagged, |
| { |
| fn value_len(&self) -> Result<Length> { |
| match self.tag_mode { |
| TagMode::Explicit => self.value.encoded_len(), |
| TagMode::Implicit => self.value.value_len(), |
| } |
| } |
| |
| fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { |
| match self.tag_mode { |
| TagMode::Explicit => self.value.encode(writer), |
| TagMode::Implicit => self.value.encode_value(writer), |
| } |
| } |
| } |
| |
| impl<T> Tagged for ContextSpecific<T> |
| where |
| T: Tagged, |
| { |
| fn tag(&self) -> Tag { |
| let constructed = match self.tag_mode { |
| TagMode::Explicit => true, |
| TagMode::Implicit => self.value.tag().is_constructed(), |
| }; |
| |
| Tag::ContextSpecific { |
| number: self.tag_number, |
| constructed, |
| } |
| } |
| } |
| |
| impl<'a, T> TryFrom<AnyRef<'a>> for ContextSpecific<T> |
| where |
| T: Decode<'a>, |
| { |
| type Error = Error; |
| |
| fn try_from(any: AnyRef<'a>) -> Result<ContextSpecific<T>> { |
| match any.tag() { |
| Tag::ContextSpecific { |
| number, |
| constructed: true, |
| } => Ok(Self { |
| tag_number: number, |
| tag_mode: TagMode::default(), |
| value: T::from_der(any.value())?, |
| }), |
| tag => Err(tag.unexpected_error(None)), |
| } |
| } |
| } |
| |
| impl<T> ValueOrd for ContextSpecific<T> |
| where |
| T: EncodeValue + ValueOrd + Tagged, |
| { |
| fn value_cmp(&self, other: &Self) -> Result<Ordering> { |
| match self.tag_mode { |
| TagMode::Explicit => self.der_cmp(other), |
| TagMode::Implicit => self.value_cmp(other), |
| } |
| } |
| } |
| |
| /// Context-specific field reference. |
| /// |
| /// This type encodes a field which is specific to a particular context |
| /// and is identified by a [`TagNumber`]. |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] |
| pub struct ContextSpecificRef<'a, T> { |
| /// Context-specific tag number sans the leading `0b10000000` class |
| /// identifier bit and `0b100000` constructed flag. |
| pub tag_number: TagNumber, |
| |
| /// Tag mode: `EXPLICIT` VS `IMPLICIT`. |
| pub tag_mode: TagMode, |
| |
| /// Value of the field. |
| pub value: &'a T, |
| } |
| |
| impl<'a, T> ContextSpecificRef<'a, T> { |
| /// Convert to a [`ContextSpecific`]. |
| fn encoder(&self) -> ContextSpecific<EncodeValueRef<'a, T>> { |
| ContextSpecific { |
| tag_number: self.tag_number, |
| tag_mode: self.tag_mode, |
| value: EncodeValueRef(self.value), |
| } |
| } |
| } |
| |
| impl<'a, T> EncodeValue for ContextSpecificRef<'a, T> |
| where |
| T: EncodeValue + Tagged, |
| { |
| fn value_len(&self) -> Result<Length> { |
| self.encoder().value_len() |
| } |
| |
| fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { |
| self.encoder().encode_value(writer) |
| } |
| } |
| |
| impl<'a, T> Tagged for ContextSpecificRef<'a, T> |
| where |
| T: Tagged, |
| { |
| fn tag(&self) -> Tag { |
| self.encoder().tag() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::ContextSpecific; |
| use crate::{asn1::BitStringRef, Decode, Encode, SliceReader, TagMode, TagNumber}; |
| use hex_literal::hex; |
| |
| // Public key data from `pkcs8` crate's `ed25519-pkcs8-v2.der` |
| const EXAMPLE_BYTES: &[u8] = |
| &hex!("A123032100A3A7EAE3A8373830BC47E1167BC50E1DB551999651E0E2DC587623438EAC3F31"); |
| |
| #[test] |
| fn round_trip() { |
| let field = ContextSpecific::<BitStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap(); |
| assert_eq!(field.tag_number.value(), 1); |
| assert_eq!( |
| field.value, |
| BitStringRef::from_bytes(&EXAMPLE_BYTES[5..]).unwrap() |
| ); |
| |
| let mut buf = [0u8; 128]; |
| let encoded = field.encode_to_slice(&mut buf).unwrap(); |
| assert_eq!(encoded, EXAMPLE_BYTES); |
| } |
| |
| #[test] |
| fn context_specific_with_explicit_field() { |
| let tag_number = TagNumber::new(0); |
| |
| // Empty message |
| let mut reader = SliceReader::new(&[]).unwrap(); |
| assert_eq!( |
| ContextSpecific::<u8>::decode_explicit(&mut reader, tag_number).unwrap(), |
| None |
| ); |
| |
| // Message containing a non-context-specific type |
| let mut reader = SliceReader::new(&hex!("020100")).unwrap(); |
| assert_eq!( |
| ContextSpecific::<u8>::decode_explicit(&mut reader, tag_number).unwrap(), |
| None |
| ); |
| |
| // Message containing an EXPLICIT context-specific field |
| let mut reader = SliceReader::new(&hex!("A003020100")).unwrap(); |
| let field = ContextSpecific::<u8>::decode_explicit(&mut reader, tag_number) |
| .unwrap() |
| .unwrap(); |
| |
| assert_eq!(field.tag_number, tag_number); |
| assert_eq!(field.tag_mode, TagMode::Explicit); |
| assert_eq!(field.value, 0); |
| } |
| |
| #[test] |
| fn context_specific_with_implicit_field() { |
| // From RFC8410 Section 10.3: |
| // <https://datatracker.ietf.org/doc/html/rfc8410#section-10.3> |
| // |
| // 81 33: [1] 00 19 BF 44 09 69 84 CD FE 85 41 BA C1 67 DC 3B |
| // 96 C8 50 86 AA 30 B6 B6 CB 0C 5C 38 AD 70 31 66 |
| // E1 |
| let context_specific_implicit_bytes = |
| hex!("81210019BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1"); |
| |
| let tag_number = TagNumber::new(1); |
| |
| let mut reader = SliceReader::new(&context_specific_implicit_bytes).unwrap(); |
| let field = ContextSpecific::<BitStringRef<'_>>::decode_implicit(&mut reader, tag_number) |
| .unwrap() |
| .unwrap(); |
| |
| assert_eq!(field.tag_number, tag_number); |
| assert_eq!(field.tag_mode, TagMode::Implicit); |
| assert_eq!( |
| field.value.as_bytes().unwrap(), |
| &context_specific_implicit_bytes[3..] |
| ); |
| } |
| |
| #[test] |
| fn context_specific_skipping_unknown_field() { |
| let tag = TagNumber::new(1); |
| let mut reader = SliceReader::new(&hex!("A003020100A103020101")).unwrap(); |
| let field = ContextSpecific::<u8>::decode_explicit(&mut reader, tag) |
| .unwrap() |
| .unwrap(); |
| assert_eq!(field.value, 1); |
| } |
| |
| #[test] |
| fn context_specific_returns_none_on_greater_tag_number() { |
| let tag = TagNumber::new(0); |
| let mut reader = SliceReader::new(&hex!("A103020101")).unwrap(); |
| assert_eq!( |
| ContextSpecific::<u8>::decode_explicit(&mut reader, tag).unwrap(), |
| None |
| ); |
| } |
| } |