use super::DataError;
#[derive(Debug)]
pub struct Daf {
pub nd: usize,
pub ni: usize,
pub summaries: Vec<Summary>,
}
#[derive(Debug)]
pub struct Summary {
pub start_et: f64,
pub end_et: f64,
pub target_id: i32,
pub center_id: i32,
pub frame_id: i32,
pub data_type: i32,
pub start_word: usize,
pub end_word: usize,
}
impl Daf {
pub fn parse(data: &[u8]) -> Result<Self, DataError> {
if data.len() < 1024 {
return Err(DataError::Parse(format!(
"DAF file too small ({} bytes)",
data.len()
)));
}
let locid = std::str::from_utf8(&data[0..8]).unwrap_or("").trim();
if !locid.starts_with("DAF") {
return Err(DataError::Parse(format!(
"Not a DAF file (locator = {:?})",
locid
)));
}
let nd_le = read_i32_le(data, 8);
let ni_le = read_i32_le(data, 12);
let nd_be = read_i32_be(data, 8);
let ni_be = read_i32_be(data, 12);
let (le, nd, ni) = if nd_le > 0 && nd_le <= 100 && ni_le > 0 && ni_le <= 100 {
(true, nd_le as usize, ni_le as usize)
} else if nd_be > 0 && nd_be <= 100 && ni_be > 0 && ni_be <= 100 {
(false, nd_be as usize, ni_be as usize)
} else {
return Err(DataError::Parse(format!(
"Cannot determine DAF endianness: ND/NI LE=({},{}), BE=({},{})",
nd_le, ni_le, nd_be, ni_be
)));
};
let read_i32 = if le { read_i32_le } else { read_i32_be };
let read_f64 = if le { read_f64_le } else { read_f64_be };
let fward = read_i32(data, 76) as usize;
if nd != 2 || ni != 6 {
return Err(DataError::Parse(format!(
"Expected SPK format (ND=2, NI=6), got ND={}, NI={}",
nd, ni
)));
}
let ss = nd + ni.div_ceil(2);
let mut summaries = Vec::new();
let mut rec = fward;
while rec > 0 {
let rec_offset = (rec - 1) * 1024;
if rec_offset + 1024 > data.len() {
return Err(DataError::Parse(format!(
"Summary record {} extends past EOF",
rec
)));
}
let next = read_f64(data, rec_offset) as i64 as usize;
let _prev = read_f64(data, rec_offset + 8);
let nsum = read_f64(data, rec_offset + 16) as usize;
for i in 0..nsum {
let off = rec_offset + 24 + i * ss * 8;
if off + ss * 8 > data.len() {
return Err(DataError::Parse(format!(
"Summary {} in record {} extends past EOF",
i, rec
)));
}
let start_et = read_f64(data, off);
let end_et = read_f64(data, off + 8);
let int_off = off + nd * 8;
let target_id = read_i32(data, int_off);
let center_id = read_i32(data, int_off + 4);
let frame_id = read_i32(data, int_off + 8);
let data_type = read_i32(data, int_off + 12);
let start_word = read_i32(data, int_off + 16) as usize;
let end_word = read_i32(data, int_off + 20) as usize;
summaries.push(Summary {
start_et,
end_et,
target_id,
center_id,
frame_id,
data_type,
start_word,
end_word,
});
}
rec = next;
}
Ok(Daf { nd, ni, summaries })
}
#[inline]
pub fn read_f64_at_word(&self, data: &[u8], word: usize) -> f64 {
let offset = (word - 1) * 8;
read_f64_le(data, offset)
}
}
#[inline]
fn read_i32_le(data: &[u8], offset: usize) -> i32 {
i32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
}
#[inline]
fn read_i32_be(data: &[u8], offset: usize) -> i32 {
i32::from_be_bytes(data[offset..offset + 4].try_into().unwrap())
}
#[inline]
fn read_f64_le(data: &[u8], offset: usize) -> f64 {
f64::from_le_bytes(data[offset..offset + 8].try_into().unwrap())
}
#[inline]
fn read_f64_be(data: &[u8], offset: usize) -> f64 {
f64::from_be_bytes(data[offset..offset + 8].try_into().unwrap())
}
#[cfg(test)]
mod tests {
use super::*;
fn write_i32_le(buf: &mut [u8], offset: usize, val: i32) {
buf[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
fn write_f64_le(buf: &mut [u8], offset: usize, val: f64) {
buf[offset..offset + 8].copy_from_slice(&val.to_le_bytes());
}
fn minimal_valid_daf() -> Vec<u8> {
let mut buf = vec![0u8; 1024];
buf[0..8].copy_from_slice(b"DAF/SPK ");
write_i32_le(&mut buf, 8, 2); write_i32_le(&mut buf, 12, 6); write_i32_le(&mut buf, 76, 0); buf
}
#[allow(clippy::too_many_arguments)]
fn daf_with_one_summary(
start_et: f64,
end_et: f64,
target_id: i32,
center_id: i32,
frame_id: i32,
data_type: i32,
start_word: i32,
end_word: i32,
) -> Vec<u8> {
let mut buf = vec![0u8; 2048];
buf[0..8].copy_from_slice(b"DAF/SPK ");
write_i32_le(&mut buf, 8, 2); write_i32_le(&mut buf, 12, 6); write_i32_le(&mut buf, 76, 2); let rec = &mut buf[1024..];
write_f64_le(rec, 0, 0.0); write_f64_le(rec, 8, 0.0); write_f64_le(rec, 16, 1.0); write_f64_le(rec, 24, start_et);
write_f64_le(rec, 32, end_et);
write_i32_le(rec, 40, target_id);
write_i32_le(rec, 44, center_id);
write_i32_le(rec, 48, frame_id);
write_i32_le(rec, 52, data_type);
write_i32_le(rec, 56, start_word);
write_i32_le(rec, 60, end_word);
buf
}
#[test]
fn parse_too_small_returns_error() {
let data = vec![0u8; 512]; let result = Daf::parse(&data);
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(msg.contains("too small") || msg.contains("DAF"));
}
#[test]
fn parse_wrong_magic_returns_error() {
let mut buf = vec![0u8; 1024];
buf[0..8].copy_from_slice(b"NOTADAF!");
let result = Daf::parse(&buf);
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(msg.contains("Not a DAF"));
}
#[test]
fn parse_bad_nd_ni_returns_error() {
let mut buf = vec![0u8; 1024];
buf[0..8].copy_from_slice(b"DAF/SPK ");
write_i32_le(&mut buf, 8, 0); write_i32_le(&mut buf, 12, 0); let result = Daf::parse(&buf);
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("endianness")
|| msg.contains("ND")
|| msg.contains("NI")
|| msg.contains("parse error")
);
}
#[test]
fn parse_non_spk_format_returns_error() {
let mut buf = vec![0u8; 1024];
buf[0..8].copy_from_slice(b"DAF/CK ");
write_i32_le(&mut buf, 8, 3); write_i32_le(&mut buf, 12, 4); write_i32_le(&mut buf, 76, 0); let result = Daf::parse(&buf);
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(
msg.contains("ND")
|| msg.contains("NI")
|| msg.contains("SPK")
|| msg.contains("parse error")
);
}
#[test]
fn parse_fward_beyond_eof_returns_error() {
let mut buf = minimal_valid_daf();
write_i32_le(&mut buf, 76, 99);
let result = Daf::parse(&buf);
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(msg.contains("EOF") || msg.contains("extends past") || msg.contains("parse error"));
}
#[test]
fn parse_minimal_valid_daf_empty_summaries() {
let buf = minimal_valid_daf();
let daf = Daf::parse(&buf).expect("should parse valid DAF");
assert_eq!(daf.nd, 2);
assert_eq!(daf.ni, 6);
assert!(daf.summaries.is_empty());
}
#[test]
fn parse_daf_with_one_summary() {
let buf = daf_with_one_summary(
0.0, 86400.0, 10, 0, 1, 2, 257, 300, );
let daf = Daf::parse(&buf).expect("should parse DAF with one summary");
assert_eq!(daf.nd, 2);
assert_eq!(daf.ni, 6);
assert_eq!(daf.summaries.len(), 1);
let s = &daf.summaries[0];
assert!((s.start_et - 0.0).abs() < 1e-10);
assert!((s.end_et - 86400.0).abs() < 1e-10);
assert_eq!(s.target_id, 10);
assert_eq!(s.center_id, 0);
assert_eq!(s.frame_id, 1);
assert_eq!(s.data_type, 2);
assert_eq!(s.start_word, 257);
assert_eq!(s.end_word, 300);
}
#[test]
fn read_f64_at_word_reads_correct_value() {
let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let mut buf = vec![0u8; 64];
let val = 42.0_f64;
buf[0..8].copy_from_slice(&val.to_le_bytes());
let result = daf.read_f64_at_word(&buf, 1);
assert!((result - 42.0).abs() < 1e-15);
}
#[test]
fn read_f64_at_word_second_word() {
let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let mut buf = vec![0u8; 64];
let val = 99.5_f64;
buf[8..16].copy_from_slice(&val.to_le_bytes());
let result = daf.read_f64_at_word(&buf, 2);
assert!((result - 99.5).abs() < 1e-15);
}
#[test]
fn parse_summary_truncated_returns_error() {
let mut buf = vec![0u8; 2048];
buf[0..8].copy_from_slice(b"DAF/SPK ");
write_i32_le(&mut buf, 8, 2); write_i32_le(&mut buf, 12, 6); write_i32_le(&mut buf, 76, 2); let rec = &mut buf[1024..];
write_f64_le(rec, 0, 0.0);
write_f64_le(rec, 8, 0.0);
write_f64_le(rec, 16, 100.0); let result = Daf::parse(&buf);
let _ = result;
}
}