use byteorder::{ByteOrder, LittleEndian};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct MassCalRecord {
pub coefficients: Vec<f64>,
}
pub fn parse(path: &Path) -> crate::Result<Vec<MassCalRecord>> {
let bytes = std::fs::read(path)?;
let header_size = 72;
let stride = 84;
if bytes.len() < header_size {
return Ok(vec![]);
}
let payload = &bytes[header_size..];
let count = payload.len() / stride;
let mut records = Vec::with_capacity(count);
for i in 0..count {
let offset = i * stride;
let num_coeff = LittleEndian::read_u32(&payload[offset..offset + 4]) as usize;
let mut coeffs = Vec::with_capacity(num_coeff);
for j in 0..std::cmp::min(num_coeff, 10) {
coeffs.push(LittleEndian::read_f64(
&payload[offset + 4 + j * 8..offset + 12 + j * 8],
));
}
records.push(MassCalRecord {
coefficients: coeffs,
});
}
Ok(records)
}
#[cfg(test)]
mod tests {
use super::*;
fn write_temp(name: &str, contents: &[u8]) -> std::path::PathBuf {
let mut path = std::env::temp_dir();
path.push(format!(
"openaraw-msmasscal-test-{}-{}",
std::process::id(),
name
));
std::fs::write(&path, contents).expect("write temp file");
path
}
fn record_bytes(coeffs: &[f64]) -> Vec<u8> {
let mut buf = Vec::with_capacity(84);
buf.extend_from_slice(&(coeffs.len() as u32).to_le_bytes());
for c in coeffs {
buf.extend_from_slice(&c.to_le_bytes());
}
buf.resize(84, 0);
buf
}
#[test]
fn parses_coefficients_per_record() {
let mut bytes = vec![0u8; 72];
let coeffs_a = [0.0003479961, 12.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
let coeffs_b = [0.0003479973, 12.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
bytes.extend(record_bytes(&coeffs_a));
bytes.extend(record_bytes(&coeffs_b));
let path = write_temp("basic.bin", &bytes);
let records = parse(&path).expect("parse should succeed");
let _ = std::fs::remove_file(&path);
assert_eq!(records.len(), 2);
assert_eq!(records[0].coefficients.len(), 10);
assert!((records[0].coefficients[0] - 0.0003479961).abs() < 1e-12);
assert!((records[1].coefficients[0] - 0.0003479973).abs() < 1e-12);
}
#[test]
fn file_smaller_than_header_returns_no_records() {
let bytes = vec![0u8; 71];
let path = write_temp("truncated.bin", &bytes);
let records = parse(&path).expect("parse should succeed");
let _ = std::fs::remove_file(&path);
assert!(records.is_empty());
}
}