use crate::error::{Error, Result};
const VERSION_SHIFT: u8 = 4;
const FINGERPRINT_FLAG_MASK: u8 = 0b0000_0100;
const RESERVED_MASK: u8 = 0b0000_1011;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BytecodeHeader {
pub version: u8,
pub fingerprint_flag: bool,
}
impl BytecodeHeader {
pub fn parse(byte: u8) -> Result<Self> {
let version = byte >> VERSION_SHIFT;
if version != 0 {
return Err(Error::UnsupportedVersion(version));
}
if byte & RESERVED_MASK != 0 {
return Err(Error::ReservedBitsSet);
}
Ok(BytecodeHeader {
version,
fingerprint_flag: byte & FINGERPRINT_FLAG_MASK != 0,
})
}
pub fn to_byte(self) -> u8 {
let mut byte = (self.version & 0x0F) << VERSION_SHIFT;
if self.fingerprint_flag {
byte |= FINGERPRINT_FLAG_MASK;
}
byte
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_no_fingerprint() {
let h = BytecodeHeader::parse(0x00).unwrap();
assert_eq!(h.version, 0);
assert!(!h.fingerprint_flag);
assert_eq!(h.to_byte(), 0x00);
}
#[test]
fn round_trip_with_fingerprint() {
let h = BytecodeHeader::parse(0x04).unwrap();
assert_eq!(h.version, 0);
assert!(h.fingerprint_flag);
assert_eq!(h.to_byte(), 0x04);
}
#[test]
fn rejects_unsupported_version() {
assert!(matches!(
BytecodeHeader::parse(0x10),
Err(Error::UnsupportedVersion(1)),
));
assert!(matches!(
BytecodeHeader::parse(0xF0),
Err(Error::UnsupportedVersion(15)),
));
}
#[test]
fn rejects_reserved_bit_0() {
assert!(matches!(
BytecodeHeader::parse(0b0000_0001),
Err(Error::ReservedBitsSet),
));
}
#[test]
fn rejects_reserved_bit_1() {
assert!(matches!(
BytecodeHeader::parse(0b0000_0010),
Err(Error::ReservedBitsSet),
));
}
#[test]
fn rejects_reserved_bit_3() {
assert!(matches!(
BytecodeHeader::parse(0b0000_1000),
Err(Error::ReservedBitsSet),
));
}
#[test]
fn rejects_combined_reserved_bits() {
assert!(matches!(
BytecodeHeader::parse(0b0000_0101),
Err(Error::ReservedBitsSet),
));
}
}