Repository: alex/rust-asn1 (language: rust)

Issue:
Figure out a plan for OPTIONAL DEFAULT
`OPTIONAL DEFAULT` in DER has the exciting properties that:

a) When the value is not present in a encoded DER, its treated as if it had the default value (obviously)
b) When the value to be written is the default value, then nothing should be written (surprising!)

Right now now you just get an `Option<T>` and its on you to implement this yourself.

We should come up with a story here.

Options:

1) Leave things as-is
2) Add it to the imperative API (exact API undecided)
3) Add it to the derive API (likely in the form of a `#[default(X)]` annotation)

(1) seems unfortunate, (2) seems challenging to find a good API, (3) seems pretty straightforward, but has the downside that it breaks the symmetry between the imperative and derive APIs.


Discussion / hints:

Fix patch:
diff --git a/asn1_derive/src/lib.rs b/asn1_derive/src/lib.rs
index d25519b..bc5ea52 100644
--- a/asn1_derive/src/lib.rs
+++ b/asn1_derive/src/lib.rs
@@ -4,7 +4,7 @@ use syn::punctuated::Punctuated;
 use syn::spanned::Spanned;
 use syn::token::Comma;
 
-#[proc_macro_derive(Asn1Read, attributes(explicit, implicit))]
+#[proc_macro_derive(Asn1Read, attributes(explicit, implicit, default))]
 pub fn derive_asn1_read(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = syn::parse_macro_input!(input as syn::DeriveInput);
 
@@ -25,7 +25,7 @@ pub fn derive_asn1_read(input: proc_macro::TokenStream) -> proc_macro::TokenStre
     proc_macro::TokenStream::from(expanded)
 }
 
-#[proc_macro_derive(Asn1Write, attributes(explicit, implicit))]
+#[proc_macro_derive(Asn1Write, attributes(explicit, implicit, default))]
 pub fn derive_asn1_write(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = syn::parse_macro_input!(input as syn::DeriveInput);
 
@@ -80,6 +80,7 @@ enum OpType {
 
 fn generate_read_element(f: &syn::Field) -> proc_macro2::TokenStream {
     let mut read_type = OpType::Regular;
+    let mut default = None;
     for attr in &f.attrs {
         if attr.path.is_ident("explicit") {
             if let OpType::Regular = read_type {
@@ -93,10 +94,15 @@ fn generate_read_element(f: &syn::Field) -> proc_macro2::TokenStream {
             } else {
                 panic!("Can't specify #[explicit] or #[implicit] more than once")
             }
+        } else if attr.path.is_ident("default") {
+            if default.is_some() {
+                panic!("Can't specify #[default] more than once");
+            }
+            default = Some(attr.parse_args::<syn::Lit>().unwrap());
         }
     }
 
-    match read_type {
+    let mut read_op = match read_type {
         OpType::Explicit(arg) => quote::quote! {
             p.read_optional_explicit_element(#arg)?
         },
@@ -106,7 +112,13 @@ fn generate_read_element(f: &syn::Field) -> proc_macro2::TokenStream {
         OpType::Regular => quote::quote! {
             p.read_element()?
         },
+    };
+    if let Some(default) = default {
+        read_op = quote::quote! {{
+            asn1::from_optional_default(#read_op, #default.into())?
+        }};
     }
+    read_op
 }
 
 fn generate_read_block(data: &syn::Data) -> proc_macro2::TokenStream {
@@ -151,9 +163,10 @@ fn generate_read_block(data: &syn::Data) -> proc_macro2::TokenStream {
 
 fn generate_write_element(
     f: &syn::Field,
-    field_read: proc_macro2::TokenStream,
+    mut field_read: proc_macro2::TokenStream,
 ) -> proc_macro2::TokenStream {
     let mut write_type = OpType::Regular;
+    let mut default = None;
     for attr in &f.attrs {
         if attr.path.is_ident("explicit") {
             if let OpType::Regular = write_type {
@@ -167,9 +180,20 @@ fn generate_write_element(
             } else {
                 panic!("Can't specify #[explicit] or #[implicit] more than once")
             }
+        } else if attr.path.is_ident("default") {
+            if default.is_some() {
+                panic!("Can't specify #[default] more than once");
+            }
+            default = Some(attr.parse_args::<syn::Lit>().unwrap());
         }
     }
 
+    if let Some(default) = default {
+        field_read = quote::quote! {&{
+            asn1::to_optional_default(#field_read, &(#default).into())
+        }}
+    }
+
     match write_type {
         OpType::Explicit(arg) => quote::quote_spanned! {f.span() =>
             w.write_optional_explicit_element(#field_read, #arg);
diff --git a/src/lib.rs b/src/lib.rs
index 5a89f0c..95a0d82 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -80,6 +80,10 @@
 //!     b: Option<u64>,
 //! }
 //! ```
+//!
+//! Fields can also be annotated with `#[default(VALUE)]` to indicate ASN.1
+//! `OPTIONAL DEFAULT` values. In this case, the field's type should be `T`,
+//! and not `Option<T>`.
 
 extern crate alloc;
 
@@ -103,3 +107,23 @@ pub use crate::writer::{write, write_single, Writer};
 
 #[cfg(feature = "derive")]
 pub use asn1_derive::{Asn1Read, Asn1Write};
+
+/// Decodes an `OPTIONAL` ASN.1 value which has a `DEFAULT`. Generaly called
+/// immediately after [`Parser::read_element`].
+pub fn from_optional_default<T: PartialEq>(v: Option<T>, default: T) -> ParseResult<T> {
+    match v {
+        Some(v) if v == default => Err(ParseError::EncodedDefault),
+        Some(v) => Ok(v),
+        None => Ok(default),
+    }
+}
+
+/// Prepares an `OPTIONAL` ASN.1 value which has a `DEFAULT` for writing.
+/// Generally called immediately before [`Writer::write_element`].
+pub fn to_optional_default<'a, T: PartialEq>(v: &'a T, default: &'a T) -> Option<&'a T> {
+    if v == default {
+        None
+    } else {
+        Some(v)
+    }
+}
diff --git a/src/parser.rs b/src/parser.rs
index c801f7d..67580a8 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -13,8 +13,10 @@ pub enum ParseError {
     IntegerOverflow,
     /// There was extraneous data in the input.
     ExtraData,
-    /// Elements of a set were not lexicographically sorted
+    /// Elements of a set were not lexicographically sorted.
     InvalidSetOrdering,
+    /// An OPTIONAL DEFAULT was written with a default value.
+    EncodedDefault,
 }
 
 /// The result of a `parse`. Either a successful value or a `ParseError`.
diff --git a/src/types.rs b/src/types.rs
index 6b421cc..8f0620d 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -59,6 +59,13 @@ impl<'a, T: SimpleAsn1Writable<'a>> Asn1Writable<'a> for T {
     }
 }
 
+impl<'a, T: SimpleAsn1Writable<'a>> SimpleAsn1Writable<'a> for &T {
+    const TAG: u8 = T::TAG;
+    fn write_data(&self, dest: &mut Vec<u8>) {
+        T::write_data(self, dest)
+    }
+}
+
 /// A TLV (type, length, value) represented as the tag and bytes content.
 /// Generally used for parsing ASN.1 `ANY` values.
 #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
@@ -895,6 +902,13 @@ impl<'a, T, const TAG: u8> Implicit<'a, T, { TAG }> {
     }
 }
 
+#[cfg(feature = "const-generics")]
+impl<'a, T, const TAG: u8> From<T> for Implicit<'a, T, { TAG }> {
+    fn from(v: T) -> Self {
+        Implicit::new(v)
+    }
+}
+
 #[cfg(feature = "const-generics")]
 impl<'a, T: SimpleAsn1Readable<'a>, const TAG: u8> SimpleAsn1Readable<'a>
     for Implicit<'a, T, { TAG }>
@@ -943,6 +957,13 @@ impl<'a, T, const TAG: u8> Explicit<'a, T, { TAG }> {
     }
 }
 
+#[cfg(feature = "const-generics")]
+impl<'a, T, const TAG: u8> From<T> for Explicit<'a, T, { TAG }> {
+    fn from(v: T) -> Self {
+        Explicit::new(v)
+    }
+}
+
 #[cfg(feature = "const-generics")]
 impl<'a, T: Asn1Readable<'a>, const TAG: u8> SimpleAsn1Readable<'a> for Explicit<'a, T, { TAG }> {
     const TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | TAG;


Test patch:
diff --git a/tests/derive_test.rs b/tests/derive_test.rs
index e08a630..9f2238c 100644
--- a/tests/derive_test.rs
+++ b/tests/derive_test.rs
@@ -151,3 +151,126 @@ fn test_implicit() {
         (Ok(ImplicitFields { a: None, b: None }), b"\x30\x00"),
     ]);
 }
+
+#[test]
+fn test_default() {
+    #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Debug)]
+    struct DefaultFields {
+        #[default(13)]
+        a: u8,
+        #[default(15)]
+        #[explicit(1)]
+        b: u8,
+        #[default(17)]
+        #[implicit(5)]
+        c: u8,
+    }
+
+    assert_roundtrips(&[
+        (
+            Ok(DefaultFields {
+                a: 13,
+                b: 15,
+                c: 17,
+            }),
+            b"\x30\x00",
+        ),
+        (
+            Ok(DefaultFields { a: 3, b: 15, c: 17 }),
+            b"\x30\x03\x02\x01\x03",
+        ),
+        (
+            Ok(DefaultFields { a: 13, b: 5, c: 17 }),
+            b"\x30\x05\xa1\x03\x02\x01\x05",
+        ),
+        (
+            Ok(DefaultFields { a: 13, b: 15, c: 7 }),
+            b"\x30\x03\x85\x01\x07",
+        ),
+        (
+            Ok(DefaultFields { a: 3, b: 5, c: 7 }),
+            b"\x30\x0b\x02\x01\x03\xa1\x03\x02\x01\x05\x85\x01\x07",
+        ),
+        (
+            Err(asn1::ParseError::EncodedDefault),
+            b"\x30\x03\x02\x01\x0d",
+        ),
+        (
+            Err(asn1::ParseError::EncodedDefault),
+            b"\x30\x05\xa1\x03\x02\x01\x0f",
+        ),
+        (
+            Err(asn1::ParseError::EncodedDefault),
+            b"\x30\x03\x85\x01\x11",
+        ),
+    ]);
+}
+
+#[test]
+#[cfg(feature = "const-generics")]
+fn test_default_const_generics() {
+    #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Debug)]
+    struct DefaultFields<'a> {
+        #[default(15)]
+        a: asn1::Explicit<'a, u8, 1>,
+        #[default(17)]
+        b: asn1::Implicit<'a, u8, 5>,
+    }
+
+    assert_roundtrips(&[
+        (
+            Ok(DefaultFields {
+                a: asn1::Explicit::new(15),
+                b: asn1::Implicit::new(17),
+            }),
+            b"\x30\x00",
+        ),
+        (
+            Ok(DefaultFields {
+                a: asn1::Explicit::new(5),
+                b: asn1::Implicit::new(17),
+            }),
+            b"\x30\x05\xa1\x03\x02\x01\x05",
+        ),
+        (
+            Ok(DefaultFields {
+                a: asn1::Explicit::new(15),
+                b: asn1::Implicit::new(7),
+            }),
+            b"\x30\x03\x85\x01\x07",
+        ),
+        (
+            Ok(DefaultFields {
+                a: asn1::Explicit::new(5),
+                b: asn1::Implicit::new(7),
+            }),
+            b"\x30\x08\xa1\x03\x02\x01\x05\x85\x01\x07",
+        ),
+        (
+            Err(asn1::ParseError::EncodedDefault),
+            b"\x30\x05\xa1\x03\x02\x01\x0f",
+        ),
+        (
+            Err(asn1::ParseError::EncodedDefault),
+            b"\x30\x03\x85\x01\x11",
+        ),
+    ]);
+}
+
+#[test]
+fn test_default_bool() {
+    #[derive(asn1::Asn1Read, asn1::Asn1Write, PartialEq, Debug)]
+    struct DefaultField {
+        #[default(false)]
+        a: bool,
+    }
+
+    assert_roundtrips(&[
+        (Ok(DefaultField { a: true }), b"\x30\x03\x01\x01\xff"),
+        (Ok(DefaultField { a: false }), b"\x30\x00"),
+        (
+            Err(asn1::ParseError::EncodedDefault),
+            b"\x30\x03\x01\x01\x00",
+        ),
+    ])
+}