From: Jeffrey Czyz Date: Fri, 24 Jun 2022 21:27:42 +0000 (-0500) Subject: Serialization macro for TLV streams X-Git-Tag: v0.0.113~52^2~1 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=904d3229235f3a7bc5b11d95bb0e53fbb51f0da4;p=rust-lightning Serialization macro for TLV streams 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. --- diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index b9722325e..37fade888 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -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(pub T); +impl Writeable for WithoutLength<&String> { + #[inline] + fn write(&self, w: &mut W) -> Result<(), io::Error> { + w.write_all(self.0.as_bytes()) + } +} +impl Readable for WithoutLength { + #[inline] + fn read(r: &mut R) -> Result { + let v: WithoutLength> = 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> { #[inline] fn write(&self, writer: &mut W) -> Result<(), io::Error> { diff --git a/lightning/src/util/ser_macros.rs b/lightning/src/util/ser_macros.rs index 019bea3ee..3ec084868 100644 --- a/lightning/src/util/ser_macros.rs +++ b/lightning/src/util/ser_macros.rs @@ -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, + )* + } + + pub(crate) struct $nameref<'a> { + $( + pub(crate) $field: Option, + )* + } + + impl<'a> $crate::util::ser::Writeable for $nameref<'a> { + fn write(&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(reader: &mut R) -> Result { + $( + 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)),* $(,)*}