#[cfg(feature = "bigsize")]
pub mod bigsize;
#[cfg(not(feature = "bigsize"))]
pub(crate) mod bigsize;
pub(crate) mod encoding;
pub mod error;
pub mod stream;
#[cfg(feature = "serde")]
mod serde;
pub use error::{Result, TlvError};
pub use stream::{TlvRecord, TlvStream};
#[doc(hidden)]
pub mod _macro_support {
use crate::error::{Result, TlvError};
pub mod bigsize {
pub use crate::bigsize::{decode, encode};
}
pub mod encoding {
pub use crate::encoding::{decode_tu64, encode_tu64};
}
pub trait TlvTu64Encode {
fn to_tu64_value(&self) -> u64;
}
impl TlvTu64Encode for u64 {
fn to_tu64_value(&self) -> u64 {
*self
}
}
impl TlvTu64Encode for u32 {
fn to_tu64_value(&self) -> u64 {
*self as u64
}
}
pub trait TlvTu64Decode: Sized {
fn from_tu64_value(v: u64) -> Result<Self>;
}
impl TlvTu64Decode for u64 {
fn from_tu64_value(v: u64) -> Result<Self> {
Ok(v)
}
}
impl TlvTu64Decode for u32 {
fn from_tu64_value(v: u64) -> Result<Self> {
u32::try_from(v).map_err(|_| TlvError::Overflow)
}
}
pub trait TlvBytesEncode {
fn to_tlv_vec(&self) -> Vec<u8>;
}
impl TlvBytesEncode for Vec<u8> {
fn to_tlv_vec(&self) -> Vec<u8> {
self.clone()
}
}
impl<const N: usize> TlvBytesEncode for [u8; N] {
fn to_tlv_vec(&self) -> Vec<u8> {
self.to_vec()
}
}
pub trait TlvBytesDecode: Sized {
fn from_tlv_raw(raw: &[u8], type_num: u64) -> Result<Self>;
}
impl TlvBytesDecode for Vec<u8> {
fn from_tlv_raw(raw: &[u8], _type_num: u64) -> Result<Self> {
Ok(raw.to_vec())
}
}
impl<const N: usize> TlvBytesDecode for [u8; N] {
fn from_tlv_raw(raw: &[u8], type_num: u64) -> Result<Self> {
raw.try_into().map_err(|_| TlvError::InvalidLength {
type_: type_num,
expected: N,
actual: raw.len(),
})
}
}
}
#[macro_export]
macro_rules! tlv_struct {
(
$(#[$struct_meta:meta])*
$vis:vis struct $name:ident {
$($rest:tt)*
}
) => {
$crate::tlv_struct!(@munch
[$(#[$struct_meta])* $vis struct $name]
[]
$($rest)*
);
};
// tt-muncher: optional field (Option<T>)
(@munch
[$($header:tt)*]
[$($acc:tt)*]
$(#[doc = $doc:literal])*
#[tlv($type_num:expr, $enc:ident)]
$field_vis:vis $field:ident : Option<$inner_ty:ty>,
$($rest:tt)*
) => {
$crate::tlv_struct!(@munch
[$($header)*]
[$($acc)*
$(#[doc = $doc])*
($type_num, $enc, optional)
$field_vis $field : Option<$inner_ty>,
]
$($rest)*
);
};
(@munch
[$($header:tt)*]
[$($acc:tt)*]
$(#[doc = $doc:literal])*
#[tlv($type_num:expr, $enc:ident)]
$field_vis:vis $field:ident : $field_ty:ty,
$($rest:tt)*
) => {
$crate::tlv_struct!(@munch
[$($header)*]
[$($acc)*
$(#[doc = $doc])*
($type_num, $enc, required)
$field_vis $field : $field_ty,
]
$($rest)*
);
};
(@munch
[$(#[$struct_meta:meta])* $vis:vis struct $name:ident]
[$($acc:tt)*]
) => {
$crate::tlv_struct!(@impl_struct
$(#[$struct_meta])*
$vis struct $name {
$($acc)*
}
);
};
// Internal: struct definition + impls
(@impl_struct
$(#[$struct_meta:meta])*
$vis:vis struct $name:ident {
$(
$(#[doc = $doc:literal])*
($type_num:expr, $enc:ident, $optionality:ident)
$field_vis:vis $field:ident : $field_ty:ty,
)*
}
) => {
$(#[$struct_meta])*
#[derive(Debug, Clone, PartialEq, Eq)]
$vis struct $name {
$(
$(#[doc = $doc])*
$field_vis $field : $field_ty,
)*
}
impl $name {
pub fn to_tlv_bytes(&self) -> $crate::Result<Vec<u8>> {
let stream: $crate::TlvStream = self.into();
stream.to_bytes()
}
pub fn from_tlv_bytes(bytes: &[u8]) -> $crate::Result<Self> {
let stream = $crate::TlvStream::from_bytes(bytes)?;
Self::try_from(&stream)
}
}
impl From<&$name> for $crate::TlvStream {
fn from(val: &$name) -> Self {
let mut stream = $crate::TlvStream::default();
$(
$crate::tlv_struct!(@encode_field stream, val, $field, $type_num, $enc, $optionality);
)*
stream
}
}
impl TryFrom<&$crate::TlvStream> for $name {
type Error = $crate::TlvError;
fn try_from(stream: &$crate::TlvStream) -> std::result::Result<Self, Self::Error> {
Ok($name {
$(
$field: $crate::tlv_struct!(@decode_field stream, $type_num, $enc, $optionality),
)*
})
}
}
#[cfg(feature = "serde")]
impl ::serde::Serialize for $name {
fn serialize<S: ::serde::Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let stream: $crate::TlvStream = self.into();
::serde::Serialize::serialize(&stream, serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
let stream = <$crate::TlvStream as ::serde::Deserialize>::deserialize(deserializer)?;
Self::try_from(&stream).map_err(::serde::de::Error::custom)
}
}
};
(@encode_field $stream:ident, $val:ident, $field:ident, $type_num:expr, tu64, required) => {
$stream.set_tu64($type_num, $crate::_macro_support::TlvTu64Encode::to_tu64_value(&$val.$field));
};
(@encode_field $stream:ident, $val:ident, $field:ident, $type_num:expr, tu64, optional) => {
if let Some(ref v) = $val.$field {
$stream.set_tu64($type_num, $crate::_macro_support::TlvTu64Encode::to_tu64_value(v));
}
};
(@encode_field $stream:ident, $val:ident, $field:ident, $type_num:expr, u64, required) => {
$stream.set_u64($type_num, $val.$field);
};
(@encode_field $stream:ident, $val:ident, $field:ident, $type_num:expr, u64, optional) => {
if let Some(v) = $val.$field {
$stream.set_u64($type_num, v);
}
};
(@encode_field $stream:ident, $val:ident, $field:ident, $type_num:expr, bytes, required) => {
$stream.insert($type_num, $crate::_macro_support::TlvBytesEncode::to_tlv_vec(&$val.$field));
};
(@encode_field $stream:ident, $val:ident, $field:ident, $type_num:expr, bytes, optional) => {
if let Some(ref v) = $val.$field {
$stream.insert($type_num, $crate::_macro_support::TlvBytesEncode::to_tlv_vec(v));
}
};
(@decode_field $stream:ident, $type_num:expr, tu64, required) => {{
let v = $stream.get_tu64($type_num)?
.ok_or($crate::TlvError::MissingRequired($type_num))?;
$crate::_macro_support::TlvTu64Decode::from_tu64_value(v)?
}};
(@decode_field $stream:ident, $type_num:expr, tu64, optional) => {{
match $stream.get_tu64($type_num)? {
Some(v) => Some($crate::_macro_support::TlvTu64Decode::from_tu64_value(v)?),
None => None,
}
}};
(@decode_field $stream:ident, $type_num:expr, u64, required) => {{
$stream.get_u64($type_num)?
.ok_or($crate::TlvError::MissingRequired($type_num))?
}};
(@decode_field $stream:ident, $type_num:expr, u64, optional) => {{
$stream.get_u64($type_num)?
}};
(@decode_field $stream:ident, $type_num:expr, bytes, required) => {{
let raw = $stream.get($type_num)
.ok_or($crate::TlvError::MissingRequired($type_num))?;
$crate::_macro_support::TlvBytesDecode::from_tlv_raw(raw, $type_num)?
}};
(@decode_field $stream:ident, $type_num:expr, bytes, optional) => {{
match $stream.get($type_num) {
Some(raw) => Some($crate::_macro_support::TlvBytesDecode::from_tlv_raw(raw, $type_num)?),
None => None,
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
tlv_struct! {
pub struct OnionPayload {
#[tlv(2, tu64)]
pub amt_to_forward: u64,
#[tlv(4, tu64)]
pub outgoing_cltv_value: u32,
#[tlv(6, bytes)]
pub short_channel_id: Option<[u8; 8]>,
#[tlv(8, bytes)]
pub payment_secret: Option<[u8; 32]>,
}
}
#[test]
fn onion_payload_roundtrip_all_fields() {
let scid = [0x00, 0x73, 0x00, 0x0f, 0x2c, 0x00, 0x07, 0x00];
let secret = [0xab; 32];
let payload = OnionPayload {
amt_to_forward: 1000,
outgoing_cltv_value: 800000,
short_channel_id: Some(scid),
payment_secret: Some(secret),
};
let bytes = payload.to_tlv_bytes().unwrap();
let decoded = OnionPayload::from_tlv_bytes(&bytes).unwrap();
assert_eq!(decoded, payload);
}
#[test]
fn onion_payload_roundtrip_optional_none() {
let payload = OnionPayload {
amt_to_forward: 500,
outgoing_cltv_value: 144,
short_channel_id: None,
payment_secret: None,
};
let bytes = payload.to_tlv_bytes().unwrap();
let decoded = OnionPayload::from_tlv_bytes(&bytes).unwrap();
assert_eq!(decoded, payload);
}
#[test]
fn onion_payload_missing_required_field() {
let mut stream = TlvStream::default();
stream.set_tu64(2, 1000);
let bytes = stream.to_bytes().unwrap();
let err = OnionPayload::from_tlv_bytes(&bytes).unwrap_err();
assert!(matches!(err, TlvError::MissingRequired(4)));
}
#[test]
fn onion_payload_wrong_length_bytes() {
let mut stream = TlvStream::default();
stream.set_tu64(2, 1000);
stream.set_tu64(4, 144);
stream.insert(6, vec![0x00; 5]);
let bytes = stream.to_bytes().unwrap();
let err = OnionPayload::from_tlv_bytes(&bytes).unwrap_err();
assert!(matches!(
err,
TlvError::InvalidLength {
type_: 6,
expected: 8,
actual: 5,
}
));
}
#[test]
fn onion_payload_to_stream_and_back() {
let payload = OnionPayload {
amt_to_forward: 42,
outgoing_cltv_value: 100,
short_channel_id: None,
payment_secret: None,
};
let stream: TlvStream = (&payload).into();
let back = OnionPayload::try_from(&stream).unwrap();
assert_eq!(back, payload);
}
tlv_struct! {
pub struct SimplePayload {
#[tlv(2, tu64)]
pub amount: u64,
#[tlv(4, tu64)]
pub cltv: u32,
}
}
#[test]
fn simple_payload_roundtrip() {
let p = SimplePayload {
amount: 999,
cltv: 800000,
};
let bytes = p.to_tlv_bytes().unwrap();
let d = SimplePayload::from_tlv_bytes(&bytes).unwrap();
assert_eq!(d, p);
}
tlv_struct! {
pub struct OptionalOnly {
#[tlv(1, tu64)]
pub a: Option<u64>,
#[tlv(3, bytes)]
pub b: Option<Vec<u8>>,
}
}
#[test]
fn optional_only_empty() {
let p = OptionalOnly { a: None, b: None };
let bytes = p.to_tlv_bytes().unwrap();
assert!(bytes.is_empty());
let d = OptionalOnly::from_tlv_bytes(&bytes).unwrap();
assert_eq!(d, p);
}
#[test]
fn optional_only_with_values() {
let p = OptionalOnly {
a: Some(42),
b: Some(vec![0xde, 0xad]),
};
let bytes = p.to_tlv_bytes().unwrap();
let d = OptionalOnly::from_tlv_bytes(&bytes).unwrap();
assert_eq!(d, p);
}
tlv_struct! {
pub struct WithRequiredBytes {
#[tlv(1, bytes)]
pub data: Vec<u8>,
#[tlv(3, tu64)]
pub count: u64,
}
}
#[test]
fn required_bytes_roundtrip() {
let p = WithRequiredBytes {
data: vec![0x01, 0x02, 0x03],
count: 7,
};
let bytes = p.to_tlv_bytes().unwrap();
let d = WithRequiredBytes::from_tlv_bytes(&bytes).unwrap();
assert_eq!(d, p);
}
tlv_struct! {
pub struct FixedU64Struct {
#[tlv(65537, u64)]
pub extra_fee: u64,
#[tlv(65539, u64)]
pub optional_fee: Option<u64>,
}
}
#[cfg(feature = "serde")]
#[test]
fn simple_payload_serde_json_roundtrip() {
let p = SimplePayload {
amount: 999,
cltv: 800000,
};
let json = serde_json::to_string(&p).unwrap();
let d: SimplePayload = serde_json::from_str(&json).unwrap();
assert_eq!(d, p);
}
#[test]
fn fixed_u64_roundtrip() {
let p = FixedU64Struct {
extra_fee: 1000,
optional_fee: Some(500),
};
let bytes = p.to_tlv_bytes().unwrap();
let d = FixedU64Struct::from_tlv_bytes(&bytes).unwrap();
assert_eq!(d, p);
let p2 = FixedU64Struct {
extra_fee: 42,
optional_fee: None,
};
let bytes2 = p2.to_tlv_bytes().unwrap();
let d2 = FixedU64Struct::from_tlv_bytes(&bytes2).unwrap();
assert_eq!(d2, p2);
}
}