use crate::types::{Frame, Peak};
pub(crate) fn frame_from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Frame> {
Ok(Frame {
id: row.get(0)?,
time: row.get(1)?,
num_scans: row.get(2)?,
num_peaks: row.get(3)?,
tims_id: row.get::<_, i64>(4)? as u64,
scan_mode: row.get::<_, Option<i64>>(5)?.unwrap_or(0) as u32,
msms_type: row.get::<_, Option<i64>>(6)?.unwrap_or(0) as u32,
mz_calibration_id: row.get::<_, Option<i64>>(7)?.unwrap_or(1) as u32,
accumulation_time: row.get(8)?,
summed_intensities: row.get::<_, Option<i64>>(9)?.map(|v| v as u64),
})
}
pub fn checked_block_len(file_len: u64, offset: u64, claimed_len: u64) -> Option<usize> {
let remaining = file_len.checked_sub(offset)?;
if claimed_len > remaining {
return None;
}
usize::try_from(claimed_len).ok()
}
pub fn decode_codec2(inner: &[u8], num_scans: u32, num_peaks: u32) -> Vec<Peak> {
let dsints = num_scans as usize + 2 * num_peaks as usize;
if inner.len() != 4 * dsints {
return Vec::new();
}
let b0 = &inner[0..dsints];
let b1 = &inner[dsints..2 * dsints];
let b2 = &inner[2 * dsints..3 * dsints];
let b3 = &inner[3 * dsints..4 * dsints];
let mut logical = Vec::with_capacity(dsints);
for i in 0..dsints {
let v = u32::from(b0[i])
| (u32::from(b1[i]) << 8)
| (u32::from(b2[i]) << 16)
| (u32::from(b3[i]) << 24);
logical.push(v);
}
let ns = num_scans as usize;
let np = num_peaks as usize;
let (header, peak_stream) = logical.split_at(ns);
let mut peaks = Vec::with_capacity(np);
let mut read: usize = 0;
if ns >= 2 {
'scans: for scan in 0..(ns - 1) {
let peaks_in_scan = (header[scan + 1] / 2) as usize;
let mut accum: u32 = u32::MAX;
for _ in 0..peaks_in_scan {
let (Some(&delta), Some(&intensity)) =
(peak_stream.get(read), peak_stream.get(read + 1))
else {
break 'scans;
};
accum = accum.wrapping_add(delta);
read += 2;
peaks.push(Peak {
scan: scan as u32,
tof: accum,
intensity,
});
}
}
}
let last_scan = ns.saturating_sub(1) as u32;
let mut accum: u32 = u32::MAX;
while peaks.len() < np {
let (Some(&delta), Some(&intensity)) = (peak_stream.get(read), peak_stream.get(read + 1))
else {
break;
};
accum = accum.wrapping_add(delta);
read += 2;
peaks.push(Peak {
scan: last_scan,
tof: accum,
intensity,
});
}
peaks
}
pub fn decode_codec1(
compressed: &[u8],
scan_offsets: &[usize],
num_peaks: u32,
max_peaks_per_scan: usize,
) -> std::result::Result<Vec<Peak>, String> {
let scan_count = scan_offsets.len().saturating_sub(1);
let mut peaks: Vec<Peak> = Vec::with_capacity(num_peaks as usize);
let scratch_cap = max_peaks_per_scan.saturating_mul(4 * 2).max(64);
let mut scratch = Vec::with_capacity(scratch_cap);
for scan in 0..scan_count {
let start = scan_offsets[scan];
let end = scan_offsets[scan + 1];
if start == end {
continue;
}
if end > compressed.len() || start > end {
return Err(format!(
"scan {scan} offsets [{start}..{end}) out of compressed slice (len={})",
compressed.len()
));
}
scratch.clear();
lzf_decompress(&compressed[start..end], &mut scratch)
.map_err(|e| format!("scan {scan}: lzf: {e}"))?;
if scratch.len() % 4 != 0 {
return Err(format!(
"scan {scan}: decompressed size {} not a multiple of 4",
scratch.len()
));
}
let mut tof: u32 = 0;
let mut prev_was_intensity = true;
for chunk in scratch.chunks_exact(4) {
#[allow(clippy::unwrap_used)]
let v = i32::from_le_bytes(chunk.try_into().unwrap());
if v >= 0 {
if prev_was_intensity {
tof = tof.wrapping_add(1);
}
peaks.push(Peak {
scan: scan as u32,
tof: tof.wrapping_sub(1),
intensity: v as u32,
});
prev_was_intensity = true;
} else {
tof = tof.wrapping_add((-v) as u32);
prev_was_intensity = false;
}
}
}
Ok(peaks)
}
pub(crate) fn lzf_decompress(
input: &[u8],
out: &mut Vec<u8>,
) -> std::result::Result<(), &'static str> {
let mut ip = 0;
while ip < input.len() {
let ctrl = input[ip];
ip += 1;
if ctrl < 32 {
let run = ctrl as usize + 1;
if ip + run > input.len() {
return Err("literal run overruns input");
}
out.extend_from_slice(&input[ip..ip + run]);
ip += run;
} else {
let mut length = (ctrl >> 5) as usize;
if length == 7 {
if ip >= input.len() {
return Err("ref length byte missing");
}
length += input[ip] as usize;
ip += 1;
}
length += 2;
if ip >= input.len() {
return Err("ref low byte missing");
}
let ref_distance = (((ctrl & 0x1f) as usize) << 8) | input[ip] as usize;
ip += 1;
let ref_pos = out
.len()
.checked_sub(ref_distance + 1)
.ok_or("ref distance precedes output start")?;
for i in 0..length {
let byte = out[ref_pos + i];
out.push(byte);
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checked_block_len_accepts_len_within_file() {
assert_eq!(checked_block_len(100, 10, 90), Some(90));
assert_eq!(checked_block_len(100, 10, 0), Some(0));
assert_eq!(checked_block_len(100, 100, 0), Some(0));
}
#[test]
fn checked_block_len_rejects_len_past_end_of_file() {
assert_eq!(checked_block_len(100, 10, 91), None);
assert_eq!(checked_block_len(100, 101, 0), None);
assert_eq!(checked_block_len(10, u64::MAX, 1), None);
}
#[test]
fn checked_block_len_never_panics_on_adversarial_input() {
for file_len in [0u64, 1, u64::MAX / 2, u64::MAX] {
for offset in [0u64, 1, u64::MAX / 2, u64::MAX] {
for claimed_len in [0u64, 1, u64::MAX / 2, u64::MAX] {
let _ = checked_block_len(file_len, offset, claimed_len);
}
}
}
}
fn build_inner(logical: &[u32]) -> Vec<u8> {
let n = logical.len();
let mut inner = vec![0u8; 4 * n];
for (i, &v) in logical.iter().enumerate() {
inner[i] = (v & 0xff) as u8;
inner[n + i] = ((v >> 8) & 0xff) as u8;
inner[2 * n + i] = ((v >> 16) & 0xff) as u8;
inner[3 * n + i] = ((v >> 24) & 0xff) as u8;
}
inner
}
#[test]
fn decode_codec2_wrong_length_returns_empty() {
assert!(decode_codec2(&[], 3, 2).is_empty());
assert!(decode_codec2(&[0u8; 27], 3, 2).is_empty());
}
#[test]
fn decode_codec2_corrupt_header_does_not_panic() {
let logical = [0u32, 1000, 1000, 1, 100, 5, 200];
let inner = build_inner(&logical);
let peaks = decode_codec2(&inner, 3, 2);
assert!(peaks.len() <= 2);
}
#[test]
fn decode_codec2_valid_decode() {
let logical = [0u32, 2, 2, 1, 100, 5, 200];
let inner = build_inner(&logical);
let peaks = decode_codec2(&inner, 3, 2);
assert_eq!(peaks.len(), 2);
assert_eq!(
(peaks[0].scan, peaks[0].tof, peaks[0].intensity),
(0, 0, 100)
);
assert_eq!(
(peaks[1].scan, peaks[1].tof, peaks[1].intensity),
(1, 4, 200)
);
}
}