#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(C)]
pub struct KernelBinding {
pub manifest_root_hash: [u8; 32],
pub policy_hash: [u8; 32],
pub binding_version: u16,
pub min_runtime_version: u16,
pub _pad0: u32,
pub allowed_segment_mask: u64,
pub _reserved: [u8; 48],
}
const _: () = assert!(core::mem::size_of::<KernelBinding>() == 128);
impl KernelBinding {
pub fn to_bytes(&self) -> [u8; 128] {
let mut buf = [0u8; 128];
buf[0x00..0x20].copy_from_slice(&self.manifest_root_hash);
buf[0x20..0x40].copy_from_slice(&self.policy_hash);
buf[0x40..0x42].copy_from_slice(&self.binding_version.to_le_bytes());
buf[0x42..0x44].copy_from_slice(&self.min_runtime_version.to_le_bytes());
buf[0x44..0x48].copy_from_slice(&self._pad0.to_le_bytes());
buf[0x48..0x50].copy_from_slice(&self.allowed_segment_mask.to_le_bytes());
buf[0x50..0x80].copy_from_slice(&self._reserved);
buf
}
pub fn from_bytes(data: &[u8; 128]) -> Self {
Self {
manifest_root_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x00..0x20]);
h
},
policy_hash: {
let mut h = [0u8; 32];
h.copy_from_slice(&data[0x20..0x40]);
h
},
binding_version: u16::from_le_bytes([data[0x40], data[0x41]]),
min_runtime_version: u16::from_le_bytes([data[0x42], data[0x43]]),
_pad0: u32::from_le_bytes([data[0x44], data[0x45], data[0x46], data[0x47]]),
allowed_segment_mask: u64::from_le_bytes([
data[0x48], data[0x49], data[0x4A], data[0x4B], data[0x4C], data[0x4D], data[0x4E],
data[0x4F],
]),
_reserved: {
let mut r = [0u8; 48];
r.copy_from_slice(&data[0x50..0x80]);
r
},
}
}
pub fn from_bytes_validated(data: &[u8; 128]) -> Result<Self, &'static str> {
let binding = Self::from_bytes(data);
if binding.binding_version == 0 {
return Err("binding_version must be > 0");
}
if binding._pad0 != 0 {
return Err("_pad0 must be zero");
}
if binding._reserved.iter().any(|&b| b != 0) {
return Err("_reserved must be all zeros");
}
Ok(binding)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_binding() -> KernelBinding {
KernelBinding {
manifest_root_hash: [0xAA; 32],
policy_hash: [0xBB; 32],
binding_version: 1,
min_runtime_version: 0,
_pad0: 0,
allowed_segment_mask: 0,
_reserved: [0; 48],
}
}
#[test]
fn binding_size_is_128() {
assert_eq!(core::mem::size_of::<KernelBinding>(), 128);
}
#[test]
fn round_trip_serialization() {
let original = sample_binding();
let bytes = original.to_bytes();
let decoded = KernelBinding::from_bytes(&bytes);
assert_eq!(decoded.manifest_root_hash, [0xAA; 32]);
assert_eq!(decoded.policy_hash, [0xBB; 32]);
assert_eq!(decoded.binding_version, 1);
assert_eq!(decoded.min_runtime_version, 0);
assert_eq!(decoded._pad0, 0);
assert_eq!(decoded.allowed_segment_mask, 0);
assert_eq!(decoded._reserved, [0; 48]);
}
#[test]
fn round_trip_with_fields() {
let binding = KernelBinding {
manifest_root_hash: [0x11; 32],
policy_hash: [0x22; 32],
binding_version: 2,
min_runtime_version: 3,
_pad0: 0,
allowed_segment_mask: 0x00FF_FFFF,
_reserved: [0; 48],
};
let bytes = binding.to_bytes();
let decoded = KernelBinding::from_bytes(&bytes);
assert_eq!(decoded.binding_version, 2);
assert_eq!(decoded.min_runtime_version, 3);
assert_eq!(decoded.allowed_segment_mask, 0x00FF_FFFF);
}
#[test]
fn field_offsets() {
let b = sample_binding();
let base = &b as *const _ as usize;
assert_eq!(&b.manifest_root_hash as *const _ as usize - base, 0x00);
assert_eq!(&b.policy_hash as *const _ as usize - base, 0x20);
assert_eq!(&b.binding_version as *const _ as usize - base, 0x40);
assert_eq!(&b.min_runtime_version as *const _ as usize - base, 0x42);
assert_eq!(&b._pad0 as *const _ as usize - base, 0x44);
assert_eq!(&b.allowed_segment_mask as *const _ as usize - base, 0x48);
assert_eq!(&b._reserved as *const _ as usize - base, 0x50);
}
#[test]
fn reserved_must_be_zero_in_new_bindings() {
let b = sample_binding();
assert!(b._reserved.iter().all(|&x| x == 0));
assert_eq!(b._pad0, 0);
}
}