use std::io::{Read, Seek};
use crate::cbin::{self, Cbin, Header};
use crate::error::Error;
use crate::formats::ne5::program::{self, Program};
use crate::types::RangedU16Pair;
pub const FORMAT: &str = "ne5l";
pub const KNOWN_VERSIONS: &[u32] = &[4];
pub const FILE_LEN: usize = program::FILE_LEN;
pub const BANK_COUNT: u16 = 1;
pub const SLOT_COUNT: u16 = 3;
pub type Location = RangedU16Pair<BANK_COUNT, SLOT_COUNT>;
pub fn location(file: &Cbin<Program>) -> Result<Location, Error> {
program::slot(&file.header)
}
pub fn new(location: Location) -> Cbin<Program> {
Cbin {
header: Header::new(FORMAT, location.inner(), 4),
body: Program::default(),
}
}
pub fn read_from(reader: &mut (impl Read + Seek)) -> Result<Cbin<Program>, Error> {
let file: Cbin<Program> = cbin::read(reader, FORMAT)?;
program::known_version(FORMAT, file.header.version, KNOWN_VERSIONS)?;
program::unset_aux(FORMAT, &file.header)?;
location(&file)?;
Ok(file)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ParseError;
use std::io::Cursor;
#[test]
fn a_live_slot_round_trips_at_the_program_length() {
let live = new((0, 2).try_into().unwrap());
let mut bytes = Vec::new();
live.write_to(&mut Cursor::new(&mut bytes)).unwrap();
assert_eq!(bytes.len(), FILE_LEN);
assert_eq!(&bytes[0x08..0x0c], FORMAT.as_bytes());
let back = read_from(&mut Cursor::new(&mut bytes)).unwrap();
assert_eq!(location(&back).unwrap(), (0, 2));
}
#[test]
fn the_live_slot_space_stops_at_three() {
for slot in 0..SLOT_COUNT {
assert!(Location::try_from((0, slot)).is_ok(), "slot {slot}");
}
assert!(Location::try_from((0, SLOT_COUNT)).is_err());
assert!(Location::try_from((1, 0)).is_err());
}
#[test]
fn a_program_is_not_accepted_as_a_live_slot() {
let program = program::new((0, 1).try_into().unwrap());
let mut bytes = Vec::new();
program.write_to(&mut Cursor::new(&mut bytes)).unwrap();
let err = read_from(&mut Cursor::new(&mut bytes))
.expect_err("a ne5p file must not read as a live slot");
assert!(
matches!(
err,
Error::Parse(ParseError::WrongFormat {
expected: FORMAT,
..
})
),
"refused for the wrong reason: {err}",
);
let mut live = Vec::new();
new((0, 1).try_into().unwrap())
.write_to(&mut Cursor::new(&mut live))
.unwrap();
let err = program::read_from(&mut Cursor::new(&mut live))
.expect_err("a ne5l file must not read as a program");
assert!(
matches!(
err,
Error::Parse(ParseError::WrongFormat {
expected: program::FORMAT,
..
})
),
"refused for the wrong reason: {err}",
);
}
#[test]
fn a_live_body_is_a_program_body() {
let mut live = Vec::new();
new((0, 1).try_into().unwrap())
.write_to(&mut Cursor::new(&mut live))
.unwrap();
let mut program = Vec::new();
program::new((0, 1).try_into().unwrap())
.write_to(&mut Cursor::new(&mut program))
.unwrap();
let differing: Vec<usize> = (0..live.len()).filter(|&i| live[i] != program[i]).collect();
assert_eq!(differing, vec![0x0b], "{differing:x?}");
}
}