carry.rs

7.5 kB · rust · 238 lines

1use super::census;2use super::geometry;3use super::models::Cell2d;4use mrlycore::errors::{value_error, Result};5use mrlycore::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    Tensor::of(FRAME.to_vec(), vec![5, 5])21}2223/// Returns the payload bytes the cell's filled sites can hold, its length header paid for.24///25/// ```26/// let cell = mrlymath::two::carpet(3, 3).unwrap();27/// assert_eq!(mrlymath::two::carry::capacity(&cell), 60);28/// ```29pub fn capacity(cell: &Cell2d) -> usize {30    census::fills(cell).saturating_sub(HEADER) / 831}3233/// Writes the payload over the cell's filled sites, repeating it until every site is spoken for.34pub fn embed(cell: &Cell2d, payload: &[u8]) -> Result<Cell2d> {35    let sites = sites(cell);36    let bits = message(payload);37    if bits.len() > sites.len() {38        return value_error(format!(39            "payload needs {} sites, the cell carries {}.",40            bits.len(),41            sites.len()42        ));43    }44    let mut out = cell.clone();45    for (k, &site) in sites.iter().enumerate() {46        out.cell.types.put(site, i64::from(bits[k % bits.len()]));47    }48    Ok(out)49}5051/// Reads the payload back, the plain cell naming the sites the carried one wrote over.52pub fn extract(carrier: &Cell2d, carried: &Cell2d) -> Result<Vec<u8>> {53    if carrier.types().shape != carried.types().shape {54        return value_error("carrier and carried must share one shape.");55    }56    let sites = sites(carrier);57    if sites.len() < HEADER {58        return value_error("carrier holds too few sites for a header.");59    }60    let bits: Vec<u8> = sites61        .iter()62        .map(|&site| u8::from(carried.types().at(site) != 0))63        .collect();64    let length = bits[..HEADER]65        .iter()66        .fold(0usize, |acc, &bit| acc << 1 | bit as usize);67    if HEADER + length * 8 > bits.len() {68        return value_error("carried length runs past the carrier.");69    }70    Ok(bits[HEADER..HEADER + length * 8]71        .chunks(8)72        .map(|byte| byte.iter().fold(0u8, |acc, &bit| acc << 1 | bit))73        .collect())74}7576/// Builds the framed sheet of four same-sized cells, the fourth carrying the payload.77pub fn sheet(cells: &[Cell2d; 4], payload: &[u8]) -> Result<Cell2d> {78    let carried = embed(&cells[3], payload)?;79    let laid = [80        cells[0].clone(),81        cells[1].clone(),82        cells[2].clone(),83        carried,84    ];85    geometry::mosaic(&frame(), &laid)86}8788/// Reads the payload back from a framed sheet, the plain fourth cell naming the sites.89pub fn read(sheet: &Cell2d, carrier: &Cell2d) -> Result<Vec<u8>> {90    let (w, h) = (carrier.width(), carrier.height());91    if sheet.width() != w * 5 || sheet.height() != h * 5 {92        return value_error("sheet must be five carriers across and down.");93    }94    extract(carrier, &block(sheet, 1, 1, w, h))95}9697fn sites(cell: &Cell2d) -> Vec<usize> {98    let types = cell.types();99    (0..types.size()).filter(|&i| types.at(i) == 1).collect()100}101102fn message(payload: &[u8]) -> Vec<u8> {103    let mut bits = spread(&(payload.len() as u32).to_be_bytes());104    bits.extend(spread(payload));105    bits106}107108fn spread(bytes: &[u8]) -> Vec<u8> {109    bytes110        .iter()111        .flat_map(|&byte| (0..8).rev().map(move |k| byte >> k & 1))112        .collect()113}114115fn block(cell: &Cell2d, row: usize, col: usize, width: usize, height: usize) -> Cell2d {116    let mut types = Tensor::new(vec![height, width]);117    for y in 0..height {118        for x in 0..width {119            types.set(120                &[y, x],121                cell.types().get(&[row * height + y, col * width + x]),122            );123        }124    }125    Cell2d::new(types)126}127128#[cfg(test)]129mod tests {130    use super::*;131    use crate::two::designs;132133    fn carrier() -> Cell2d {134        designs::carpet(3, 3).unwrap()135    }136137    #[test]138    fn payload_round_trips_through_the_filled_sites() {139        let plain = carrier();140        let payload = b"Hello, World!";141        let carried = embed(&plain, payload).unwrap();142        assert_eq!(carried.types().shape, plain.types().shape);143        assert_ne!(carried.types(), plain.types());144        assert_eq!(extract(&plain, &carried).unwrap(), payload);145    }146147    #[test]148    fn every_payload_length_survives() {149        let plain = carrier();150        for length in [0usize, 1, 2, 7, 8, 59, 60] {151            let payload: Vec<u8> = (0..length).map(|i| (i * 37 % 251) as u8).collect();152            let carried = embed(&plain, &payload).unwrap();153            assert_eq!(154                extract(&plain, &carried).unwrap(),155                payload,156                "length {length}"157            );158        }159    }160161    #[test]162    fn capacity_is_the_fills_less_the_header() {163        let plain = carrier();164        assert_eq!(census::fills(&plain), 512);165        assert_eq!(capacity(&plain), 60);166        let full = vec![7u8; capacity(&plain)];167        assert!(embed(&plain, &full).is_ok());168        let over = vec![7u8; capacity(&plain) + 1];169        assert!(embed(&plain, &over).is_err());170        assert_eq!(capacity(&designs::ones(2, 1).unwrap()), 0);171    }172173    #[test]174    fn the_payload_repeats_across_the_spare_sites() {175        let plain = carrier();176        let carried = embed(&plain, b"ab").unwrap();177        let sites = sites(&plain);178        let bits = message(b"ab");179        for (k, &site) in sites.iter().enumerate() {180            assert_eq!(181                carried.types().at(site) as u8,182                bits[k % bits.len()],183                "site {site}"184            );185        }186    }187188    #[test]189    fn the_carrier_names_the_sites() {190        let plain = carrier();191        let carried = embed(&plain, b"secret").unwrap();192        let wrong = designs::net(3, 3).unwrap();193        assert!(extract(&wrong, &carried).unwrap_or_default() != b"secret".to_vec());194        assert!(extract(&plain, &designs::carpet(3, 2).unwrap()).is_err());195        assert!(extract(&designs::ones(2, 1).unwrap(), &designs::ones(2, 1).unwrap()).is_err());196    }197198    #[test]199    fn a_stray_length_is_refused() {200        let plain = carrier();201        let mut carried = embed(&plain, b"x").unwrap();202        for &site in sites(&plain).iter().take(HEADER) {203            carried.cell.types.put(site, 1);204        }205        assert!(extract(&plain, &carried).is_err());206    }207208    #[test]209    fn the_sheet_frames_the_carrier() {210        let tiles = [211            designs::carpet(3, 3).unwrap(),212            designs::vtree(3, 3).unwrap().rotate(1),213            designs::vtree(3, 3).unwrap(),214            designs::carpet(3, 3).unwrap(),215        ];216        let framed = sheet(&tiles, b"Hello, World!").unwrap();217        assert_eq!((framed.width(), framed.height()), (135, 135));218        assert_eq!(read(&framed, &tiles[3]).unwrap(), b"Hello, World!");219        let corner = block(&framed, 0, 0, 27, 27);220        assert_eq!(corner.types(), tiles[0].types());221        assert!(read(&tiles[3].clone(), &tiles[3]).is_err());222    }223224    #[test]225    fn the_frame_names_four_tiles() {226        let mask = frame();227        assert_eq!(mask.shape, vec![5, 5]);228        assert_eq!(mask.get(&[0, 0]), 0);229        assert_eq!(mask.get(&[0, 1]), 1);230        assert_eq!(mask.get(&[1, 0]), 2);231        assert_eq!(mask.get(&[2, 2]), 3);232        let mut seen = [0usize; 4];233        for &value in mask.bytes() {234            seen[value as usize] += 1;235        }236        assert_eq!(seen, [4, 6, 6, 9]);237    }238}