crw: split decode_entropy under the cognitive-complexity limit

main's workspace lints now deny cognitive_complexity above 30, and decode_entropy scored 41.
The emit macro becomes a Chains struct, unstuffing its own function, and the three stream-end
checks one. Same algorithm: the pinned CC0 digests still match byte for byte.

Also correct two record lines: the G5 is the one file that does not match, and the frozen
code tables are not the only logs that ship.
This commit is contained in:
Dylan M. Taylor committed 2026-10-28 22:15:50 -04:00
1 parent fea36c4309
commit b600e6a5cd
3 files changed
+71 -72

No files matched your search

+2 -2
View File
@@ -111,5 +111,5 @@ which the Rust implementation may normalise, provided the verification table of
## 5 Verification
See `03-verification.md` (written after the freeze): full-frame exact-match decode
against LibRaw 0.22.2 for every sample file, public and private, with zero tuning
after the freeze commit.
against LibRaw 0.22.2, with zero tuning after the freeze commit: every sample file, public
and private, matches except the PowerShot G5, which the decoder refuses (28 of 29).
+2 -2
View File
@@ -29,8 +29,8 @@ renumbered `01`–`05` in reading order.
- `logs/table_sel{0,1,2}.json`: **the frozen code tables**, the source of truth that
`tools/gen_tables.py` renders into `../src/tables.rs`. `table_sel0_d60class.json` is the
revision-1 selector-0 table, kept for the record. These are the only logs that ship with the
crate: without them the frozen tables cannot be regenerated or audited.
revision-1 selector-0 table, kept for the record. These are the frozen code-table logs that ship
with the crate: without them the frozen tables cannot be regenerated or audited.
- `logs/private-corpus.tsv`: names, sizes and SHA-256 of the maintainer's own files. No image
content, metadata or thumbnails are recorded anywhere here.
- `logs/private/` (gitignored): per-file dumps of the private files, which can hold personal
+67 -68
View File
@@ -647,13 +647,7 @@ fn build_lut(codes: &[(u16, u8, u8, u8)]) -> Vec<u32> {
/// initialised to 512 and reset at every row start. A stream that ends early leaves the remaining
/// samples at their chain values (the frozen stream-end rule).
fn decode_entropy(entropy: &[u8], w: usize, h: usize, tables: &tables::SelTables) -> Result<Vec<u16>> {
// unstuff: an 0x00 after an 0xff is a stuffing byte
let mut d = Vec::with_capacity(entropy.len());
let mut i = 0;
while let Some(&b) = entropy.get(i) {
d.push(b);
i += if b == 0xff && entropy.get(i + 1) == Some(&0x00) { 2 } else { 1 };
}
let d = unstuff(entropy);
let nbits = d.len().saturating_mul(8);
let peek16 = |pos: usize| -> u16 {
let (byte, sh) = (pos >> 3, pos & 7);
@@ -664,65 +658,35 @@ fn decode_entropy(entropy: &[u8], w: usize, h: usize, tables: &tables::SelTables
let luts = [build_lut(tables.first), build_lut(tables.main)];
let n = w.checked_mul(h).ok_or(Error::LimitExceeded("image too large"))?;
let mut dec = vec![0u16; n];
let (mut ce, mut co): (i32, i32) = (512, 512);
let mut out = Chains { dec: vec![0u16; n], w, qi: 0, ce: 512, co: 512 };
let mut carry: i32 = 0;
let mut pos = 0usize;
let mut qi = 0usize;
// emit one diff: row-start chain reset, parity chain update, 16-bit masked store
macro_rules! emit {
($d:expr) => {{
if qi % w == 0 {
ce = 512;
co = 512;
}
let c = if qi % 2 == 0 { &mut ce } else { &mut co };
*c = c.wrapping_add($d);
if let Some(p) = dec.get_mut(qi) {
*p = (*c & 0xffff) as u16;
}
qi += 1;
}};
}
'frame: while qi < n {
let block_end = n.min((qi / 64 + 1) * 64);
while out.qi < n {
let block_end = n.min((out.qi / 64 + 1) * 64);
let mut tok = 0u32;
while qi < block_end {
while out.qi < block_end {
// stream exhausted: every remaining sample keeps its chain value
if pos >= nbits.saturating_sub(1) {
// stream exhausted: every remaining sample keeps its chain value
while qi < n {
emit!(0);
}
break 'frame;
out.fill(n);
break;
}
let e = luts[usize::from(tok > 0)].get(peek16(pos) as usize).copied().unwrap_or(0);
if e == 0 {
return Err(corrupt("undecodable entropy code"));
}
let (len, rr, s) = ((e >> 16) as usize, ((e >> 8) & 0xff) as usize, e & 0xff);
if pos.saturating_add(len) > nbits {
while qi < n {
emit!(0);
}
break 'frame;
let (len, mut rr, s) = ((e >> 16) as usize, ((e >> 8) & 0xff) as usize, e & 0xff);
if pos.saturating_add(len).saturating_add(s as usize) > nbits {
out.fill(n);
break;
}
pos += len;
let mut rr = rr;
if s == 0 && rr == 0 && tok > 0 {
tok += 1;
if s == 0 && rr == 0 && tok > 1 {
// end of block
while qi < block_end {
emit!(0);
}
tok += 1;
out.fill(block_end);
continue;
}
let diff: i32 = if s > 0 {
if pos.saturating_add(s as usize) > nbits {
while qi < n {
emit!(0);
}
break 'frame;
}
let m = (peek16(pos) >> (16 - s)) as i32;
pos += s as usize;
// JPEG magnitude rule
@@ -730,32 +694,67 @@ fn decode_entropy(entropy: &[u8], w: usize, h: usize, tables: &tables::SelTables
} else {
0
};
let mut first = qi.is_multiple_of(64);
let mut first = out.qi.is_multiple_of(64);
if first && rr > 0 {
// the block's first sample covered by the skip: its diff is exactly the carry
emit!(carry);
out.emit(carry);
rr -= 1;
first = false;
}
for _ in 0..rr {
if qi >= block_end {
break;
}
emit!(0);
}
if qi >= block_end {
tok += 1;
out.fill(block_end.min(out.qi + rr));
if out.qi >= block_end {
continue;
}
let dd = if first {
if first {
carry = carry.wrapping_add(diff);
carry
out.emit(carry);
} else {
diff
};
emit!(dd);
tok += 1;
out.emit(diff);
}
}
}
Ok(out.dec)
}
/// Drop the stuffing byte: an 0x00 after an 0xff.
fn unstuff(entropy: &[u8]) -> Vec<u8> {
let mut d = Vec::with_capacity(entropy.len());
let mut i = 0;
while let Some(&b) = entropy.get(i) {
d.push(b);
i += if b == 0xff && entropy.get(i + 1) == Some(&0x00) { 2 } else { 1 };
}
d
}
/// The decoded samples and the two parity DPCM chains that produce them.
struct Chains {
dec: Vec<u16>,
w: usize,
qi: usize,
ce: i32,
co: i32,
}
impl Chains {
/// Emit one diff: row-start chain reset, parity chain update, 16-bit masked store.
fn emit(&mut self, d: i32) {
if self.qi.is_multiple_of(self.w) {
self.ce = 512;
self.co = 512;
}
let c = if self.qi.is_multiple_of(2) { &mut self.ce } else { &mut self.co };
*c = c.wrapping_add(d);
if let Some(p) = self.dec.get_mut(self.qi) {
*p = (*c & 0xffff) as u16;
}
self.qi += 1;
}
/// Emit zero diffs up to sample `end`.
fn fill(&mut self, end: usize) {
while self.qi < end {
self.emit(0);
}
}
Ok(dec)
}