use crate::bitstream::{BitReader, BitWriter};
use crate::error::Error;
use crate::header::Header;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkHeader {
pub version: u8,
pub chunk_set_id: u32,
pub count: u8,
pub index: u8,
}
impl ChunkHeader {
pub fn write(&self, w: &mut BitWriter) -> Result<(), Error> {
if !(1..=64).contains(&(self.count as u32)) {
return Err(Error::ChunkCountOutOfRange { count: self.count });
}
if self.index >= self.count {
return Err(Error::ChunkIndexOutOfRange {
index: self.index,
count: self.count,
});
}
if self.chunk_set_id >= (1 << 20) {
return Err(Error::ChunkSetIdOutOfRange {
id: self.chunk_set_id,
});
}
w.write_bits(u64::from(self.version & 0b1111), 4);
w.write_bits(1, 1); w.write_bits(u64::from(self.chunk_set_id), 20);
w.write_bits((self.count - 1) as u64, 6); w.write_bits(u64::from(self.index), 6);
Ok(())
}
pub fn read(r: &mut BitReader) -> Result<Self, Error> {
let version = r.read_bits(4)? as u8;
if version != Header::WF_REDESIGN_VERSION {
return Err(Error::WireVersionMismatch { got: version });
}
let chunked = r.read_bits(1)? != 0;
if !chunked {
return Err(Error::ChunkHeaderChunkedFlagMissing);
}
let chunk_set_id = r.read_bits(20)? as u32;
let count = (r.read_bits(6)? + 1) as u8;
let index = r.read_bits(6)? as u8;
Ok(Self {
version,
chunk_set_id,
count,
index,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::header::Header;
#[test]
fn chunk_header_round_trip() {
let h = ChunkHeader {
version: Header::WF_REDESIGN_VERSION,
chunk_set_id: 0xABCDE,
count: 3,
index: 1,
};
let mut w = BitWriter::new();
h.write(&mut w).unwrap();
assert_eq!(w.bit_len(), 37);
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(ChunkHeader::read(&mut r).unwrap(), h);
}
#[test]
fn chunk_header_count_64_round_trip() {
let h = ChunkHeader {
version: Header::WF_REDESIGN_VERSION,
chunk_set_id: 0,
count: 64,
index: 63,
};
let mut w = BitWriter::new();
h.write(&mut w).unwrap();
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(ChunkHeader::read(&mut r).unwrap(), h);
}
#[test]
fn chunk_header_count_zero_rejected() {
let h = ChunkHeader {
version: Header::WF_REDESIGN_VERSION,
chunk_set_id: 0,
count: 0,
index: 0,
};
let mut w = BitWriter::new();
assert!(matches!(
h.write(&mut w),
Err(Error::ChunkCountOutOfRange { count: 0 })
));
}
#[test]
fn chunk_header_rejects_v0x_version() {
let mut w = BitWriter::new();
w.write_bits(0, 4); w.write_bits(1, 1); w.write_bits(0, 20); w.write_bits(0, 6); w.write_bits(0, 6); assert_eq!(w.bit_len(), 37);
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert!(matches!(
ChunkHeader::read(&mut r),
Err(Error::WireVersionMismatch { got: 0 })
));
}
}
use crate::identity::Md1EncodingId;
pub fn derive_chunk_set_id(id: &Md1EncodingId) -> u32 {
let bytes = id.as_bytes();
((bytes[0] as u32) << 12) | ((bytes[1] as u32) << 4) | ((bytes[2] as u32) >> 4)
}
#[cfg(test)]
mod chunk_set_id_tests {
use super::*;
#[test]
fn derive_chunk_set_id_deterministic() {
let mut bytes = [0u8; 16];
bytes[0] = 0xab;
bytes[1] = 0xcd;
bytes[2] = 0xe1;
bytes[3] = 0x23;
let id = Md1EncodingId::new(bytes);
let csid_a = derive_chunk_set_id(&id);
let csid_b = derive_chunk_set_id(&id);
assert_eq!(csid_a, csid_b);
}
#[test]
fn derive_chunk_set_id_msb_first_extraction() {
let mut bytes = [0u8; 16];
bytes[0] = 0xAB;
bytes[1] = 0xCD;
bytes[2] = 0xEF;
let id = Md1EncodingId::new(bytes);
assert_eq!(derive_chunk_set_id(&id), 0xABCDE);
}
}
use crate::encode::Descriptor;
pub const SINGLE_STRING_PAYLOAD_BIT_LIMIT: usize = 64 * 5;
pub fn split(d: &Descriptor) -> Result<Vec<String>, Error> {
use crate::bitstream::BitWriter;
use crate::encode::encode_payload;
use crate::identity::compute_md1_encoding_id;
let (payload_bytes, _payload_bits) = encode_payload(d)?;
let md1_id = compute_md1_encoding_id(d)?;
let chunk_set_id = derive_chunk_set_id(&md1_id);
let payload_bit_count_for_sizing = payload_bytes.len() * 8;
let chunks_needed = payload_bit_count_for_sizing.div_ceil(SINGLE_STRING_PAYLOAD_BIT_LIMIT);
if chunks_needed > 64 {
return Err(Error::ChunkCountExceedsMax {
needed: chunks_needed,
});
}
let count: u8 = if chunks_needed == 0 {
1
} else {
chunks_needed as u8
};
let bytes_per_chunk = payload_bytes.len().div_ceil(count as usize);
let mut chunks = Vec::with_capacity(count as usize);
for index in 0..count {
let start_byte = (index as usize) * bytes_per_chunk;
let end_byte = ((index as usize + 1) * bytes_per_chunk).min(payload_bytes.len());
let chunk_payload_bytes = &payload_bytes[start_byte..end_byte];
let header = ChunkHeader {
version: Header::WF_REDESIGN_VERSION,
chunk_set_id,
count,
index,
};
let mut w = BitWriter::new();
header.write(&mut w)?;
for byte in chunk_payload_bytes {
w.write_bits(u64::from(*byte), 8);
}
let chunk_bit_count = 37 + 8 * chunk_payload_bytes.len();
let bytes = w.into_bytes();
let s = crate::codex32::wrap_payload(&bytes, chunk_bit_count)?;
chunks.push(s);
}
Ok(chunks)
}
use crate::decode::decode_payload;
pub fn reassemble(strings: &[&str]) -> Result<Descriptor, Error> {
use crate::bitstream::BitReader;
use crate::codex32::unwrap_string;
use crate::identity::compute_md1_encoding_id;
if strings.is_empty() {
return Err(Error::ChunkSetEmpty);
}
let mut parsed: Vec<(ChunkHeader, Vec<u8>)> = Vec::with_capacity(strings.len());
for s in strings {
let (bytes, symbol_aligned_bit_count) = unwrap_string(s)?;
let mut r = BitReader::with_bit_limit(&bytes, symbol_aligned_bit_count);
let header = ChunkHeader::read(&mut r)?;
let payload_byte_count = (symbol_aligned_bit_count - 37) / 8;
let mut chunk_payload_bytes = Vec::with_capacity(payload_byte_count);
for _ in 0..payload_byte_count {
let v = r.read_bits(8)? as u8;
chunk_payload_bytes.push(v);
}
parsed.push((header, chunk_payload_bytes));
}
let (h0, _) = &parsed[0];
let expected_count = h0.count;
let expected_csid = h0.chunk_set_id;
let expected_version = h0.version;
for (h, _) in &parsed {
if h.count != expected_count
|| h.chunk_set_id != expected_csid
|| h.version != expected_version
{
return Err(Error::ChunkSetInconsistent);
}
}
if parsed.len() != expected_count as usize {
return Err(Error::ChunkSetIncomplete {
got: parsed.len(),
expected: expected_count as usize,
});
}
parsed.sort_by_key(|(h, _)| h.index);
for (i, (h, _)) in parsed.iter().enumerate() {
if h.index as usize != i {
return Err(Error::ChunkIndexGap {
expected: i as u8,
got: h.index,
});
}
}
let mut full_bytes = Vec::new();
for (_, chunk_bytes) in &parsed {
full_bytes.extend_from_slice(chunk_bytes);
}
let descriptor = decode_payload(&full_bytes, full_bytes.len() * 8)?;
let md1_id = compute_md1_encoding_id(&descriptor)?;
let derived_csid = derive_chunk_set_id(&md1_id);
if derived_csid != expected_csid {
return Err(Error::ChunkSetIdMismatch {
expected: expected_csid,
derived: derived_csid,
});
}
Ok(descriptor)
}