1use crc::{CRC_16_IBM_3740, Crc};
46
47use super::puncture::Mode;
48
49pub const HEADER_BYTES: usize = 4;
51
52pub const INFO_BYTES_PER_BLOCK: usize = 12;
55
56pub const MAX_BLOCKS_PER_FRAME: usize = 32;
59
60pub const MAX_PAYLOAD_BYTES: usize = MAX_BLOCKS_PER_FRAME * INFO_BYTES_PER_BLOCK;
63
64const CRC16_ALGO: Crc<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
67
68#[derive(Copy, Clone, Debug, Eq, PartialEq)]
73pub struct FrameHeader {
74 pub mode: Mode,
75 pub block_count: u8,
77 pub app_type: u8,
79 pub sequence: u8,
81}
82
83#[derive(Copy, Clone, Debug, Eq, PartialEq)]
85pub enum PackError {
86 InvalidBlockCount(u8),
88 InvalidAppType(u8),
90 InvalidSequence(u8),
92 PayloadTooLarge(usize),
94}
95
96#[derive(Copy, Clone, Debug, Eq, PartialEq)]
98pub enum UnpackError {
99 Truncated,
101 CrcMismatch { expected: u16, computed: u16 },
103 ReservedNotZero(u8),
105}
106
107pub fn pack_header(header: &FrameHeader, payload: &[u8]) -> Result<[u8; HEADER_BYTES], PackError> {
117 if !(1..=MAX_BLOCKS_PER_FRAME as u8).contains(&header.block_count) {
118 return Err(PackError::InvalidBlockCount(header.block_count));
119 }
120 if header.app_type > 15 {
121 return Err(PackError::InvalidAppType(header.app_type));
122 }
123 if header.sequence > 31 {
124 return Err(PackError::InvalidSequence(header.sequence));
125 }
126 if payload.len() > MAX_PAYLOAD_BYTES {
127 return Err(PackError::PayloadTooLarge(payload.len()));
128 }
129 let blocks_bits = u16::from(header.block_count - 1) & 0x1F;
130 let app_bits = u16::from(header.app_type) & 0xF;
131 let seq_bits = u16::from(header.sequence) & 0x1F;
132 let header_word: u16 = (blocks_bits << 11) | (app_bits << 7) | (seq_bits << 2);
133
134 let mut out = [0u8; HEADER_BYTES];
135 out[0..2].copy_from_slice(&header_word.to_be_bytes());
136 let mut crc_input = Vec::with_capacity(2 + payload.len());
137 crc_input.extend_from_slice(&out[0..2]);
138 crc_input.extend_from_slice(payload);
139 let crc = crc16(&crc_input);
140 out[2..4].copy_from_slice(&crc.to_be_bytes());
141 Ok(out)
142}
143
144pub fn unpack_header(bytes: &[u8], mode: Mode) -> Result<(FrameHeader, &[u8]), UnpackError> {
155 if bytes.len() < HEADER_BYTES {
156 return Err(UnpackError::Truncated);
157 }
158 let header_word = u16::from_be_bytes([bytes[0], bytes[1]]);
159 let crc_recv = u16::from_be_bytes([bytes[2], bytes[3]]);
160 let payload = &bytes[HEADER_BYTES..];
161
162 let mut crc_input = Vec::with_capacity(2 + payload.len());
163 crc_input.extend_from_slice(&bytes[..2]);
164 crc_input.extend_from_slice(payload);
165 let crc_calc = crc16(&crc_input);
166 if crc_calc != crc_recv {
167 return Err(UnpackError::CrcMismatch {
168 expected: crc_recv,
169 computed: crc_calc,
170 });
171 }
172
173 let blocks_code = ((header_word >> 11) & 0x1F) as u8;
174 let app_type = ((header_word >> 7) & 0x0F) as u8;
175 let sequence = ((header_word >> 2) & 0x1F) as u8;
176 let reserved = (header_word & 0x3) as u8;
177 if reserved != 0 {
178 return Err(UnpackError::ReservedNotZero(reserved));
179 }
180 let block_count = blocks_code + 1;
181
182 Ok((
183 FrameHeader {
184 mode,
185 block_count,
186 app_type,
187 sequence,
188 },
189 payload,
190 ))
191}
192
193pub fn crc16(bytes: &[u8]) -> u16 {
197 CRC16_ALGO.checksum(bytes)
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 fn sample_header() -> FrameHeader {
205 FrameHeader {
206 mode: Mode::Robust,
207 block_count: 5,
208 app_type: 1,
209 sequence: 7,
210 }
211 }
212
213 #[test]
214 fn pack_unpack_roundtrip_empty_payload() {
215 let h = sample_header();
216 let bytes = pack_header(&h, &[]).unwrap();
217 let mut all = Vec::new();
218 all.extend_from_slice(&bytes);
219 let (h2, p2) = unpack_header(&all, Mode::Robust).unwrap();
220 assert_eq!(h, h2);
221 assert!(p2.is_empty());
222 }
223
224 #[test]
225 fn pack_unpack_roundtrip_with_payload() {
226 let h = sample_header();
227 let payload: Vec<u8> = (0..60).collect();
228 let bytes = pack_header(&h, &payload).unwrap();
229 let mut all = Vec::new();
230 all.extend_from_slice(&bytes);
231 all.extend_from_slice(&payload);
232 let (h2, p2) = unpack_header(&all, Mode::Robust).unwrap();
233 assert_eq!(h, h2);
234 assert_eq!(p2, &payload[..]);
235 }
236
237 #[test]
238 fn pack_unpack_roundtrip_all_modes() {
239 for mode in [
240 Mode::Robust,
241 Mode::Standard,
242 Mode::UltraRobust,
243 Mode::Express,
244 ] {
245 let h = FrameHeader {
246 mode,
247 block_count: 1,
248 app_type: 0,
249 sequence: 0,
250 };
251 let bytes = pack_header(&h, b"hi").unwrap();
252 let mut all = Vec::new();
253 all.extend_from_slice(&bytes);
254 all.extend_from_slice(b"hi");
255 let (h2, p2) = unpack_header(&all, mode).unwrap();
256 assert_eq!(h, h2);
257 assert_eq!(p2, b"hi");
258 }
259 }
260
261 #[test]
262 fn pack_rejects_invalid_block_count() {
263 for bad in [0u8, 33, 200] {
264 let h = FrameHeader {
265 mode: Mode::Robust,
266 block_count: bad,
267 app_type: 0,
268 sequence: 0,
269 };
270 assert_eq!(
271 pack_header(&h, &[]).unwrap_err(),
272 PackError::InvalidBlockCount(bad),
273 );
274 }
275 }
276
277 #[test]
278 fn unpack_detects_header_bit_flip() {
279 let h = sample_header();
280 let mut bytes = Vec::new();
281 bytes.extend_from_slice(&pack_header(&h, b"hello").unwrap());
282 bytes.extend_from_slice(b"hello");
283 bytes[0] ^= 0x40;
284 match unpack_header(&bytes, Mode::Robust) {
285 Err(UnpackError::CrcMismatch { .. }) => {}
286 other => panic!("expected CrcMismatch, got {other:?}"),
287 }
288 }
289
290 #[test]
291 fn unpack_detects_payload_bit_flip() {
292 let h = sample_header();
293 let mut bytes = Vec::new();
294 bytes.extend_from_slice(&pack_header(&h, b"hello").unwrap());
295 bytes.extend_from_slice(b"hello");
296 let pos = HEADER_BYTES + 2;
297 bytes[pos] ^= 0x01;
298 match unpack_header(&bytes, Mode::Robust) {
299 Err(UnpackError::CrcMismatch { .. }) => {}
300 other => panic!("expected CrcMismatch, got {other:?}"),
301 }
302 }
303
304 #[test]
305 fn unpack_rejects_truncated() {
306 for n in 0..HEADER_BYTES {
307 let bytes = vec![0u8; n];
308 assert_eq!(
309 unpack_header(&bytes, Mode::Robust).unwrap_err(),
310 UnpackError::Truncated,
311 );
312 }
313 }
314
315 #[test]
316 fn crc16_canonical_check_value() {
317 assert_eq!(crc16(b"123456789"), 0x29B1);
318 }
319
320 #[test]
321 fn capacity_constants_consistent() {
322 assert_eq!(MAX_BLOCKS_PER_FRAME, 32);
323 assert_eq!(INFO_BYTES_PER_BLOCK, 12);
324 assert_eq!(MAX_PAYLOAD_BYTES, 32 * 12); }
326}