diff --git a/asn1rs-macros/src/ast/mod.rs b/asn1rs-macros/src/ast/mod.rs index a9b943db..849f40e3 100644 --- a/asn1rs-macros/src/ast/mod.rs +++ b/asn1rs-macros/src/ast/mod.rs @@ -1,7 +1,9 @@ mod range; mod tag; -use asn1rs_model::model::{Definition, Enumerated, Field, Model, Range, Tag, Type}; +use asn1rs_model::model::{ + Choice, ChoiceVariant, Definition, Enumerated, Field, Model, Range, Tag, Type, +}; use proc_macro::TokenStream; use quote::quote; use range::MaybeRanged; @@ -63,17 +65,11 @@ pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream { if let Some(removed) = removed { match removed.parse_args::() { Ok(asn) => { - let parsed = asn1rs_model::model::Asn { - tag: asn.tag, - r#type: match asn.r#type { - Some(some) => { - if let Type::TypeReference(_) = some { - let ty = field.ty.clone(); - Type::TypeReference(quote! { #ty }.to_string()) - } else { - some - } - } + 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 => { return TokenStream::from( syn::Error::new(field.span(), "Missing ASN-Type") @@ -81,11 +77,6 @@ pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream { ); } }, - }; - fields.push(Field { - name: field.ident.as_ref().map(ToString::to_string).unwrap(), - role: parsed, - optional: asn.optional, }); } Err(e) => return TokenStream::from(e.to_compile_error()), @@ -125,6 +116,74 @@ pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream { Item::Enum(enm) } + Item::Enum(mut enm) if asn_type_decl == "choice" => { + let data_enum = enm.variants.iter().all(|v| !v.fields.is_empty()); + let variants = enm + .variants + .iter_mut() + .map(|v| { + if v.fields.len() != 1 { + panic!("Variants of CHOICE have to have exactly one field"); + } + let mut attr = None; + 'inner: for i in 0..v.attrs.len() { + if v.attrs[i] + .path + .segments + .first() + .unwrap() + .ident + .to_string() + .eq("asn") + { + attr = Some(v.attrs.remove(i)); + break 'inner; + } + } + let attr = attr.expect("Missing #[asn(..)] attribute"); + + match attr.parse_args::() { + Ok(asn) => match into_asn(&v.fields.iter().next().unwrap().ty, asn) { + Some(asn) => { + let name = v.ident.to_string(); + Ok(ChoiceVariant { + name, + tag: asn.tag, + r#type: asn.r#type, + }) + } + None => Err(TokenStream::from( + syn::Error::new(v.span(), "Missing ASN-Type").to_compile_error(), + )), + }, + Err(e) => Err(TokenStream::from(e.to_compile_error())), + } + }) + .collect::>(); + + if data_enum { + // TODO extensible + // TODO tags + let choice = Choice::from_variants({ + let mut new = Vec::with_capacity(variants.len()); + for var in variants { + new.push(match var { + Ok(variant) => variant, + Err(e) => return e, + }); + } + new + }); + model.definitions.push(Definition( + enm.ident.to_string(), + Type::Choice(choice).untagged(), + )); + } else { + // mixed case + panic!("CHOICE does not allow any Variant to not have data attached!"); + } + Item::Enum(enm) + } item => item, }; @@ -146,6 +205,22 @@ pub(crate) fn parse(attr: TokenStream, item: TokenStream) -> TokenStream { result } +fn into_asn(ty: &syn::Type, asn: Asn) -> Option { + Some(asn1rs_model::model::Asn { + tag: asn.tag, + r#type: match asn.r#type { + Some(some) => { + if let Type::TypeReference(_) = some { + Type::TypeReference(quote! { #ty }.to_string()) + } else { + some + } + } + None => return None, + }, + }) +} + #[derive(Debug, Default)] struct Asn { r#type: Option, diff --git a/asn1rs-model/src/gen/rust/walker.rs b/asn1rs-model/src/gen/rust/walker.rs index 1cf1b43f..722ba583 100644 --- a/asn1rs-model/src/gen/rust/walker.rs +++ b/asn1rs-model/src/gen/rust/walker.rs @@ -1,6 +1,6 @@ use crate::gen::rust::GeneratorSupplement; use crate::gen::RustCodeGenerator; -use crate::model::rust::PlainEnum; +use crate::model::rust::{DataEnum, PlainEnum}; use crate::model::{Definition, Model, Range, Rust, RustType}; use codegen::{Block, Impl, Scope}; use std::fmt::Display; @@ -31,8 +31,16 @@ impl AsnDefWalker { name, CRATE_SYN_PREFIX, name )); } - Rust::DataEnum(_) => {} - Rust::TupleStruct(_) => {} + Rust::DataEnum(enm) => { + scope.raw(&format!( + "type AsnDef{} = {}Choice<{}>;", + name, CRATE_SYN_PREFIX, name + )); + for (field, r#type) in enm.variants() { + self.write_type_declaration(scope, &name, &field, r#type); + } + } + Rust::TupleStruct(_) => unimplemented!("TupleStruct in Walker::write_type_definitions"), } } @@ -87,8 +95,8 @@ impl AsnDefWalker { Rust::Enum(plain) => { self.write_enumerated_constraint(scope, &name, plain); } - Rust::DataEnum(_) => {} - Rust::TupleStruct(_) => {} + Rust::DataEnum(data) => self.write_choice_constraint(scope, &name, data), + Rust::TupleStruct(_) => unimplemented!("TupleStruct for Walker::write_constraints"), } } @@ -251,6 +259,71 @@ impl AsnDefWalker { ); } + fn write_choice_constraint(&self, scope: &mut Scope, name: &str, choice: &DataEnum) { + let mut imp = Impl::new(name); + imp.impl_trait(format!("{}choice::Constraint", CRATE_SYN_PREFIX)); + + imp.new_fn("to_choice_index") + .arg_ref_self() + .ret("usize") + .push_block({ + let mut match_block = Block::new("match self"); + for (index, (variant, _type)) in choice.variants().enumerate() { + match_block.line(format!("Self::{}(_) => {},", variant, index)); + } + match_block + }); + + imp.new_fn("write_content") + .generic(&format!("W: {}Writer", CRATE_SYN_PREFIX)) + .arg_ref_self() + .arg("writer", "&mut W") + .ret("Result<(), W::Error>") + .push_block({ + let mut match_block = Block::new("match self"); + for (variant, _type) in choice.variants() { + let combined = Self::combined_field_type_name(name, variant); + match_block.line(format!( + "Self::{}(c) => AsnDef{}::write_value(writer, c),", + variant, combined + )); + } + match_block + }); + + imp.new_fn("read_content") + .generic(&format!("R: {}Reader", CRATE_SYN_PREFIX)) + .arg("index", "usize") + .arg("reader", "&mut R") + .ret("Result, R::Error>") + .push_block({ + let mut match_block = Block::new("match index"); + for (index, (variant, _type)) in choice.variants().enumerate() { + let combined = Self::combined_field_type_name(name, variant); + match_block.line(format!( + "{} => Ok(Some(Self::{}(AsnDef{}::read_value(reader)?))),", + index, variant, combined + )); + } + match_block.line("_ => Ok(None),"); + match_block + }); + + Self::insert_consts( + scope, + imp, + &[ + format!("const NAME: &'static str = \"{}\";", name), + format!("const VARIANT_COUNT: usize = {};", choice.len()), + format!( + "const STD_VARIANT_COUNT: usize = {};", + choice.last_standard_index().unwrap_or_else(|| choice.len()) + ), + format!("const EXTENSIBLE: bool = {};", choice.is_extensible()), + ], + ); + } + fn write_integer_constraint_type( scope: &mut Scope, name: &str, diff --git a/asn1rs-model/src/model/mod.rs b/asn1rs-model/src/model/mod.rs index 806eef41..e20ac1a2 100644 --- a/asn1rs-model/src/model/mod.rs +++ b/asn1rs-model/src/model/mod.rs @@ -695,7 +695,6 @@ pub struct Choice { } impl Choice { - #[cfg(test)] pub fn from_variants(variants: Vec) -> Self { Self { variants, @@ -774,9 +773,9 @@ impl TryFrom<&mut Peekable>> for Choice { #[derive(Debug, Clone, PartialOrd, PartialEq)] pub struct ChoiceVariant { - pub(crate) name: String, - pub(crate) tag: Option, - pub(crate) r#type: Type, + pub name: String, + pub tag: Option, + pub r#type: Type, } impl ChoiceVariant { diff --git a/src/io/uper.rs b/src/io/uper.rs index 81cc7dee..0c1166b4 100644 --- a/src/io/uper.rs +++ b/src/io/uper.rs @@ -96,6 +96,7 @@ pub trait Reader { } } + /// Range is inclusive fn read_int(&mut self, range: (i64, i64)) -> Result { let (lower, upper) = range; let leading_zeros = ((upper - lower) as u64).leading_zeros(); @@ -233,6 +234,7 @@ pub trait Writer { } } + /// Range is inclusive fn write_int(&mut self, value: i64, range: (i64, i64)) -> Result<(), Error> { let (lower, upper) = range; let value = { diff --git a/src/syn/choice.rs b/src/syn/choice.rs new file mode 100644 index 00000000..33590cf8 --- /dev/null +++ b/src/syn/choice.rs @@ -0,0 +1,44 @@ +use crate::syn::{ReadableType, Reader, WritableType, Writer}; +use core::marker::PhantomData; + +pub struct Choice(PhantomData); + +impl Default for Choice { + fn default() -> Self { + Self(Default::default()) + } +} + +pub trait Constraint: Sized { + const NAME: &'static str; + const VARIANT_COUNT: usize; + const STD_VARIANT_COUNT: usize; + const EXTENSIBLE: bool = false; + + fn to_choice_index(&self) -> usize; + + fn write_content(&self, writer: &mut W) -> Result<(), W::Error>; + + fn read_content(index: usize, reader: &mut R) -> Result, R::Error>; +} + +impl WritableType for Choice { + type Type = C; + + #[inline] + fn write_value( + writer: &mut W, + value: &Self::Type, + ) -> Result<(), ::Error> { + writer.write_choice(value) + } +} + +impl ReadableType for Choice { + type Type = C; + + #[inline] + fn read_value(reader: &mut R) -> Result::Error> { + reader.read_choice::() + } +} diff --git a/src/syn/io/println.rs b/src/syn/io/println.rs index c7396591..5687d0cc 100644 --- a/src/syn/io/println.rs +++ b/src/syn/io/println.rs @@ -50,6 +50,26 @@ impl Writer for PrintlnWriter { }) } + fn write_choice(&mut self, choice: &C) -> Result<(), Self::Error> { + self.indented_println(&format!("Write choice {}", C::NAME)); + self.with_increased_indentation(|w| { + if C::EXTENSIBLE { + w.indented_println("extensible"); + } else { + w.indented_println("normal"); + } + w.with_increased_indentation(|w| { + w.indented_println(&format!( + "choice_index {}/{}/{}", + choice.to_choice_index(), + C::STD_VARIANT_COUNT, + C::VARIANT_COUNT + )); + choice.write_content(w) + }) + }) + } + fn write_opt(&mut self, value: Option<&T::Type>) -> Result<(), Self::Error> { self.indented_println("Writing OPTIONAL"); self.with_increased_indentation(|w| { diff --git a/src/syn/io/uper.rs b/src/syn/io/uper.rs index 1675b90a..0f2805f5 100644 --- a/src/syn/io/uper.rs +++ b/src/syn/io/uper.rs @@ -68,11 +68,26 @@ impl Writer for UperWriter { } else { self.buffer.write_int( enumerated.to_choice_index() as i64, - (0, C::STD_VARIANT_COUNT as i64), + (0, C::STD_VARIANT_COUNT as i64 - 1), ) } } + fn write_choice(&mut self, choice: &C) -> Result<(), Self::Error> { + if C::EXTENSIBLE { + self.buffer.write_choice_index_extensible( + choice.to_choice_index() as u64, + C::STD_VARIANT_COUNT as u64, + )?; + } else { + self.buffer.write_int( + choice.to_choice_index() as i64, + (0, C::STD_VARIANT_COUNT as i64 - 1), + )?; + } + choice.write_content(self) + } + fn write_opt( &mut self, value: Option<&::Type>, @@ -159,7 +174,7 @@ impl Reader for UperReader { .read_choice_index_extensible(C::STD_VARIANT_COUNT as u64) .map(|v| v as usize) } else { - self.read_int((0, C::STD_VARIANT_COUNT as i64)) + self.read_int((0, C::STD_VARIANT_COUNT as i64 - 1)) .map(|v| v as usize) } .and_then(|index| { @@ -168,6 +183,21 @@ impl Reader for UperReader { }) } + fn read_choice(&mut self) -> Result { + if C::EXTENSIBLE { + self.buffer + .read_choice_index_extensible(C::STD_VARIANT_COUNT as u64) + .map(|v| v as usize) + } else { + self.read_int((0, C::STD_VARIANT_COUNT as i64 - 1)) + .map(|v| v as usize) + } + .and_then(|index| { + C::read_content(index, self)? + .ok_or_else(|| UperError::InvalidChoiceIndex(index, C::VARIANT_COUNT)) + }) + } + fn read_opt( &mut self, ) -> Result::Type>, Self::Error> { diff --git a/src/syn/mod.rs b/src/syn/mod.rs index 93d7bb14..f0bad910 100644 --- a/src/syn/mod.rs +++ b/src/syn/mod.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; +pub mod choice; pub mod complex; pub mod enumerated; pub mod io; @@ -8,6 +9,7 @@ pub mod optional; pub mod sequence; pub mod utf8string; +pub use choice::Choice; pub use complex::Complex; pub use enumerated::Enumerated; pub use numbers::Integer; @@ -35,6 +37,8 @@ pub trait Reader { fn read_enumerated(&mut self) -> Result; + fn read_choice(&mut self) -> Result; + fn read_opt(&mut self) -> Result, Self::Error>; fn read_int(&mut self, range: (i64, i64)) -> Result; @@ -86,6 +90,8 @@ pub trait Writer { enumerated: &C, ) -> Result<(), Self::Error>; + fn write_choice(&mut self, choice: &C) -> Result<(), Self::Error>; + fn write_opt(&mut self, value: Option<&T::Type>) -> Result<(), Self::Error>; fn write_int(&mut self, value: i64, range: (i64, i64)) -> Result<(), Self::Error>; diff --git a/tests/basic_proc_macro_attribute.rs b/tests/basic_proc_macro_attribute.rs index cb4f426f..7b822d76 100644 --- a/tests/basic_proc_macro_attribute.rs +++ b/tests/basic_proc_macro_attribute.rs @@ -1,6 +1,7 @@ #![allow(unused)] use asn1rs::syn::io::{UperReader, UperWriter}; +use asn1rs::syn::Reader; use asn1rs_macros::asn; #[asn(sequence)] @@ -151,3 +152,82 @@ fn pizza_test_uper_3() { assert_eq!(pizza, uper.read::().unwrap()); assert_eq!(0, uper.bits_remaining()); } + +#[asn(choice)] +#[derive(Debug, PartialOrd, PartialEq)] +pub enum WhatToEat { + #[asn(complex)] + Potato(Potato), + #[asn(complex)] + Pizza(Pizza), +} + +#[test] +fn what_to_eat_test_uper_1() { + let mut uper = UperWriter::default(); + let what = WhatToEat::Pizza(Pizza { + size: 3, + topping: Topping::EvenLessPineapple, + }); + uper.write(&what).unwrap(); + // https://asn1.io/asn1playground/ + assert_eq!(&[0xC8], uper.byte_content()); + assert_eq!(5, uper.bit_len()); + let mut uper = uper.into_reader(); + assert_eq!(what, uper.read::().unwrap()); + assert_eq!(0, uper.bits_remaining()); +} + +#[test] +fn what_to_eat_test_uper_2() { + let mut uper = UperWriter::default(); + let what = WhatToEat::Potato(Potato { + size: 13, + size2: 37, + size3: 42, + string: "such tasty potato".to_string(), + }); + uper.write(&what).unwrap(); + // https://asn1.io/asn1playground/ + assert_eq!( + &[ + 0x00, 0x86, 0x80, 0x92, 0x9E, 0x11, 0x73, 0x75, 0x63, 0x68, 0x20, 0x74, 0x61, 0x73, + 0x74, 0x79, 0x20, 0x70, 0x6F, 0x74, 0x61, 0x74, 0x6F + ], + uper.byte_content() + ); + assert_eq!(23 * 8, uper.bit_len()); + let mut uper = uper.into_reader(); + assert_eq!(what, uper.read::().unwrap()); + assert_eq!(0, uper.bits_remaining()); +} + +/* +BasicSchema DEFINITIONS AUTOMATIC TAGS ::= +BEGIN + Potato ::= SEQUENCE { + size INTEGER, + size2 INTEGER, + size3 INTEGER(12..128), + string Utf8String + } + + Topping ::= ENUMERATED + { + not_pineapple, + even_less_pineapple, + no_pineapple_at_all + } + + Pizza ::= SEQUENCE { + size INTEGER(1..4), + topping Topping + } + + WhatToEat ::= CHOICE { + potato Potato, + pizza Pizza + } +END + +*/