nodedb_array/segment/format/
framing.rs1use crate::error::{ArrayError, ArrayResult};
12
13pub const FRAMING_OVERHEAD: usize = 8;
15
16pub struct BlockFraming;
17
18impl BlockFraming {
19 pub fn encode(payload: &[u8], out: &mut Vec<u8>) -> usize {
22 let len = payload.len() as u32;
23 out.extend_from_slice(&len.to_le_bytes());
24 out.extend_from_slice(payload);
25 let crc = crc32c::crc32c(payload);
26 out.extend_from_slice(&crc.to_le_bytes());
27 FRAMING_OVERHEAD + payload.len()
28 }
29
30 pub fn decode(bytes: &[u8]) -> ArrayResult<(&[u8], usize)> {
33 if bytes.len() < FRAMING_OVERHEAD {
34 return Err(ArrayError::SegmentCorruption {
35 detail: format!("framed block truncated: {} bytes", bytes.len()),
36 });
37 }
38 let len = u32::from_le_bytes(read_u32_le(bytes, 0)) as usize;
39 let total = FRAMING_OVERHEAD + len;
40 if bytes.len() < total {
41 return Err(ArrayError::SegmentCorruption {
42 detail: format!(
43 "framed block claims len={len} but buffer has {}",
44 bytes.len() - 4
45 ),
46 });
47 }
48 let payload = &bytes[4..4 + len];
49 let crc_stored = u32::from_le_bytes(read_u32_le(bytes, 4 + len));
50 let crc_calc = crc32c::crc32c(payload);
51 if crc_stored != crc_calc {
52 return Err(ArrayError::SegmentCorruption {
53 detail: format!(
54 "framed block CRC mismatch: stored={crc_stored:08x} \
55 calc={crc_calc:08x}"
56 ),
57 });
58 }
59 Ok((payload, total))
60 }
61}
62
63#[inline]
64fn read_u32_le(bytes: &[u8], offset: usize) -> [u8; 4] {
65 let mut out = [0u8; 4];
66 out.copy_from_slice(&bytes[offset..offset + 4]);
67 out
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn framing_round_trip() {
76 let payload = b"hello world";
77 let mut buf = Vec::new();
78 let n = BlockFraming::encode(payload, &mut buf);
79 assert_eq!(n, FRAMING_OVERHEAD + payload.len());
80 let (got, total) = BlockFraming::decode(&buf).unwrap();
81 assert_eq!(got, payload);
82 assert_eq!(total, n);
83 }
84
85 #[test]
86 fn framing_round_trip_empty() {
87 let mut buf = Vec::new();
88 BlockFraming::encode(&[], &mut buf);
89 let (got, total) = BlockFraming::decode(&buf).unwrap();
90 assert_eq!(got, &[] as &[u8]);
91 assert_eq!(total, FRAMING_OVERHEAD);
92 }
93
94 #[test]
95 fn framing_rejects_truncated_header() {
96 assert!(BlockFraming::decode(&[0u8; 3]).is_err());
97 }
98
99 #[test]
100 fn framing_rejects_short_payload() {
101 let mut buf = Vec::new();
102 BlockFraming::encode(b"hi", &mut buf);
103 let truncated = &buf[..buf.len() - 3];
105 assert!(BlockFraming::decode(truncated).is_err());
106 }
107
108 #[test]
109 fn framing_rejects_corrupt_payload() {
110 let mut buf = Vec::new();
111 BlockFraming::encode(b"data", &mut buf);
112 buf[4] ^= 0xFF;
114 assert!(BlockFraming::decode(&buf).is_err());
115 }
116}