use super::{BitReader, BitWriter, ExiError, ExiResult};
const COOKIE: [u8; 4] = *b"$EXI";
const VERSION: u8 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
pub cookie: bool,
pub version: u8,
pub preview: bool,
}
impl Default for Header {
fn default() -> Self {
Self { cookie: false, version: VERSION, preview: false }
}
}
impl Header {
pub const ISO15118: Self = Self { cookie: false, version: VERSION, preview: false };
}
pub fn write_header(w: &mut BitWriter<'_>, header: Header) -> ExiResult<()> {
debug_assert!(w.is_aligned(), "the EXI header must start byte-aligned");
if header.cookie {
w.write_bytes(&COOKIE)?;
}
w.write_bits(0b10, 2)?; w.write_bit(false)?; w.write_bit(header.preview)?;
if header.version == 0 {
return Err(ExiError::BadHeader);
}
let mut remaining = u32::from(header.version) - 1;
while remaining >= 15 {
w.write_bits(15, 4)?;
remaining -= 15;
}
w.write_bits(u64::from(remaining), 4)
}
pub fn read_header(r: &mut BitReader<'_>) -> ExiResult<Header> {
let mut cookie = false;
if r.is_aligned() && r.bits_remaining() >= 32 {
let mut probe = r.clone();
let mut buf = [0u8; 4];
if probe.read_bytes(&mut buf).is_ok() && buf == COOKIE {
*r = probe;
cookie = true;
}
}
if r.read_bits(2)? != 0b10 {
return Err(ExiError::BadHeader);
}
if r.read_bit()? {
return Err(ExiError::BadHeader);
}
let preview = r.read_bit()?;
let mut version: u32 = 1;
loop {
let group = r.read_bits(4)?;
version += u32::try_from(group).map_err(|_| ExiError::BadHeader)?;
if group != 15 {
break;
}
if version > u32::from(u8::MAX) {
return Err(ExiError::BadHeader);
}
}
let version = u8::try_from(version).map_err(|_| ExiError::BadHeader)?;
if version != VERSION {
return Err(ExiError::BadHeader);
}
Ok(Header { cookie, version, preview })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iso15118_header_is_a_single_0x80_byte() {
let mut buf = [0u8; 8];
let mut w = BitWriter::new(&mut buf);
write_header(&mut w, Header::ISO15118).unwrap();
assert_eq!(w.bit_len(), 8);
let len = w.finish().unwrap();
assert_eq!(&buf[..len], &[0x80]);
}
#[test]
fn roundtrips() {
let mut buf = [0u8; 8];
let mut w = BitWriter::new(&mut buf);
write_header(&mut w, Header::ISO15118).unwrap();
let len = w.finish().unwrap();
let mut r = BitReader::new(&buf[..len]);
assert_eq!(read_header(&mut r).unwrap(), Header::ISO15118);
}
#[test]
fn cookie_is_accepted_on_input() {
let bytes = [b'$', b'E', b'X', b'I', 0x80];
let mut r = BitReader::new(&bytes);
let h = read_header(&mut r).unwrap();
assert!(h.cookie);
assert_eq!(r.bit_pos(), 40);
}
#[test]
fn wrong_distinguishing_bits_are_rejected() {
let mut r = BitReader::new(&[0x00]);
assert_eq!(read_header(&mut r), Err(ExiError::BadHeader));
}
#[test]
fn an_options_document_is_rejected() {
let mut r = BitReader::new(&[0b1010_0000]);
assert_eq!(read_header(&mut r), Err(ExiError::BadHeader));
}
#[test]
fn unknown_version_is_rejected() {
let mut r = BitReader::new(&[0b1000_0001]);
assert_eq!(read_header(&mut r), Err(ExiError::BadHeader));
}
#[test]
fn empty_input_is_rejected() {
let mut r = BitReader::new(&[]);
assert_eq!(read_header(&mut r), Err(ExiError::UnexpectedEnd));
}
}