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}