Skip to main content

mk_codec/bytecode/
header.rs

1//! 1-byte bytecode header — version + reserved + fingerprint flag.
2//!
3//! Per `design/SPEC_mk_v0_1.md` §3.1 (closure Q-8 lock):
4//!
5//! ```text
6//! bit 7-4: version (4 bits)        — 0x0 in v0.1
7//! bit 3:   reserved                — MUST be 0 in v0.1
8//! bit 2:   fingerprint flag        — 1 if origin_fingerprint is present
9//! bit 1:   reserved                — MUST be 0 in v0.1
10//! bit 0:   reserved                — MUST be 0 in v0.1
11//! ```
12//!
13//! Valid v0.1 header bytes: `0x00` and `0x04`. mk1's bit-allocation
14//! shape mirrors md1's; bit-2 fingerprint-flag semantics align at the
15//! cross-format pattern level (D-14).
16
17use crate::error::{Error, Result};
18
19/// Version field width (bits).
20const VERSION_SHIFT: u8 = 4;
21
22/// Bit-2 fingerprint flag mask.
23const FINGERPRINT_FLAG_MASK: u8 = 0b0000_0100;
24
25/// Reserved-bit mask in v0.1: bits 0, 1, 3.
26const RESERVED_MASK: u8 = 0b0000_1011;
27
28/// Parsed mk1 bytecode header.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct BytecodeHeader {
31    /// Version field, range 0..=15.
32    pub version: u8,
33    /// Bit 2: when `true`, `origin_fingerprint` is present in the payload.
34    pub fingerprint_flag: bool,
35}
36
37impl BytecodeHeader {
38    /// Parse a single byte into a `BytecodeHeader`. Rejects unknown
39    /// versions and any reserved bits set.
40    pub fn parse(byte: u8) -> Result<Self> {
41        let version = byte >> VERSION_SHIFT;
42        if version != 0 {
43            return Err(Error::UnsupportedVersion(version));
44        }
45        if byte & RESERVED_MASK != 0 {
46            return Err(Error::ReservedBitsSet);
47        }
48        Ok(BytecodeHeader {
49            version,
50            fingerprint_flag: byte & FINGERPRINT_FLAG_MASK != 0,
51        })
52    }
53
54    /// Serialize the header to its single-byte wire form.
55    pub fn to_byte(self) -> u8 {
56        let mut byte = (self.version & 0x0F) << VERSION_SHIFT;
57        if self.fingerprint_flag {
58            byte |= FINGERPRINT_FLAG_MASK;
59        }
60        byte
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn round_trip_no_fingerprint() {
70        let h = BytecodeHeader::parse(0x00).unwrap();
71        assert_eq!(h.version, 0);
72        assert!(!h.fingerprint_flag);
73        assert_eq!(h.to_byte(), 0x00);
74    }
75
76    #[test]
77    fn round_trip_with_fingerprint() {
78        let h = BytecodeHeader::parse(0x04).unwrap();
79        assert_eq!(h.version, 0);
80        assert!(h.fingerprint_flag);
81        assert_eq!(h.to_byte(), 0x04);
82    }
83
84    #[test]
85    fn rejects_unsupported_version() {
86        // version=1 in bits 7-4
87        assert!(matches!(
88            BytecodeHeader::parse(0x10),
89            Err(Error::UnsupportedVersion(1)),
90        ));
91        // version=15 in bits 7-4
92        assert!(matches!(
93            BytecodeHeader::parse(0xF0),
94            Err(Error::UnsupportedVersion(15)),
95        ));
96    }
97
98    #[test]
99    fn rejects_reserved_bit_0() {
100        assert!(matches!(
101            BytecodeHeader::parse(0b0000_0001),
102            Err(Error::ReservedBitsSet),
103        ));
104    }
105
106    #[test]
107    fn rejects_reserved_bit_1() {
108        assert!(matches!(
109            BytecodeHeader::parse(0b0000_0010),
110            Err(Error::ReservedBitsSet),
111        ));
112    }
113
114    #[test]
115    fn rejects_reserved_bit_3() {
116        assert!(matches!(
117            BytecodeHeader::parse(0b0000_1000),
118            Err(Error::ReservedBitsSet),
119        ));
120    }
121
122    #[test]
123    fn rejects_combined_reserved_bits() {
124        // Bit 2 set (fp flag, allowed) + bit 0 set (reserved, rejected)
125        assert!(matches!(
126            BytecodeHeader::parse(0b0000_0101),
127            Err(Error::ReservedBitsSet),
128        ));
129    }
130}