From f3e98c0c051a2fb6635e0d0bf4ce0bcc7806311a Mon Sep 17 00:00:00 2001 From: Marrub Date: Mon, 4 Mar 2019 23:57:25 -0500 Subject: [PATCH] extra sanity checks and tests --- source/durandal/bin.rs | 32 ++++++++++++++++++ source/marathon/pict.rs | 6 +++- source/marathon/shp.rs | 19 ++++++----- source/marathon/trm.rs | 74 +++++++++++++++++++++++------------------ tests/map.rs | 34 +------------------ tests/misc.rs | 19 +---------- tests/phy.rs | 27 +++++++++++++++ tests/pict.rs | 23 +++++++++++++ tests/shp.rs | 17 ++++++++++ tests/snd.rs | 15 +++++++++ tests/trm.rs | 47 ++++++++++++++++++++++++++ 11 files changed, 220 insertions(+), 93 deletions(-) create mode 100644 tests/phy.rs create mode 100644 tests/pict.rs create mode 100644 tests/shp.rs create mode 100644 tests/snd.rs create mode 100644 tests/trm.rs diff --git a/source/durandal/bin.rs b/source/durandal/bin.rs index 7a2e4d2..2854015 100644 --- a/source/durandal/bin.rs +++ b/source/durandal/bin.rs @@ -299,6 +299,38 @@ pub fn rd_array(b: &[u8], read: F) -> ResultS> 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(b: &[u8], n: usize, read: F) + -> ResultS<(Vec, 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 diff --git a/source/marathon/pict.rs b/source/marathon/pict.rs index 696ba53..8706834 100644 --- a/source/marathon/pict.rs +++ b/source/marathon/pict.rs @@ -207,6 +207,10 @@ pub fn load_pict(b: &[u8]) -> ResultS 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, depth: u16) -> ResultS> +fn expand_data(b: Vec, depth: u16) -> ResultS> { let mut o = Vec::with_capacity(match depth { 4 => b.len() * 2, diff --git a/source/marathon/shp.rs b/source/marathon/shp.rs index 6566b62..e8e5f11 100644 --- a/source/marathon/shp.rs +++ b/source/marathon/shp.rs @@ -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 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 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> 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)); diff --git a/source/marathon/trm.rs b/source/marathon/trm.rs index e7672da..cc0192d 100644 --- a/source/marathon/trm.rs +++ b/source/marathon/trm.rs @@ -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 +/// 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 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 +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 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; diff --git a/tests/map.rs b/tests/map.rs index 4337f55..03f5db0 100644 --- a/tests/map.rs +++ b/tests/map.rs @@ -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() { diff --git a/tests/misc.rs b/tests/misc.rs index 14be221..048e7e9 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -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() { diff --git a/tests/phy.rs b/tests/phy.rs new file mode 100644 index 0000000..ff40dd4 --- /dev/null +++ b/tests/phy.rs @@ -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 diff --git a/tests/pict.rs b/tests/pict.rs new file mode 100644 index 0000000..bbf747d --- /dev/null +++ b/tests/pict.rs @@ -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 diff --git a/tests/shp.rs b/tests/shp.rs new file mode 100644 index 0000000..619353e --- /dev/null +++ b/tests/shp.rs @@ -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 diff --git a/tests/snd.rs b/tests/snd.rs new file mode 100644 index 0000000..452284a --- /dev/null +++ b/tests/snd.rs @@ -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 diff --git a/tests/trm.rs b/tests/trm.rs new file mode 100644 index 0000000..3b539d3 --- /dev/null +++ b/tests/trm.rs @@ -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