Serialization macro for TLV streams
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 24 Jun 2022 21:27:42 +0000 (16:27 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 4 Nov 2022 20:09:19 +0000 (15:09 -0500)
BOLT 12's offer message is encoded as a TLV stream (i.e., a sequence of
TLV records). impl_writeable_tlv_based can't be used because it writes
the overall length of the struct, whereas TLV streams only include the
length of each TLV record. Add a `tlv_stream` macro for defining structs
used in encoding.

TLV records containing a single variable-length type should not encode
the types length in the value since it is redundant. Add a wrapper type
that can be used within a TLV stream to support the correct behavior
during serialization and de-serialization.

lightning/src/util/ser.rs
lightning/src/util/ser_macros.rs

index b9722325eed04dfab630487da04e0f6b0877b2f3..37fade88835a4089faea83deeb7ff7623e8ca6d0 100644 (file)
@@ -419,6 +419,9 @@ macro_rules! impl_writeable_primitive {
                                }
                        }
                }
+               impl From<$val_type> for HighZeroBytesDroppedBigSize<$val_type> {
+                       fn from(val: $val_type) -> Self { Self(val) }
+               }
        }
 }
 
@@ -518,6 +521,23 @@ impl Readable for [u16; 8] {
 /// Used to prevent encoding the length twice.
 pub(crate) struct WithoutLength<T>(pub T);
 
+impl Writeable for WithoutLength<&String> {
+       #[inline]
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               w.write_all(self.0.as_bytes())
+       }
+}
+impl Readable for WithoutLength<String> {
+       #[inline]
+       fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
+               let v: WithoutLength<Vec<u8>> = Readable::read(r)?;
+               Ok(Self(String::from_utf8(v.0).map_err(|_| DecodeError::InvalidValue)?))
+       }
+}
+impl<'a> From<&'a String> for WithoutLength<&'a String> {
+       fn from(s: &'a String) -> Self { Self(s) }
+}
+
 impl<'a, T: Writeable> Writeable for WithoutLength<&'a Vec<T>> {
        #[inline]
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
index 019bea3ee935d00d898973f5a619583f7a12584f..3ec0848680ffe62a2532e03d47d1fc5b62d7f4b8 100644 (file)
@@ -26,6 +26,12 @@ macro_rules! encode_tlv {
                        field.write($stream)?;
                }
        };
+       ($stream: expr, $type: expr, $field: expr, (option, encoding: ($fieldty: ty, $encoding: ident))) => {
+               encode_tlv!($stream, $type, $field.map(|f| $encoding(f)), option);
+       };
+       ($stream: expr, $type: expr, $field: expr, (option, encoding: $fieldty: ty)) => {
+               encode_tlv!($stream, $type, $field, option);
+       };
 }
 
 macro_rules! encode_tlv_stream {
@@ -121,6 +127,9 @@ macro_rules! check_tlv_order {
        ($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (option: $trait: ident $(, $read_arg: expr)?)) => {{
                // no-op
        }};
+       ($last_seen_type: expr, $typ: expr, $type: expr, $field: ident, (option, encoding: $encoding: tt)) => {{
+               // no-op
+       }};
 }
 
 macro_rules! check_missing_tlv {
@@ -150,6 +159,9 @@ macro_rules! check_missing_tlv {
        ($last_seen_type: expr, $type: expr, $field: ident, (option: $trait: ident $(, $read_arg: expr)?)) => {{
                // no-op
        }};
+       ($last_seen_type: expr, $type: expr, $field: ident, (option, encoding: $encoding: tt)) => {{
+               // no-op
+       }};
 }
 
 macro_rules! decode_tlv {
@@ -172,6 +184,15 @@ macro_rules! decode_tlv {
        ($reader: expr, $field: ident, (option: $trait: ident $(, $read_arg: expr)?)) => {{
                $field = Some($trait::read(&mut $reader $(, $read_arg)*)?);
        }};
+       ($reader: expr, $field: ident, (option, encoding: ($fieldty: ty, $encoding: ident))) => {{
+               $field = {
+                       let field: $encoding<$fieldty> = ser::Readable::read(&mut $reader)?;
+                       Some(field.0)
+               };
+       }};
+       ($reader: expr, $field: ident, (option, encoding: $fieldty: ty)) => {{
+               decode_tlv!($reader, $field, option);
+       }};
 }
 
 // `$decode_custom_tlv` is a closure that may be optionally provided to handle custom message types.
@@ -441,6 +462,75 @@ macro_rules! impl_writeable_tlv_based {
        }
 }
 
+/// Defines a struct for a TLV stream and a similar struct using references for non-primitive types,
+/// implementing [`Readable`] for the former and [`Writeable`] for the latter. Useful as an
+/// intermediary format when reading or writing a type encoded as a TLV stream. Note that each field
+/// representing a TLV record has its type wrapped with an [`Option`]. A tuple consisting of a type
+/// and a serialization wrapper may be given in place of a type when custom serialization is
+/// required.
+///
+/// [`Readable`]: crate::util::ser::Readable
+/// [`Writeable`]: crate::util::ser::Writeable
+macro_rules! tlv_stream {
+       ($name:ident, $nameref:ident, {
+               $(($type:expr, $field:ident : $fieldty:tt)),* $(,)*
+       }) => {
+               #[derive(Debug)]
+               struct $name {
+                       $(
+                               $field: Option<tlv_record_type!($fieldty)>,
+                       )*
+               }
+
+               pub(crate) struct $nameref<'a> {
+                       $(
+                               pub(crate) $field: Option<tlv_record_ref_type!($fieldty)>,
+                       )*
+               }
+
+               impl<'a> $crate::util::ser::Writeable for $nameref<'a> {
+                       fn write<W: $crate::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::io::Error> {
+                               encode_tlv_stream!(writer, {
+                                       $(($type, self.$field, (option, encoding: $fieldty))),*
+                               });
+                               Ok(())
+                       }
+               }
+
+               impl $crate::util::ser::Readable for $name {
+                       fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, $crate::ln::msgs::DecodeError> {
+                               $(
+                                       init_tlv_field_var!($field, option);
+                               )*
+                               decode_tlv_stream!(reader, {
+                                       $(($type, $field, (option, encoding: $fieldty))),*
+                               });
+
+                               Ok(Self {
+                                       $(
+                                               $field: $field
+                                       ),*
+                               })
+                       }
+               }
+       }
+}
+
+macro_rules! tlv_record_type {
+       (($type:ty, $wrapper:ident)) => { $type };
+       ($type:ty) => { $type };
+}
+
+macro_rules! tlv_record_ref_type {
+       (char) => { char };
+       (u8) => { u8 };
+       ((u16, $wrapper: ident)) => { u16 };
+       ((u32, $wrapper: ident)) => { u32 };
+       ((u64, $wrapper: ident)) => { u64 };
+       (($type:ty, $wrapper:ident)) => { &'a $type };
+       ($type:ty) => { &'a $type };
+}
+
 macro_rules! _impl_writeable_tlv_based_enum_common {
        ($st: ident, $(($variant_id: expr, $variant_name: ident) =>
                {$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}