tlv_rs/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3
4use core::marker::PhantomData;
5
6pub mod raw_tlv;
7
8#[cfg(feature = "alloc")]
9extern crate alloc;
10
11use raw_tlv::RawTLV;
12use scroll::{
13    ctx::{MeasureWith, SizeWith, TryFromCtx, TryIntoCtx},
14    Endian, Pread, Pwrite,
15};
16
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
18/// A TLV.
19///
20/// This has to be constructed with `..Default::default()` as internally there exists a [PhantomData].
21/// The first type parameter is the raw type of the tlv type.
22/// The second is the strongly typed tlv type, which has to implement conversions from and into the raw tlv type.
23/// The third parameter is the type of the length of the TLV.
24/// The last parameter is a constant boolean, which describes if the fields should be encoded using big endian.
25pub struct TLV<Type, Length, EncodedType, Payload> {
26    pub tlv_type: EncodedType,
27    pub payload: Payload,
28    pub _phantom: PhantomData<(Type, Length)>,
29}
30impl<
31        'a,
32        Type: TryFromCtx<'a, Endian, Error = scroll::Error>
33            + TryIntoCtx<Endian, Error = scroll::Error>
34            + From<EncodedType>,
35        Length: 'a
36            + TryFromCtx<'a, Endian, Error = scroll::Error>
37            + TryIntoCtx<Endian, Error = scroll::Error>
38            + TryInto<usize>
39            + TryFrom<usize>,
40        EncodedType: 'a + From<Type>,
41        Payload: TryFromCtx<'a, Error = scroll::Error> + TryIntoCtx<Error = scroll::Error> + MeasureWith<()>,
42    > TLV<Type, Length, EncodedType, Payload>
43{
44    /// Wrapper around scroll Pread.
45    pub fn from_bytes(bytes: &'a [u8], big_endian: bool) -> Result<Self, scroll::Error> {
46        bytes.pread_with(
47            0,
48            if big_endian {
49                Endian::Big
50            } else {
51                Endian::Little
52            },
53        )
54    }
55    /// Serialize into the buffer.
56    pub fn into_bytes(self, buf: &mut [u8], big_endian: bool) -> Result<usize, scroll::Error> {
57        buf.pwrite_with(
58            self,
59            0,
60            if big_endian {
61                Endian::Big
62            } else {
63                Endian::Little
64            },
65        )
66    }
67    /// Serialize into a [heapless::Vec].
68    pub fn into_bytes_capped<const N: usize>(
69        self,
70        big_endian: bool,
71    ) -> Result<heapless::Vec<u8, N>, scroll::Error> {
72        let mut buf = [0x00; N];
73        self.into_bytes(&mut buf, big_endian)?;
74        Ok(heapless::Vec::<u8, N>::from_slice(&buf).unwrap())
75    }
76
77    #[cfg(feature = "alloc")]
78    // NOTE: This isn't checked, for being panic free, since allocations can panic.
79    /// Write the bytes to a [Vec](alloc::vec::Vec).
80    ///
81    /// This only reserves exactly as many bytes as needed.
82    pub fn to_bytes_dynamic(
83        &'a self,
84        big_endian: bool,
85    ) -> Result<alloc::vec::Vec<u8>, scroll::Error> {
86        let mut buf = alloc::vec::Vec::new();
87        buf.reserve_exact(self.measure_with(&()));
88
89        self.clone().into_bytes(buf.as_mut_slice(), big_endian)?;
90        Ok(buf)
91    }
92}
93impl<Type: SizeWith, Length: SizeWith, EncodedType: From<Type>, Payload: MeasureWith<()>>
94    MeasureWith<()> for TLV<Type, Length, EncodedType, Payload>
95{
96    fn measure_with(&self, ctx: &()) -> usize {
97        Type::size_with(ctx) + Length::size_with(ctx) + self.payload.measure_with(ctx)
98    }
99}
100impl<
101        'a,
102        Type: TryFromCtx<'a, Endian, Error = scroll::Error>,
103        Length: TryFromCtx<'a, Endian, Error = scroll::Error> + TryInto<usize>,
104        EncodedType: 'a + From<Type>,
105        Payload: TryFromCtx<'a, Error = scroll::Error>,
106    > TryFromCtx<'a, Endian> for TLV<Type, Length, EncodedType, Payload>
107{
108    type Error = scroll::Error;
109    fn try_from_ctx(from: &'a [u8], ctx: Endian) -> Result<(Self, usize), Self::Error> {
110        let (raw_tlv, len) =
111            <RawTLV<'a, Type, Length> as TryFromCtx<'a, Endian>>::try_from_ctx(from, ctx)?;
112        Ok((
113            Self {
114                tlv_type: raw_tlv.tlv_type.into(),
115                payload: raw_tlv.slice.pread(0)?,
116                _phantom: PhantomData,
117            },
118            len,
119        ))
120    }
121}
122impl<
123        Type: TryIntoCtx<Endian, Error = scroll::Error>,
124        Length: TryIntoCtx<Endian, Error = scroll::Error> + TryFrom<usize>,
125        EncodedType: Into<Type>,
126        Payload: TryIntoCtx<Error = scroll::Error> + MeasureWith<()>,
127    > TryIntoCtx<Endian> for TLV<Type, Length, EncodedType, Payload>
128{
129    type Error = scroll::Error;
130    fn try_into_ctx(self, buf: &mut [u8], ctx: Endian) -> Result<usize, Self::Error> {
131        let mut offset = 0;
132
133        buf.gwrite_with(self.tlv_type.into(), &mut offset, ctx)?;
134
135        let len = match Length::try_from(self.payload.measure_with(&())) {
136            Ok(len) => len,
137            Err(_) => {
138                return Err(scroll::Error::BadInput {
139                    size: offset,
140                    msg: "Couldn't convert usize to Length",
141                })
142            }
143        };
144        buf.gwrite_with(len, &mut offset, ctx)?;
145
146        buf.gwrite(self.payload, &mut offset)?;
147
148        Ok(offset)
149    }
150}