1use core::fmt;
4
5pub const LNMP_MAGIC: [u8; 4] = *b"LNMP";
7
8pub const LNMP_CONTAINER_VERSION_1: u8 = 1;
10
11pub const LNMP_HEADER_SIZE: usize = 12;
13
14pub const LNMP_FLAG_CHECKSUM_REQUIRED: u16 = 0x0001;
16pub const LNMP_FLAG_COMPRESSED: u16 = 0x0002;
18pub const LNMP_FLAG_ENCRYPTED: u16 = 0x0004;
20pub const LNMP_FLAG_QSIG: u16 = 0x0008;
22pub const LNMP_FLAG_QKEX: u16 = 0x0010;
24pub const LNMP_FLAG_EXT_META_BLOCK: u16 = 0x8000;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[repr(u8)]
31pub enum LnmpFileMode {
32 Text = 0x01,
34 Binary = 0x02,
36 Stream = 0x03,
38 Delta = 0x04,
40 QuantumSafe = 0x05,
42 Embedding = 0x06,
44}
45
46impl LnmpFileMode {
47 pub fn from_byte(value: u8) -> Result<Self, LnmpContainerError> {
49 match value {
50 0x01 => Ok(Self::Text),
51 0x02 => Ok(Self::Binary),
52 0x03 => Ok(Self::Stream),
53 0x04 => Ok(Self::Delta),
54 0x05 => Ok(Self::QuantumSafe),
55 0x06 => Ok(Self::Embedding),
56 other => Err(LnmpContainerError::UnknownMode(other)),
57 }
58 }
59
60 pub const fn as_byte(self) -> u8 {
62 self as u8
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct LnmpContainerHeader {
69 pub version: u8,
71 pub mode: LnmpFileMode,
73 pub flags: u16,
75 pub metadata_len: u32,
77}
78
79impl LnmpContainerHeader {
80 pub const fn new(mode: LnmpFileMode) -> Self {
82 Self {
83 version: LNMP_CONTAINER_VERSION_1,
84 mode,
85 flags: 0,
86 metadata_len: 0,
87 }
88 }
89
90 pub fn parse(bytes: &[u8]) -> Result<Self, LnmpContainerError> {
92 if bytes.len() < LNMP_HEADER_SIZE {
93 return Err(LnmpContainerError::TruncatedHeader);
94 }
95
96 if bytes[0..4] != LNMP_MAGIC {
97 return Err(LnmpContainerError::InvalidMagic);
98 }
99
100 let version = bytes[4];
101 if version != LNMP_CONTAINER_VERSION_1 {
102 return Err(LnmpContainerError::UnsupportedVersion(version));
103 }
104
105 let mode = LnmpFileMode::from_byte(bytes[5])?;
106 let flags = u16::from_be_bytes([bytes[6], bytes[7]]);
107 let metadata_len = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
108
109 Ok(Self {
110 version,
111 mode,
112 flags,
113 metadata_len,
114 })
115 }
116
117 pub fn encode(&self) -> [u8; LNMP_HEADER_SIZE] {
119 let mut buf = [0u8; LNMP_HEADER_SIZE];
120 buf[0..4].copy_from_slice(&LNMP_MAGIC);
121 buf[4] = self.version;
122 buf[5] = self.mode.as_byte();
123 buf[6..8].copy_from_slice(&self.flags.to_be_bytes());
124 buf[8..12].copy_from_slice(&self.metadata_len.to_be_bytes());
125 buf
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum LnmpContainerError {
132 TruncatedHeader,
134 InvalidMagic,
136 UnsupportedVersion(u8),
138 UnknownMode(u8),
140}
141
142impl fmt::Display for LnmpContainerError {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 match self {
145 LnmpContainerError::TruncatedHeader => write!(f, "LNMP header is truncated"),
146 LnmpContainerError::InvalidMagic => write!(f, "LNMP magic does not match"),
147 LnmpContainerError::UnsupportedVersion(v) => {
148 write!(f, "LNMP header version {v} is not supported")
149 }
150 LnmpContainerError::UnknownMode(mode) => {
151 write!(f, "LNMP mode {mode:#04x} is not recognized")
152 }
153 }
154 }
155}
156
157impl std::error::Error for LnmpContainerError {}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn parse_valid_header() {
165 let header = LnmpContainerHeader::new(LnmpFileMode::Binary);
166 let encoded = header.encode();
167 let parsed = LnmpContainerHeader::parse(&encoded).unwrap();
168 assert_eq!(parsed.mode, LnmpFileMode::Binary);
169 assert_eq!(parsed.version, LNMP_CONTAINER_VERSION_1);
170 assert_eq!(parsed.flags, 0);
171 assert_eq!(parsed.metadata_len, 0);
172 }
173
174 #[test]
175 fn detect_invalid_magic() {
176 let mut bytes = [0u8; LNMP_HEADER_SIZE];
177 bytes[0..4].copy_from_slice(b"FOO!");
178 assert!(matches!(
179 LnmpContainerHeader::parse(&bytes),
180 Err(LnmpContainerError::InvalidMagic)
181 ));
182 }
183
184 #[test]
185 fn detect_unknown_mode() {
186 let mut header = LnmpContainerHeader::new(LnmpFileMode::Text).encode();
187 header[5] = 0xFF;
188 assert!(matches!(
189 LnmpContainerHeader::parse(&header),
190 Err(LnmpContainerError::UnknownMode(0xFF))
191 ));
192 }
193
194 #[test]
195 fn detect_truncated_header() {
196 assert!(matches!(
197 LnmpContainerHeader::parse(&[0u8; 4]),
198 Err(LnmpContainerError::TruncatedHeader)
199 ));
200 }
201
202 #[test]
203 fn test_embedding_mode() {
204 let header = LnmpContainerHeader::new(LnmpFileMode::Embedding);
205 let encoded = header.encode();
206 let parsed = LnmpContainerHeader::parse(&encoded).unwrap();
207 assert_eq!(parsed.mode, LnmpFileMode::Embedding);
208 }
209}