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

Issue:
Implement serialization



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 496cfa5..5b7100d 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,17 @@ match result {
 }
 ```
 
+And to write that structure, you would do:
+
+```rust
+let result = asn1::write(|w| {
+	w.write_element_with_type::<asn1::Sequence>(&|w: &mut asn1::Writer| {
+		w.write_element(r);
+		w.write_element(s);
+	})
+});
+```
+
 [travis-image]: https://travis-ci.org/alex/rust-asn1.svg?branch=master
 [travis-link]: https://travis-ci.org/alex/rust-asn1
 [deps-rs-image]: https://deps.rs/repo/github/alex/rust-asn1/status.svg
diff --git a/examples/no_std.rs b/examples/no_std.rs
index 7bada4a..c68fcd5 100644
--- a/examples/no_std.rs
+++ b/examples/no_std.rs
@@ -13,4 +13,18 @@ fn main() {
         Ok((r, s)) => unsafe { libc::printf(b"r=%ld, s=%ld\n\x00".as_ptr() as *const i8, r, s) },
         Err(_) => unsafe { libc::printf("Error\n\x00".as_ptr() as *const i8) },
     };
+
+    let computed = asn1::write(|w| {
+        w.write_element_with_type::<asn1::Sequence>(&|w: &mut asn1::Writer| {
+            w.write_element(1i64);
+            w.write_element(3i64);
+        });
+    });
+    unsafe {
+        libc::printf(
+            "Original length: %ld\nComputed length: %ld\n\x00".as_ptr() as *const i8,
+            data.len() as i64,
+            computed.len() as i64,
+        );
+    }
 }
diff --git a/src/bit_string.rs b/src/bit_string.rs
index 213842c..5d18fa2 100644
--- a/src/bit_string.rs
+++ b/src/bit_string.rs
@@ -1,5 +1,5 @@
 /// Represents an ASN.1 `BIT STRING`.
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
 pub struct BitString<'a> {
     data: &'a [u8],
     padding_bits: u8,
diff --git a/src/lib.rs b/src/lib.rs
index 70a3339..4c777ce 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -42,6 +42,7 @@ mod bit_string;
 mod object_identitifer;
 mod parser;
 mod types;
+mod writer;
 
 pub use crate::bit_string::BitString;
 pub use crate::object_identitifer::ObjectIdentifier;
@@ -49,3 +50,4 @@ pub use crate::parser::{parse, ParseError, ParseResult};
 pub use crate::types::{
     Choice1, Choice2, Choice3, Explicit, Implicit, PrintableString, Sequence, UtcTime,
 };
+pub use crate::writer::{write, Writer};
diff --git a/src/object_identitifer.rs b/src/object_identitifer.rs
index 807b34f..ed59552 100644
--- a/src/object_identitifer.rs
+++ b/src/object_identitifer.rs
@@ -7,10 +7,10 @@ use alloc::vec::Vec;
 /// recommended practice for handling them is to create some `ObjectIdentifier` constants with
 /// `ObjectIdentifier::from_string` and then compare ObjectIdentifiers you get from parsing to
 /// those.
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Clone)]
 pub struct ObjectIdentifier<'a> {
     // Store the OID as DER encoded. This means we can 0-copy on parse.
-    der_encoded: Cow<'a, [u8]>,
+    pub(crate) der_encoded: Cow<'a, [u8]>,
 }
 
 fn _read_base128_int<I: Iterator<Item = u8>>(mut reader: I) -> Option<u32> {
diff --git a/src/types.rs b/src/types.rs
index 44d903b..60b10ab 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,9 +1,11 @@
+use core::convert::TryInto;
 use core::marker::PhantomData;
 use core::mem;
 
 use chrono::{Datelike, TimeZone, Timelike};
 
 use crate::parser::Parser;
+use crate::writer::Writer;
 use crate::{parse, BitString, ObjectIdentifier, ParseError, ParseResult};
 
 const CONTEXT_SPECIFIC: u8 = 0x80;
@@ -18,7 +20,10 @@ pub trait Asn1Element<'a>: Sized {
 pub trait SimpleAsn1Element<'a>: Sized {
     const TAG: u8;
     type ParsedType;
+    type WriteType;
+
     fn parse_data(data: &'a [u8]) -> ParseResult<Self::ParsedType>;
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType);
 }
 
 impl<'a, T: SimpleAsn1Element<'a>> Asn1Element<'a> for T {
@@ -36,17 +41,21 @@ impl<'a, T: SimpleAsn1Element<'a>> Asn1Element<'a> for T {
 impl SimpleAsn1Element<'_> for () {
     const TAG: u8 = 0x05;
     type ParsedType = ();
+    type WriteType = ();
     fn parse_data(data: &[u8]) -> ParseResult<()> {
         match data {
             b"" => Ok(()),
             _ => Err(ParseError::InvalidValue),
         }
     }
+
+    fn write_data(_dest: &mut Vec<u8>, _val: ()) {}
 }
 
 impl SimpleAsn1Element<'_> for bool {
     const TAG: u8 = 0x1;
     type ParsedType = bool;
+    type WriteType = bool;
     fn parse_data(data: &[u8]) -> ParseResult<bool> {
         match data {
             b"\x00" => Ok(false),
@@ -54,29 +63,51 @@ impl SimpleAsn1Element<'_> for bool {
             _ => Err(ParseError::InvalidValue),
         }
     }
+
+    fn write_data(dest: &mut Vec<u8>, val: bool) {
+        if val {
+            dest.push(0xff);
+        } else {
+            dest.push(0x00);
+        }
+    }
 }
 
 impl<'a> SimpleAsn1Element<'a> for &'a [u8] {
     const TAG: u8 = 0x04;
     type ParsedType = &'a [u8];
+    type WriteType = &'a [u8];
     fn parse_data(data: &'a [u8]) -> ParseResult<&'a [u8]> {
         Ok(data)
     }
+
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        dest.extend_from_slice(val);
+    }
 }
 
-/// Placeholder type for use with `Parser.read_element` for parsing an ASN.1 `PrintableString`.
-/// Parsing a `PrintableString` will return an `&str` containing only valid characers.
-pub enum PrintableString {}
+/// Type for use with `Parser.read_element` and `Writer.write_element` for
+/// handling ASN.1 `PrintableString`.  Parsing a `PrintableString` will return
+/// an `&str` containing only valid characers.
+#[derive(Clone)]
+pub struct PrintableString<'a>(&'a str);
 
-impl<'a> SimpleAsn1Element<'a> for PrintableString {
-    const TAG: u8 = 0x13;
-    type ParsedType = &'a str;
-    fn parse_data(data: &'a [u8]) -> ParseResult<&'a str> {
+impl<'a> PrintableString<'a> {
+    pub fn new(s: &'a str) -> Option<PrintableString<'a>> {
+        if PrintableString::verify(s.as_bytes()) {
+            Some(PrintableString(s))
+        } else {
+            None
+        }
+    }
+
+    fn verify(data: &[u8]) -> bool {
         for b in data {
             match b {
                 b'A'..=b'Z'
                 | b'a'..=b'z'
                 | b'0'..=b'9'
+                | b' '
                 | b'\''
                 | b'('
                 | b')'
@@ -88,14 +119,29 @@ impl<'a> SimpleAsn1Element<'a> for PrintableString {
                 | b':'
                 | b'='
                 | b'?' => {}
-                _ => return Err(ParseError::InvalidValue),
+                _ => return false,
             };
         }
+        true
+    }
+}
+
+impl<'a> SimpleAsn1Element<'a> for PrintableString<'a> {
+    const TAG: u8 = 0x13;
+    type ParsedType = &'a str;
+    type WriteType = PrintableString<'a>;
+    fn parse_data(data: &'a [u8]) -> ParseResult<&'a str> {
+        if !PrintableString::verify(data) {
+            return Err(ParseError::InvalidValue);
+        }
         // TODO: This value is always valid utf-8 because we just verified the contents, but I
         // don't want to call an unsafe function, so we end up validating it twice. If your profile
         // says this is slow, now you know why.
         Ok(core::str::from_utf8(data).unwrap())
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        dest.extend_from_slice(val.0.as_bytes());
+    }
 }
 
 macro_rules! impl_asn1_element_for_int {
@@ -103,6 +149,7 @@ macro_rules! impl_asn1_element_for_int {
         impl SimpleAsn1Element<'_> for $t {
             const TAG: u8 = 0x02;
             type ParsedType = Self;
+            type WriteType = Self;
             fn parse_data(mut data: &[u8]) -> ParseResult<Self::ParsedType> {
                 if data.is_empty() {
                     return Err(ParseError::InvalidValue);
@@ -136,6 +183,19 @@ macro_rules! impl_asn1_element_for_int {
                 ret >>= (8 * mem::size_of::<Self>()) - data.len() * 8;
                 Ok(ret)
             }
+            fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+                let mut num_bytes = 1;
+                let mut v: $t = val;
+                #[allow(unused_comparisons)]
+                while v > 127 || ($signed && v < (-128i64) as $t) {
+                    num_bytes += 1;
+                    v = v.checked_shr(8).unwrap_or(0);
+                }
+
+                for i in (1..num_bytes + 1).rev() {
+                    dest.push((val >> ((i - 1) * 8)) as u8);
+                }
+            }
         }
     };
 }
@@ -148,20 +208,29 @@ impl_asn1_element_for_int!(u64; false);
 impl<'a> SimpleAsn1Element<'a> for ObjectIdentifier<'a> {
     const TAG: u8 = 0x06;
     type ParsedType = ObjectIdentifier<'a>;
+    type WriteType = ObjectIdentifier<'a>;
     fn parse_data(data: &'a [u8]) -> ParseResult<ObjectIdentifier<'a>> {
         ObjectIdentifier::from_der(data).ok_or(ParseError::InvalidValue)
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        dest.extend_from_slice(&val.der_encoded);
+    }
 }
 
 impl<'a> SimpleAsn1Element<'a> for BitString<'a> {
     const TAG: u8 = 0x03;
     type ParsedType = BitString<'a>;
+    type WriteType = BitString<'a>;
     fn parse_data(data: &'a [u8]) -> ParseResult<BitString<'a>> {
         if data.is_empty() {
             return Err(ParseError::InvalidValue);
         }
         BitString::new(&data[1..], data[0]).ok_or(ParseError::InvalidValue)
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        dest.push(val.padding_bits());
+        dest.extend_from_slice(val.as_bytes());
+    }
 }
 
 /// Placeholder type for use with `Parser.read_element` for parsing an ASN.1 `UTCTime`. Parsing a
@@ -174,9 +243,15 @@ const UTCTIME_WITH_SECONDS: &str = "%y%m%d%H%M%SZ";
 const UTCTIME_WITH_OFFSET: &str = "%y%m%d%H%M%z";
 const UTCTIME: &str = "%y%m%d%H%MZ";
 
+fn push_two_digits(dest: &mut Vec<u8>, val: u8) {
+    dest.push(b'0' + ((val / 10) % 10));
+    dest.push(b'0' + (val % 10));
+}
+
 impl SimpleAsn1Element<'_> for UtcTime {
     const TAG: u8 = 0x17;
     type ParsedType = chrono::DateTime<chrono::Utc>;
+    type WriteType = chrono::DateTime<chrono::Utc>;
     fn parse_data(data: &[u8]) -> ParseResult<Self::ParsedType> {
         let data = std::str::from_utf8(data).map_err(|_| ParseError::InvalidValue)?;
 
@@ -215,6 +290,24 @@ impl SimpleAsn1Element<'_> for UtcTime {
             None => Err(ParseError::InvalidValue),
         }
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        let year = if 1950 <= val.year() && val.year() < 2000 {
+            val.year() - 1900
+        } else if 2000 <= val.year() && val.year() < 2050 {
+            val.year() - 2000
+        } else {
+            panic!("Can't write a datetime with a year outside [1950, 2050) as a UTCTIME");
+        };
+        push_two_digits(dest, year.try_into().unwrap());
+        push_two_digits(dest, val.month().try_into().unwrap());
+        push_two_digits(dest, val.day().try_into().unwrap());
+
+        push_two_digits(dest, val.hour().try_into().unwrap());
+        push_two_digits(dest, val.minute().try_into().unwrap());
+        push_two_digits(dest, val.second().try_into().unwrap());
+
+        dest.push(b'Z');
+    }
 }
 
 impl<'a, T: SimpleAsn1Element<'a>> Asn1Element<'a> for Option<T> {
@@ -297,9 +390,14 @@ impl<'a> Sequence<'a> {
 impl<'a> SimpleAsn1Element<'a> for Sequence<'a> {
     const TAG: u8 = 0x10 | CONSTRUCTED;
     type ParsedType = Sequence<'a>;
+    type WriteType = &'a dyn Fn(&mut Writer);
     fn parse_data(data: &'a [u8]) -> ParseResult<Sequence<'a>> {
         Ok(Sequence::new(data))
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        let mut w = Writer::new_with_storage(dest);
+        val(&mut w);
+    }
 }
 
 /// `Implicit` is a type which wraps another ASN.1 type, indicating that the tag is an ASN.1
@@ -314,9 +412,13 @@ impl<'a, T: SimpleAsn1Element<'a>, const TAG: u8> SimpleAsn1Element<'a>
 {
     const TAG: u8 = CONTEXT_SPECIFIC | TAG | (T::TAG & CONSTRUCTED);
     type ParsedType = T::ParsedType;
+    type WriteType = T::WriteType;
     fn parse_data(data: &'a [u8]) -> ParseResult<T::ParsedType> {
         T::parse_data(data)
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        T::write_data(dest, val);
+    }
 }
 
 /// `Explicit` is a type which wraps another ASN.1 type, indicating that the tag is an ASN.1
@@ -326,10 +428,30 @@ pub struct Explicit<'a, T: Asn1Element<'a>, const TAG: u8> {
     _lifetime: PhantomData<&'a ()>,
 }
 
-impl<'a, T: Asn1Element<'a>, const TAG: u8> SimpleAsn1Element<'a> for Explicit<'a, T, { TAG }> {
+impl<'a, T: SimpleAsn1Element<'a>, const TAG: u8> SimpleAsn1Element<'a>
+    for Explicit<'a, T, { TAG }>
+{
     const TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | TAG;
     type ParsedType = T::ParsedType;
+    type WriteType = T::WriteType;
     fn parse_data(data: &'a [u8]) -> ParseResult<T::ParsedType> {
         parse(data, |p| p.read_element::<T>())
     }
+    fn write_data(dest: &mut Vec<u8>, val: Self::WriteType) {
+        Writer::new_with_storage(dest).write_element_with_type::<T>(val);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::PrintableString;
+
+    #[test]
+    fn test_printable_string_new() {
+        assert!(PrintableString::new("abc").is_some());
+        assert!(PrintableString::new("").is_some());
+        assert!(PrintableString::new(" ").is_some());
+        assert!(PrintableString::new("%").is_none());
+        assert!(PrintableString::new("\x00").is_none());
+    }
 }
diff --git a/src/writer.rs b/src/writer.rs
new file mode 100644
index 0000000..00eca26
--- /dev/null
+++ b/src/writer.rs
@@ -0,0 +1,261 @@
+fn _length_length(length: usize) -> u8 {
+    let mut i = length;
+    let mut num_bytes = 1;
+    while i > 255 {
+        num_bytes += 1;
+        i >>= 8;
+    }
+    num_bytes
+}
+fn _insert_at_position(vec: &mut Vec<u8>, pos: usize, data: &[u8]) {
+    for _ in 0..data.len() {
+        vec.push(0);
+    }
+    let src_range = pos..vec.len() - data.len();
+    vec.copy_within(src_range, pos + data.len());
+    vec[pos..pos + data.len()].copy_from_slice(data);
+}
+
+enum Storage<'a> {
+    Owned(Vec<u8>),
+    Borrowed(&'a mut Vec<u8>),
+}
+
+impl Storage<'_> {
+    fn as_mut_ref(&mut self) -> &mut Vec<u8> {
+        match self {
+            Storage::Owned(ref mut v) => v,
+            Storage::Borrowed(v) => v,
+        }
+    }
+}
+
+pub struct Writer<'a> {
+    data: Storage<'a>,
+}
+
+impl Writer<'_> {
+    fn new() -> Writer<'static> {
+        Writer {
+            data: Storage::Owned(vec![]),
+        }
+    }
+
+    pub(crate) fn new_with_storage<'a>(data: &'a mut Vec<u8>) -> Writer<'a> {
+        Writer {
+            data: Storage::Borrowed(data),
+        }
+    }
+
+    pub fn write_element<'a, T>(&mut self, val: T)
+    where
+        T: crate::types::SimpleAsn1Element<'a, WriteType = T>,
+    {
+        self.write_element_with_type::<T>(val);
+    }
+
+    pub fn write_element_with_type<'a, T>(&mut self, val: T::WriteType)
+    where
+        T: crate::types::SimpleAsn1Element<'a>,
+    {
+        self.data.as_mut_ref().push(T::TAG);
+        // Push a 0-byte placeholder for the length. Needing only a single byte
+        // for the element is probably the most common case.
+        self.data.as_mut_ref().push(0);
+        let start_len = self.data.as_mut_ref().len();
+        T::write_data(self.data.as_mut_ref(), val);
+        let added_len = self.data.as_mut_ref().len() - start_len;
+        if added_len >= 128 {
+            let n = _length_length(added_len);
+            self.data.as_mut_ref()[start_len - 1] = 0x80 | n;
+            let mut length_buf = [0u8; 8];
+            for (pos, i) in (1..n + 1).rev().enumerate() {
+                length_buf[pos] = (added_len >> ((i - 1) * 8)) as u8;
+            }
+            _insert_at_position(self.data.as_mut_ref(), start_len, &length_buf[..n as usize]);
+        } else {
+            self.data.as_mut_ref()[start_len - 1] = added_len as u8;
+        }
+    }
+
+    fn build(self) -> Vec<u8> {
+        match self.data {
+            Storage::Owned(v) => v,
+            Storage::Borrowed(_) => panic!("Can't call build with borrowed storage"),
+        }
+    }
+}
+
+pub fn write<F: Fn(&mut Writer)>(f: F) -> Vec<u8> {
+    let mut w = Writer::new();
+    f(&mut w);
+    w.build()
+}
+
+#[cfg(test)]
+mod tests {
+    use chrono::{TimeZone, Utc};
+
+    use super::{_insert_at_position, write, Writer};
+    use crate::types::SimpleAsn1Element;
+    use crate::{
+        BitString, Explicit, Implicit, ObjectIdentifier, PrintableString, Sequence, UtcTime,
+    };
+
+    fn assert_writes<'a, T>(data: &[(T::WriteType, &[u8])])
+    where
+        T: SimpleAsn1Element<'a>,
+        T::WriteType: Clone,
+    {
+        for (val, expected) in data {
+            let result = write(|w| {
+                w.write_element_with_type::<T>(val.clone());
+            });
+            assert_eq!(&result, expected);
+        }
+    }
+
+    #[test]
+    fn test_insert_at_position() {
+        let mut v = vec![1, 2, 3, 4];
+        _insert_at_position(&mut v, 2, &[5, 6]);
+        assert_eq!(&v, &[1, 2, 5, 6, 3, 4]);
+    }
+
+    #[test]
+    fn test_write_element() {
+        assert_eq!(write(|w| w.write_element(())), b"\x05\x00");
+    }
+
+    #[test]
+    fn test_write_null() {
+        assert_writes::<()>(&[((), b"\x05\x00")]);
+    }
+
+    #[test]
+    fn test_write_bool() {
+        assert_writes::<bool>(&[(false, b"\x01\x01\x00"), (true, b"\x01\x01\xff")]);
+    }
+
+    #[test]
+    fn test_write_octet_string() {
+        assert_writes::<&[u8]>(&[
+            (b"", b"\x04\x00"),
+            (b"\x01\x02\x03", b"\x04\x03\x01\x02\x03"),
+            (b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", b"\x04\x81\x81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+        ])
+    }
+
+    #[test]
+    fn test_write_printable_string() {
+        assert_writes::<PrintableString>(&[
+            (
+                PrintableString::new("Test User 1").unwrap(),
+                b"\x13\x0bTest User 1",
+            ),
+            (
+                PrintableString::new("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx").unwrap(),
+                b"\x13\x81\x80xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+            ),
+        ]);
+    }
+
+    #[test]
+    fn test_write_i64() {
+        assert_writes::<i64>(&[
+            (0, b"\x02\x01\x00"),
+            (127, b"\x02\x01\x7f"),
+            (128, b"\x02\x02\x00\x80"),
+            (255, b"\x02\x02\x00\xff"),
+            (256, b"\x02\x02\x01\x00"),
+            (-1, b"\x02\x01\xff"),
+            (-128, b"\x02\x01\x80"),
+            (-129, b"\x02\x02\xff\x7f"),
+        ]);
+    }
+
+    #[test]
+    fn test_write_i8() {
+        assert_writes::<i8>(&[
+            (0, b"\x02\x01\x00"),
+            (127, b"\x02\x01\x7f"),
+            (-1, b"\x02\x01\xff"),
+            (-128, b"\x02\x01\x80"),
+        ]);
+    }
+
+    #[test]
+    fn test_write_object_identifier() {
+        assert_writes::<ObjectIdentifier>(&[
+            (
+                ObjectIdentifier::from_string("1.2.840.113549").unwrap(),
+                b"\x06\x06\x2a\x86\x48\x86\xf7\x0d",
+            ),
+            (
+                ObjectIdentifier::from_string("1.2.3.4").unwrap(),
+                b"\x06\x03\x2a\x03\x04",
+            ),
+            (
+                ObjectIdentifier::from_string("1.2.840.133549.1.1.5").unwrap(),
+                b"\x06\x09\x2a\x86\x48\x88\x93\x2d\x01\x01\x05",
+            ),
+            (
+                ObjectIdentifier::from_string("2.100.3").unwrap(),
+                b"\x06\x03\x81\x34\x03",
+            ),
+        ]);
+    }
+
+    #[test]
+    fn test_write_bit_string() {
+        assert_writes::<BitString>(&[
+            (BitString::new(b"", 0).unwrap(), b"\x03\x01\x00"),
+            (BitString::new(b"\x80", 7).unwrap(), b"\x03\x02\x07\x80"),
+            (
+                BitString::new(b"\x81\xf0", 4).unwrap(),
+                b"\x03\x03\x04\x81\xf0",
+            ),
+        ]);
+    }
+
+    #[test]
+    fn test_write_utctime() {
+        assert_writes::<UtcTime>(&[
+            (
+                Utc.ymd(1991, 5, 6).and_hms(23, 45, 40),
+                b"\x17\x0d910506234540Z",
+            ),
+            (Utc.timestamp(0, 0), b"\x17\x0d700101000000Z"),
+            (Utc.timestamp(1258325776, 0), b"\x17\x0d091115225616Z"),
+        ]);
+    }
+
+    #[test]
+    fn test_write_sequence() {
+        assert_eq!(
+            write(|w| {
+                w.write_element_with_type::<Sequence>(&|w: &mut Writer| w.write_element(()))
+            }),
+            b"\x30\x02\x05\x00"
+        );
+        assert_eq!(
+            write(|w| {
+                w.write_element_with_type::<Sequence>(&|w: &mut Writer| w.write_element(true))
+            }),
+            b"\x30\x03\x01\x01\xff"
+        );
+    }
+
+    #[test]
+    fn test_write_implicit() {
+        assert_writes::<Implicit<bool, 2>>(&[(true, b"\x82\x01\xff"), (false, b"\x82\x01\x00")]);
+    }
+
+    #[test]
+    fn test_write_explicit() {
+        assert_writes::<Explicit<bool, 2>>(&[
+            (true, b"\xa2\x03\x01\x01\xff"),
+            (false, b"\xa2\x03\x01\x01\x00"),
+        ]);
+    }
+}


Test patch: