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.
This commit is contained in:
Jeffrey Czyz 2022-06-24 16:27:42 -05:00
parent 227fd51cb4
commit 904d322923
No known key found for this signature in database
GPG key ID: 912EF12EA67705F5
2 changed files with 110 additions and 0 deletions

View 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> {

View 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)),* $(,)*}