Expand description
BER/DER Custom Derive Attributes
BER/DER Sequence parsers
BER
To derive a BER SEQUENCE
parser, add the BerSequence
derive attribute to an existing struct. Parsers will be derived automatically for all fields, which must implement the FromBer
trait.
For ex:
#[derive(Debug, PartialEq, BerSequence)]
pub struct S {
a: u32,
b: u16,
c: u16,
}
let (rest, result) = S::from_ber(input)?;
After parsing b, any bytes that were leftover and not used to fill val will be returned in rest
.
When parsing a SEQUENCE
into a struct, any trailing elements of the SEQUENCE
that do
not have matching fields in val will not be included in rest
, as these are considered
valid elements of the SEQUENCE
and not trailing data.
DER
To derive a DER
parser, use the DerSequence
custom attribute.
Note: the DerSequence
attributes derive both BER
and DER
parsers.
Tagged values
EXPLICIT
There are several ways of parsing tagged values: either using types like TaggedExplicit
, or using custom annotations.
Using TaggedExplicit
works as usual. The only drawback is that the type is visible in the structure, so accessing the value must be done using .as_ref()
or .into_inner()
:
#[derive(Debug, PartialEq, DerSequence)]
pub struct S2 {
a: u16,
}
// test with EXPLICIT Vec
#[derive(Debug, PartialEq, DerSequence)]
pub struct S {
// a INTEGER
a: u32,
// b INTEGER
b: u16,
// c [0] EXPLICIT SEQUENCE OF S2
c: TaggedExplicit<Vec<S2>, Error, 0>,
}
let (rem, result) = S::from_ber(input)?;
// Get a reference on c (type is &Vec<S2>)
let ref_c = result.c.as_ref();
Note: tags are context-specific by default. To specify other kind of tags (like APPLICATION
) use TaggedValue
.
tag_explicit
To “hide” the tag from the parser, the tag_explicit
attribute is provided. This attribute must specify the tag value (as an integer), and will automatically wrap reading the value with the specified tag.
#[derive(Debug, PartialEq, DerSequence)]
pub struct S {
// a [0] EXPLICIT INTEGER
#[tag_explicit(0)]
a: u16,
}
let (rem, result) = S::from_ber(input)?;
This method handles transparently the encapsulation and the read of the tagged value.
Note: tags are context-specific by default. To specify other kind of tags (like APPLICATION
) add the tag class before the value in the tag_explicit
attribute.
For ex: tag_explicit(APPLICATION 0)
or tag_explicit(PRIVATE 2)
.
Tagged optional values
The optional
custom attribute can be used in addition of tag_explicit
to specify that the value is OPTIONAL
.
The type of the annotated field member must be resolvable to Option
.
#[derive(Debug, PartialEq, DerSequence)]
pub struct S {
// a [0] EXPLICIT INTEGER OPTIONAL
#[tag_explicit(0)]
#[optional]
a: Option<u16>,
// b INTEGER
b: u16,
}
let (rem, result) = S::from_ber(input)?;
IMPLICIT
Tagged IMPLICIT
values are handled similarly as for EXPLICIT
, and can be parsed either using the TaggedImplicit
type, or using the tag_implicit
custom attribute.
For ex:
#[derive(Debug, PartialEq, DerSequence)]
pub struct S {
// a [0] IMPLICIT INTEGER OPTIONAL
#[tag_implicit(0)]
#[optional]
a: Option<u16>,
// b INTEGER
b: u16,
}
let (rem, result) = S::from_ber(input)?;
OPTIONAL
values (not tagged)
The optional
custom attribute can be specified to indicate the value is OPTIONAL
.
#[derive(Debug, PartialEq, DerSequence)]
pub struct S {
// a INTEGER
a: u16,
// b INTEGER OPTIONAL
#[optional]
b: Option<u16>,
}
let (rem, result) = S::from_ber(input)?;
Important: there are several limitations to this attribute.
In particular, the parser is eager: when an OPTIONAL
value of some type is followed by another value (not OPTIONAL
) of the same type, this can create problem.
If only one value is present, the parser will affect it to the first field, and then raise an error because the second is absent.
Note that this does not concern tagged optional values (unless they have the same tag).
DEFAULT
The default
custom attribute can be specified to indicate the value has a DEFAULT
attribute. The value can also be marked as
OPTIONAL
, but this can be omitted.
Since the value can always be obtained, the type should not be Option<T>
, but should use T
directly.
#[derive(Debug, PartialEq, DerSequence)]
#[debug_derive]
pub struct S {
// a INTEGER
a: u16,
// b INTEGER DEFAULT 0
#[default(0_u16)]
b: u16,
}
let (rem, result) = S::from_ber(input)?;
Limitations are the same as for OPTIONAL
attribute.
Debugging
To help debugging the generated code, the #[debug_derive]
attribute has been added.
When this attribute is specified, the generated code will be printed to stderr
during compilation.
Example:
use asn1_rs::*;
#[derive(BerSequence)]
#[debug_derive]
struct S {
a: u32,
}
BER/DER Set parsers
Parsing BER/DER SET
objects is very similar to SEQUENCE
. Use the BerSet
and DerSet
custom derive attributes on the structure, and everything else is exactly the same as for sequences (see above for documentation).
Example:
use std::collections::BTreeSet;
// `Ord` is needed because we will parse as a `BTreeSet` later
#[derive(Debug, DerSet, PartialEq, Eq, PartialOrd, Ord)]
pub struct S2 {
a: u16,
}
// test with EXPLICIT Vec
#[derive(Debug, PartialEq, DerSet)]
pub struct S {
// a INTEGER
a: u32,
// b INTEGER
b: u16,
// c [0] EXPLICIT SET OF S2
c: TaggedExplicit<BTreeSet<S2>, Error, 0>,
}
let (rem, result) = S::from_ber(input)?;
// Get a reference on c (type is &BTreeSet<S2>)
let ref_c = result.c.as_ref();
Advanced
Custom errors
Derived parsers can use the error
attribute to specify the error type of the parser.
The custom error type must implement From<Error>
, so the derived parsers will transparently convert errors using the Into
trait.
Example:
#[derive(Debug, PartialEq)]
pub enum MyError {
NotYetImplemented,
}
impl From<asn1_rs::Error> for MyError {
fn from(_: asn1_rs::Error) -> Self {
MyError::NotYetImplemented
}
}
#[derive(DerSequence)]
#[error(MyError)]
pub struct T2 {
pub a: u32,
}
Mapping errors
Sometimes, it is necessary to map the returned error to another type, for example when a subparser returns a different error type than the parser’s, and the Into
trait cannot be implemented. This is often used in combination with the error
attribute, but can also be used alone.
The map_err
attribute can be used to specify a function or closure to map errors. The function signature is fn (e1: E1) -> E2
.
Example:
#[derive(Debug, PartialEq)]
pub enum MyError {
NotYetImplemented,
}
impl From<asn1_rs::Error> for MyError {
fn from(_: asn1_rs::Error) -> Self {
MyError::NotYetImplemented
}
}
#[derive(DerSequence)]
#[error(MyError)]
pub struct T2 {
pub a: u32,
}
// subparser returns an error of type MyError,
// which is mapped to `Error`
#[derive(DerSequence)]
pub struct T4 {
#[map_err(|_| Error::BerTypeError)]
pub a: T2,
}
Note: when deriving BER and DER parsers, errors paths are different (TryFrom
returns the error type, while FromDer
returns a ParseResult
). Some code will be inserted by the map_err
attribute to handle this transparently and keep the same function signature.