1use crate::physical::PhysicalRead;
28use crate::physical::PhysicalWrite;
29
30pub const ASM_TM: [u8; 4] = [0x1A, 0xCF, 0xFC, 0x1D];
32
33pub const ASM_TM_INVERTED: [u8; 4] = [0xE5, 0x30, 0x03, 0xE2];
36
37pub const ASM_PROXIMITY1: [u8; 3] = [0xFA, 0xF3, 0x20];
39
40#[derive(Debug, Copy, Clone, Eq, PartialEq)]
42pub enum CaduError {
43 BufferTooSmall {
45 required: usize,
47 provided: usize,
49 },
50 InputTooShort,
52 AsmMismatch,
54}
55
56impl core::fmt::Display for CaduError {
57 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58 match self {
59 Self::BufferTooSmall { required, provided } => {
60 write!(f, "buffer too small: need {required}, have {provided}")
61 }
62 Self::InputTooShort => write!(f, "input too short"),
63 Self::AsmMismatch => write!(f, "ASM mismatch"),
64 }
65 }
66}
67
68impl core::error::Error for CaduError {}
69
70pub fn encode_cadu(asm: &[u8], frame: &[u8], output: &mut [u8]) -> Result<usize, CaduError> {
75 let total = asm.len() + frame.len();
76 if output.len() < total {
77 return Err(CaduError::BufferTooSmall {
78 required: total,
79 provided: output.len(),
80 });
81 }
82 output[..asm.len()].copy_from_slice(asm);
83 output[asm.len()..total].copy_from_slice(frame);
84 Ok(total)
85}
86
87pub fn decode_cadu<'a>(asm: &[u8], cadu: &'a [u8]) -> Result<&'a [u8], CaduError> {
91 if cadu.len() < asm.len() {
92 return Err(CaduError::InputTooShort);
93 }
94 if &cadu[..asm.len()] != asm {
95 return Err(CaduError::AsmMismatch);
96 }
97 Ok(&cadu[asm.len()..])
98}
99
100pub struct FrameSync<'a> {
107 asm: &'a [u8],
108 frame_len: usize,
109}
110
111impl<'a> FrameSync<'a> {
112 pub fn new(asm: &'a [u8], frame_len: usize) -> Self {
117 Self { asm, frame_len }
118 }
119
120 pub fn cadu_len(&self) -> usize {
122 self.asm.len() + self.frame_len
123 }
124
125 pub fn find_frame<'b>(&self, data: &'b [u8]) -> Option<(usize, &'b [u8])> {
132 let cadu_len = self.cadu_len();
133 if data.len() < cadu_len {
134 return None;
135 }
136
137 let search_end = data.len() - cadu_len + 1;
138 for offset in 0..search_end {
139 if &data[offset..offset + self.asm.len()] == self.asm {
140 let frame_start = offset + self.asm.len();
141 let frame_end = frame_start + self.frame_len;
142 return Some((offset, &data[frame_start..frame_end]));
143 }
144 }
145 None
146 }
147
148 pub fn find_all_frames<'b>(&'b self, data: &'b [u8]) -> FrameIter<'b> {
154 FrameIter {
155 sync: self,
156 data,
157 pos: 0,
158 }
159 }
160}
161
162pub struct FrameIter<'a> {
164 sync: &'a FrameSync<'a>,
165 data: &'a [u8],
166 pos: usize,
167}
168
169impl<'a> Iterator for FrameIter<'a> {
170 type Item = (usize, &'a [u8]);
171
172 fn next(&mut self) -> Option<Self::Item> {
173 let remaining = &self.data[self.pos..];
174 let result = self.sync.find_frame(remaining);
175 if let Some((rel_offset, frame)) = result {
176 let abs_offset = self.pos + rel_offset;
177 self.pos = abs_offset + self.sync.asm.len();
179 Some((abs_offset, frame))
180 } else {
181 None
182 }
183 }
184}
185
186pub struct AsmFramer {
188 asm: &'static [u8],
189}
190
191impl AsmFramer {
192 pub fn tm() -> Self {
194 Self { asm: &ASM_TM }
195 }
196
197 pub fn proximity1() -> Self {
199 Self {
200 asm: &ASM_PROXIMITY1,
201 }
202 }
203}
204
205impl crate::coding::Framer for AsmFramer {
206 type Error = CaduError;
207
208 fn frame(&self, data: &[u8], output: &mut [u8]) -> Result<usize, Self::Error> {
209 encode_cadu(self.asm, data, output)
210 }
211}
212
213pub struct AsmDeframer {
215 asm: &'static [u8],
216 frame_len: usize,
217}
218
219impl AsmDeframer {
220 pub fn tm(frame_len: usize) -> Self {
222 Self {
223 asm: &ASM_TM,
224 frame_len,
225 }
226 }
227
228 pub fn proximity1(frame_len: usize) -> Self {
230 Self {
231 asm: &ASM_PROXIMITY1,
232 frame_len,
233 }
234 }
235}
236
237impl crate::coding::Deframer for AsmDeframer {
238 type Error = CaduError;
239
240 fn deframe<'a>(&self, data: &'a [u8], output: &mut [u8]) -> Result<usize, Self::Error> {
241 let sync = FrameSync::new(self.asm, self.frame_len);
242 let (_offset, frame) = sync.find_frame(data).ok_or(CaduError::AsmMismatch)?;
243 let len = frame.len().min(output.len());
244 output[..len].copy_from_slice(&frame[..len]);
245 Ok(len)
246 }
247}
248
249pub struct AsmWriter<W, const BUF: usize> {
251 writer: W,
252 asm: &'static [u8],
253 buffer: [u8; BUF],
254}
255
256#[derive(Debug, Clone)]
258pub enum AsmWriterError<E> {
259 Cadu(CaduError),
261 Writer(E),
263}
264
265impl<E: core::fmt::Display> core::fmt::Display for AsmWriterError<E> {
266 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
267 match self {
268 Self::Cadu(e) => write!(f, "ASM: {e:?}"),
269 Self::Writer(e) => write!(f, "writer: {e}"),
270 }
271 }
272}
273
274impl<E: core::error::Error> core::error::Error for AsmWriterError<E> {}
275
276impl<W, const BUF: usize> AsmWriter<W, BUF> {
277 pub fn tm(writer: W) -> Self {
279 Self {
280 writer,
281 asm: &ASM_TM,
282 buffer: [0u8; BUF],
283 }
284 }
285
286 pub fn proximity1(writer: W) -> Self {
288 Self {
289 writer,
290 asm: &ASM_PROXIMITY1,
291 buffer: [0u8; BUF],
292 }
293 }
294}
295
296impl<W: PhysicalWrite, const BUF: usize> PhysicalWrite for AsmWriter<W, BUF> {
297 type Error = AsmWriterError<W::Error>;
298
299 async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
300 let len = encode_cadu(self.asm, data, &mut self.buffer).map_err(AsmWriterError::Cadu)?;
301 self.writer
302 .write(&self.buffer[..len])
303 .await
304 .map_err(AsmWriterError::Writer)
305 }
306}
307
308pub struct FrameSyncReader<R, const BUF: usize> {
311 reader: R,
312 asm: &'static [u8],
313 frame_len: usize,
314 buffer: [u8; BUF],
315}
316
317#[derive(Debug, Clone)]
319pub enum FrameSyncReaderError<E> {
320 NoFrame,
322 Reader(E),
324}
325
326impl<E: core::fmt::Display> core::fmt::Display for FrameSyncReaderError<E> {
327 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
328 match self {
329 Self::NoFrame => write!(f, "no ASM-aligned frame found"),
330 Self::Reader(e) => write!(f, "reader: {e}"),
331 }
332 }
333}
334
335impl<E: core::error::Error> core::error::Error for FrameSyncReaderError<E> {}
336
337impl<R, const BUF: usize> FrameSyncReader<R, BUF> {
338 pub fn tm(reader: R, frame_len: usize) -> Self {
341 Self {
342 reader,
343 asm: &ASM_TM,
344 frame_len,
345 buffer: [0u8; BUF],
346 }
347 }
348
349 pub fn proximity1(reader: R, frame_len: usize) -> Self {
351 Self {
352 reader,
353 asm: &ASM_PROXIMITY1,
354 frame_len,
355 buffer: [0u8; BUF],
356 }
357 }
358}
359
360impl<R: PhysicalRead, const BUF: usize> PhysicalRead for FrameSyncReader<R, BUF> {
361 type Error = FrameSyncReaderError<R::Error>;
362
363 async fn read(&mut self, output: &mut [u8]) -> Result<usize, Self::Error> {
364 let len = self
365 .reader
366 .read(&mut self.buffer)
367 .await
368 .map_err(FrameSyncReaderError::Reader)?;
369
370 let sync = FrameSync::new(self.asm, self.frame_len);
371 let Some((_offset, frame)) = sync.find_frame(&self.buffer[..len]) else {
372 return Err(FrameSyncReaderError::NoFrame);
373 };
374
375 let copy_len = frame.len().min(output.len());
376 output[..copy_len].copy_from_slice(&frame[..copy_len]);
377 Ok(copy_len)
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn encode_tm_cadu() {
387 let frame = [0xAA; 16];
388 let mut buf = [0u8; 20];
389 let len = encode_cadu(&ASM_TM, &frame, &mut buf).unwrap();
390
391 assert_eq!(len, 20);
392 assert_eq!(&buf[..4], &ASM_TM);
393 assert_eq!(&buf[4..20], &frame);
394 }
395
396 #[test]
397 fn encode_proximity1_cadu() {
398 let frame = [0xBB; 10];
399 let mut buf = [0u8; 13];
400 let len = encode_cadu(&ASM_PROXIMITY1, &frame, &mut buf).unwrap();
401
402 assert_eq!(len, 13);
403 assert_eq!(&buf[..3], &ASM_PROXIMITY1);
404 assert_eq!(&buf[3..13], &frame);
405 }
406
407 #[test]
408 fn encode_buffer_too_small() {
409 let frame = [0u8; 16];
410 let mut buf = [0u8; 10]; let err = encode_cadu(&ASM_TM, &frame, &mut buf);
412 assert!(matches!(
413 err,
414 Err(CaduError::BufferTooSmall {
415 required: 20,
416 provided: 10,
417 })
418 ));
419 }
420
421 #[test]
422 fn decode_tm_cadu() {
423 let mut cadu = [0u8; 20];
424 cadu[..4].copy_from_slice(&ASM_TM);
425 cadu[4..].fill(0xCC);
426
427 let frame = decode_cadu(&ASM_TM, &cadu).unwrap();
428 assert_eq!(frame.len(), 16);
429 assert!(frame.iter().all(|&b| b == 0xCC));
430 }
431
432 #[test]
433 fn decode_asm_mismatch() {
434 let cadu = [0u8; 20]; let err = decode_cadu(&ASM_TM, &cadu);
436 assert!(matches!(err, Err(CaduError::AsmMismatch)));
437 }
438
439 #[test]
440 fn decode_input_too_short() {
441 let cadu = [0x1A, 0xCF]; let err = decode_cadu(&ASM_TM, &cadu);
443 assert!(matches!(err, Err(CaduError::InputTooShort)));
444 }
445
446 #[test]
447 fn frame_sync_find_single() {
448 let frame_len = 8;
449 let sync = FrameSync::new(&ASM_TM, frame_len);
450
451 let mut data = [0u8; 32];
453 data[5..9].copy_from_slice(&ASM_TM);
454 data[9..17].fill(0xDD);
455
456 let (offset, frame) = sync.find_frame(&data).unwrap();
457 assert_eq!(offset, 5);
458 assert_eq!(frame.len(), 8);
459 assert!(frame.iter().all(|&b| b == 0xDD));
460 }
461
462 #[test]
463 fn frame_sync_find_multiple() {
464 let frame_len = 4;
465 let sync = FrameSync::new(&ASM_TM, frame_len);
466 let cadu_len = sync.cadu_len(); let mut data = [0u8; 16];
470 data[0..4].copy_from_slice(&ASM_TM);
472 data[4..8].fill(0x11);
473 data[8..12].copy_from_slice(&ASM_TM);
475 data[12..16].fill(0x22);
476
477 let frames: heapless::Vec<(usize, &[u8]), 4> = sync.find_all_frames(&data).collect();
478
479 assert_eq!(frames.len(), 2);
480 assert_eq!(frames[0].0, 0);
481 assert!(frames[0].1.iter().all(|&b| b == 0x11));
482 assert_eq!(frames[1].0, cadu_len);
483 assert!(frames[1].1.iter().all(|&b| b == 0x22));
484 }
485
486 #[test]
487 fn frame_sync_no_match() {
488 let sync = FrameSync::new(&ASM_TM, 8);
489 let data = [0u8; 32]; assert!(sync.find_frame(&data).is_none());
491 }
492
493 #[test]
494 fn frame_sync_incomplete_frame() {
495 let sync = FrameSync::new(&ASM_TM, 100);
496 let mut data = [0u8; 20];
498 data[0..4].copy_from_slice(&ASM_TM);
499 assert!(sync.find_frame(&data).is_none());
500 }
501
502 #[test]
503 fn roundtrip_encode_decode() {
504 let frame = [0x42; 64];
505 let mut cadu_buf = [0u8; 68];
506
507 let len = encode_cadu(&ASM_TM, &frame, &mut cadu_buf).unwrap();
508 let decoded = decode_cadu(&ASM_TM, &cadu_buf[..len]).unwrap();
509
510 assert_eq!(decoded, &frame);
511 }
512
513 #[test]
514 fn proximity1_roundtrip() {
515 let frame = [0x77; 32];
516 let mut cadu_buf = [0u8; 35];
517
518 let len = encode_cadu(&ASM_PROXIMITY1, &frame, &mut cadu_buf).unwrap();
519 let decoded = decode_cadu(&ASM_PROXIMITY1, &cadu_buf[..len]).unwrap();
520
521 assert_eq!(decoded, &frame);
522 }
523}