use super::codec::Layout;
use super::Chain;
use crate::error::ParseError;
pub(super) const ROOT_KEY: usize = 5;
pub const fn packet_len(layout: Layout) -> usize {
match layout {
Layout::V2 => 381,
Layout::V3 | Layout::V4 => 128,
}
}
const fn later_header_len(layout: Layout) -> usize {
match layout {
Layout::V2 => 372,
Layout::V3 | Layout::V4 => 116,
}
}
const fn preamble(chain: Chain) -> usize {
match chain {
Chain::Early => 1092,
Chain::Library2 => 990,
Chain::Wide => 852,
}
}
pub fn first_header_len(layout: Layout, chain: Chain, cat_len: usize, map_len: usize) -> usize {
let used = cat_len + map_len;
let mut room = preamble(chain);
while room <= used {
room += packet_len(layout);
}
room - used
}
pub fn header_len(
layout: Layout,
chain: Chain,
index: usize,
cat_len: usize,
map_len: usize,
) -> usize {
if index > 0 {
later_header_len(layout)
} else {
first_header_len(layout, chain, cat_len, map_len)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stroke {
pub root_key: u8,
pub packets: Option<usize>,
}
pub fn read(
payload: &[u8],
chain: Chain,
index: usize,
cat_len: usize,
map_len: usize,
) -> Result<Stroke, ParseError> {
let root_key = *payload.get(ROOT_KEY).ok_or_else(|| {
ParseError::AssertFail(format!("stroke {index} is {} bytes", payload.len()))
})?;
let header = header_len(Layout::V2, chain, index, cat_len, map_len);
let packets = payload
.len()
.checked_sub(header)
.filter(|body| body % packet_len(Layout::V2) == 0)
.map(|body| body / packet_len(Layout::V2));
Ok(Stroke { root_key, packets })
}
pub fn set_root_key(payload: &mut [u8], note: u8) -> Result<(), ParseError> {
let slot = payload
.get_mut(ROOT_KEY)
.ok_or_else(|| ParseError::AssertFail("stroke is too short to hold a root key".into()))?;
*slot = note;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn map_len(z: usize) -> usize {
801 + 15 * (z - 1)
}
const OUR_CAT: usize = 24;
fn stroke(index: usize, zones: usize, packets: usize, root: u8) -> Vec<u8> {
let mut v = vec![
0u8;
header_len(Layout::V2, Chain::Library2, index, OUR_CAT, map_len(zones))
+ packets * packet_len(Layout::V2)
];
v[ROOT_KEY] = root;
v
}
#[test]
fn header_shrinks_only_for_the_first_stroke() {
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, OUR_CAT, map_len(1)),
165
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, OUR_CAT, map_len(2)),
150
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, OUR_CAT, map_len(3)),
135
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 1, OUR_CAT, map_len(2)),
372
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 2, OUR_CAT, map_len(3)),
372
);
}
#[test]
fn the_preamble_grows_by_a_packet_rather_than_going_negative() {
for (zones, header) in [(4, 120), (6, 90), (8, 60), (12, 381), (16, 321)] {
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, OUR_CAT, map_len(zones)),
header,
"{zones} zones"
);
}
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, OUR_CAT, map_len(11)),
15
);
}
#[test]
fn a_smaller_cat_section_lends_its_bytes_to_the_header() {
const VENDOR_CAT: usize = 9;
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, VENDOR_CAT, map_len(6)),
105
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, VENDOR_CAT, map_len(11)),
30
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, VENDOR_CAT, map_len(12)),
15
);
assert_eq!(
header_len(Layout::V2, Chain::Library2, 0, VENDOR_CAT, map_len(16)),
336
);
}
#[test]
fn the_pre_library_2_chain_spends_its_missing_cat_on_the_header() {
const NO_CAT: usize = 0;
let early_map = |z: usize| 786 + 12 * z;
for (zones, header) in [
(1, 294),
(16, 114),
(25, 6),
(27, 363),
(35, 267),
(68, 252),
] {
assert_eq!(
header_len(Layout::V2, Chain::Early, 0, NO_CAT, early_map(zones)),
header,
"{zones} zones"
);
}
assert_eq!(
header_len(Layout::V2, Chain::Early, 1, NO_CAT, early_map(10)),
372
);
}
#[test]
fn the_wide_chain_budgets_its_preamble_the_same_way() {
const WIDE_CAT: usize = 8;
for (layout, map, header) in [
(Layout::V3, 792, 52),
(Layout::V3, 792 + 16 * 11, 4),
(Layout::V3, 792 + 16 * 12, 116),
(Layout::V4, 1340, 16),
(Layout::V4, 1340 + 16, 128),
] {
assert_eq!(
header_len(layout, Chain::Wide, 0, WIDE_CAT, map),
header,
"{layout:?}, a {map}-byte map"
);
}
assert_eq!(header_len(Layout::V3, Chain::Wide, 1, WIDE_CAT, 792), 116);
assert_eq!(header_len(Layout::V4, Chain::Wide, 3, WIDE_CAT, 1340), 116);
}
#[test]
fn reads_root_key_and_packet_count() {
let s = read(
&stroke(0, 1, 4, 60),
Chain::Library2,
0,
OUR_CAT,
map_len(1),
)
.unwrap();
assert_eq!(s.root_key, 60);
assert_eq!(s.packets, Some(4));
}
#[test]
fn lengths_from_the_corpus_decompose_exactly() {
for (index, zones, cat, len, packets) in [
(0, 1, OUR_CAT, 1689, 4), (0, 2, OUR_CAT, 1674, 4), (1, 2, OUR_CAT, 1896, 4),
(0, 3, OUR_CAT, 2040, 5),
(2, 3, OUR_CAT, 1896, 4),
(0, 1, OUR_CAT, 165, 0), (0, 4, OUR_CAT, 2787, 7), (0, 12, OUR_CAT, 3048, 7), (0, 16, OUR_CAT, 2988, 7),
(0, 6, 9, 10773, 28), (0, 11, 9, 7269, 19), (0, 16, 9, 1860, 4), ] {
let mut v = vec![0u8; len];
v[ROOT_KEY] = 60;
let s = read(&v, Chain::Library2, index, cat, map_len(zones))
.unwrap_or_else(|e| panic!("stroke {index} of {zones}, {len} bytes: {e}"));
assert_eq!(
s.packets,
Some(packets),
"stroke {index} of {zones}, {len} bytes"
);
}
}
#[test]
fn a_length_that_is_not_header_plus_packets_has_no_count() {
let mut v = stroke(0, 1, 2, 60);
v.push(0);
assert_eq!(
read(&v, Chain::Library2, 0, OUR_CAT, map_len(1))
.unwrap()
.packets,
None
);
}
#[test]
fn a_stroke_shorter_than_its_header_has_no_count() {
assert_eq!(
read(&[0u8; 8], Chain::Library2, 1, OUR_CAT, map_len(2))
.unwrap()
.packets,
None
);
}
#[test]
fn set_root_key_moves_one_byte() {
let before = stroke(0, 1, 1, 60);
let mut after = before.clone();
set_root_key(&mut after, 48).unwrap();
let differing: Vec<_> = (0..before.len())
.filter(|&i| before[i] != after[i])
.collect();
assert_eq!(differing, vec![ROOT_KEY]);
}
}