extra sanity checks and tests

png-branch
an 2019-03-04 23:57:25 -05:00
parent 6e3fff0080
commit f3e98c0c05
11 changed files with 220 additions and 93 deletions

View File

@ -299,6 +299,38 @@ pub fn rd_array<T, F>(b: &[u8], read: F) -> ResultS<Vec<T>>
Ok(v)
}
/// Applies a read function a number of times over a slice.
///
/// Applies `read` over `b`, resulting in a vector of its return values. Each
/// iteration will pass a slice of `b` to `read` for it to read from, and then
/// increments the slice index by the second return value. When `n` elements
/// have been read, the function returns.
///
/// # Panics
///
/// A panic will occur if the `read` function returns a disjoint index or
/// otherwise panics (by an out of bounds index to `b` or otherwise.)
///
/// # Errors
///
/// Execution will return the result of `read` if `read` returns an error.
pub fn rd_array_num<T, F>(b: &[u8], n: usize, read: F)
-> ResultS<(Vec<T>, usize)>
where T: Sized,
F: Fn(&[u8]) -> ResultS<(T, usize)>
{
let mut v = Vec::with_capacity(n);
let mut p = 0;
for _ in 0..n {
let (r, s) = read(&b[p..])?;
v.push(r);
p += s;
}
Ok((v, p))
}
/// Applies a read function over a slice with an offset table.
///
/// Applies `read` over each offset in `b`, of which there are `num` amount of

View File

@ -207,6 +207,10 @@ pub fn load_pict(b: &[u8]) -> ResultS<Image8>
w = u16[8] usize;
}
if w * h > 16_000_000 {
bail!("image is too large");
}
let im = Image8::new(w, h);
let mut p = 10; // size of header
@ -425,7 +429,7 @@ impl ReadRleData for u8
}
/// Expand packed pixel data based on bit depth.
pub fn expand_data(b: Vec<u8>, depth: u16) -> ResultS<Vec<u8>>
fn expand_data(b: Vec<u8>, depth: u16) -> ResultS<Vec<u8>>
{
let mut o = Vec::with_capacity(match depth {
4 => b.len() * 2,

View File

@ -23,12 +23,12 @@ fn read_color(b: &[u8], clut: &mut [ColorShp]) -> ResultS<()>
}?;
let cr = ColorShp::Opaque{r, g, b, l};
clut[usize::from(i)] = cr;
*ok!(clut.get_mut(usize::from(i)), "bad index")? = cr;
Ok(())
}
/// Reads all color tables.
pub fn color_tables(b: &[u8],
fn color_tables(b: &[u8],
tab_ofs: usize,
tab_num: usize,
clr_num: usize)
@ -36,14 +36,14 @@ pub fn color_tables(b: &[u8],
{
let end = tab_num * clr_num * 8;
let b = &b[tab_ofs..tab_ofs + end];
let b = ok!(b.get(tab_ofs..tab_ofs + end), "bad offset")?;
let mut v = vec![vec![ColorShp::Translucent; clr_num]; tab_num];
let mut p = 0;
for clut in v.iter_mut().take(tab_num) {
for _ in 0..clr_num {
read_color(&b[p..p + 8], clut)?;
read_color(ok!(b.get(p..p + 8), "not enough data")?, clut)?;
p += 8;
}
}
@ -98,7 +98,7 @@ pub fn read_bitmap(b: &[u8]) -> ResultS<Bitmap>
bmp.cr.push(0);
}
bmp.cr.extend_from_slice(&b[p..p + end]);
bmp.cr.extend_from_slice(ok!(b.get(p..p + end), "not enough data")?);
for _ in lst..pitch {
bmp.cr.push(0);
@ -112,7 +112,8 @@ pub fn read_bitmap(b: &[u8]) -> ResultS<Bitmap>
bail!("invalid scanline");
}
bmp.cr.extend_from_slice(&b[p..p + width * height]);
bmp.cr.extend_from_slice(ok!(b.get(p..p + width * height),
"not enough data")?);
}
Ok(bmp)
@ -215,13 +216,15 @@ pub fn read_shapes(b: &[u8]) -> ResultS<Vec<CollectionDef>>
let c_lo = if lo_ofs == usize_from_u32(u32::max_value()) {
None
} else {
Some(read_collection(&b[lo_ofs..lo_ofs + lo_len])?)
let dat = ok!(b.get(lo_ofs..lo_ofs + lo_len), "bad offset")?;
Some(read_collection(dat)?)
};
let c_hi = if hi_ofs == usize_from_u32(u32::max_value()) {
None
} else {
Some(read_collection(&b[hi_ofs..hi_ofs + hi_len])?)
let dat = ok!(b.get(hi_ofs..hi_ofs + hi_len), "bad offset")?;
Some(read_collection(dat)?)
};
cl.push((c_lo, c_hi));

View File

@ -1,23 +1,21 @@
//! Structures used by Marathon's Map format's terminal definitions.
use crate::durandal::{err::*, text::*};
use crate::durandal::{bin::*, err::*, text::*};
use bitflags::bitflags;
/// Reads a `Group`.
pub fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
/// Reads an `InterGroup`.
pub fn read_group(b: &[u8]) -> ResultS<(InterGroup, usize)>
{
read_data! {
12, BE in b =>
flags = u16[0];
ttype = u16[2];
pdata = u16[4];
start = u16[6] usize;
size = u16[8] usize;
beg = u16[6] usize;
len = u16[8] usize;
lines = u16[10];
}
let text = ok!(text.get(start..start + size), "not enough data")?;
let text = mac_roman_cstr(text)?;
let flags = flag_ok!(GroupFlags, flags)?;
let ttype = match ttype {
0 => GroupType::Logon(pdata),
@ -40,11 +38,11 @@ pub fn read_group(b: &[u8], text: &[u8]) -> ResultS<Group>
n => return Err(ReprError::new(n).into()),
};
Ok(Group{flags, ttype, lines, text})
Ok((InterGroup{flags, ttype, lines, beg, len}, 12))
}
/// Reads a `Face`.
pub fn read_face(b: &[u8]) -> ResultS<Face>
pub fn read_face(b: &[u8]) -> ResultS<(Face, usize)>
{
read_data! {
6, BE in b =>
@ -53,15 +51,12 @@ pub fn read_face(b: &[u8]) -> ResultS<Face>
color = u16[4];
}
Ok(Face{start, face, color})
Ok((Face{start, face, color}, 6))
}
/// Reads a `term` chunk.
pub fn read_term(b: &[u8]) -> ResultS<(Terminal, usize)>
{
const SIZE_GROUP: usize = 12;
const SIZE_FACE: usize = 6;
read_data! {
10, BE in b =>
end = u16[0] usize;
@ -73,32 +68,34 @@ pub fn read_term(b: &[u8]) -> ResultS<(Terminal, usize)>
let encoded = encoded != 0;
let (i_grp, x) = rd_array_num(&b[10..], group_n, read_group)?;
let (faces, y) = rd_array_num(&b[10 + x..], face_n, read_face)?;
let text = ok!(b.get(10 + x + y..end), "not enough data")?;
let text = if encoded {fuck_string(text)} else {text.to_vec()};
let mut groups = Vec::with_capacity(group_n);
let mut faces = Vec::with_capacity(face_n);
let mut p = 10;
for grp in &i_grp {
let flags = grp.flags;
let ttype = grp.ttype;
let lines = grp.lines;
let beg = grp.beg;
let len = grp.len;
let text = ok!(text.get(beg..beg + len), "bad offset")?;
let text = mac_roman_cstr(text)?;
let text_st = p + SIZE_GROUP * group_n + SIZE_FACE * face_n;
let text = ok!(b.get(text_st..end), "bad offset")?;
let text = if encoded {
fuck_string(text)
} else {
text.to_vec()
};
for _ in 0..group_n {
groups.push(read_group(ok!(b.get(p..), "not enough data")?, &text)?);
p += SIZE_GROUP;
}
for _ in 0..face_n {
faces.push(read_face(ok!(b.get(p..), "not enough data")?)?);
p += SIZE_FACE;
groups.push(Group{flags, ttype, lines, text});
}
Ok((Terminal{lines, groups, faces}, end))
}
impl Default for GroupType
{
fn default() -> Self {GroupType::Unfinished}
}
/// A terminal definition, with collections of groups and faces.
#[derive(Debug, PartialEq, serde::Serialize)]
pub struct Terminal
@ -127,8 +124,19 @@ pub struct Group
pub text: String,
}
/// Interim structure.
#[derive(Debug)]
pub struct InterGroup
{
flags: GroupFlags,
ttype: GroupType,
lines: u16,
beg: usize,
len: usize,
}
/// The command of a `Group`.
#[derive(Debug, PartialEq, serde::Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize)]
pub enum GroupType
{
Logon(u16),
@ -152,7 +160,7 @@ pub enum GroupType
bitflags! {
/// Flags for `Group`.
#[derive(serde::Serialize)]
#[derive(Default, serde::Serialize)]
pub struct GroupFlags: u16
{
const DrawOnRight = 1;

View File

@ -1,39 +1,7 @@
use maraiah::{durandal::{bin, fixed::*}, marathon::{map, trm}};
use maraiah::{durandal::{bin, fixed::*}, marathon::map};
include!("data/rand.rs");
#[test]
fn read_term_must_process()
{
const INPUT: &[u8] = include_bytes!("data/term.in");
let inp = bin::rd_array(INPUT, trm::read_term).unwrap();
let out = include!("data/term.out");
// for better debug output, we iterate over each item
assert_eq!(inp.len(), out.len());
for (itrm, otrm) in inp.iter().zip(&out) {
assert_eq!(itrm.groups.len(), otrm.groups.len());
for (igrp, ogrp) in itrm.groups.iter().zip(&otrm.groups) {
assert_eq!(igrp, ogrp);
}
assert_eq!(itrm.faces.len(), otrm.faces.len());
for (ifac, ofac) in itrm.faces.iter().zip(&otrm.faces) {
assert_eq!(ifac, ofac);
}
}
}
#[test]
fn trm_must_not_process()
{
for inp in &RANDOM {
assert!(bin::rd_array(inp, trm::read_term).is_err());
}
}
#[test]
fn read_minf_must_process()
{

View File

@ -1,24 +1,7 @@
use maraiah::{durandal::image::Color8, marathon::{machdr, pict}};
use maraiah::marathon::machdr;
include!("data/rand.rs");
#[test]
fn get_clut_must_process()
{
const INPUT: &[u8] = include_bytes!("data/clut.in");
const OUTPUT: [Color8; 256] = include!("data/clut.out");
assert_eq!(pict::get_clut(INPUT).unwrap(), (OUTPUT.to_vec(), 2056));
}
#[test]
fn get_clut_must_not_process()
{
for inp in &RANDOM {
assert!(pict::get_clut(inp).is_err());
}
}
#[test]
fn machdr_must_process()
{

27
tests/phy.rs Normal file
View File

@ -0,0 +1,27 @@
use maraiah::{durandal::bin, marathon::phy};
include!("data/rand.rs");
#[test]
fn phy_must_not_process()
{
// these functions must not succeed
for inp in &RANDOM {
assert!(bin::rd_array(inp, phy::read_fxpx).is_err());
assert!(bin::rd_array(inp, phy::read_mnpx).is_err());
assert!(bin::rd_array(inp, phy::read_prpx).is_err());
assert!(bin::rd_array(inp, phy::read_wppx).is_err());
}
}
#[test]
#[allow(unused_must_use)]
fn phy_wont_panic()
{
// these functions can succeed but must never panic
for inp in &RANDOM {
bin::rd_array(inp, phy::read_pxpx);
}
}
// EOF

23
tests/pict.rs Normal file
View File

@ -0,0 +1,23 @@
use maraiah::{durandal::image::Color8, marathon::pict};
include!("data/rand.rs");
#[test]
fn get_clut_must_process()
{
const INPUT: &[u8] = include_bytes!("data/clut.in");
const OUTPUT: [Color8; 256] = include!("data/clut.out");
assert_eq!(pict::get_clut(INPUT).unwrap(), (OUTPUT.to_vec(), 2056));
}
#[test]
fn pict_must_not_process()
{
for inp in &RANDOM {
assert!(pict::get_clut(inp).is_err());
assert!(pict::load_pict(inp).is_err());
}
}
// EOF

17
tests/shp.rs Normal file
View File

@ -0,0 +1,17 @@
use maraiah::marathon::shp;
include!("data/rand.rs");
#[test]
fn shp_must_not_process()
{
for inp in &RANDOM {
assert!(shp::read_bitmap(inp).is_err());
assert!(shp::read_collection(inp).is_err());
assert!(shp::read_frame(inp).is_err());
assert!(shp::read_sequence(inp).is_err());
assert!(shp::read_shapes(inp).is_err());
}
}
// EOF

15
tests/snd.rs Normal file
View File

@ -0,0 +1,15 @@
use maraiah::marathon::snd;
include!("data/rand.rs");
#[test]
fn snd_must_not_process()
{
for inp in &RANDOM {
assert!(snd::read_sound(inp).is_err());
assert!(snd::read_sound_def(inp).is_err());
assert!(snd::read_sounds(inp).is_err());
}
}
// EOF

47
tests/trm.rs Normal file
View File

@ -0,0 +1,47 @@
use maraiah::{durandal::bin, marathon::trm};
include!("data/rand.rs");
#[test]
fn read_term_must_process()
{
const INPUT: &[u8] = include_bytes!("data/term.in");
let inp = bin::rd_array(INPUT, trm::read_term).unwrap();
let out = include!("data/term.out");
// for better debug output, we iterate over each item
assert_eq!(inp.len(), out.len());
for (itrm, otrm) in inp.iter().zip(&out) {
assert_eq!(itrm.groups.len(), otrm.groups.len());
for (igrp, ogrp) in itrm.groups.iter().zip(&otrm.groups) {
assert_eq!(igrp, ogrp);
}
assert_eq!(itrm.faces.len(), otrm.faces.len());
for (ifac, ofac) in itrm.faces.iter().zip(&otrm.faces) {
assert_eq!(ifac, ofac);
}
}
}
#[test]
fn trm_must_not_process()
{
for inp in &RANDOM {
assert!(bin::rd_array(inp, trm::read_group).is_err());
assert!(bin::rd_array(inp, trm::read_term).is_err());
}
}
#[test]
#[allow(unused_must_use)]
fn trm_wont_panic()
{
for inp in &RANDOM {
bin::rd_array(inp, trm::read_face);
}
}
// EOF