1use crate::config::{FecMode, Modulation, OfdmConfig};
4use crate::crc::crc16_ccitt;
5
6#[derive(Clone, Debug)]
7pub struct PacketInfo {
8 pub version: u8,
10 pub mod_id: u8,
12 pub session_id: u16,
14 pub frag_index: u8,
16 pub frag_count: u8,
18 pub payload: Vec<u8>,
20}
21
22pub fn split_payload(payload: &[u8], chunk_size: usize) -> Vec<Vec<u8>> {
30 payload
31 .chunks(chunk_size)
32 .map(|c| c.to_vec())
33 .collect::<Vec<_>>()
34}
35
36pub fn build_packet_bytes(
46 payload: &[u8],
47 frag_index: u8,
48 frag_count: u8,
49 cfg: &OfdmConfig,
50) -> Vec<u8> {
51 let mut body = Vec::with_capacity(11 + payload.len());
52 body.extend_from_slice(&[0xA5, 0x5A]);
53 body.push(1); body.push(cfg.modulation.mod_id());
55 body.extend_from_slice(&cfg.session_id.to_be_bytes());
56 body.push(frag_index);
57 body.push(frag_count);
58 body.push(payload.len() as u8);
59 body.extend_from_slice(payload);
60 let crc = crc16_ccitt(&body);
61 body.extend_from_slice(&crc.to_be_bytes());
62 body
63}
64
65pub fn parse_packet_bytes(rx: &[u8]) -> Option<(PacketInfo, usize)> {
72 if rx.len() < 11 || rx[0] != 0xA5 || rx[1] != 0x5A {
73 return None;
74 }
75 let plen = rx[8] as usize;
76 let total = 9 + plen + 2;
77 if rx.len() < total {
78 return None;
79 }
80 let body = &rx[..9 + plen];
81 let rx_crc = u16::from_be_bytes([rx[9 + plen], rx[10 + plen]]);
82 if crc16_ccitt(body) != rx_crc {
83 return None;
84 }
85 Some((
86 PacketInfo {
87 version: rx[2],
88 mod_id: rx[3],
89 session_id: u16::from_be_bytes([rx[4], rx[5]]),
90 frag_index: rx[6],
91 frag_count: rx[7],
92 payload: rx[9..9 + plen].to_vec(),
93 },
94 total,
95 ))
96}
97
98#[derive(Clone, Debug)]
99pub struct PacketParseAttempt {
100 pub preamble_ok: bool,
102 pub enough_for_header: bool,
104 pub payload_len: Option<usize>,
106 pub total_len: Option<usize>,
108 pub enough_for_total: bool,
110 pub crc_ok: bool,
112 pub parsed: Option<PacketInfo>,
114}
115
116pub fn inspect_packet_bytes(rx: &[u8]) -> PacketParseAttempt {
117 if rx.len() < 9 {
118 return PacketParseAttempt {
119 preamble_ok: rx.len() >= 2 && rx[0] == 0xA5 && rx[1] == 0x5A,
120 enough_for_header: false,
121 payload_len: None,
122 total_len: None,
123 enough_for_total: false,
124 crc_ok: false,
125 parsed: None,
126 };
127 }
128
129 let preamble_ok = rx[0] == 0xA5 && rx[1] == 0x5A;
130 let plen = rx[8] as usize;
131 let total = 9 + plen + 2;
132 let enough_for_total = rx.len() >= total;
133 let crc_ok = if preamble_ok && enough_for_total {
134 let body = &rx[..9 + plen];
135 let rx_crc = u16::from_be_bytes([rx[9 + plen], rx[10 + plen]]);
136 crc16_ccitt(body) == rx_crc
137 } else {
138 false
139 };
140 let parsed = if preamble_ok && crc_ok {
141 Some(PacketInfo {
142 version: rx[2],
143 mod_id: rx[3],
144 session_id: u16::from_be_bytes([rx[4], rx[5]]),
145 frag_index: rx[6],
146 frag_count: rx[7],
147 payload: rx[9..9 + plen].to_vec(),
148 })
149 } else {
150 None
151 };
152 PacketParseAttempt {
153 preamble_ok,
154 enough_for_header: true,
155 payload_len: Some(plen),
156 total_len: Some(total),
157 enough_for_total,
158 crc_ok,
159 parsed,
160 }
161}
162
163pub fn bytes_to_bits(bytes: &[u8]) -> Vec<u8> {
170 let mut bits = Vec::with_capacity(bytes.len() * 8);
171 for &b in bytes {
172 for shift in (0..8).rev() {
173 bits.push((b >> shift) & 1);
174 }
175 }
176 bits
177}
178
179pub fn bits_to_bytes(bits: &[u8]) -> Vec<u8> {
186 let nbytes = bits.len() / 8;
187 let mut out = vec![0u8; nbytes];
188 for i in 0..nbytes {
189 let mut v = 0u8;
190 for b in 0..8 {
191 v = (v << 1) | (bits[i * 8 + b] & 1);
192 }
193 out[i] = v;
194 }
195 out
196}
197
198pub fn fec_encoded_bits_len(raw_bits_len: usize, mode: FecMode) -> usize {
199 match mode {
200 FecMode::None => raw_bits_len,
201 FecMode::Hamming74 => raw_bits_len.div_ceil(4) * 7,
202 }
203}
204
205pub fn fec_encode_bits(bits: &[u8], mode: FecMode) -> Vec<u8> {
206 match mode {
207 FecMode::None => bits.to_vec(),
208 FecMode::Hamming74 => {
209 let mut out = Vec::with_capacity(fec_encoded_bits_len(bits.len(), mode));
210 let mut i = 0usize;
211 while i < bits.len() {
212 let d1 = bits.get(i).copied().unwrap_or(0) & 1;
213 let d2 = bits.get(i + 1).copied().unwrap_or(0) & 1;
214 let d3 = bits.get(i + 2).copied().unwrap_or(0) & 1;
215 let d4 = bits.get(i + 3).copied().unwrap_or(0) & 1;
216 let p1 = d1 ^ d2 ^ d4;
217 let p2 = d1 ^ d3 ^ d4;
218 let p3 = d2 ^ d3 ^ d4;
219 out.extend_from_slice(&[p1, p2, d1, p3, d2, d3, d4]);
220 i += 4;
221 }
222 out
223 }
224 }
225}
226
227pub fn fec_decode_bits(bits: &[u8], mode: FecMode) -> Vec<u8> {
228 match mode {
229 FecMode::None => bits.to_vec(),
230 FecMode::Hamming74 => {
231 let mut out = Vec::with_capacity((bits.len() / 7) * 4);
232 for chunk in bits.chunks_exact(7) {
233 let mut c = [
234 chunk[0] & 1,
235 chunk[1] & 1,
236 chunk[2] & 1,
237 chunk[3] & 1,
238 chunk[4] & 1,
239 chunk[5] & 1,
240 chunk[6] & 1,
241 ];
242 let s1 = c[0] ^ c[2] ^ c[4] ^ c[6];
243 let s2 = c[1] ^ c[2] ^ c[5] ^ c[6];
244 let s3 = c[3] ^ c[4] ^ c[5] ^ c[6];
245 let syndrome = (s1 | (s2 << 1) | (s3 << 2)) as usize;
246 if (1..=7).contains(&syndrome) {
247 c[syndrome - 1] ^= 1;
248 }
249 out.extend_from_slice(&[c[2], c[4], c[5], c[6]]);
250 }
251 out
252 }
253 }
254}
255
256pub fn modulation_from_id(id: u8) -> Option<Modulation> {
263 match id {
264 1 => Some(Modulation::Bpsk),
265 2 => Some(Modulation::Qpsk),
266 _ => None,
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn split_payload_chunks_correctly() {
277 let p: Vec<u8> = (0..10).collect();
278 let chunks = split_payload(&p, 4);
279 assert_eq!(chunks.len(), 3);
280 assert_eq!(chunks[0], vec![0, 1, 2, 3]);
281 assert_eq!(chunks[1], vec![4, 5, 6, 7]);
282 assert_eq!(chunks[2], vec![8, 9]);
283 }
284
285 #[test]
286 fn bits_bytes_roundtrip() {
288 let data = vec![0x00, 0xA5, 0x5A, 0xFF, 0x13];
289 let bits = bytes_to_bits(&data);
290 let back = bits_to_bytes(&bits);
291 assert_eq!(back, data);
292 }
293
294 #[test]
295 fn packet_build_parse_roundtrip() {
297 let cfg = OfdmConfig::default();
298 let payload = vec![1, 2, 3, 4, 5];
299 let pkt = build_packet_bytes(&payload, 2, 9, &cfg);
300 let (info, used) = parse_packet_bytes(&pkt).expect("packet must parse");
301 assert_eq!(used, pkt.len());
302 assert_eq!(info.version, 1);
303 assert_eq!(info.mod_id, cfg.modulation.mod_id());
304 assert_eq!(info.session_id, cfg.session_id);
305 assert_eq!(info.frag_index, 2);
306 assert_eq!(info.frag_count, 9);
307 assert_eq!(info.payload, payload);
308 }
309
310 #[test]
311 fn packet_crc_error_is_rejected() {
313 let cfg = OfdmConfig::default();
314 let payload = vec![10, 20, 30];
315 let mut pkt = build_packet_bytes(&payload, 0, 1, &cfg);
316 let n = pkt.len();
317 pkt[n - 1] ^= 0x01;
318 assert!(parse_packet_bytes(&pkt).is_none());
319 }
320
321 #[test]
322 fn hamming74_roundtrip_and_single_bit_correction() {
323 let raw = bytes_to_bits(&[0xA5, 0x5A, 0x13, 0x7C]);
324 let mut enc = fec_encode_bits(&raw, FecMode::Hamming74);
325 assert_eq!(enc.len(), raw.len() / 4 * 7);
326 enc[5] ^= 1;
327 enc[20] ^= 1;
328 let dec = fec_decode_bits(&enc, FecMode::Hamming74);
329 assert_eq!(dec, raw);
330 }
331}
332
333