use crate::snapshot::error::SnapshotWriteError;
use crate::snapshot::format::{
align_up, checked_u32, write_u16_le, write_u32_le, write_u8, SectionKind, HEADER_SIZE,
SECTION_ALIGNMENT, SECTION_FLAG_REQUIRED, SECTION_RECORD_SIZE, SNAPSHOT_FEATURE_FLAGS,
SNAPSHOT_MAGIC, SNAPSHOT_MAJOR_VERSION, SNAPSHOT_MINOR_VERSION,
};
#[derive(Debug)]
pub(crate) struct EmittedSection {
pub kind: SectionKind,
pub bytes: Vec<u8>,
pub count: u32,
}
#[derive(Debug, Default)]
pub(crate) struct SnapshotAssembler {
sections: Vec<EmittedSection>,
}
impl SnapshotAssembler {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, section: EmittedSection) {
self.sections.push(section);
}
pub fn finish(mut self) -> Result<Vec<u8>, SnapshotWriteError> {
self.sections.sort_by_key(|s| canonical_index_of(s.kind));
let section_count =
checked_u32(self.sections.len()).ok_or(SnapshotWriteError::SectionTooLarge)?;
let section_table_byte_len = section_count
.checked_mul(SECTION_RECORD_SIZE)
.ok_or(SnapshotWriteError::SectionTooLarge)?;
let mut cursor = HEADER_SIZE
.checked_add(section_table_byte_len)
.ok_or(SnapshotWriteError::SectionTooLarge)?;
let mut plan: Vec<SectionPlan> = Vec::with_capacity(self.sections.len());
for section in &self.sections {
let offset = align_up(cursor).ok_or(SnapshotWriteError::SectionTooLarge)?;
let byte_len =
checked_u32(section.bytes.len()).ok_or(SnapshotWriteError::SectionTooLarge)?;
let end = offset
.checked_add(byte_len)
.ok_or(SnapshotWriteError::SectionTooLarge)?;
plan.push(SectionPlan { offset, byte_len });
cursor = end;
}
let total_len = cursor;
let mut buf = Vec::with_capacity(total_len as usize);
write_header(&mut buf, section_count);
debug_assert_eq!(buf.len() as u32, HEADER_SIZE);
for (section, slot) in self.sections.iter().zip(plan.iter()) {
write_section_record(&mut buf, section.kind, section.count, *slot);
}
debug_assert_eq!(buf.len() as u32, HEADER_SIZE + section_table_byte_len);
for (section, slot) in self.sections.iter().zip(plan.iter()) {
while (buf.len() as u32) < slot.offset {
buf.push(0);
}
buf.extend_from_slice(§ion.bytes);
}
debug_assert_eq!(buf.len() as u32, total_len);
Ok(buf)
}
}
#[derive(Debug, Clone, Copy)]
struct SectionPlan {
offset: u32,
byte_len: u32,
}
fn canonical_index_of(kind: SectionKind) -> usize {
SectionKind::ALL_IN_ORDER
.iter()
.position(|k| *k == kind)
.expect("SectionKind::ALL_IN_ORDER covers every variant")
}
fn write_header(buf: &mut Vec<u8>, section_count: u32) {
buf.extend_from_slice(&SNAPSHOT_MAGIC);
write_u16_le(buf, SNAPSHOT_MAJOR_VERSION);
write_u16_le(buf, SNAPSHOT_MINOR_VERSION);
write_u32_le(buf, SNAPSHOT_FEATURE_FLAGS);
write_u32_le(buf, HEADER_SIZE);
write_u32_le(buf, HEADER_SIZE);
let section_count_u16 = section_count as u16;
write_u16_le(buf, section_count_u16);
write_u16_le(buf, 0); write_u32_le(buf, 0); }
fn write_section_record(buf: &mut Vec<u8>, kind: SectionKind, count: u32, plan: SectionPlan) {
write_u16_le(buf, kind.as_u16());
let flags = if kind.is_core() {
SECTION_FLAG_REQUIRED
} else {
0
};
write_u16_le(buf, flags);
write_u32_le(buf, plan.offset);
write_u32_le(buf, plan.byte_len);
write_u32_le(buf, count);
write_u16_le(buf, kind.record_size());
write_u8(buf, SECTION_ALIGNMENT as u8);
write_u8(buf, 0); }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_assembler_writes_only_header() {
let bytes = SnapshotAssembler::new().finish().unwrap();
assert_eq!(bytes.len(), HEADER_SIZE as usize);
assert_eq!(&bytes[..8], b"OXMF2AST");
assert_eq!(&bytes[20..24], &HEADER_SIZE.to_le_bytes());
assert_eq!(&bytes[24..26], &0u16.to_le_bytes());
assert_eq!(&bytes[26..32], &[0u8; 6]);
}
#[test]
fn pushes_in_any_order_emits_canonical_order() {
let mut a = SnapshotAssembler::new();
a.push(EmittedSection {
kind: SectionKind::StringData,
bytes: vec![],
count: 0,
});
a.push(EmittedSection {
kind: SectionKind::Roots,
bytes: vec![],
count: 0,
});
let bytes = a.finish().unwrap();
let kind_a = u16::from_le_bytes([bytes[32], bytes[33]]);
let kind_b = u16::from_le_bytes([
bytes[32 + SECTION_RECORD_SIZE as usize],
bytes[33 + SECTION_RECORD_SIZE as usize],
]);
assert_eq!(kind_a, SectionKind::Roots.as_u16());
assert_eq!(kind_b, SectionKind::StringData.as_u16());
}
#[test]
fn pads_between_sections_with_zero_to_eight_byte_alignment() {
let mut a = SnapshotAssembler::new();
a.push(EmittedSection {
kind: SectionKind::StringData,
bytes: vec![1, 2, 3],
count: 0,
});
a.push(EmittedSection {
kind: SectionKind::ExtendedData,
bytes: vec![9, 9],
count: 0,
});
let bytes = a.finish().unwrap();
assert_eq!(&bytes[72..75], &[1, 2, 3]);
assert_eq!(&bytes[75..80], &[0, 0, 0, 0, 0]);
assert_eq!(&bytes[80..82], &[9, 9]);
assert_eq!(bytes.len(), 82);
}
}