lnmp_envelope/
binary_codec.rs

1//! Binary TLV (Type-Length-Value) codec for envelope metadata
2//!
3//! Encodes envelope metadata as TLV entries for the LNMP container
4//! metadata extension block.
5//!
6//! ## TLV Format
7//!
8//! ```text
9//! Type (1 byte) | Length (2 bytes, BE) | Value (Length bytes)
10//! ```
11//!
12//! ## Type Codes
13//!
14//! - `0x10`: Timestamp (u64 big-endian)
15//! - `0x11`: Source (UTF-8 string)
16//! - `0x12`: TraceID (UTF-8 string)
17//! - `0x13`: Sequence (u64 big-endian)
18//! - `0x14`: Labels (reserved)
19//!
20//! ## Canonical Ordering
21//!
22//! TLV entries MUST appear in ascending type order for determinism.
23
24use crate::{EnvelopeError, EnvelopeMetadata, Result};
25use std::io::{Read, Write};
26
27/// TLV type codes
28pub mod tlv_type {
29    /// Timestamp field (u64 big-endian, Unix epoch milliseconds)
30    pub const TIMESTAMP: u8 = 0x10;
31    /// Source identifier field (UTF-8 string)
32    pub const SOURCE: u8 = 0x11;
33    /// Trace ID field (UTF-8 string)
34    pub const TRACE_ID: u8 = 0x12;
35    /// Sequence number field ( u64 big-endian)
36    pub const SEQUENCE: u8 = 0x13;
37    /// Labels field (reserved for future use)
38    pub const LABELS: u8 = 0x14;
39}
40
41/// Binary TLV encoder for envelope metadata
42pub struct TlvEncoder;
43
44impl TlvEncoder {
45    /// Encodes metadata to TLV binary format
46    ///
47    /// Entries are written in canonical order:
48    /// 1. Timestamp (0x10)
49    /// 2. Source (0x11)
50    /// 3. TraceID (0x12)
51    /// 4. Sequence (0x13)
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// use lnmp_envelope::{EnvelopeMetadata, binary_codec::TlvEncoder};
57    ///
58    /// let mut metadata = EnvelopeMetadata::new();
59    /// metadata.timestamp = Some(1732373147000);
60    /// metadata.source = Some("auth-service".to_string());
61    ///
62    /// let bytes = TlvEncoder::encode(&metadata).unwrap();
63    /// assert!(bytes.len() > 0);
64    /// ```
65    pub fn encode(metadata: &EnvelopeMetadata) -> Result<Vec<u8>> {
66        let mut buf = Vec::new();
67
68        // Canonical order: timestamp, source, trace_id, sequence
69
70        if let Some(ts) = metadata.timestamp {
71            Self::write_timestamp(&mut buf, ts)?;
72        }
73
74        if let Some(ref source) = metadata.source {
75            Self::write_source(&mut buf, source)?;
76        }
77
78        if let Some(ref trace_id) = metadata.trace_id {
79            Self::write_trace_id(&mut buf, trace_id)?;
80        }
81
82        if let Some(seq) = metadata.sequence {
83            Self::write_sequence(&mut buf, seq)?;
84        }
85
86        // Labels reserved for future
87
88        Ok(buf)
89    }
90
91    fn write_timestamp<W: Write>(w: &mut W, ts: u64) -> Result<()> {
92        w.write_all(&[tlv_type::TIMESTAMP])?;
93        w.write_all(&8u16.to_be_bytes())?;
94        w.write_all(&ts.to_be_bytes())?;
95        Ok(())
96    }
97
98    fn write_source<W: Write>(w: &mut W, source: &str) -> Result<()> {
99        let bytes = source.as_bytes();
100        if bytes.len() > u16::MAX as usize {
101            return Err(EnvelopeError::StringTooLong(
102                "source".to_string(),
103                u16::MAX as usize,
104            ));
105        }
106
107        w.write_all(&[tlv_type::SOURCE])?;
108        w.write_all(&(bytes.len() as u16).to_be_bytes())?;
109        w.write_all(bytes)?;
110        Ok(())
111    }
112
113    fn write_trace_id<W: Write>(w: &mut W, trace_id: &str) -> Result<()> {
114        let bytes = trace_id.as_bytes();
115        if bytes.len() > u16::MAX as usize {
116            return Err(EnvelopeError::StringTooLong(
117                "trace_id".to_string(),
118                u16::MAX as usize,
119            ));
120        }
121
122        w.write_all(&[tlv_type::TRACE_ID])?;
123        w.write_all(&(bytes.len() as u16).to_be_bytes())?;
124        w.write_all(bytes)?;
125        Ok(())
126    }
127
128    fn write_sequence<W: Write>(w: &mut W, seq: u64) -> Result<()> {
129        w.write_all(&[tlv_type::SEQUENCE])?;
130        w.write_all(&8u16.to_be_bytes())?;
131        w.write_all(&seq.to_be_bytes())?;
132        Ok(())
133    }
134}
135
136/// Binary TLV decoder for envelope metadata
137pub struct TlvDecoder;
138
139impl TlvDecoder {
140    /// Decodes metadata from TLV binary format
141    ///
142    /// Unknown TRV types are skipped gracefully for forward compatibility.
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use lnmp_envelope::binary_codec::{TlvEncoder, TlvDecoder};
148    /// use lnmp_envelope::EnvelopeMetadata;
149    ///
150    /// let mut original = EnvelopeMetadata::new();
151    /// original.timestamp = Some(1732373147000);
152    /// original.source = Some("test".to_string());
153    ///
154    /// let bytes = TlvEncoder::encode(&original).unwrap();
155    /// let decoded = TlvDecoder::decode(&bytes).unwrap();
156    ///
157    /// assert_eq!(original, decoded);
158    /// ```
159    pub fn decode(data: &[u8]) -> Result<EnvelopeMetadata> {
160        let mut metadata = EnvelopeMetadata::new();
161        let mut cursor = std::io::Cursor::new(data);
162        let mut last_type: Option<u8> = None;
163
164        while cursor.position() < data.len() as u64 {
165            let tlv_type = Self::read_u8(&mut cursor)?;
166            let length = Self::read_u16_be(&mut cursor)?;
167
168            // Check canonical ordering
169            if let Some(prev) = last_type {
170                if tlv_type <= prev {
171                    return Err(EnvelopeError::NonCanonicalOrder(tlv_type, prev));
172                }
173            }
174            last_type = Some(tlv_type);
175
176            match tlv_type {
177                tlv_type::TIMESTAMP => {
178                    // Check for duplicate
179                    if metadata.timestamp.is_some() {
180                        return Err(EnvelopeError::DuplicateTlvEntry(tlv_type));
181                    }
182                    metadata.timestamp = Some(Self::read_timestamp(&mut cursor, length)?);
183                }
184                tlv_type::SOURCE => {
185                    // Check for duplicate
186                    if metadata.source.is_some() {
187                        return Err(EnvelopeError::DuplicateTlvEntry(tlv_type));
188                    }
189                    metadata.source = Some(Self::read_string(&mut cursor, length)?);
190                }
191                tlv_type::TRACE_ID => {
192                    // Check for duplicate
193                    if metadata.trace_id.is_some() {
194                        return Err(EnvelopeError::DuplicateTlvEntry(tlv_type));
195                    }
196                    metadata.trace_id = Some(Self::read_string(&mut cursor, length)?);
197                }
198                tlv_type::SEQUENCE => {
199                    // Check for duplicate
200                    if metadata.sequence.is_some() {
201                        return Err(EnvelopeError::DuplicateTlvEntry(tlv_type));
202                    }
203                    metadata.sequence = Some(Self::read_sequence(&mut cursor, length)?);
204                }
205                _ => {
206                    // Unknown type - skip gracefully
207                    Self::skip(&mut cursor, length as usize)?;
208                }
209            }
210        }
211
212        Ok(metadata)
213    }
214
215    fn read_u8<R: Read>(r: &mut R) -> Result<u8> {
216        let mut buf = [0u8; 1];
217        r.read_exact(&mut buf)
218            .map_err(|_| EnvelopeError::UnexpectedEof(0))?;
219        Ok(buf[0])
220    }
221
222    fn read_u16_be<R: Read>(r: &mut R) -> Result<u16> {
223        let mut buf = [0u8; 2];
224        r.read_exact(&mut buf)
225            .map_err(|_| EnvelopeError::UnexpectedEof(0))?;
226        Ok(u16::from_be_bytes(buf))
227    }
228
229    fn read_u64_be<R: Read>(r: &mut R) -> Result<u64> {
230        let mut buf = [0u8; 8];
231        r.read_exact(&mut buf)
232            .map_err(|_| EnvelopeError::UnexpectedEof(0))?;
233        Ok(u64::from_be_bytes(buf))
234    }
235
236    fn read_timestamp<R: Read>(r: &mut R, length: u16) -> Result<u64> {
237        if length != 8 {
238            return Err(EnvelopeError::InvalidTlvLength(length as usize));
239        }
240        Self::read_u64_be(r)
241    }
242
243    fn read_sequence<R: Read>(r: &mut R, length: u16) -> Result<u64> {
244        if length != 8 {
245            return Err(EnvelopeError::InvalidTlvLength(length as usize));
246        }
247        Self::read_u64_be(r)
248    }
249
250    fn read_string<R: Read>(r: &mut R, length: u16) -> Result<String> {
251        let mut buf = vec![0u8; length as usize];
252        r.read_exact(&mut buf)
253            .map_err(|_| EnvelopeError::UnexpectedEof(0))?;
254        String::from_utf8(buf).map_err(|e| e.into())
255    }
256
257    fn skip<R: Read>(r: &mut R, length: usize) -> Result<()> {
258        let mut buf = vec![0u8; length];
259        r.read_exact(&mut buf)
260            .map_err(|_| EnvelopeError::UnexpectedEof(0))?;
261        Ok(())
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_encode_decode_timestamp_only() {
271        let mut metadata = EnvelopeMetadata::new();
272        metadata.timestamp = Some(1732373147000);
273
274        let bytes = TlvEncoder::encode(&metadata).unwrap();
275        let decoded = TlvDecoder::decode(&bytes).unwrap();
276
277        assert_eq!(metadata, decoded);
278    }
279
280    #[test]
281    fn test_encode_decode_all_fields() {
282        let mut metadata = EnvelopeMetadata::new();
283        metadata.timestamp = Some(1732373147000);
284        metadata.source = Some("auth-service".to_string());
285        metadata.trace_id = Some("abc-123-xyz".to_string());
286        metadata.sequence = Some(42);
287
288        let bytes = TlvEncoder::encode(&metadata).unwrap();
289        let decoded = TlvDecoder::decode(&bytes).unwrap();
290
291        assert_eq!(metadata, decoded);
292    }
293
294    #[test]
295    fn test_encode_canonical_order() {
296        let mut metadata = EnvelopeMetadata::new();
297        metadata.sequence = Some(42); // Set last
298        metadata.timestamp = Some(123); // Set first
299
300        let bytes = TlvEncoder::encode(&metadata).unwrap();
301
302        // Check bytes start with timestamp type (0x10)
303        assert_eq!(bytes[0], tlv_type::TIMESTAMP);
304    }
305
306    #[test]
307    fn test_decode_rejects_duplicate() {
308        let mut buf = Vec::new();
309
310        // Write timestamp twice
311        buf.write_all(&[tlv_type::TIMESTAMP]).unwrap();
312        buf.write_all(&8u16.to_be_bytes()).unwrap();
313        buf.write_all(&123u64.to_be_bytes()).unwrap();
314
315        buf.write_all(&[tlv_type::TIMESTAMP]).unwrap();
316        buf.write_all(&8u16.to_be_bytes()).unwrap();
317        buf.write_all(&456u64.to_be_bytes()).unwrap();
318
319        let result = TlvDecoder::decode(&buf);
320        assert!(result.is_err());
321        // Duplicate is caught as NonCanonicalOrder (type <= prev)
322        assert!(matches!(
323            result,
324            Err(EnvelopeError::NonCanonicalOrder(_, _))
325        ));
326    }
327
328    #[test]
329    fn test_decode_rejects_non_canonical_order() {
330        let mut buf = Vec::new();
331
332        // Write source before timestamp (wrong order)
333        buf.write_all(&[tlv_type::SOURCE]).unwrap();
334        buf.write_all(&4u16.to_be_bytes()).unwrap();
335        buf.write_all(b"test").unwrap();
336
337        buf.write_all(&[tlv_type::TIMESTAMP]).unwrap();
338        buf.write_all(&8u16.to_be_bytes()).unwrap();
339        buf.write_all(&123u64.to_be_bytes()).unwrap();
340
341        let result = TlvDecoder::decode(&buf);
342        assert!(result.is_err());
343        assert!(matches!(
344            result,
345            Err(EnvelopeError::NonCanonicalOrder(_, _))
346        ));
347    }
348
349    #[test]
350    fn test_decode_skips_unknown_type() {
351        let mut buf = Vec::new();
352
353        // Write timestamp
354        buf.write_all(&[tlv_type::TIMESTAMP]).unwrap();
355        buf.write_all(&8u16.to_be_bytes()).unwrap();
356        buf.write_all(&123u64.to_be_bytes()).unwrap();
357
358        // Write unknown type (0xFF)
359        buf.write_all(&[0xFF]).unwrap();
360        buf.write_all(&4u16.to_be_bytes()).unwrap();
361        buf.write_all(&[0xAA, 0xBB, 0xCC, 0xDD]).unwrap();
362
363        let decoded = TlvDecoder::decode(&buf).unwrap();
364        assert_eq!(decoded.timestamp, Some(123));
365        assert!(decoded.source.is_none());
366    }
367}