payload.rs

8.0 kB · rust · 257 lines

1use super::census;2use super::geometry;3use super::Cell2d;4use crate::core::error::{value_error, Result};5use crate::core::tensor::Tensor;67const HEADER: usize = 32;89#[rustfmt::skip]10const FRAME: [u8; 25] = [11    0, 1, 1, 1, 0,12    2, 3, 3, 3, 2,13    2, 3, 3, 3, 2,14    2, 3, 3, 3, 2,15    0, 1, 1, 1, 0,16];1718/// The five by five mask a carried mosaic lays its four tiles out under.19pub fn frame() -> Tensor {20    let mut frame = Tensor::new(vec![5, 5]);21    for (flat, &value) in FRAME.iter().enumerate().take(frame.size()) {22        frame.put(flat, i64::from(value));23    }24    frame25}2627/// Returns the payload bytes the cell's filled sites can hold, its length header paid for.28///29/// ```30/// let cell = mrlyrs::math::two::carpet(3, 3).unwrap();31/// assert_eq!(mrlyrs::math::two::payload::capacity(&cell), 60);32/// ```33pub fn capacity(cell: &Cell2d) -> usize {34    census::fills(cell).saturating_sub(HEADER) / 835}3637/// Writes the payload over the cell's filled sites, repeating it until every site is spoken for.38///39/// # Errors40///41/// Errors when the payload needs more sites than the cell carries.42pub fn embed(cell: &Cell2d, payload: &[u8]) -> Result<Cell2d> {43    let sites = sites(cell);44    let bits = message(payload);45    if bits.len() > sites.len() {46        return value_error(format!(47            "payload needs {} sites, the cell carries {}.",48            bits.len(),49            sites.len()50        ));51    }52    let mut out = cell.clone();53    for (k, &site) in sites.iter().enumerate() {54        out.cell.types.put(site, i64::from(bits[k % bits.len()]));55    }56    Ok(out)57}5859/// Reads the payload back, the plain cell naming the sites the carried one wrote over.60///61/// # Errors62///63/// Errors when the two cells differ in shape, or the header does not fit.64pub fn extract(carrier: &Cell2d, carried: &Cell2d) -> Result<Vec<u8>> {65    if carrier.types().shape != carried.types().shape {66        return value_error("carrier and carried must share one shape.");67    }68    let sites = sites(carrier);69    if sites.len() < HEADER {70        return value_error("carrier holds too few sites for a header.");71    }72    let bits: Vec<u8> = sites73        .iter()74        .map(|&site| u8::from(carried.types().at(site) != 0))75        .collect();76    let length = bits[..HEADER]77        .iter()78        .fold(0usize, |acc, &bit| acc << 1 | bit as usize);79    if HEADER + length * 8 > bits.len() {80        return value_error("carried length runs past the carrier.");81    }82    Ok(bits[HEADER..HEADER + length * 8]83        .chunks(8)84        .map(|byte| byte.iter().fold(0u8, |acc, &bit| acc << 1 | bit))85        .collect())86}8788/// Builds the framed sheet of four same-sized cells, the fourth carrying the payload.89///90/// # Errors91///92/// Errors when the payload needs more sites than the carrier holds.93pub fn sheet(cells: &[Cell2d; 4], payload: &[u8]) -> Result<Cell2d> {94    let carried = embed(&cells[3], payload)?;95    let laid = [96        cells[0].clone(),97        cells[1].clone(),98        cells[2].clone(),99        carried,100    ];101    geometry::mosaic(&frame(), &laid)102}103104/// Reads the payload back from a framed sheet, the plain fourth cell naming the sites.105///106/// # Errors107///108/// Errors when the sheet is not five carriers across and down.109pub fn read(sheet: &Cell2d, carrier: &Cell2d) -> Result<Vec<u8>> {110    let (w, h) = (carrier.width(), carrier.height());111    if sheet.width() != w * 5 || sheet.height() != h * 5 {112        return value_error("sheet must be five carriers across and down.");113    }114    extract(carrier, &block(sheet, 1, 1, w, h)?)115}116117fn sites(cell: &Cell2d) -> Vec<usize> {118    let types = cell.types();119    (0..types.size()).filter(|&i| types.at(i) == 1).collect()120}121122fn message(payload: &[u8]) -> Vec<u8> {123    let mut bits = spread(&(payload.len() as u32).to_be_bytes());124    bits.extend(spread(payload));125    bits126}127128fn spread(bytes: &[u8]) -> Vec<u8> {129    bytes130        .iter()131        .flat_map(|&byte| (0..8).rev().map(move |k| byte >> k & 1))132        .collect()133}134135fn block(cell: &Cell2d, row: usize, col: usize, width: usize, height: usize) -> Result<Cell2d> {136    let source = cell.types();137    let mut types = Tensor::new(vec![height, width]);138    for y in 0..height {139        for x in 0..width {140            let from = source.index(&[row * height + y, col * width + x]);141            types.put(types.index(&[y, x]), source.at(from));142        }143    }144    Cell2d::new(types)145}146147#[cfg(test)]148mod tests {149    use super::*;150    use crate::math::two::designs;151152    fn carrier() -> Cell2d {153        designs::carpet(3, 3).unwrap()154    }155156    #[test]157    fn payload_round_trips_through_the_filled_sites() {158        let plain = carrier();159        let payload = b"Hello, World!";160        let carried = embed(&plain, payload).unwrap();161        assert_eq!(carried.types().shape, plain.types().shape);162        assert_ne!(carried.types(), plain.types());163        assert_eq!(extract(&plain, &carried).unwrap(), payload);164    }165166    #[test]167    fn every_payload_length_survives() {168        let plain = carrier();169        for length in [0usize, 1, 2, 7, 8, 59, 60] {170            let payload: Vec<u8> = (0..length).map(|i| (i * 37 % 251) as u8).collect();171            let carried = embed(&plain, &payload).unwrap();172            assert_eq!(173                extract(&plain, &carried).unwrap(),174                payload,175                "length {length}"176            );177        }178    }179180    #[test]181    fn capacity_is_the_fills_less_the_header() {182        let plain = carrier();183        assert_eq!(census::fills(&plain), 512);184        assert_eq!(capacity(&plain), 60);185        let full = vec![7u8; capacity(&plain)];186        assert!(embed(&plain, &full).is_ok());187        let over = vec![7u8; capacity(&plain) + 1];188        assert!(embed(&plain, &over).is_err());189        assert_eq!(capacity(&designs::ones(2, 1).unwrap()), 0);190    }191192    #[test]193    fn the_payload_repeats_across_the_spare_sites() {194        let plain = carrier();195        let carried = embed(&plain, b"ab").unwrap();196        let sites = sites(&plain);197        let bits = message(b"ab");198        for (k, &site) in sites.iter().enumerate() {199            assert_eq!(200                carried.types().at(site) as u8,201                bits[k % bits.len()],202                "site {site}"203            );204        }205    }206207    #[test]208    fn the_carrier_names_the_sites() {209        let plain = carrier();210        let carried = embed(&plain, b"secret").unwrap();211        let wrong = designs::net(3, 3).unwrap();212        assert!(extract(&wrong, &carried).unwrap_or_default() != b"secret".to_vec());213        assert!(extract(&plain, &designs::carpet(3, 2).unwrap()).is_err());214        assert!(extract(&designs::ones(2, 1).unwrap(), &designs::ones(2, 1).unwrap()).is_err());215    }216217    #[test]218    fn refuses_a_stray_length() {219        let plain = carrier();220        let mut carried = embed(&plain, b"x").unwrap();221        for &site in sites(&plain).iter().take(HEADER) {222            carried.cell.types.put(site, 1);223        }224        assert!(extract(&plain, &carried).is_err());225    }226227    #[test]228    fn the_sheet_frames_the_carrier() {229        let tiles = [230            designs::carpet(3, 3).unwrap(),231            designs::vtree(3, 3).unwrap().rotate(1).unwrap(),232            designs::vtree(3, 3).unwrap(),233            designs::carpet(3, 3).unwrap(),234        ];235        let framed = sheet(&tiles, b"Hello, World!").unwrap();236        assert_eq!((framed.width(), framed.height()), (135, 135));237        assert_eq!(read(&framed, &tiles[3]).unwrap(), b"Hello, World!");238        let corner = block(&framed, 0, 0, 27, 27).unwrap();239        assert_eq!(corner.types(), tiles[0].types());240        assert!(read(&tiles[3].clone(), &tiles[3]).is_err());241    }242243    #[test]244    fn the_frame_names_four_tiles() {245        let mask = frame();246        assert_eq!(mask.shape, vec![5, 5]);247        assert_eq!(mask.get(&[0, 0]).unwrap(), 0);248        assert_eq!(mask.get(&[0, 1]).unwrap(), 1);249        assert_eq!(mask.get(&[1, 0]).unwrap(), 2);250        assert_eq!(mask.get(&[2, 2]).unwrap(), 3);251        let mut seen = [0usize; 4];252        for &value in mask.bytes().unwrap() {253            seen[value as usize] += 1;254        }255        assert_eq!(seen, [4, 6, 6, 9]);256    }257}