Skip to content

Commit

Permalink
Refactor to represent OPTIONAL in Type instead of an boolean flag
Browse files Browse the repository at this point in the history
This change allows representing recursive OPTIONAL types
  • Loading branch information
kellerkindt committed Apr 30, 2020
1 parent 6ae4163 commit b2679ef
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 54 deletions.
59 changes: 34 additions & 25 deletions asn1rs-macros/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use quote::quote;
use range::MaybeRanged;
use std::str::FromStr;
use syn::export::TokenStream2;
use syn::parenthesized;
use syn::parse::{Parse, ParseBuffer};
use syn::spanned::Spanned;
use syn::{parse_macro_input, AttributeArgs, Meta};
Expand Down Expand Up @@ -67,7 +68,6 @@ pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream {
Ok(asn) => {
fields.push(Field {
name: field.ident.as_ref().map(ToString::to_string).unwrap(),
optional: asn.optional,
role: match into_asn(&field.ty, asn) {
Some(asn) => asn,
None => {
Expand Down Expand Up @@ -225,37 +225,27 @@ fn into_asn(ty: &syn::Type, asn: Asn) -> Option<asn1rs_model::model::Asn> {
struct Asn {
r#type: Option<Type>,
tag: Option<Tag>,
// TODO allow nested optional
/// this needs refactoring as well as [`Type`] because it does not support nested `Optional`s
///
/// [`Type`]: asn1rs_model::model::Type
optional: bool,
}

impl Parse for Asn {
fn parse<'a>(input: &'a ParseBuffer<'a>) -> syn::Result<Self> {
let mut asn = Self::default();
let mut first = true;

while !input.cursor().eof() {
let ident = input.step(|c| c.ident().ok_or_else(|| c.error("Expected ASN-Type")))?;
match ident.to_string().to_lowercase().as_str() {
"utf8string" if first => asn.r#type = Some(Type::UTF8String),
"octet_string" if first => asn.r#type = Some(Type::OctetString),
"integer" if first => {
let range = MaybeRanged::parse(input)?;
asn.r#type = Some(Type::Integer(range.0.map(|(min, max)| Range(min, max))));
}
"complex" if first => asn.r#type = Some(Type::TypeReference(String::default())),
"tag" if !first => {
let tag = AttrTag::parse(input)?;
asn.tag = Some(tag.0);
}
// TODO allow nested optional, see comment in Asn above
"optional" if !first => {
asn.optional = true;
if asn.r#type.is_none() {
asn.r#type = Some(parse_type(input)?);
} else {
let ident =
input.step(|c| c.ident().ok_or_else(|| c.error("Expected ASN-Type")))?;
match ident.to_string().to_lowercase().as_str() {
"tag" => {
let tag = AttrTag::parse(input)?;
asn.tag = Some(tag.0);
}
attribute => {
return Err(input.error(format!("Unexpected attribute: `{}`", attribute)))
}
}
r#type => return Err(input.error(format!("Unexpected attribute: `{}`", r#type))),
}
if !input.cursor().eof() && !input.peek(syn::token::Comma) {
return Err(input.error("Attributes must be separated by comma"));
Expand All @@ -265,8 +255,27 @@ impl Parse for Asn {
.ok_or_else(|| input.error("Attributes must be separated by comma"))
})?;
}
first = false;
}
Ok(asn)
}
}

fn parse_type<'a>(input: &'a ParseBuffer<'a>) -> syn::Result<Type> {
let ident = input.step(|c| c.ident().ok_or_else(|| c.error("Expected ASN-Type")))?;
match ident.to_string().to_lowercase().as_str() {
"utf8string" => Ok(Type::UTF8String),
"octet_string" => Ok(Type::OctetString),
"integer" => {
let range = MaybeRanged::parse(input)?;
Ok(Type::Integer(range.0.map(|(min, max)| Range(min, max))))
}
"complex" => Ok(Type::TypeReference(String::default())),
"optional" => {
let content;
parenthesized!(content in input);
let inner = parse_type(&content)?;
Ok(Type::Optional(Box::new(inner)))
}
r#type => Err(input.error(format!("Unexpected attribute: `{}`", r#type))),
}
}
36 changes: 17 additions & 19 deletions asn1rs-model/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,10 @@ impl Model<Asn> {
name,
role: Self::read_role_given_text(iter, token.into_text_or_else(Error::no_text)?)?
.opt_tagged(tag),
optional: false,
};
let mut token = Self::next(iter)?;
if let Some(_optional_flag) = token.text().map(|s| s.eq_ignore_ascii_case("OPTIONAL")) {
field.optional = true;
field.role.optional();
token = Self::next(iter)?;
}

Expand Down Expand Up @@ -529,7 +528,6 @@ impl Tagged for Definition<Asn> {
pub struct Field<T> {
pub name: String,
pub role: T,
pub optional: bool,
}

impl<T: Tagged> Tagged for Field<T> {
Expand Down Expand Up @@ -627,6 +625,11 @@ pub struct Asn {
}

impl Asn {
pub fn optional(&mut self) {
let optional = self.r#type.clone().optional();
self.r#type = optional;
}

pub const fn opt_tagged(tag: Option<Tag>, r#type: Type) -> Self {
Self { tag, r#type }
}
Expand Down Expand Up @@ -667,6 +670,8 @@ pub enum Type {
UTF8String,
OctetString,

Optional(Box<Type>),

SequenceOf(Box<Type>),
Sequence(Vec<Field<Asn>>),
Enumerated(Enumerated),
Expand All @@ -675,6 +680,10 @@ pub enum Type {
}

impl Type {
pub fn optional(self) -> Self {
Self::Optional(Box::new(self))
}

pub const fn opt_tagged(self, tag: Option<Tag>) -> Asn {
Asn::opt_tagged(tag, self)
}
Expand Down Expand Up @@ -965,22 +974,18 @@ pub(crate) mod tests {
Field {
name: "small".into(),
role: Type::Integer(Some(Range(0, 255))).untagged(),
optional: false,
},
Field {
name: "bigger".into(),
role: Type::Integer(Some(Range(0, 65535))).untagged(),
optional: false,
},
Field {
name: "negative".into(),
role: Type::Integer(Some(Range(-1, 255))).untagged(),
optional: false,
},
Field {
name: "unlimited".into(),
role: Type::Integer(None).untagged(),
optional: true,
role: Type::Integer(None).optional().untagged(),
}
])
.untagged()
Expand Down Expand Up @@ -1020,8 +1025,8 @@ pub(crate) mod tests {
role: Type::Enumerated(Enumerated::from_names(
["ABORT", "RETURN", "CONFIRM", "MAYDAY", "THE_CAKE_IS_A_LIE",].iter()
))
.optional()
.untagged(),
optional: true,
}])
.untagged(),
),
Expand Down Expand Up @@ -1078,23 +1083,21 @@ pub(crate) mod tests {
name: "also-ones".into(),
role: Type::SequenceOf(Box::new(Type::Integer(Some(Range(0, 1)))))
.untagged(),
optional: false,
},
Field {
name: "nesteds".into(),
role: Type::SequenceOf(Box::new(Type::SequenceOf(Box::new(
Type::Integer(Some(Range(0, 1)))
))))
.untagged(),
optional: false,
},
Field {
name: "optionals".into(),
role: Type::SequenceOf(Box::new(Type::SequenceOf(Box::new(
Type::Integer(None)
))))
.optional()
.untagged(),
optional: true,
},
])
.untagged()
Expand Down Expand Up @@ -1168,7 +1171,6 @@ pub(crate) mod tests {
ChoiceVariant::name_type("neither", Type::TypeReference("Neither".into())),
]))
.untagged(),
optional: false,
}])
.untagged(),
),
Expand Down Expand Up @@ -1206,23 +1208,21 @@ pub(crate) mod tests {
Field {
name: "ones".into(),
role: Type::Integer(Some(Range(0, 1))).untagged(),
optional: false,
},
Field {
name: "list-ones".into(),
role: Type::SequenceOf(Box::new(Type::Integer(Some(Range(0, 1)))))
.untagged(),
optional: false,
},
Field {
name: "optional-ones".into(),
role: Type::SequenceOf(Box::new(Type::Integer(Some(Range(0, 1)))))
.optional()
.untagged(),
optional: true,
},
])
.optional()
.untagged(),
optional: true,
}])
.untagged()
),
Expand Down Expand Up @@ -1493,13 +1493,11 @@ pub(crate) mod tests {
Field {
name: "abc".to_string(),
role: Type::Integer(None).tagged(Tag::ContextSpecific(1)),
optional: false,
},
Field {
name: "def".to_string(),
role: Type::Integer(Some(Range(0, 255)))
.tagged(Tag::ContextSpecific(2)),
optional: false,
}
])
.tagged(Tag::Universal(2)),
Expand Down
25 changes: 15 additions & 10 deletions asn1rs-model/src/model/rust.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::model::Definition;
use crate::model::Import;
use crate::model::Model;
use crate::model::Range;
use crate::model::Type as AsnType;
use crate::model::{Asn, ChoiceVariant};
use crate::model::{Definition, Type};

const I8_MAX: i64 = i8::max_value() as i64;
const I16_MAX: i64 = i16::max_value() as i64;
Expand Down Expand Up @@ -333,6 +333,13 @@ impl Model<Rust> {
defs.push(Definition(name.into(), Rust::TupleStruct(rust_type)));
}

AsnType::Optional(inner) => {
let inner = RustType::Option(Box::new(Self::definition_type_to_rust_type(
name, inner, defs,
)));
defs.push(Definition(name.into(), Rust::TupleStruct(inner)))
}

AsnType::Sequence(fields) => {
let mut rust_fields = Vec::with_capacity(fields.len());

Expand All @@ -341,11 +348,7 @@ impl Model<Rust> {
let rust_role =
Self::definition_type_to_rust_type(&rust_name, &field.role.r#type, defs);
let rust_field_name = rust_field_name(&field.name);
if field.optional {
rust_fields.push((rust_field_name, RustType::Option(Box::new(rust_role))));
} else {
rust_fields.push((rust_field_name, rust_role));
}
rust_fields.push((rust_field_name, rust_role));
}

defs.push(Definition(name.into(), Rust::Struct(rust_fields)));
Expand Down Expand Up @@ -421,6 +424,9 @@ impl Model<Rust> {
AsnType::Integer(None) => RustType::U64(None),
AsnType::UTF8String => RustType::String,
AsnType::OctetString => RustType::VecU8,
Type::Optional(inner) => RustType::Option(Box::new(
Self::definition_type_to_rust_type(name, inner, defs),
)),
AsnType::SequenceOf(asn) => RustType::Vec(Box::new(
Self::definition_type_to_rust_type(name, asn, defs),
)),
Expand Down Expand Up @@ -860,8 +866,9 @@ mod tests {
"OptionalStructListTest".into(),
AsnType::Sequence(vec![Field {
name: "strings".into(),
role: AsnType::SequenceOf(Box::new(AsnType::UTF8String)).untagged(),
optional: true,
role: AsnType::SequenceOf(Box::new(AsnType::UTF8String))
.optional()
.untagged(),
}])
.untagged(),
));
Expand Down Expand Up @@ -890,7 +897,6 @@ mod tests {
AsnType::Sequence(vec![Field {
name: "strings".into(),
role: AsnType::SequenceOf(Box::new(AsnType::UTF8String)).untagged(),
optional: false,
}])
.untagged(),
));
Expand Down Expand Up @@ -922,7 +928,6 @@ mod tests {
AsnType::UTF8String,
))))
.untagged(),
optional: false,
}])
.untagged(),
));
Expand Down
20 changes: 20 additions & 0 deletions tests/basic_proc_macro_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,23 @@ fn are_we_binary_yet_uper() {
assert_eq!(are_we, uper.read::<AreWeBinaryYet>().unwrap());
assert_eq!(0, uper.bits_remaining());
}

#[asn(sequence)]
#[derive(Debug, PartialOrd, PartialEq)]
pub struct Optional {
#[asn(optional(integer))]
value: Option<u64>,
}

#[test]
fn optional_test_uper() {
let mut uper = UperWriter::default();
let v = Optional { value: Some(1337) };
uper.write(&v).unwrap();
// https://asn1.io/asn1playground/
assert_eq!(&[0x81, 0x02, 0x9C, 0x80], uper.byte_content());
assert_eq!(3 * 8 + 1, uper.bit_len());
let mut uper = uper.into_reader();
assert_eq!(v, uper.read::<Optional>().unwrap());
assert_eq!(0, uper.bits_remaining());
}

0 comments on commit b2679ef

Please sign in to comment.