use crate::error::ParseError;
pub const HEADER_LEN: usize = 9;
pub const CONTAINER: &[u8; 3] = b"NWS";
pub const HDR: &[u8; 3] = b"hdr";
pub const CAT: &[u8; 3] = b"cat";
pub const MAP: &[u8; 3] = b"map";
pub const STK: &[u8; 3] = b"stk";
pub const STY: &[u8; 3] = b"sty";
#[derive(Clone, PartialEq, Eq)]
pub struct Section {
pub tag: [u8; 3],
pub version: u8,
pub payload: Vec<u8>,
}
impl Section {
pub fn encoded_len(&self) -> usize {
HEADER_LEN + self.payload.len()
}
pub fn tag_str(&self) -> String {
String::from_utf8_lossy(&self.tag).into_owned()
}
pub fn is(&self, tag: &[u8; 3]) -> bool {
&self.tag == tag
}
pub fn write_to(&self, w: &mut impl std::io::Write) -> Result<(), ParseError> {
let head = |w: &mut dyn std::io::Write| -> std::io::Result<()> {
w.write_all(&self.tag)?;
w.write_all(&[0, self.version])?;
w.write_all(&(self.payload.len() as u32).to_be_bytes())?;
w.write_all(&self.payload)
};
head(w).map_err(|e| ParseError::AssertFail(format!("writing a section: {e}")))
}
}
pub fn read_chain(r: &mut impl std::io::Read) -> Result<Vec<Section>, ParseError> {
let mut sections = Vec::new();
let mut pos: u64 = 0;
loop {
let head = match read_head(r, pos)? {
Some(head) => head,
None => return Ok(sections),
};
let len = u32::from_be_bytes([head[5], head[6], head[7], head[8]]) as usize;
let mut payload = vec![0u8; len];
r.read_exact(&mut payload).map_err(|_| {
ParseError::AssertFail(format!(
"section {} at {pos} declares {len} bytes but the body ends first",
String::from_utf8_lossy(&head[..3]),
))
})?;
pos += (HEADER_LEN + len) as u64;
sections.push(Section {
tag: [head[0], head[1], head[2]],
version: head[4],
payload,
});
}
}
fn read_head(r: &mut impl std::io::Read, at: u64) -> Result<Option<[u8; 9]>, ParseError> {
let mut head = [0u8; HEADER_LEN];
let mut got = 0;
while got < HEADER_LEN {
match r.read(&mut head[got..]) {
Ok(0) if got == 0 => return Ok(None),
Ok(0) => {
return Err(ParseError::AssertFail(format!(
"truncated section header at {at}"
)))
}
Ok(n) => got += n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(ParseError::AssertFail(format!("reading a section: {e}"))),
}
}
Ok(Some(head))
}
pub const HEADER4_LEN: usize = 12;
pub const CONTAINER4: &[u8; 4] = b"NSMP";
pub const HDR4: &[u8; 4] = b"\0hdr";
pub const CAT4: &[u8; 4] = b"\0cat";
pub const MAP4: &[u8; 4] = b"\0map";
pub const STK4: &[u8; 4] = b"\0stk";
pub const STY4: &[u8; 4] = b"\0sty";
pub const META4: &[u8; 4] = b"meta";
#[derive(Clone, PartialEq, Eq)]
pub struct Section4 {
pub tag: [u8; 4],
pub version: u32,
pub payload: Vec<u8>,
}
impl Section4 {
pub fn encoded_len(&self) -> usize {
HEADER4_LEN + self.payload.len()
}
pub fn tag_str(&self) -> String {
String::from_utf8_lossy(&self.tag)
.trim_start_matches('\0')
.to_owned()
}
pub fn is(&self, tag: &[u8; 4]) -> bool {
&self.tag == tag
}
pub fn write_to(&self, w: &mut impl std::io::Write) -> Result<(), ParseError> {
let head = |w: &mut dyn std::io::Write| -> std::io::Result<()> {
w.write_all(&self.tag)?;
w.write_all(&self.version.to_be_bytes())?;
w.write_all(&(self.payload.len() as u32).to_be_bytes())?;
w.write_all(&self.payload)
};
head(w).map_err(|e| ParseError::AssertFail(format!("writing a section: {e}")))
}
}
pub fn read_chain4(r: &mut impl std::io::Read) -> Result<Vec<Section4>, ParseError> {
let mut sections = Vec::new();
let mut pos: u64 = 0;
loop {
let mut head = [0u8; HEADER4_LEN];
if !read_exact_or_end(r, &mut head, pos)? {
return Ok(sections);
}
let len = u32::from_be_bytes([head[8], head[9], head[10], head[11]]) as usize;
let mut payload = vec![0u8; len];
r.read_exact(&mut payload).map_err(|_| {
ParseError::AssertFail(format!(
"section {} at {pos} declares {len} bytes but the body ends first",
String::from_utf8_lossy(&head[..4]),
))
})?;
pos += (HEADER4_LEN + len) as u64;
sections.push(Section4 {
tag: [head[0], head[1], head[2], head[3]],
version: u32::from_be_bytes([head[4], head[5], head[6], head[7]]),
payload,
});
}
}
fn read_exact_or_end(
r: &mut impl std::io::Read,
buf: &mut [u8],
at: u64,
) -> Result<bool, ParseError> {
let mut got = 0;
while got < buf.len() {
match r.read(&mut buf[got..]) {
Ok(0) if got == 0 => return Ok(false),
Ok(0) => {
return Err(ParseError::AssertFail(format!(
"truncated section header at {at}"
)))
}
Ok(n) => got += n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(e) => return Err(ParseError::AssertFail(format!("reading a section: {e}"))),
}
}
Ok(true)
}
pub fn find4<'a>(sections: &'a [Section4], tag: &[u8; 4]) -> Option<&'a Section4> {
sections.iter().find(|s| s.is(tag))
}
impl std::fmt::Debug for Section4 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Section4")
.field("tag", &self.tag_str())
.field("version", &self.version)
.field("len", &self.payload.len())
.finish()
}
}
pub fn find<'a>(sections: &'a [Section], tag: &[u8; 3]) -> Option<&'a Section> {
sections.iter().find(|s| s.is(tag))
}
pub fn find_mut<'a>(sections: &'a mut [Section], tag: &[u8; 3]) -> Option<&'a mut Section> {
sections.iter_mut().find(|s| s.is(tag))
}
impl std::fmt::Debug for Section {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Section")
.field("tag", &self.tag_str())
.field("version", &self.version)
.field("len", &self.payload.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn section(tag: &[u8; 3], version: u8, payload: &[u8]) -> Vec<u8> {
let mut v = tag.to_vec();
v.push(0);
v.push(version);
v.extend_from_slice(&(payload.len() as u32).to_be_bytes());
v.extend_from_slice(payload);
v
}
#[test]
fn chain_round_trips() {
let mut bytes = section(CONTAINER, 11, &[]);
bytes.extend(section(HDR, 9, &[1, 2, 3]));
bytes.extend(section(STK, 9, &[4; 20]));
let chain = read_chain(&mut bytes.as_slice()).unwrap();
assert_eq!(chain.len(), 3);
assert_eq!(chain[0].payload.len(), 0);
assert_eq!(chain[1].payload, vec![1, 2, 3]);
assert_eq!(chain[2].version, 9);
let mut out = Vec::new();
for s in &chain {
s.write_to(&mut out).unwrap();
}
assert_eq!(out, bytes);
}
#[test]
fn length_is_big_endian() {
let bytes = section(HDR, 1, &[0; 258]);
let chain = read_chain(&mut bytes.as_slice()).unwrap();
assert_eq!(chain[0].payload.len(), 258);
}
#[test]
fn overrunning_length_is_an_error() {
let mut bytes = section(HDR, 1, &[7; 4]);
bytes[8] = 200; assert!(read_chain(&mut bytes.as_slice()).is_err());
}
#[test]
fn trailing_bytes_are_an_error() {
let mut bytes = section(HDR, 1, &[7; 4]);
bytes.extend_from_slice(&[0, 0, 0]); assert!(read_chain(&mut bytes.as_slice()).is_err());
}
fn section4(tag: &[u8; 4], version: u32, payload: &[u8]) -> Vec<u8> {
let mut v = tag.to_vec();
v.extend_from_slice(&version.to_be_bytes());
v.extend_from_slice(&(payload.len() as u32).to_be_bytes());
v.extend_from_slice(payload);
v
}
#[test]
fn chain4_round_trips() {
let mut bytes = section4(CONTAINER4, 30, &[0, 2, 0, 0x0c]);
bytes.extend(section4(HDR4, 10, &[1; 112]));
bytes.extend(section4(STK4, 11, &[4; 20]));
let chain = read_chain4(&mut bytes.as_slice()).unwrap();
assert_eq!(chain.len(), 3);
assert_eq!(chain[0].payload.len(), 4);
assert_eq!(chain[1].version, 10);
assert_eq!(chain[2].tag_str(), "stk");
let mut out = Vec::new();
for s in &chain {
s.write_to(&mut out).unwrap();
}
assert_eq!(out, bytes);
}
#[test]
fn chain4_overrun_and_truncation_are_errors() {
let mut bytes = section4(HDR4, 1, &[7; 4]);
bytes[11] = 200; assert!(read_chain4(&mut bytes.as_slice()).is_err());
let mut bytes = section4(HDR4, 1, &[7; 4]);
bytes.extend_from_slice(&[0; 5]); assert!(read_chain4(&mut bytes.as_slice()).is_err());
}
#[test]
fn repeated_tags_are_all_kept() {
let mut bytes = section(STK, 9, &[1]);
bytes.extend(section(STK, 9, &[2]));
bytes.extend(section(STK, 9, &[3]));
let chain = read_chain(&mut bytes.as_slice()).unwrap();
assert_eq!(chain.len(), 3);
assert_eq!(
chain.iter().map(|s| s.payload[0]).collect::<Vec<_>>(),
vec![1, 2, 3]
);
}
}