use openmassspec_core::{
write_indexed_mzml, CvTerm, Polarity, RunMetadata, ScanMode, SpectrumRecord, SpectrumSource,
};
fn sha1_hex(message: &[u8]) -> String {
let mut h0: u32 = 0x67452301;
let mut h1: u32 = 0xEFCDAB89;
let mut h2: u32 = 0x98BADCFE;
let mut h3: u32 = 0x10325476;
let mut h4: u32 = 0xC3D2E1F0;
let bit_len = (message.len() as u64) * 8;
let mut padded = message.to_vec();
padded.push(0x80);
while padded.len() % 64 != 56 {
padded.push(0);
}
padded.extend_from_slice(&bit_len.to_be_bytes());
for block in padded.chunks(64) {
let mut w = [0u32; 80];
for (i, word) in w.iter_mut().enumerate().take(16) {
*word = u32::from_be_bytes(block[i * 4..i * 4 + 4].try_into().unwrap());
}
for i in 16..80 {
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
}
let (mut a, mut b, mut c, mut d, mut e) = (h0, h1, h2, h3, h4);
for (i, &wi) in w.iter().enumerate() {
let (f, k) = match i {
0..=19 => ((b & c) | ((!b) & d), 0x5A827999u32),
20..=39 => (b ^ c ^ d, 0x6ED9EBA1),
40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDC),
_ => (b ^ c ^ d, 0xCA62C1D6),
};
let temp = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(k)
.wrapping_add(wi);
e = d;
d = c;
c = b.rotate_left(30);
b = a;
a = temp;
}
h0 = h0.wrapping_add(a);
h1 = h1.wrapping_add(b);
h2 = h2.wrapping_add(c);
h3 = h3.wrapping_add(d);
h4 = h4.wrapping_add(e);
}
[h0, h1, h2, h3, h4]
.iter()
.map(|w| format!("{:08x}", w))
.collect()
}
struct OneSpectrumSource {
meta: RunMetadata,
spectrum: SpectrumRecord,
}
impl OneSpectrumSource {
fn new() -> Self {
Self {
meta: RunMetadata {
source_file_name: "checksum-fixture.raw".into(),
source_file_format: CvTerm::new("MS:1000563", "Thermo RAW format"),
native_id_format: CvTerm::new("MS:1000768", "Thermo nativeID format"),
instrument: CvTerm::new("MS:1001911", "Q Exactive"),
instrument_serial_number: None,
acquisition_software_name: None,
acquisition_software_version: None,
software_name: "checksum-test".into(),
software_version: "0.0.0".into(),
start_timestamp: None,
mobility_array_kind: None,
analyzers: Vec::new(),
},
spectrum: SpectrumRecord {
index: 0,
scan_number: 1,
native_id: "controllerType=0 controllerNumber=1 scan=1".into(),
ms_level: 1,
polarity: Some(Polarity::Positive),
scan_mode: Some(ScanMode::Centroid),
analyzer: None,
filter: None,
retention_time_sec: 0.0,
total_ion_current: None,
base_peak_mz: None,
base_peak_intensity: None,
low_mz: None,
high_mz: None,
ion_injection_time_ms: None,
inv_mobility: None,
faims_cv: None,
precursor: None,
mz: vec![100.0, 200.0, 300.0],
intensity: vec![10.0, 5.0, 1.0],
inv_mobility_per_peak: None,
},
}
}
}
impl SpectrumSource for OneSpectrumSource {
fn run_metadata(&self) -> RunMetadata {
self.meta.clone()
}
fn iter_spectra<'a>(&'a mut self) -> Box<dyn Iterator<Item = SpectrumRecord> + 'a> {
Box::new(std::iter::once(self.spectrum.clone()))
}
fn spectrum_count_hint(&self) -> Option<usize> {
Some(1)
}
}
#[test]
fn embedded_file_checksum_matches_independent_sha1_recompute() {
let mut src = OneSpectrumSource::new();
let mut buf = Vec::new();
write_indexed_mzml(&mut src, &mut buf).unwrap();
let needle = b"</indexList>\n";
let end_of_index_list = buf
.windows(needle.len())
.position(|w| w == needle)
.expect("generated document must contain a closing </indexList> tag")
+ needle.len();
let expected = sha1_hex(&buf[..end_of_index_list]);
let s = String::from_utf8(buf.clone()).unwrap();
let open = s
.find("<fileChecksum>")
.expect("fileChecksum element present")
+ "<fileChecksum>".len();
let close = s[open..]
.find("</fileChecksum>")
.expect("fileChecksum closing tag present")
+ open;
let embedded = &s[open..close];
assert_eq!(
embedded, expected,
"embedded <fileChecksum> should equal an independently-recomputed \
SHA-1 of the bytes written up to and including </indexList>"
);
assert_eq!(embedded.len(), 40);
assert!(embedded.chars().all(|c| c.is_ascii_hexdigit()));
}