1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Single-payload header (5 bits) per SPEC v0.30 §2.1.
//!
//! bit 4: divergent-paths flag (0 = shared origin path, 1 = divergent)
//! bits 3..0: 4-bit version field (v0.30 = 4; usable WF-redesign set {4, 8, 12} per §2.4)
//!
//! The chunk header (`chunk.rs`, SPEC v0.30 §2.2) is a separate 37-bit form
//! with a different first-symbol layout — chunked-flag relocated to bit 0 of
//! the first 5-bit symbol enabling in-band auto-dispatch per SPEC §2.3. v0.x
//! single-payload (version=0) and v0.x chunked-misread-as-version=2 are both
//! rejected with `Error::WireVersionMismatch` per the SPEC §2.5 trace.
use crate::bitstream::{BitReader, BitWriter};
use crate::error::Error;
/// 5-bit single-payload header per SPEC v0.30 §2.1.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Header {
/// Wire-format generation (4 bits). v0.30 = 4.
pub version: u8,
/// Bit 4: false = shared origin path, true = divergent per-`@N` paths.
pub divergent_paths: bool,
}
impl Header {
/// Wire-format version constant for v0.30 (the redesign cycle).
/// Usable WF-redesign version set per SPEC §2.4: {4, 8, 12}.
pub const WF_REDESIGN_VERSION: u8 = 4;
/// Encode the 5-bit header into the bit stream.
pub fn write(&self, w: &mut BitWriter) {
let bits = (u64::from(self.divergent_paths) << 4) | u64::from(self.version & 0b1111);
w.write_bits(bits, 5);
}
/// Decode the 5-bit header from the bit stream. Rejects inputs whose
/// version field ≠ `WF_REDESIGN_VERSION` (4 in this release) with
/// `Error::WireVersionMismatch` per SPEC §2.5.
pub fn read(r: &mut BitReader) -> Result<Self, Error> {
let bits = r.read_bits(5)?;
let divergent_paths = (bits >> 4) & 1 != 0;
let version = (bits & 0b1111) as u8;
if version != Self::WF_REDESIGN_VERSION {
return Err(Error::WireVersionMismatch { got: version });
}
Ok(Self {
version,
divergent_paths,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_round_trip_shared() {
let h = Header {
version: Header::WF_REDESIGN_VERSION,
divergent_paths: false,
};
let mut w = BitWriter::new();
h.write(&mut w);
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(Header::read(&mut r).unwrap(), h);
}
#[test]
fn header_round_trip_divergent() {
let h = Header {
version: Header::WF_REDESIGN_VERSION,
divergent_paths: true,
};
let mut w = BitWriter::new();
h.write(&mut w);
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(Header::read(&mut r).unwrap(), h);
}
/// SPEC v0.30 §2.5 v0.x rejection trace. Two arms cover (a) v0.x
/// single-payload (version=0) and (b) v0.x chunked read as single-
/// payload (auto-dispatch read by `decode.rs` per §2.3 routes to
/// `Header::read` and the embedded chunked-flag bit is parsed as
/// version bit v0, yielding got=2). Both arms reject cleanly with
/// `Error::WireVersionMismatch`.
#[test]
fn header_rejects_version_mismatch() {
// Arm 1: v0.x single-payload (paths=0, version=0)
// first 5 bits MSB-first = [0][0][0][0][0] = 0b00000
// packed MSB-aligned byte = 0b00000_000 = 0x00
let bytes = vec![0x00];
let mut r = BitReader::new(&bytes);
assert!(matches!(
Header::read(&mut r),
Err(Error::WireVersionMismatch { got: 0 })
));
// Arm 2: v0.x chunked-misread-as-single-payload per SPEC §2.5
// v0.x chunked first 5 bits = [v_msb=0][v=0][v_lsb=0][chunked=1][reserved=0]
// = 0b00010 (numeric value 2). Read as v0.30 single-payload:
// bit 4 = paths = 0; bits 3..0 = version = 0b0010 = 2.
// packed MSB-aligned byte = 0b00010_000 = 0x10
let bytes = vec![0x10];
let mut r = BitReader::new(&bytes);
assert!(matches!(
Header::read(&mut r),
Err(Error::WireVersionMismatch { got: 2 })
));
}
#[test]
fn header_common_case_byte_value() {
// Common case: version=4 (v0.30), divergent_paths=false ⇒
// first 5 bits = [paths=0][v3=0][v2=1][v1=0][v0=0] = 0b00100 = 0x04
// packed MSB-aligned byte = 0b00100_000 = 0x20
let h = Header {
version: Header::WF_REDESIGN_VERSION,
divergent_paths: false,
};
let mut w = BitWriter::new();
h.write(&mut w);
assert_eq!(w.into_bytes(), vec![0x20]);
}
}