Skip to content

Commit 544f2bd

Browse files
committed
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.
1 parent d232a69 commit 544f2bd

File tree

2 files changed

+191
-0
lines changed

2 files changed

+191
-0
lines changed

lightning/src/util/ser.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,15 @@ macro_rules! impl_writeable_primitive {
452452
}
453453
}
454454
}
455+
impl From<$val_type> for HighZeroBytesDroppedBigSize<$val_type> {
456+
fn from(val: $val_type) -> Self { Self(val) }
457+
}
458+
impl From<&$val_type> for HighZeroBytesDroppedBigSize<$val_type> {
459+
fn from(val: &$val_type) -> Self { Self(*val) }
460+
}
461+
impl From<HighZeroBytesDroppedBigSize<$val_type>> for $val_type {
462+
fn from(val: HighZeroBytesDroppedBigSize<$val_type>) -> Self { val.0 }
463+
}
455464
}
456465
}
457466

@@ -524,6 +533,62 @@ impl_array!(PUBLIC_KEY_SIZE); // for PublicKey
524533
impl_array!(COMPACT_SIGNATURE_SIZE); // for Signature
525534
impl_array!(1300); // for OnionPacket.hop_data
526535

536+
/// For variable-length values within TLV record where the length is encoded as part of the record.
537+
/// Used to prevent encoding the length twice.
538+
#[derive(Clone)]
539+
pub(crate) struct WithoutLength<T>(pub T);
540+
541+
impl Writeable for WithoutLength<String> {
542+
#[inline]
543+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
544+
w.write_all(self.0.as_bytes())
545+
}
546+
}
547+
impl Writeable for WithoutLength<&String> {
548+
#[inline]
549+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
550+
w.write_all(self.0.as_bytes())
551+
}
552+
}
553+
impl Readable for WithoutLength<String> {
554+
#[inline]
555+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
556+
let v: VecReadWrapper<u8> = Readable::read(r)?;
557+
Ok(Self(String::from_utf8(v.0).map_err(|_| DecodeError::InvalidValue)?))
558+
}
559+
}
560+
impl<'a> From<&'a String> for WithoutLength<&'a String> {
561+
fn from(s: &'a String) -> Self { Self(s) }
562+
}
563+
impl From<WithoutLength<String>> for String {
564+
fn from(s: WithoutLength<String>) -> Self { s.0 }
565+
}
566+
567+
impl<T: Writeable> Writeable for WithoutLength<Vec<T>> {
568+
#[inline]
569+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
570+
VecWriteWrapper(&self.0).write(w)
571+
}
572+
}
573+
impl<T: Writeable> Writeable for WithoutLength<&Vec<T>> {
574+
#[inline]
575+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
576+
VecWriteWrapper(self.0).write(w)
577+
}
578+
}
579+
impl<T: Readable> Readable for WithoutLength<Vec<T>> {
580+
#[inline]
581+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
582+
Ok(Self(<VecReadWrapper<T> as Readable>::read(r)?.0))
583+
}
584+
}
585+
impl<'a, T> From<&'a Vec<T>> for WithoutLength<&'a Vec<T>> {
586+
fn from(v: &'a Vec<T>) -> Self { Self(v) }
587+
}
588+
impl<T> From<WithoutLength<Vec<T>>> for Vec<T> {
589+
fn from(s: WithoutLength<Vec<T>>) -> Self { s.0 }
590+
}
591+
527592
// HashMap
528593
impl<K, V> Writeable for HashMap<K, V>
529594
where K: Writeable + Eq + Hash,

lightning/src/util/ser_macros.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,132 @@ macro_rules! impl_writeable_tlv_based {
427427
}
428428
}
429429

430+
/// Defines a struct for a TLV stream and a similar struct using references for non-primitive types,
431+
/// implementing [`Readable`] for the former and [`Writeable`] for the latter. Useful as an
432+
/// intermediary format when reading or writing a type encoded as a TLV stream.
433+
///
434+
/// [`Readable`]: crate::util::ser::Readable
435+
/// [`Writeable`]: crate::util::ser::Writeable
436+
macro_rules! tlv_stream {
437+
(struct $name:ident {
438+
$(($type:expr, $field:ident : $fieldty:ident$(<$gen:ident>)?)),* $(,)*
439+
}) => {
440+
#[derive(Debug)]
441+
struct $name {
442+
$(
443+
$field: Option<tlv_record_type!($fieldty$(<$gen>)?)>,
444+
)*
445+
}
446+
447+
pub(crate) mod reference {
448+
$(
449+
tlv_record_import!($fieldty$(<$gen>)?);
450+
)*
451+
452+
pub(crate) struct $name<'a> {
453+
$(
454+
pub(crate) $field: Option<tlv_record_ref_type!($fieldty$(<$gen>)?)>,
455+
)*
456+
}
457+
458+
impl<'a> ::util::ser::Writeable for $name<'a> {
459+
fn write<W: ::util::ser::Writer>(&self, writer: &mut W) -> Result<(), $crate::io::Error> {
460+
encode_tlv_stream!(writer, { $(($type, self.$field, option)),* });
461+
Ok(())
462+
}
463+
}
464+
}
465+
466+
impl ::util::ser::Readable for $name {
467+
fn read<R: $crate::io::Read>(reader: &mut R) -> Result<Self, ::ln::msgs::DecodeError> {
468+
$(
469+
init_tlv_field_var!($field, option);
470+
)*
471+
decode_tlv_stream!(reader, {$(($type, $field, option)),*});
472+
473+
Ok(Self {
474+
$(
475+
$field: init_tlv_based_struct_field!($field, option)
476+
),*
477+
})
478+
}
479+
}
480+
}
481+
}
482+
483+
macro_rules! tlv_record_type {
484+
(u8) => {
485+
u8
486+
};
487+
(u16) => {
488+
::util::ser::HighZeroBytesDroppedBigSize<u16>
489+
};
490+
(u32) => {
491+
::util::ser::HighZeroBytesDroppedBigSize<u32>
492+
};
493+
(u64) => {
494+
::util::ser::HighZeroBytesDroppedBigSize<u64>
495+
};
496+
(char) => {
497+
char
498+
};
499+
(String) => {
500+
::util::ser::WithoutLength<String>
501+
};
502+
(Vec<$type:ty>) => {
503+
::util::ser::WithoutLength<Vec<$type>>
504+
};
505+
($type:ident$(<$gen:ident>)?) => {
506+
$type$(<$gen>)?
507+
};
508+
}
509+
510+
macro_rules! tlv_record_ref_type {
511+
(u8) => {
512+
u8
513+
};
514+
(u16) => {
515+
::util::ser::HighZeroBytesDroppedBigSize<u16>
516+
};
517+
(u32) => {
518+
::util::ser::HighZeroBytesDroppedBigSize<u32>
519+
};
520+
(u64) => {
521+
::util::ser::HighZeroBytesDroppedBigSize<u64>
522+
};
523+
(char) => {
524+
char
525+
};
526+
(String) => {
527+
::util::ser::WithoutLength<&'a String>
528+
};
529+
(Vec<$type:ty>) => {
530+
::util::ser::WithoutLength<&'a Vec<$type>>
531+
};
532+
($type:ident$(<$gen:ident>)?) => {
533+
&'a $type$(<$gen>)?
534+
};
535+
}
536+
537+
macro_rules! tlv_record_import {
538+
(u8) => {};
539+
(u16) => {};
540+
(u32) => {};
541+
(u64) => {};
542+
(char) => {};
543+
(String) => {};
544+
(Vec<$type:ident>) => {
545+
tlv_record_import!($type);
546+
};
547+
($type:ident<$gen:ident>) => {
548+
tlv_record_import!($type);
549+
tlv_record_import!($gen);
550+
};
551+
($type:ident) => {
552+
use super::$type;
553+
};
554+
}
555+
430556
macro_rules! _impl_writeable_tlv_based_enum_common {
431557
($st: ident, $(($variant_id: expr, $variant_name: ident) =>
432558
{$(($type: expr, $field: ident, $fieldty: tt)),* $(,)*}

0 commit comments

Comments
 (0)