Skip to main content

lnc_core/
tlv.rs

1//! TLV (Type-Length-Value) encoding for LANCE records
2//!
3//! Based on Architecture Section 2: TLV Record Format
4//!
5//! Standard TLV Header (5 bytes):
6//! - Type: 1 byte (0x00-0xFE for standard types, 0xFF for extended)
7//! - Length: 4 bytes (little-endian, payload length)
8//!
9//! Extended TLV Header (7 bytes, when type == 0xFF):
10//! - Type marker: 1 byte (0xFF)
11//! - Extended type: 2 bytes (little-endian)
12//! - Length: 4 bytes (little-endian)
13
14use crate::{EXTENDED_TLV_HEADER_SIZE, EXTENDED_TYPE_MARKER, LanceError, Result, TLV_HEADER_SIZE};
15
16/// Standard record types (0x00-0xFE)
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[repr(u8)]
19pub enum RecordType {
20    /// Raw payload data
21    Data = 0x00,
22    /// JSON-encoded payload
23    Json = 0x01,
24    /// MessagePack-encoded payload
25    MsgPack = 0x02,
26    /// Protobuf-encoded payload
27    Protobuf = 0x03,
28    /// Avro-encoded payload
29    Avro = 0x04,
30    /// Control message (internal)
31    Control = 0xFE,
32    /// Extended type marker (use `extended_type` field)
33    Extended = 0xFF,
34}
35
36impl TryFrom<u8> for RecordType {
37    type Error = LanceError;
38
39    fn try_from(value: u8) -> Result<Self> {
40        match value {
41            0x01 => Ok(Self::Json),
42            0x02 => Ok(Self::MsgPack),
43            0x03 => Ok(Self::Protobuf),
44            0x04 => Ok(Self::Avro),
45            0xFE => Ok(Self::Control),
46            0xFF => Ok(Self::Extended),
47            // 0x00 and unknown types are treated as raw data
48            _ => Ok(Self::Data),
49        }
50    }
51}
52
53/// Parsed TLV header
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct Header {
56    /// Record type (standard or extended marker)
57    pub record_type: RecordType,
58    /// Extended type (only valid if `record_type` == Extended)
59    pub extended_type: u16,
60    /// Payload length in bytes
61    pub length: u32,
62    /// Total header size (5 for standard, 7 for extended)
63    pub header_size: usize,
64}
65
66impl Header {
67    /// Create a standard header
68    #[must_use]
69    pub const fn new(record_type: RecordType, length: u32) -> Self {
70        Self {
71            record_type,
72            extended_type: 0,
73            length,
74            header_size: TLV_HEADER_SIZE,
75        }
76    }
77
78    /// Create an extended header
79    #[must_use]
80    pub const fn new_extended(extended_type: u16, length: u32) -> Self {
81        Self {
82            record_type: RecordType::Extended,
83            extended_type,
84            length,
85            header_size: EXTENDED_TLV_HEADER_SIZE,
86        }
87    }
88
89    /// Total record size (header + payload)
90    #[must_use]
91    pub const fn total_size(&self) -> usize {
92        self.header_size + self.length as usize
93    }
94
95    /// Encode header to bytes
96    #[must_use]
97    pub fn encode(&self) -> Vec<u8> {
98        if self.record_type == RecordType::Extended {
99            let mut buf = Vec::with_capacity(EXTENDED_TLV_HEADER_SIZE);
100            buf.push(EXTENDED_TYPE_MARKER);
101            buf.extend_from_slice(&self.extended_type.to_le_bytes());
102            buf.extend_from_slice(&self.length.to_le_bytes());
103            buf
104        } else {
105            let mut buf = Vec::with_capacity(TLV_HEADER_SIZE);
106            buf.push(self.record_type as u8);
107            buf.extend_from_slice(&self.length.to_le_bytes());
108            buf
109        }
110    }
111}
112
113/// Parse a TLV header from a byte slice.
114///
115/// Returns the parsed header or an error if the buffer is too small
116/// or contains invalid data.
117///
118/// # Errors
119/// Returns an error if the buffer is too small for the header.
120pub fn parse_header(data: &[u8]) -> Result<Header> {
121    if data.len() < TLV_HEADER_SIZE {
122        return Err(LanceError::BufferTooSmall {
123            required: TLV_HEADER_SIZE,
124            available: data.len(),
125        });
126    }
127
128    let type_byte = data[0];
129
130    if type_byte == EXTENDED_TYPE_MARKER {
131        // Extended header
132        if data.len() < EXTENDED_TLV_HEADER_SIZE {
133            return Err(LanceError::BufferTooSmall {
134                required: EXTENDED_TLV_HEADER_SIZE,
135                available: data.len(),
136            });
137        }
138
139        let extended_type = u16::from_le_bytes([data[1], data[2]]);
140        let length = u32::from_le_bytes([data[3], data[4], data[5], data[6]]);
141
142        Ok(Header {
143            record_type: RecordType::Extended,
144            extended_type,
145            length,
146            header_size: EXTENDED_TLV_HEADER_SIZE,
147        })
148    } else {
149        // Standard header
150        let record_type = RecordType::try_from(type_byte)?;
151        let length = u32::from_le_bytes([data[1], data[2], data[3], data[4]]);
152
153        Ok(Header {
154            record_type,
155            extended_type: 0,
156            length,
157            header_size: TLV_HEADER_SIZE,
158        })
159    }
160}
161
162#[cfg(test)]
163#[allow(clippy::unwrap_used)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn test_standard_header_roundtrip() {
169        let header = Header::new(RecordType::Json, 1024);
170        let encoded = header.encode();
171        assert_eq!(encoded.len(), TLV_HEADER_SIZE);
172
173        let parsed = parse_header(&encoded).unwrap();
174        assert_eq!(parsed.record_type, RecordType::Json);
175        assert_eq!(parsed.length, 1024);
176        assert_eq!(parsed.header_size, TLV_HEADER_SIZE);
177    }
178
179    #[test]
180    fn test_extended_header_roundtrip() {
181        let header = Header::new_extended(0x1234, 65536);
182        let encoded = header.encode();
183        assert_eq!(encoded.len(), EXTENDED_TLV_HEADER_SIZE);
184
185        let parsed = parse_header(&encoded).unwrap();
186        assert_eq!(parsed.record_type, RecordType::Extended);
187        assert_eq!(parsed.extended_type, 0x1234);
188        assert_eq!(parsed.length, 65536);
189        assert_eq!(parsed.header_size, EXTENDED_TLV_HEADER_SIZE);
190    }
191
192    #[test]
193    fn test_buffer_too_small() {
194        let data = [0x00, 0x01, 0x02]; // Only 3 bytes
195        let result = parse_header(&data);
196        assert!(result.is_err());
197    }
198
199    #[test]
200    fn test_total_size() {
201        let header = Header::new(RecordType::Data, 100);
202        assert_eq!(header.total_size(), TLV_HEADER_SIZE + 100);
203
204        let ext_header = Header::new_extended(0x01, 200);
205        assert_eq!(ext_header.total_size(), EXTENDED_TLV_HEADER_SIZE + 200);
206    }
207}