diff --git a/asn1rs-model/src/asn/tag.rs b/asn1rs-model/src/asn/tag.rs index a7c511e..9f9cd17 100644 --- a/asn1rs-model/src/asn/tag.rs +++ b/asn1rs-model/src/asn/tag.rs @@ -73,6 +73,16 @@ impl Tag { pub const DEFAULT_UNIVERSAL_STRING: Tag = Tag::Universal(28); /// ITU-T Rec. X.680, 41 pub const DEFAULT_BMP_STRING: Tag = Tag::Universal(30); + + #[inline] + pub fn value(self) -> usize { + match self { + Tag::Universal(value) => value, + Tag::Application(value) => value, + Tag::ContextSpecific(value) => value, + Tag::Private(value) => value, + } + } } impl> TryFrom<&mut Peekable> for Tag { diff --git a/src/protocol/basic/distinguished/mod.rs b/src/protocol/basic/distinguished/mod.rs new file mode 100644 index 0000000..24b12d1 --- /dev/null +++ b/src/protocol/basic/distinguished/mod.rs @@ -0,0 +1,101 @@ +use crate::protocol::basic::err::Error; +use crate::protocol::basic::{BasicRead, BasicWrite}; +use crate::rw::{BasicReader, BasicWriter}; +use asn1rs_model::asn::Tag; +use std::io::{Read, Write}; + +pub type DER = DistinguishedEncodingRules; +pub struct DistinguishedEncodingRules; + +impl DistinguishedEncodingRules { + #[inline] + pub fn writer>(write: W) -> BasicWriter { + BasicWriter::from(write) + } + + #[inline] + pub fn reader>(read: W) -> BasicReader { + BasicReader::from(read) + } +} + +const CLASS_BITS_MASK: u8 = 0b_11_000000; +const CLASS_BITS_UNIVERSAL: u8 = 0b_00_000000; +const CLASS_BITS_APPLICATION: u8 = 0b_01_000000; +const CLASS_BITS_CONTEXT_SPECIFIC: u8 = 0b_10_000000; +const CLASS_BITS_PRIVATE: u8 = 0b_11_000000; + +const LENGTH_MASK: u8 = 0b_11_000000; +const LENGTH_SHORT_FORM: u8 = 0b_01_000000; + +impl BasicRead for T { + type Flavor = DistinguishedEncodingRules; + + fn read_identifier(&mut self) -> Result { + let mut byte = [0x00]; + self.read_exact(&mut byte[..])?; + let class = byte[0] & CLASS_BITS_MASK; + let value = byte[0] & !CLASS_BITS_MASK; + // TODO assumption: number contains the primitive / constructed flag + // TODO assumption: number not greater than the octets remaining bits + Ok(match class { + CLASS_BITS_UNIVERSAL => Tag::Universal(usize::from(value)), + CLASS_BITS_APPLICATION => Tag::Application(usize::from(value)), + CLASS_BITS_CONTEXT_SPECIFIC => Tag::ContextSpecific(usize::from(value)), + CLASS_BITS_PRIVATE => Tag::Private(usize::from(value)), + _ => unreachable!(), + }) + } + + #[inline] + fn read_length(&mut self) -> Result { + let mut bytes = [0u8; 1]; + self.read_exact(&mut bytes[..])?; + if bytes[0] & LENGTH_MASK == LENGTH_SHORT_FORM { + Ok(usize::from(bytes[0] & !LENGTH_MASK)) + } else { + todo!() + } + } + + #[inline] + fn read_boolean(&mut self) -> Result { + let mut byte = [0u8; 1]; + self.read_exact(&mut byte[..])?; + Ok(byte[0] != 0x00) + } +} + +impl BasicWrite for T { + type Flavor = DistinguishedEncodingRules; + + fn write_identifier(&mut self, tag: Tag) -> Result<(), Error> { + let mut identifier_octet: u8 = match tag { + Tag::Universal(_) => CLASS_BITS_UNIVERSAL, + Tag::Application(_) => CLASS_BITS_APPLICATION, + Tag::ContextSpecific(_) => CLASS_BITS_CONTEXT_SPECIFIC, + Tag::Private(_) => CLASS_BITS_PRIVATE, + }; + // TODO assumption: number contains the primitive / constructed flag + // TODO assumption: number not greater than the octets remaining bits + identifier_octet |= tag.value() as u8; + Ok(self.write_all(&[identifier_octet])?) + } + + #[inline] + fn write_length(&mut self, length: usize) -> Result<(), Error> { + let byte = if length < 64 { + // short form 8.1.3.4 + LENGTH_SHORT_FORM | (length as u8) + } else { + // long form 8.1.3.5 + todo!() + }; + Ok(self.write_all(&[byte])?) + } + + #[inline] + fn write_boolean(&mut self, value: bool) -> Result<(), Error> { + Ok(self.write_all(&[if value { 0x01 } else { 0x00 }])?) + } +} diff --git a/src/protocol/basic/err.rs b/src/protocol/basic/err.rs new file mode 100644 index 0000000..7087d29 --- /dev/null +++ b/src/protocol/basic/err.rs @@ -0,0 +1,100 @@ +use asn1rs_model::asn::Tag; +use backtrace::Backtrace; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::Range; + +pub struct Error(pub(crate) Box); + +impl Error { + #[inline] + pub fn kind(&self) -> &ErrorKind { + &self.0.kind + } + + #[cold] + #[inline(never)] + pub fn unexpected_tag(expected: Tag, got: Tag) -> Self { + Self::from(ErrorKind::UnexpectedTypeTag { expected, got }) + } + + #[cold] + #[inline(never)] + pub fn unexpected_length(expected: Range, got: usize) -> Self { + Self::from(ErrorKind::UnexpectedTypeLength { expected, got }) + } +} + +impl From for Error { + #[inline] + fn from(kind: ErrorKind) -> Self { + Error(Box::new(Inner::from(kind))) + } +} + +impl From for Error { + #[inline] + fn from(e: std::io::Error) -> Self { + Self::from(ErrorKind::IoError(e)) + } +} + +impl Debug for Error { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.0.kind)?; + let mut backtrace = self.0.backtrace.clone(); + backtrace.resolve(); + writeln!(f, "{backtrace:?}") + } +} + +impl std::error::Error for Error { + fn description(&self) -> &str { + "encoding or decoding with basic rules failed" + } +} + +#[derive(Debug)] +pub(crate) struct Inner { + pub(crate) kind: ErrorKind, + pub(crate) backtrace: Backtrace, +} + +impl From for Inner { + #[inline] + fn from(kind: ErrorKind) -> Self { + Self { + kind, + backtrace: Backtrace::new_unresolved(), + } + } +} + +#[derive(Debug)] +pub enum ErrorKind { + UnexpectedTypeTag { expected: Tag, got: Tag }, + UnexpectedTypeLength { expected: Range, got: usize }, + IoError(std::io::Error), +} + +impl Display for ErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ErrorKind::UnexpectedTypeTag { expected, got } => { + write!(f, "Expected tag {expected:?} but got {got:?}") + } + ErrorKind::UnexpectedTypeLength { expected, got } => { + write!(f, "Expected length in range {expected:?} but got {got:?}") + } + ErrorKind::IoError(e) => { + write!(f, "Experienced underlying IO error: {e:?}") + } + } + } +} diff --git a/src/protocol/basic/mod.rs b/src/protocol/basic/mod.rs new file mode 100644 index 0000000..902e822 --- /dev/null +++ b/src/protocol/basic/mod.rs @@ -0,0 +1,44 @@ +//! This module contains defines traits to encode and decode basic ASN.1 primitives and types of +//! the basic family (BER, DER, CER). + +mod distinguished; +mod err; + +pub use distinguished::*; +pub use err::Error; + +use asn1rs_model::asn::Tag; + +/// According to ITU-T X.690 +pub trait BasicRead { + type Flavor; + + /// According to ITU-T X.690, chapter 8.1.2, an identifier octet contains the class and number + /// of the type. + fn read_identifier(&mut self) -> Result; + + /// According to ITU-T X.690, chapter 8.1.3, the length is encoded in at least one byte, in + /// either the short (8.1.3.4) or long (8.1.3.5) form + fn read_length(&mut self) -> Result; + + /// According to ITU-T X.690, chapter 8.2, the boolean type is represented in a single byte, + /// where 0 represents `false` and any other value represents `true`. + fn read_boolean(&mut self) -> Result; +} + +/// According to ITU-T X.690 +pub trait BasicWrite { + type Flavor; + + /// According to ITU-T X.690, chapter 8.1.2, an identifier octet contains the class and number + /// of the type. + fn write_identifier(&mut self, tag: Tag) -> Result<(), Error>; + + /// According to ITU-T X.690, chapter 8.1.3, the length is encoded in at least one byte, in + /// either the short (8.1.3.4) or long (8.1.3.5) form + fn write_length(&mut self, length: usize) -> Result<(), Error>; + + /// According to ITU-T X.690, chapter 8.2, the boolean type is represented in a single byte, + /// where 0 represents `false` and any other value represents `true`. + fn write_boolean(&mut self, value: bool) -> Result<(), Error>; +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index b46e089..4780792 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -6,6 +6,7 @@ //! ::io::... Other ASN.1 representations (e.g der, xer, ber, ...) //! ``` +pub mod basic; pub mod per; #[cfg(feature = "protobuf")] pub mod protobuf; diff --git a/src/rw/der.rs b/src/rw/der.rs new file mode 100644 index 0000000..2c5333a --- /dev/null +++ b/src/rw/der.rs @@ -0,0 +1,293 @@ +use crate::descriptor::numbers::Number; +use crate::descriptor::sequence::Constraint; +use crate::descriptor::{Null, ReadableType, Reader, WritableType, Writer}; +use crate::protocol::basic::Error; +use crate::protocol::basic::{BasicRead, BasicWrite}; +use asn1rs_model::asn::Tag; + +pub struct BasicWriter { + write: W, +} + +impl From for BasicWriter { + #[inline] + fn from(write: W) -> Self { + Self { write } + } +} + +impl BasicWriter { + #[inline] + pub fn into_inner(self) -> W { + self.write + } +} + +impl Writer for BasicWriter { + type Error = Error; + + fn write_sequence Result<(), Self::Error>>( + &mut self, + _f: F, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_sequence_of( + &mut self, + _slice: &[T::Type], + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_set Result<(), Self::Error>>( + &mut self, + _f: F, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_set_of( + &mut self, + _slice: &[T::Type], + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_enumerated( + &mut self, + _enumerated: &C, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_choice( + &mut self, + _choice: &C, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_opt(&mut self, _value: Option<&T::Type>) -> Result<(), Self::Error> { + todo!() + } + + fn write_default< + C: crate::descriptor::default::Constraint, + T: WritableType, + >( + &mut self, + _value: &T::Type, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_number>( + &mut self, + _value: T, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_utf8string( + &mut self, + _value: &str, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_ia5string( + &mut self, + _value: &str, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_numeric_string( + &mut self, + _value: &str, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_visible_string( + &mut self, + _value: &str, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_printable_string( + &mut self, + _value: &str, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_octet_string( + &mut self, + _value: &[u8], + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_bit_string( + &mut self, + _value: &[u8], + _bit_len: u64, + ) -> Result<(), Self::Error> { + todo!() + } + + fn write_boolean( + &mut self, + value: bool, + ) -> Result<(), Self::Error> { + self.write.write_identifier(C::TAG)?; + self.write.write_length(1)?; + self.write.write_boolean(value)?; + Ok(()) + } + + fn write_null( + &mut self, + _value: &Null, + ) -> Result<(), Self::Error> { + todo!() + } +} + +pub struct BasicReader { + read: R, +} + +impl From for BasicReader { + #[inline] + fn from(read: W) -> Self { + Self { read } + } +} + +impl BasicReader { + #[inline] + pub fn into_inner(self) -> W { + self.read + } +} + +impl Reader for BasicReader { + type Error = Error; + + fn read_sequence Result>( + &mut self, + _f: F, + ) -> Result { + todo!() + } + + fn read_sequence_of( + &mut self, + ) -> Result, Self::Error> { + todo!() + } + + fn read_set Result>( + &mut self, + _f: F, + ) -> Result { + todo!() + } + + fn read_set_of( + &mut self, + ) -> Result, Self::Error> { + todo!() + } + + fn read_enumerated( + &mut self, + ) -> Result { + todo!() + } + + fn read_choice(&mut self) -> Result { + todo!() + } + + fn read_opt(&mut self) -> Result, Self::Error> { + todo!() + } + + fn read_default, T: ReadableType>( + &mut self, + ) -> Result { + todo!() + } + + fn read_number>( + &mut self, + ) -> Result { + todo!() + } + + fn read_utf8string( + &mut self, + ) -> Result { + todo!() + } + + fn read_ia5string( + &mut self, + ) -> Result { + todo!() + } + + fn read_numeric_string( + &mut self, + ) -> Result { + todo!() + } + + fn read_visible_string( + &mut self, + ) -> Result { + todo!() + } + + fn read_printable_string( + &mut self, + ) -> Result { + todo!() + } + + fn read_octet_string( + &mut self, + ) -> Result, Self::Error> { + todo!() + } + + fn read_bit_string( + &mut self, + ) -> Result<(Vec, u64), Self::Error> { + todo!() + } + + fn read_boolean( + &mut self, + ) -> Result { + let identifier = self.read.read_identifier()?; + if identifier.value() != Tag::DEFAULT_BOOLEAN.value() { + return Err(Error::unexpected_tag(Tag::DEFAULT_BOOLEAN, identifier)); + } + let expecting = 1_usize..2_usize; + let length = self.read.read_length()?; + if !expecting.contains(&length) { + return Err(Error::unexpected_length(expecting, length)); + } + self.read.read_boolean() + } + + fn read_null(&mut self) -> Result { + todo!() + } +} diff --git a/src/rw/mod.rs b/src/rw/mod.rs index cb23d30..34ebd05 100644 --- a/src/rw/mod.rs +++ b/src/rw/mod.rs @@ -1,3 +1,4 @@ +mod der; mod println; #[cfg(feature = "protobuf")] mod proto_read; @@ -5,6 +6,7 @@ mod proto_read; mod proto_write; mod uper; +pub use der::*; pub use println::*; #[cfg(feature = "protobuf")] pub use proto_read::*; diff --git a/tests/der_basic_boolean.rs b/tests/der_basic_boolean.rs new file mode 100644 index 0000000..c86d7ac --- /dev/null +++ b/tests/der_basic_boolean.rs @@ -0,0 +1,23 @@ +use asn1rs::descriptor::boolean::NoConstraint; +use asn1rs::descriptor::{Boolean, ReadableType, WritableType}; +use asn1rs::prelude::basic::DER; + +#[test] +pub fn test_der_basic_boolean() { + for bool_value in [true, false] { + let mut buffer = Vec::new(); + let mut writer = DER::writer(&mut buffer); + + Boolean::::write_value(&mut writer, &bool_value).unwrap(); + + assert_eq!( + &[0x01, 0x41, if bool_value { 0x01 } else { 0x00 }], + &buffer[..] + ); + + let mut reader = DER::reader(&buffer[..]); + let result = Boolean::::read_value(&mut reader).unwrap(); + + assert_eq!(bool_value, result) + } +}