use oxideav_core::{Error, Result};
pub const SYNC_WORD_PLAIN: u16 = 0xAC40;
pub const SYNC_WORD_CRC: u16 = 0xAC41;
#[derive(Debug, Clone, Copy)]
pub struct SyncFrame<'a> {
pub payload: &'a [u8],
pub crc_protected: bool,
pub sync_offset: usize,
pub total_len: usize,
}
pub fn find_sync_frame(data: &[u8]) -> Option<SyncFrame<'_>> {
if data.len() < 4 {
return None;
}
let mut i = 0usize;
while i + 4 <= data.len() {
let sync = u16::from_be_bytes([data[i], data[i + 1]]);
if sync == SYNC_WORD_PLAIN || sync == SYNC_WORD_CRC {
if let Ok(frame) = try_parse_frame_at(data, i) {
return Some(frame);
}
}
i += 1;
}
None
}
fn try_parse_frame_at(data: &[u8], offset: usize) -> Result<SyncFrame<'_>> {
if offset + 4 > data.len() {
return Err(Error::invalid("ac4: sync frame truncated"));
}
let sync = u16::from_be_bytes([data[offset], data[offset + 1]]);
let crc_protected = sync == SYNC_WORD_CRC;
if !crc_protected && sync != SYNC_WORD_PLAIN {
return Err(Error::invalid("ac4: not a sync word"));
}
let fs_short = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as u32;
let (frame_size, header_len) = if fs_short == 0xFFFF {
if offset + 7 > data.len() {
return Err(Error::invalid("ac4: extended frame_size truncated"));
}
let fs_ext = ((data[offset + 4] as u32) << 16)
| ((data[offset + 5] as u32) << 8)
| (data[offset + 6] as u32);
(fs_ext, 7)
} else {
(fs_short, 4)
};
let crc_len = if crc_protected { 2 } else { 0 };
let payload_start = offset + header_len;
let payload_end = payload_start + frame_size as usize;
if payload_end + crc_len > data.len() {
return Err(Error::invalid("ac4: payload extends past buffer"));
}
Ok(SyncFrame {
payload: &data[payload_start..payload_end],
crc_protected,
sync_offset: offset,
total_len: payload_end + crc_len - offset,
})
}
pub fn crc16(input: &[u8]) -> u16 {
const POLY: u32 = 0x8005;
let mut crc: u32 = 0x0000;
for &b in input {
crc ^= (b as u32) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
}
(crc & 0xFFFF) as u16
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_plain_sync() {
let data = [0xAC, 0x40, 0x00, 0x03, 0x11, 0x22, 0x33];
let f = find_sync_frame(&data).expect("should sync");
assert_eq!(f.payload, &[0x11, 0x22, 0x33]);
assert!(!f.crc_protected);
assert_eq!(f.total_len, 7);
}
#[test]
fn parse_extended_size() {
let fs: u32 = 0x10_0000;
let mut data = vec![0xAC, 0x40, 0xFF, 0xFF];
data.push(((fs >> 16) & 0xFF) as u8);
data.push(((fs >> 8) & 0xFF) as u8);
data.push((fs & 0xFF) as u8);
data.extend(std::iter::repeat(0u8).take(fs as usize));
let f = find_sync_frame(&data).expect("should sync");
assert_eq!(f.payload.len(), fs as usize);
assert_eq!(f.total_len, fs as usize + 7);
}
#[test]
fn crc16_zero_empty() {
assert_eq!(crc16(&[]), 0x0000);
}
}