S564 · SOURCE-BOUND GATE EVIDENCE
S564 · R1 güncelleme: paket manifesti ve hash zinciri modeli
tam S564 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S564 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S564Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s564-r1-update-package-manifest-hash-chain-model
uygulama/model · focused test · Operations · 3 exact excerpt
sequence-bound=true · implementation-bound=true
01 · Yürütme / doğrulama kodu
Kapının gerçek repository sözleşmesi
tam dosyaL1–L1059
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model.rs::S564 r1 update package manifest hash chain model implementation
#![allow(unexpected_cfgs)]
//! S564 models the R1 update package manifest and its hash chain as a pure
//! source/host model: bounded manifest entries (path <= 64 bytes, byte
//! count, SHA-256 digest, role `Kernel`/`Dtb`/`Config`/`App`), a manifest
//! header (monotonic version, predecessor manifest hash, entry count <= 16),
//! a deterministic canonical encoding whose SHA-256 is the manifest hash,
//! predecessor -> successor chain verification, duplicate-path and size-limit
//! rejection, and an exact-match check of an observed package against the
//! head manifest. SHA-256 is implemented here (pure, `no_std`, streaming)
//! and is pinned against the FIPS 180-4 vectors for `""` and `"abc"`; S565
//! reuses it. The S545 candidate package identities (image, DTB,
//! `config.txt`) are carried as a fixture and must match byte-for-byte.
//!
//! Signature verification is out of scope for S564 and is explicitly
//! refused (`SignatureOutOfScope`); it belongs to S566 / R2.
//!
//! The gate makes no hardware claim: no SD card, no UART, no board, no
//! update transaction exists for S564; physical observations = 0,
//! `RUNBOOK_EXECUTED_IN_S564=NO`, Boot-to-UI physically observed = false and
//! R1 acceptance complete = false. Nothing here is wired into a boot, IRQ,
//! scheduler or driver path; the focused host test is the only caller. It
//! performs no device operation and does not rerun S540 or S543, whose
//! physical RED verdicts remain immutable.
//!
//! Predecessor: S563 (bounded recovery fault containment model). Next gate:
//! S565 (staged update apply/rollback model).
use alloc::vec::Vec;
pub const S564_SEQUENCE: usize = 564;
pub const S564_EXPECTED_PREDECESSOR: usize = 563;
pub const S564_R1_STAGE: u8 = 4;
pub const S564_R1_RANGE_FIRST: usize = 536;
pub const S564_R1_RANGE_LAST: usize = 568;
pub const S564_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S564_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S564_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S564_SD_WRITES: usize = 0;
pub const S564_UART_OPENS: usize = 0;
pub const S564_POWER_TRANSITIONS: usize = 0;
pub const S564_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S564_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S564_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S564_AUTOMATIC_PROMOTION: bool = false;
pub const S564_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S564_HARDWARE_PRESENT: bool = false;
pub const S564_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S564: bool = false;
pub const S564_SHA256_DIGEST_LEN: usize = 32;
pub const S564_SHA256_BLOCK_LEN: usize = 64;
pub const S564_MANIFEST_PATH_MAX_LEN: usize = 64;
pub const S564_MANIFEST_MAX_ENTRIES: usize = 16;
pub const S564_MANIFEST_MAGIC: [u8; 8] = *b"ASOSMF01";
pub const S564_MANIFEST_HEADER_ENCODED_LEN: usize = 8 + 4 + 32 + 1;
pub const S564_MANIFEST_ENTRY_ENCODED_MAX_LEN: usize = 1 + 1 + S564_MANIFEST_PATH_MAX_LEN + 8 + 32;
pub const S564_MANIFEST_ENCODED_MAX_LEN: usize = S564_MANIFEST_HEADER_ENCODED_LEN
+ S564_MANIFEST_MAX_ENTRIES * S564_MANIFEST_ENTRY_ENCODED_MAX_LEN;
pub const S564_GENESIS_VERSION: u32 = 1;
pub const S564_GENESIS_PREDECESSOR_HASH: [u8; 32] = [0; 32];
pub const S564_MAX_CHAIN_LENGTH: usize = 8;
pub const S564_MAX_ENTRY_BYTES: u64 = 64 * 1024 * 1024;
pub const S564_MAX_PACKAGE_BYTES: u64 = 256 * 1024 * 1024;
pub const S564_SIGNATURE_SCOPE_SEQUENCE: usize = 566;
pub const S564_SIGNATURE_VERIFIED: bool = false;
pub const S545_PACKAGE_IMAGE_PATH: &str = "aselsanos-rpi5.img";
pub const S545_PACKAGE_IMAGE_BYTES: u64 = 945_760;
pub const S545_PACKAGE_IMAGE_SHA256_HEX: &str =
"ed1901a991e2f9e9ae3c16f254147a2b0180686a8d70ca5d7353374fee08d467";
pub const S545_PACKAGE_IMAGE_SHA256: [u8; 32] = [
0xed, 0x19, 0x01, 0xa9, 0x91, 0xe2, 0xf9, 0xe9, 0xae, 0x3c, 0x16, 0xf2, 0x54, 0x14, 0x7a, 0x2b,
0x01, 0x80, 0x68, 0x6a, 0x8d, 0x70, 0xca, 0x5d, 0x73, 0x53, 0x37, 0x4f, 0xee, 0x08, 0xd4, 0x67,
];
pub const S545_PACKAGE_DTB_PATH: &str = "bcm2712-rpi-5-b.dtb";
pub const S545_PACKAGE_DTB_BYTES: u64 = 78_703;
pub const S545_PACKAGE_DTB_SHA256_HEX: &str =
"40a2fbe9c29e8b9a4912cf726a943068defb779fc052ec38e457a79c58abca00";
pub const S545_PACKAGE_DTB_SHA256: [u8; 32] = [
0x40, 0xa2, 0xfb, 0xe9, 0xc2, 0x9e, 0x8b, 0x9a, 0x49, 0x12, 0xcf, 0x72, 0x6a, 0x94, 0x30, 0x68,
0xde, 0xfb, 0x77, 0x9f, 0xc0, 0x52, 0xec, 0x38, 0xe4, 0x57, 0xa7, 0x9c, 0x58, 0xab, 0xca, 0x00,
];
pub const S545_PACKAGE_CONFIG_PATH: &str = "config.txt";
pub const S545_PACKAGE_CONFIG_BYTES: u64 = 420;
pub const S545_PACKAGE_CONFIG_SHA256_HEX: &str =
"aef848bf6e0c324148eade5054a15c71a1e8c04814a3ed2e680056f87c1f9bba";
pub const S545_PACKAGE_CONFIG_SHA256: [u8; 32] = [
0xae, 0xf8, 0x48, 0xbf, 0x6e, 0x0c, 0x32, 0x41, 0x48, 0xea, 0xde, 0x50, 0x54, 0xa1, 0x5c, 0x71,
0xa1, 0xe8, 0xc0, 0x48, 0x14, 0xa3, 0xed, 0x2e, 0x68, 0x00, 0x56, 0xf8, 0x7c, 0x1f, 0x9b, 0xba,
];
pub const S545_PACKAGE_ENTRY_COUNT: usize = 3;
pub const S545_PACKAGE_TOTAL_BYTES: u64 =
S545_PACKAGE_IMAGE_BYTES + S545_PACKAGE_DTB_BYTES + S545_PACKAGE_CONFIG_BYTES;
// ---------------------------------------------------------------------------
// SHA-256 (FIPS 180-4), pure and streaming.
// ---------------------------------------------------------------------------
const SHA256_INITIAL_STATE: [u32; 8] = [
0x6a09_e667,
0xbb67_ae85,
0x3c6e_f372,
0xa54f_f53a,
0x510e_527f,
0x9b05_688c,
0x1f83_d9ab,
0x5be0_cd19,
];
const SHA256_ROUND_CONSTANTS: [u32; 64] = [
0x428a_2f98,
0x7137_4491,
0xb5c0_fbcf,
0xe9b5_dba5,
0x3956_c25b,
0x59f1_11f1,
0x923f_82a4,
0xab1c_5ed5,
0xd807_aa98,
0x1283_5b01,
0x2431_85be,
0x550c_7dc3,
0x72be_5d74,
0x80de_b1fe,
0x9bdc_06a7,
0xc19b_f174,
0xe49b_69c1,
0xefbe_4786,
0x0fc1_9dc6,
0x240c_a1cc,
0x2de9_2c6f,
0x4a74_84aa,
0x5cb0_a9dc,
0x76f9_88da,
0x983e_5152,
0xa831_c66d,
0xb003_27c8,
0xbf59_7fc7,
0xc6e0_0bf3,
0xd5a7_9147,
0x06ca_6351,
0x1429_2967,
0x27b7_0a85,
0x2e1b_2138,
0x4d2c_6dfc,
0x5338_0d13,
0x650a_7354,
0x766a_0abb,
0x81c2_c92e,
0x9272_2c85,
0xa2bf_e8a1,
0xa81a_664b,
0xc24b_8b70,
0xc76c_51a3,
0xd192_e819,
0xd699_0624,
0xf40e_3585,
0x106a_a070,
0x19a4_c116,
0x1e37_6c08,
0x2748_774c,
0x34b0_bcb5,
0x391c_0cb3,
0x4ed8_aa4a,
0x5b9c_ca4f,
0x682e_6ff3,
0x748f_82ee,
0x78a5_636f,
0x84c8_7814,
0x8cc7_0208,
0x90be_fffa,
0xa450_6ceb,
0xbef9_a3f7,
0xc671_78f2,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564Sha256 {
state: [u32; 8],
buffer: [u8; S564_SHA256_BLOCK_LEN],
buffer_len: usize,
total_len: u64,
}
impl Default for G8lS564Sha256 {
fn default() -> Self {
Self::new()
}
}
impl G8lS564Sha256 {
pub const fn new() -> Self {
Self {
state: SHA256_INITIAL_STATE,
buffer: [0; S564_SHA256_BLOCK_LEN],
buffer_len: 0,
total_len: 0,
}
}
fn compress(state: &mut [u32; 8], block: &[u8; S564_SHA256_BLOCK_LEN]) {
let mut w = [0u32; 64];
for (i, word) in w.iter_mut().take(16).enumerate() {
*word = u32::from_be_bytes([
block[4 * i],
block[4 * i + 1],
block[4 * i + 2],
block[4 * i + 3],
]);
}
for i in 16..64 {
let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16]
.wrapping_add(s0)
.wrapping_add(w[i - 7])
.wrapping_add(s1);
}
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
for i in 0..64 {
let big_s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
let ch = (e & f) ^ (!e & g);
let t1 = h
.wrapping_add(big_s1)
.wrapping_add(ch)
.wrapping_add(SHA256_ROUND_CONSTANTS[i])
.wrapping_add(w[i]);
let big_s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
let maj = (a & b) ^ (a & c) ^ (b & c);
let t2 = big_s0.wrapping_add(maj);
h = g;
g = f;
f = e;
e = d.wrapping_add(t1);
d = c;
c = b;
b = a;
a = t1.wrapping_add(t2);
}
for (slot, value) in state.iter_mut().zip([a, b, c, d, e, f, g, h]) {
*slot = slot.wrapping_add(value);
}
}
pub fn update(&mut self, bytes: &[u8]) -> Result<(), G8lS564ManifestChainError> {
self.total_len = self
.total_len
.checked_add(bytes.len() as u64)
.filter(|total| *total <= u64::MAX / 8)
.ok_or(G8lS564ManifestChainError::MessageTooLong)?;
let mut rest = bytes;
if self.buffer_len > 0 {
let take = (S564_SHA256_BLOCK_LEN - self.buffer_len).min(rest.len());
self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&rest[..take]);
self.buffer_len += take;
rest = &rest[take..];
if self.buffer_len == S564_SHA256_BLOCK_LEN {
let block = self.buffer;
Self::compress(&mut self.state, &block);
self.buffer_len = 0;
}
}
if self.buffer_len == 0 {
let mut chunks = rest.chunks_exact(S564_SHA256_BLOCK_LEN);
for chunk in &mut chunks {
let mut block = [0u8; S564_SHA256_BLOCK_LEN];
block.copy_from_slice(chunk);
Self::compress(&mut self.state, &block);
}
let tail = chunks.remainder();
self.buffer[..tail.len()].copy_from_slice(tail);
self.buffer_len = tail.len();
}
Ok(())
}
pub fn finalize(mut self) -> [u8; S564_SHA256_DIGEST_LEN] {
let bit_len = self.total_len.wrapping_mul(8);
let mut block = [0u8; S564_SHA256_BLOCK_LEN];
block[..self.buffer_len].copy_from_slice(&self.buffer[..self.buffer_len]);
block[self.buffer_len] = 0x80;
if self.buffer_len + 1 > S564_SHA256_BLOCK_LEN - 8 {
Self::compress(&mut self.state, &block);
block = [0u8; S564_SHA256_BLOCK_LEN];
}
block[S564_SHA256_BLOCK_LEN - 8..].copy_from_slice(&bit_len.to_be_bytes());
Self::compress(&mut self.state, &block);
let mut digest = [0u8; S564_SHA256_DIGEST_LEN];
for (chunk, word) in digest.chunks_exact_mut(4).zip(self.state) {
chunk.copy_from_slice(&word.to_be_bytes());
}
digest
}
}
pub fn sha256_digest(bytes: &[u8]) -> [u8; S564_SHA256_DIGEST_LEN] {
let mut hasher = G8lS564Sha256::new();
// A slice can never exceed the 2^61-byte SHA-256 message bound.
let _ = hasher.update(bytes);
hasher.finalize()
}
pub fn digest_to_hex(digest: &[u8; S564_SHA256_DIGEST_LEN]) -> [u8; 64] {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = [0u8; 64];
for (i, byte) in digest.iter().enumerate() {
out[2 * i] = HEX[(byte >> 4) as usize];
out[2 * i + 1] = HEX[(byte & 0x0f) as usize];
}
out
}
pub fn parse_hex_digest(hex: &str) -> Option<[u8; S564_SHA256_DIGEST_LEN]> {
let bytes = hex.as_bytes();
if bytes.len() != 64 {
return None;
}
let nibble = |c: u8| match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
_ => None,
};
let mut digest = [0u8; S564_SHA256_DIGEST_LEN];
for (i, pair) in bytes.chunks_exact(2).enumerate() {
digest[i] = (nibble(pair[0])? << 4) | nibble(pair[1])?;
}
Some(digest)
}
// ---------------------------------------------------------------------------
// Manifest model.
// ---------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS564EntryRole {
Kernel,
Dtb,
Config,
App,
}
impl G8lS564EntryRole {
pub const fn code(self) -> u8 {
match self {
Self::Kernel => 1,
Self::Dtb => 2,
Self::Config => 3,
Self::App => 4,
}
}
pub const fn from_code(code: u8) -> Option<Self> {
match code {
1 => Some(Self::Kernel),
2 => Some(Self::Dtb),
3 => Some(Self::Config),
4 => Some(Self::App),
_ => None,
}
}
pub const fn is_singleton(self) -> bool {
!matches!(self, Self::App)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ManifestPath {
bytes: [u8; S564_MANIFEST_PATH_MAX_LEN],
len: u8,
}
impl G8lS564ManifestPath {
pub const EMPTY: Self = Self {
bytes: [0; S564_MANIFEST_PATH_MAX_LEN],
len: 0,
};
pub fn new(path: &str) -> Result<Self, G8lS564ManifestChainError> {
Self::from_bytes(path.as_bytes())
}
pub fn from_bytes(path: &[u8]) -> Result<Self, G8lS564ManifestChainError> {
if path.is_empty() {
return Err(G8lS564ManifestChainError::PathEmpty);
}
if path.len() > S564_MANIFEST_PATH_MAX_LEN {
return Err(G8lS564ManifestChainError::PathTooLong);
}
if !path
.iter()
.all(|&c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'-' | b'_' | b'/'))
{
return Err(G8lS564ManifestChainError::PathInvalidCharacter);
}
if path[0] == b'/' || path[path.len() - 1] == b'/' {
return Err(G8lS564ManifestChainError::PathInvalidShape);
}
if path
.split(|c| *c == b'/')
.any(|segment| segment.is_empty() || segment == b"." || segment == b"..")
{
return Err(G8lS564ManifestChainError::PathInvalidShape);
}
let mut bytes = [0u8; S564_MANIFEST_PATH_MAX_LEN];
bytes[..path.len()].copy_from_slice(path);
Ok(Self {
bytes,
len: path.len() as u8,
})
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len as usize]
}
pub const fn len(&self) -> usize {
self.len as usize
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ManifestEntry {
pub path: G8lS564ManifestPath,
pub bytes: u64,
pub sha256: [u8; S564_SHA256_DIGEST_LEN],
pub role: G8lS564EntryRole,
}
impl G8lS564ManifestEntry {
pub const EMPTY: Self = Self {
path: G8lS564ManifestPath::EMPTY,
bytes: 0,
sha256: [0; S564_SHA256_DIGEST_LEN],
role: G8lS564EntryRole::App,
};
pub fn new(
path: &str,
bytes: u64,
sha256: [u8; S564_SHA256_DIGEST_LEN],
role: G8lS564EntryRole,
) -> Result<Self, G8lS564ManifestChainError> {
let entry = Self {
path: G8lS564ManifestPath::new(path)?,
bytes,
sha256,
role,
};
entry.validate()?;
Ok(entry)
}
pub fn validate(&self) -> Result<(), G8lS564ManifestChainError> {
G8lS564ManifestPath::from_bytes(self.path.as_bytes())?;
if self.bytes == 0 {
return Err(G8lS564ManifestChainError::EntryBytesZero);
}
if self.bytes > S564_MAX_ENTRY_BYTES {
return Err(G8lS564ManifestChainError::EntryBytesTooLarge);
}
Ok(())
}
pub const fn encoded_len(&self) -> usize {
1 + 1 + self.path.len() + 8 + S564_SHA256_DIGEST_LEN
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ManifestHeader {
pub version: u32,
pub predecessor_manifest_hash: [u8; S564_SHA256_DIGEST_LEN],
pub entry_count: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564Manifest {
pub header: G8lS564ManifestHeader,
pub entries: [G8lS564ManifestEntry; S564_MANIFEST_MAX_ENTRIES],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ManifestSummary {
pub entry_count: usize,
pub total_bytes: u64,
pub kernel_entries: usize,
pub dtb_entries: usize,
pub config_entries: usize,
pub app_entries: usize,
pub encoded_len: usize,
}
impl G8lS564Manifest {
pub fn new(
version: u32,
predecessor_manifest_hash: [u8; S564_SHA256_DIGEST_LEN],
entries: &[G8lS564ManifestEntry],
) -> Result<Self, G8lS564ManifestChainError> {
if entries.len() > S564_MANIFEST_MAX_ENTRIES {
return Err(G8lS564ManifestChainError::EntryCountTooLarge);
}
let mut table = [G8lS564ManifestEntry::EMPTY; S564_MANIFEST_MAX_ENTRIES];
table[..entries.len()].copy_from_slice(entries);
let manifest = Self {
header: G8lS564ManifestHeader {
version,
predecessor_manifest_hash,
entry_count: entries.len() as u8,
},
entries: table,
};
manifest.validate()?;
Ok(manifest)
}
pub fn entries(&self) -> &[G8lS564ManifestEntry] {
let count = (self.header.entry_count as usize).min(S564_MANIFEST_MAX_ENTRIES);
&self.entries[..count]
}
pub const fn is_genesis(&self) -> bool {
let mut i = 0;
while i < S564_SHA256_DIGEST_LEN {
if self.header.predecessor_manifest_hash[i] != 0 {
return false;
}
i += 1;
}
true
}
pub fn validate(&self) -> Result<G8lS564ManifestSummary, G8lS564ManifestChainError> {
if self.header.version == 0 {
return Err(G8lS564ManifestChainError::VersionZero);
}
let count = self.header.entry_count as usize;
if count == 0 {
return Err(G8lS564ManifestChainError::EntryCountZero);
}
if count > S564_MANIFEST_MAX_ENTRIES {
return Err(G8lS564ManifestChainError::EntryCountTooLarge);
}
if self.entries[count..]
.iter()
.any(|entry| *entry != G8lS564ManifestEntry::EMPTY)
{
return Err(G8lS564ManifestChainError::EntryCountMismatch);
}
let mut summary = G8lS564ManifestSummary {
entry_count: count,
total_bytes: 0,
kernel_entries: 0,
dtb_entries: 0,
config_entries: 0,
app_entries: 0,
encoded_len: S564_MANIFEST_HEADER_ENCODED_LEN,
};
for (index, entry) in self.entries().iter().enumerate() {
entry.validate()?;
if self.entries()[..index]
.iter()
.any(|earlier| earlier.path == entry.path)
{
return Err(G8lS564ManifestChainError::DuplicatePath);
}
summary.total_bytes = summary
.total_bytes
.checked_add(entry.bytes)
.ok_or(G8lS564ManifestChainError::PackageBytesOverflow)?;
let slot = match entry.role {
G8lS564EntryRole::Kernel => &mut summary.kernel_entries,
G8lS564EntryRole::Dtb => &mut summary.dtb_entries,
G8lS564EntryRole::Config => &mut summary.config_entries,
G8lS564EntryRole::App => &mut summary.app_entries,
};
*slot += 1;
if entry.role.is_singleton() && *slot > 1 {
return Err(G8lS564ManifestChainError::SingletonRoleRepeated);
}
summary.encoded_len += entry.encoded_len();
}
if summary.total_bytes > S564_MAX_PACKAGE_BYTES {
return Err(G8lS564ManifestChainError::PackageBytesTooLarge);
}
Ok(summary)
}
}
pub fn encode_manifest(manifest: &G8lS564Manifest) -> Result<Vec<u8>, G8lS564ManifestChainError> {
let summary = manifest.validate()?;
let mut out = Vec::with_capacity(summary.encoded_len);
out.extend_from_slice(&S564_MANIFEST_MAGIC);
out.extend_from_slice(&manifest.header.version.to_le_bytes());
out.extend_from_slice(&manifest.header.predecessor_manifest_hash);
out.push(manifest.header.entry_count);
for entry in manifest.entries() {
out.push(entry.role.code());
out.push(entry.path.len() as u8);
out.extend_from_slice(entry.path.as_bytes());
out.extend_from_slice(&entry.bytes.to_le_bytes());
out.extend_from_slice(&entry.sha256);
}
if out.len() != summary.encoded_len || out.len() > S564_MANIFEST_ENCODED_MAX_LEN {
return Err(G8lS564ManifestChainError::EncodingLengthMismatch);
}
Ok(out)
}
pub fn decode_manifest(encoded: &[u8]) -> Result<G8lS564Manifest, G8lS564ManifestChainError> {
if encoded.len() > S564_MANIFEST_ENCODED_MAX_LEN {
return Err(G8lS564ManifestChainError::EncodingTooLong);
}
let mut cursor = 0usize;
let mut take = |len: usize| -> Result<&[u8], G8lS564ManifestChainError> {
let end = cursor
.checked_add(len)
.filter(|end| *end <= encoded.len())
.ok_or(G8lS564ManifestChainError::EncodingTruncated)?;
let slice = &encoded[cursor..end];
cursor = end;
Ok(slice)
};
if take(8)? != S564_MANIFEST_MAGIC {
return Err(G8lS564ManifestChainError::EncodingMagicMismatch);
}
let mut version = [0u8; 4];
version.copy_from_slice(take(4)?);
let mut predecessor = [0u8; S564_SHA256_DIGEST_LEN];
predecessor.copy_from_slice(take(S564_SHA256_DIGEST_LEN)?);
let entry_count = take(1)?[0] as usize;
if entry_count > S564_MANIFEST_MAX_ENTRIES {
return Err(G8lS564ManifestChainError::EntryCountTooLarge);
}
let mut entries = [G8lS564ManifestEntry::EMPTY; S564_MANIFEST_MAX_ENTRIES];
for entry in entries.iter_mut().take(entry_count) {
let role = G8lS564EntryRole::from_code(take(1)?[0])
.ok_or(G8lS564ManifestChainError::InvalidRole)?;
let path_len = take(1)?[0] as usize;
let path = G8lS564ManifestPath::from_bytes(take(path_len)?)?;
let mut bytes = [0u8; 8];
bytes.copy_from_slice(take(8)?);
let mut sha256 = [0u8; S564_SHA256_DIGEST_LEN];
sha256.copy_from_slice(take(S564_SHA256_DIGEST_LEN)?);
*entry = G8lS564ManifestEntry {
path,
bytes: u64::from_le_bytes(bytes),
sha256,
role,
};
}
if cursor != encoded.len() {
return Err(G8lS564ManifestChainError::EncodingTrailingBytes);
}
let manifest = G8lS564Manifest {
header: G8lS564ManifestHeader {
version: u32::from_le_bytes(version),
predecessor_manifest_hash: predecessor,
entry_count: entry_count as u8,
},
entries,
};
manifest.validate()?;
Ok(manifest)
}
pub fn manifest_hash(
manifest: &G8lS564Manifest,
) -> Result<[u8; S564_SHA256_DIGEST_LEN], G8lS564ManifestChainError> {
Ok(sha256_digest(&encode_manifest(manifest)?))
}
// ---------------------------------------------------------------------------
// Chain verification.
// ---------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ChainReceipt {
pub length: usize,
pub genesis_version: u32,
pub head_version: u32,
pub genesis_manifest_hash: [u8; S564_SHA256_DIGEST_LEN],
pub head_manifest_hash: [u8; S564_SHA256_DIGEST_LEN],
pub head_summary: G8lS564ManifestSummary,
}
pub fn verify_chain_link(
predecessor: &G8lS564Manifest,
successor: &G8lS564Manifest,
) -> Result<[u8; S564_SHA256_DIGEST_LEN], G8lS564ManifestChainError> {
let predecessor_hash = manifest_hash(predecessor)?;
successor.validate()?;
if successor.is_genesis() {
return Err(G8lS564ManifestChainError::PredecessorHashZero);
}
if successor.header.predecessor_manifest_hash != predecessor_hash {
return Err(G8lS564ManifestChainError::PredecessorHashMismatch);
}
if successor.header.version <= predecessor.header.version {
return Err(G8lS564ManifestChainError::VersionNotMonotonic);
}
manifest_hash(successor)
}
pub fn verify_manifest_chain(
chain: &[G8lS564Manifest],
) -> Result<G8lS564ChainReceipt, G8lS564ManifestChainError> {
let (genesis, rest) = chain
.split_first()
.ok_or(G8lS564ManifestChainError::ChainEmpty)?;
if chain.len() > S564_MAX_CHAIN_LENGTH {
return Err(G8lS564ManifestChainError::ChainTooLong);
}
let mut head_summary = genesis.validate()?;
if !genesis.is_genesis() {
return Err(G8lS564ManifestChainError::GenesisPredecessorHashNotZero);
}
let genesis_manifest_hash = manifest_hash(genesis)?;
let mut head_manifest_hash = genesis_manifest_hash;
let mut predecessor = genesis;
for successor in rest {
head_manifest_hash = verify_chain_link(predecessor, successor)?;
head_summary = successor.validate()?;
predecessor = successor;
}
Ok(G8lS564ChainReceipt {
length: chain.len(),
genesis_version: genesis.header.version,
head_version: predecessor.header.version,
genesis_manifest_hash,
head_manifest_hash,
head_summary,
})
}
// ---------------------------------------------------------------------------
// Observed package exact match.
// ---------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ObservedFile {
pub path: G8lS564ManifestPath,
pub bytes: u64,
pub sha256: [u8; S564_SHA256_DIGEST_LEN],
}
impl G8lS564ObservedFile {
pub fn new(
path: &str,
bytes: u64,
sha256: [u8; S564_SHA256_DIGEST_LEN],
) -> Result<Self, G8lS564ManifestChainError> {
Ok(Self {
path: G8lS564ManifestPath::new(path)?,
bytes,
sha256,
})
}
}
pub fn verify_package_against_manifest(
manifest: &G8lS564Manifest,
observed: &[G8lS564ObservedFile],
) -> Result<usize, G8lS564ManifestChainError> {
let summary = manifest.validate()?;
if observed.len() != summary.entry_count {
return Err(G8lS564ManifestChainError::ObservedFileCountMismatch);
}
for (index, file) in observed.iter().enumerate() {
if observed[..index]
.iter()
.any(|earlier| earlier.path == file.path)
{
return Err(G8lS564ManifestChainError::ObservedPathDuplicate);
}
let entry = manifest
.entries()
.iter()
.find(|entry| entry.path == file.path)
.ok_or(G8lS564ManifestChainError::ObservedPathUnknown)?;
if entry.bytes != file.bytes {
return Err(G8lS564ManifestChainError::ObservedBytesMismatch);
}
if entry.sha256 != file.sha256 {
return Err(G8lS564ManifestChainError::ObservedDigestMismatch);
}
}
if manifest
.entries()
.iter()
.any(|entry| !observed.iter().any(|file| file.path == entry.path))
{
return Err(G8lS564ManifestChainError::ManifestEntryUnobserved);
}
Ok(observed.len())
}
pub fn canonical_s545_package_entries() -> [G8lS564ManifestEntry; S545_PACKAGE_ENTRY_COUNT] {
[
G8lS564ManifestEntry {
path: G8lS564ManifestPath::from_bytes(S545_PACKAGE_IMAGE_PATH.as_bytes())
.unwrap_or(G8lS564ManifestPath::EMPTY),
bytes: S545_PACKAGE_IMAGE_BYTES,
sha256: S545_PACKAGE_IMAGE_SHA256,
role: G8lS564EntryRole::Kernel,
},
G8lS564ManifestEntry {
path: G8lS564ManifestPath::from_bytes(S545_PACKAGE_DTB_PATH.as_bytes())
.unwrap_or(G8lS564ManifestPath::EMPTY),
bytes: S545_PACKAGE_DTB_BYTES,
sha256: S545_PACKAGE_DTB_SHA256,
role: G8lS564EntryRole::Dtb,
},
G8lS564ManifestEntry {
path: G8lS564ManifestPath::from_bytes(S545_PACKAGE_CONFIG_PATH.as_bytes())
.unwrap_or(G8lS564ManifestPath::EMPTY),
bytes: S545_PACKAGE_CONFIG_BYTES,
sha256: S545_PACKAGE_CONFIG_SHA256,
role: G8lS564EntryRole::Config,
},
]
}
pub fn canonical_s545_genesis_manifest() -> Result<G8lS564Manifest, G8lS564ManifestChainError> {
G8lS564Manifest::new(
S564_GENESIS_VERSION,
S564_GENESIS_PREDECESSOR_HASH,
&canonical_s545_package_entries(),
)
}
pub fn canonical_s545_observed_package() -> [G8lS564ObservedFile; S545_PACKAGE_ENTRY_COUNT] {
let entries = canonical_s545_package_entries();
let observe = |entry: &G8lS564ManifestEntry| G8lS564ObservedFile {
path: entry.path,
bytes: entry.bytes,
sha256: entry.sha256,
};
[
observe(&entries[0]),
observe(&entries[1]),
observe(&entries[2]),
]
}
// ---------------------------------------------------------------------------
// Fail-closed publication service.
// ---------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS564ManifestChainReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub r1_stage: u8,
pub chain: G8lS564ChainReceipt,
pub observed_files_matched: usize,
pub signature_verified: bool,
pub signature_scope_sequence: usize,
pub s540_physical_verdict_retained_red: bool,
pub s543_physical_verdict_retained_red: bool,
pub automatic_promotion: bool,
pub hardware_present: bool,
pub supported_profile_runtime_observations: usize,
pub physical_observations: usize,
pub runbook_executed: bool,
}
#[derive(Debug)]
pub struct G8lS564ManifestChainState {
receipt: Option<G8lS564ManifestChainReceipt>,
}
impl G8lS564ManifestChainState {
pub const fn new() -> Self {
Self { receipt: None }
}
pub const fn receipt(&self) -> Option<G8lS564ManifestChainReceipt> {
self.receipt
}
}
impl Default for G8lS564ManifestChainState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS564ManifestChainOutcome {
Published(G8lS564ManifestChainReceipt),
Retained(G8lS564ManifestChainReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS564ManifestChainError {
MessageTooLong,
ChainEmpty,
ChainTooLong,
VersionZero,
EntryCountZero,
EntryCountTooLarge,
EntryCountMismatch,
PathEmpty,
PathTooLong,
PathInvalidCharacter,
PathInvalidShape,
DuplicatePath,
EntryBytesZero,
EntryBytesTooLarge,
PackageBytesOverflow,
PackageBytesTooLarge,
SingletonRoleRepeated,
InvalidRole,
EncodingLengthMismatch,
EncodingTooLong,
EncodingTruncated,
EncodingMagicMismatch,
EncodingTrailingBytes,
GenesisPredecessorHashNotZero,
PredecessorHashZero,
PredecessorHashMismatch,
VersionNotMonotonic,
ObservedFileCountMismatch,
ObservedPathDuplicate,
ObservedPathUnknown,
ObservedBytesMismatch,
ObservedDigestMismatch,
ManifestEntryUnobserved,
SignatureOutOfScope,
PublishedStateDrift,
}
impl G8lS564ManifestChainError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::MessageTooLong => 1,
Self::ChainEmpty => 2,
Self::ChainTooLong => 3,
Self::VersionZero => 4,
Self::EntryCountZero => 5,
Self::EntryCountTooLarge => 6,
Self::EntryCountMismatch => 7,
Self::PathEmpty => 8,
Self::PathTooLong => 9,
Self::PathInvalidCharacter => 10,
Self::PathInvalidShape => 11,
Self::DuplicatePath => 12,
Self::EntryBytesZero => 13,
Self::EntryBytesTooLarge => 14,
Self::PackageBytesOverflow => 15,
Self::PackageBytesTooLarge => 16,
Self::SingletonRoleRepeated => 17,
Self::InvalidRole => 18,
Self::EncodingLengthMismatch => 19,
Self::EncodingTooLong => 20,
Self::EncodingTruncated => 21,
Self::EncodingMagicMismatch => 22,
Self::EncodingTrailingBytes => 23,
Self::GenesisPredecessorHashNotZero => 24,
Self::PredecessorHashZero => 25,
Self::PredecessorHashMismatch => 26,
Self::VersionNotMonotonic => 27,
Self::ObservedFileCountMismatch => 28,
Self::ObservedPathDuplicate => 29,
Self::ObservedPathUnknown => 30,
Self::ObservedBytesMismatch => 31,
Self::ObservedDigestMismatch => 32,
Self::ManifestEntryUnobserved => 33,
Self::SignatureOutOfScope => 34,
Self::PublishedStateDrift => 35,
}
}
pub const ALL: [Self; 35] = [
Self::MessageTooLong,
Self::ChainEmpty,
Self::ChainTooLong,
Self::VersionZero,
Self::EntryCountZero,
Self::EntryCountTooLarge,
Self::EntryCountMismatch,
Self::PathEmpty,
Self::PathTooLong,
Self::PathInvalidCharacter,
Self::PathInvalidShape,
Self::DuplicatePath,
Self::EntryBytesZero,
Self::EntryBytesTooLarge,
Self::PackageBytesOverflow,
Self::PackageBytesTooLarge,
Self::SingletonRoleRepeated,
Self::InvalidRole,
Self::EncodingLengthMismatch,
Self::EncodingTooLong,
Self::EncodingTruncated,
Self::EncodingMagicMismatch,
Self::EncodingTrailingBytes,
Self::GenesisPredecessorHashNotZero,
Self::PredecessorHashZero,
Self::PredecessorHashMismatch,
Self::VersionNotMonotonic,
Self::ObservedFileCountMismatch,
Self::ObservedPathDuplicate,
Self::ObservedPathUnknown,
Self::ObservedBytesMismatch,
Self::ObservedDigestMismatch,
Self::ManifestEntryUnobserved,
Self::SignatureOutOfScope,
Self::PublishedStateDrift,
];
}
/// Verifies a bounded manifest chain, matches the observed package against
/// the head manifest byte-for-byte, and publishes or retains an exact receipt.
/// Signature verification is not modelled; requesting it fails closed.
pub fn service_s564_model_manifest_chain_verify(
state: &mut G8lS564ManifestChainState,
chain: &[G8lS564Manifest],
observed_package: &[G8lS564ObservedFile],
signature_verification_requested: bool,
) -> Result<G8lS564ManifestChainOutcome, G8lS564ManifestChainError> {
if signature_verification_requested {
return Err(G8lS564ManifestChainError::SignatureOutOfScope);
}
let chain_receipt = verify_manifest_chain(chain)?;
let head = &chain[chain.len() - 1];
let observed_files_matched = verify_package_against_manifest(head, observed_package)?;
let receipt = G8lS564ManifestChainReceipt {
sequence: S564_SEQUENCE,
predecessor_sequence: S564_EXPECTED_PREDECESSOR,
r1_stage: S564_R1_STAGE,
chain: chain_receipt,
observed_files_matched,
signature_verified: S564_SIGNATURE_VERIFIED,
signature_scope_sequence: S564_SIGNATURE_SCOPE_SEQUENCE,
s540_physical_verdict_retained_red: S564_S540_PHYSICAL_VERDICT_RETAINED_RED,
s543_physical_verdict_retained_red: S564_S543_PHYSICAL_VERDICT_RETAINED_RED,
automatic_promotion: S564_AUTOMATIC_PROMOTION,
hardware_present: S564_HARDWARE_PRESENT,
supported_profile_runtime_observations: S564_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
physical_observations: S564_PHYSICAL_OBSERVATIONS,
runbook_executed: RUNBOOK_EXECUTED_IN_S564,
};
if let Some(published) = state.receipt {
if published != receipt {
return Err(G8lS564ManifestChainError::PublishedStateDrift);
}
return Ok(G8lS564ManifestChainOutcome::Retained(published));
}
state.receipt = Some(receipt);
Ok(G8lS564ManifestChainOutcome::Published(receipt))
}
snippet sha256: 67360375745a…file sha256: 67360375745a…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L802
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model.rs::S564 r1 update package manifest hash chain model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
const S545_SHA256SUMS: &str =
include_str!("../../evidence/rpi5/r1/sequence-545-candidate-freeze/package/SHA256SUMS");
const S545_IMAGE: &[u8] = include_bytes!(
"../../evidence/rpi5/r1/sequence-545-candidate-freeze/package/aselsanos-rpi5.img"
);
const S545_DTB: &[u8] = include_bytes!(
"../../evidence/rpi5/r1/sequence-545-candidate-freeze/package/bcm2712-rpi-5-b.dtb"
);
const S545_CONFIG: &[u8] =
include_bytes!("../../evidence/rpi5/r1/sequence-545-candidate-freeze/package/config.txt");
const SHA256_EMPTY: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
const SHA256_ABC: &str = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
const SHA256_448_BIT: &str = "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1";
const SHA256_MILLION_A: &str = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0";
fn hex(digest: &[u8; 32]) -> String {
String::from_utf8(digest_to_hex(digest).to_vec()).unwrap()
}
fn app_entry(path: &str, bytes: u64, seed: u8) -> G8lS564ManifestEntry {
G8lS564ManifestEntry::new(path, bytes, [seed; 32], G8lS564EntryRole::App).unwrap()
}
fn genesis() -> G8lS564Manifest {
canonical_s545_genesis_manifest().unwrap()
}
fn successor_of(predecessor: &G8lS564Manifest, version: u32) -> G8lS564Manifest {
let mut entries = canonical_s545_package_entries().to_vec();
entries.push(app_entry("apps/dialer.elf", 4096, 0x5a));
G8lS564Manifest::new(version, manifest_hash(predecessor).unwrap(), &entries).unwrap()
}
fn observed_for(manifest: &G8lS564Manifest) -> Vec<G8lS564ObservedFile> {
manifest
.entries()
.iter()
.map(|entry| G8lS564ObservedFile {
path: entry.path,
bytes: entry.bytes,
sha256: entry.sha256,
})
.collect()
}
fn publish(
state: &mut G8lS564ManifestChainState,
chain: &[G8lS564Manifest],
) -> Result<G8lS564ManifestChainOutcome, G8lS564ManifestChainError> {
let observed = observed_for(&chain[chain.len() - 1]);
service_s564_model_manifest_chain_verify(state, chain, &observed, false)
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S564_SEQUENCE, 564);
assert_eq!(S564_EXPECTED_PREDECESSOR, 563);
assert_eq!(S564_R1_STAGE, 4);
assert_eq!(S564_R1_RANGE_FIRST, 536);
assert_eq!(S564_R1_RANGE_LAST, 568);
assert_eq!(S564_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S564_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S564_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S564_SD_WRITES, 0);
assert_eq!(S564_UART_OPENS, 0);
assert_eq!(S564_POWER_TRANSITIONS, 0);
assert_eq!(S564_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S564_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S564_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S564_AUTOMATIC_PROMOTION);
assert!(!S564_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S564_HARDWARE_PRESENT);
assert!(!S564_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S564);
assert_eq!(S564_MANIFEST_PATH_MAX_LEN, 64);
assert_eq!(S564_MANIFEST_MAX_ENTRIES, 16);
assert_eq!(S564_MAX_CHAIN_LENGTH, 8);
assert_eq!(S564_MANIFEST_ENCODED_MAX_LEN, 45 + 16 * 106);
assert_eq!(S564_SIGNATURE_SCOPE_SEQUENCE, 566);
assert!(!S564_SIGNATURE_VERIFIED);
assert_eq!(S545_PACKAGE_TOTAL_BYTES, 945_760 + 78_703 + 420);
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model";
assert!(MAIN.contains(&format!("mod {module};")));
assert!(SIMULATION_LIB.contains(&format!("pub mod {module};")));
}
#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
for forbidden in [
"unsafe",
"asm!",
"write_volatile",
"crate::uart",
"crate::arch",
"#[no_mangle]",
"spin::",
"std::",
"/dev/disk",
"/dev/cu.",
"diskutil",
"dd if=",
] {
assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
}
assert!(SOURCE.contains("performs no device operation"));
assert!(SOURCE.contains("does not rerun S540 or S543"));
assert!(SOURCE.contains("Signature verification is out of scope"));
assert!(SOURCE.contains("S564_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S564: bool = false"));
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let codes: BTreeSet<u64> = G8lS564ManifestChainError::ALL
.into_iter()
.map(G8lS564ManifestChainError::diagnostic_code)
.collect();
assert_eq!(codes.len(), G8lS564ManifestChainError::ALL.len());
assert_eq!(codes.len(), 35);
assert!(!codes.contains(&0));
assert_eq!(
G8lS564ManifestChainError::PublishedStateDrift.diagnostic_code(),
35
);
}
#[test]
fn sha256_matches_known_vectors_empty_and_abc() {
assert_eq!(hex(&sha256_digest(b"")), SHA256_EMPTY);
assert_eq!(hex(&sha256_digest(b"abc")), SHA256_ABC);
assert_eq!(
hex(&sha256_digest(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
)),
SHA256_448_BIT
);
assert_eq!(parse_hex_digest(SHA256_ABC), Some(sha256_digest(b"abc")));
assert_eq!(parse_hex_digest("ba78"), None);
assert_eq!(parse_hex_digest(&SHA256_ABC.to_uppercase()), None);
}
#[test]
fn sha256_streaming_split_matches_one_shot_for_every_block_boundary() {
let million_a = vec![b'a'; 1_000_000];
assert_eq!(hex(&sha256_digest(&million_a)), SHA256_MILLION_A);
let message: Vec<u8> = (0..200u32).map(|i| (i * 7 % 251) as u8).collect();
let one_shot = sha256_digest(&message);
for split in [0usize, 1, 55, 56, 63, 64, 65, 119, 120, 127, 128, 199, 200] {
let mut hasher = G8lS564Sha256::new();
hasher.update(&message[..split]).unwrap();
hasher.update(&message[split..]).unwrap();
assert_eq!(hasher.finalize(), one_shot, "split at {split}");
}
for len in 0..=130usize {
let mut hasher = G8lS564Sha256::default();
for byte in &message[..len] {
hasher.update(core::slice::from_ref(byte)).unwrap();
}
assert_eq!(hasher.finalize(), sha256_digest(&message[..len]), "len {len}");
}
}
#[test]
fn s545_package_fixture_matches_sha256sums_and_real_files() {
let expected: Vec<(&str, &str)> = S545_SHA256SUMS
.lines()
.map(|line| {
let (digest, name) = line.split_once(" ").unwrap();
(name, digest)
})
.collect();
assert_eq!(
expected,
vec![
(S545_PACKAGE_IMAGE_PATH, S545_PACKAGE_IMAGE_SHA256_HEX),
(S545_PACKAGE_DTB_PATH, S545_PACKAGE_DTB_SHA256_HEX),
(S545_PACKAGE_CONFIG_PATH, S545_PACKAGE_CONFIG_SHA256_HEX),
]
);
assert_eq!(hex(&S545_PACKAGE_IMAGE_SHA256), S545_PACKAGE_IMAGE_SHA256_HEX);
assert_eq!(hex(&S545_PACKAGE_DTB_SHA256), S545_PACKAGE_DTB_SHA256_HEX);
assert_eq!(hex(&S545_PACKAGE_CONFIG_SHA256), S545_PACKAGE_CONFIG_SHA256_HEX);
assert_eq!(S545_IMAGE.len() as u64, S545_PACKAGE_IMAGE_BYTES);
assert_eq!(S545_DTB.len() as u64, S545_PACKAGE_DTB_BYTES);
assert_eq!(S545_CONFIG.len() as u64, S545_PACKAGE_CONFIG_BYTES);
assert_eq!(sha256_digest(S545_IMAGE), S545_PACKAGE_IMAGE_SHA256);
assert_eq!(sha256_digest(S545_DTB), S545_PACKAGE_DTB_SHA256);
assert_eq!(sha256_digest(S545_CONFIG), S545_PACKAGE_CONFIG_SHA256);
let observed = [
G8lS564ObservedFile::new(
S545_PACKAGE_IMAGE_PATH,
S545_IMAGE.len() as u64,
sha256_digest(S545_IMAGE),
)
.unwrap(),
G8lS564ObservedFile::new(
S545_PACKAGE_DTB_PATH,
S545_DTB.len() as u64,
sha256_digest(S545_DTB),
)
.unwrap(),
G8lS564ObservedFile::new(
S545_PACKAGE_CONFIG_PATH,
S545_CONFIG.len() as u64,
sha256_digest(S545_CONFIG),
)
.unwrap(),
];
assert_eq!(observed, canonical_s545_observed_package());
assert_eq!(verify_package_against_manifest(&genesis(), &observed), Ok(3));
}
#[test]
fn canonical_s545_genesis_manifest_publishes_an_exact_receipt() {
let manifest = genesis();
assert!(manifest.is_genesis());
assert_eq!(manifest.header.version, S564_GENESIS_VERSION);
assert_eq!(manifest.entries().len(), S545_PACKAGE_ENTRY_COUNT);
let summary = manifest.validate().unwrap();
assert_eq!(summary.total_bytes, S545_PACKAGE_TOTAL_BYTES);
assert_eq!(
(
summary.kernel_entries,
summary.dtb_entries,
summary.config_entries,
summary.app_entries
),
(1, 1, 1, 0)
);
assert_eq!(summary.encoded_len, 45 + (42 + 18) + (42 + 19) + (42 + 10));
let mut state = G8lS564ManifestChainState::new();
let G8lS564ManifestChainOutcome::Published(receipt) = publish(&mut state, &[manifest]).unwrap()
else {
panic!("first S564 publication missing")
};
assert_eq!(state.receipt(), Some(receipt));
assert_eq!(receipt.sequence, 564);
assert_eq!(receipt.predecessor_sequence, 563);
assert_eq!(receipt.r1_stage, 4);
assert_eq!(receipt.chain.length, 1);
assert_eq!(receipt.chain.genesis_version, 1);
assert_eq!(receipt.chain.head_version, 1);
assert_eq!(receipt.chain.genesis_manifest_hash, receipt.chain.head_manifest_hash);
assert_eq!(receipt.chain.head_manifest_hash, manifest_hash(&manifest).unwrap());
assert_eq!(receipt.chain.head_summary, summary);
assert_eq!(receipt.observed_files_matched, 3);
assert!(!receipt.signature_verified);
assert_eq!(receipt.signature_scope_sequence, 566);
assert!(receipt.s540_physical_verdict_retained_red);
assert!(receipt.s543_physical_verdict_retained_red);
assert!(!receipt.automatic_promotion);
assert!(!receipt.hardware_present);
assert_eq!(receipt.supported_profile_runtime_observations, 0);
assert_eq!(receipt.physical_observations, 0);
assert!(!receipt.runbook_executed);
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let chain = [genesis()];
let mut state = G8lS564ManifestChainState::new();
let G8lS564ManifestChainOutcome::Published(receipt) = publish(&mut state, &chain).unwrap()
else {
panic!("first publication missing")
};
assert_eq!(
publish(&mut state, &chain),
Ok(G8lS564ManifestChainOutcome::Retained(receipt))
);
assert_eq!(state.receipt(), Some(receipt));
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let first = genesis();
let mut state = G8lS564ManifestChainState::new();
publish(&mut state, &[first]).unwrap();
let longer = [first, successor_of(&first, 2)];
assert_eq!(
publish(&mut state, &longer),
Err(G8lS564ManifestChainError::PublishedStateDrift)
);
let mut relabelled = first;
relabelled.entries[2].role = G8lS564EntryRole::App;
assert_eq!(
publish(&mut state, &[relabelled]),
Err(G8lS564ManifestChainError::PublishedStateDrift)
);
assert_eq!(state.receipt().map(|receipt| receipt.chain.length), Some(1));
}
#[test]
fn two_link_chain_with_monotonic_version_verifies_and_hashes_the_head() {
let first = genesis();
let second = successor_of(&first, 2);
let third = successor_of(&second, 7);
let receipt = verify_manifest_chain(&[first, second, third]).unwrap();
assert_eq!(receipt.length, 3);
assert_eq!(receipt.genesis_version, 1);
assert_eq!(receipt.head_version, 7);
assert_eq!(receipt.genesis_manifest_hash, manifest_hash(&first).unwrap());
assert_eq!(receipt.head_manifest_hash, manifest_hash(&third).unwrap());
assert_ne!(receipt.genesis_manifest_hash, receipt.head_manifest_hash);
assert_eq!(receipt.head_summary.entry_count, 4);
assert_eq!(receipt.head_summary.app_entries, 1);
assert_eq!(
receipt.head_summary.total_bytes,
S545_PACKAGE_TOTAL_BYTES + 4096
);
assert_eq!(
verify_chain_link(&first, &second),
Ok(manifest_hash(&second).unwrap())
);
let mut state = G8lS564ManifestChainState::new();
let G8lS564ManifestChainOutcome::Published(published) =
publish(&mut state, &[first, second, third]).unwrap()
else {
panic!("chain publication missing")
};
assert_eq!(published.chain, receipt);
assert_eq!(published.observed_files_matched, 4);
}
#[test]
fn predecessor_hash_mismatch_or_zero_fails_closed() {
let first = genesis();
let mut forged = successor_of(&first, 2);
forged.header.predecessor_manifest_hash[31] ^= 0x01;
assert_eq!(
verify_chain_link(&first, &forged),
Err(G8lS564ManifestChainError::PredecessorHashMismatch)
);
assert_eq!(
verify_manifest_chain(&[first, forged]),
Err(G8lS564ManifestChainError::PredecessorHashMismatch)
);
let mut zeroed = successor_of(&first, 2);
zeroed.header.predecessor_manifest_hash = S564_GENESIS_PREDECESSOR_HASH;
assert_eq!(
verify_chain_link(&first, &zeroed),
Err(G8lS564ManifestChainError::PredecessorHashZero)
);
let second = successor_of(&first, 2);
assert_eq!(
verify_manifest_chain(&[second]),
Err(G8lS564ManifestChainError::GenesisPredecessorHashNotZero)
);
let mut altered_predecessor = first;
altered_predecessor.entries[2].bytes = 421;
assert_eq!(
verify_manifest_chain(&[altered_predecessor, second]),
Err(G8lS564ManifestChainError::PredecessorHashMismatch)
);
}
#[test]
fn version_must_increase_strictly_along_the_chain() {
let first = genesis();
for version in [0u32, 1] {
let mut entries = canonical_s545_package_entries().to_vec();
entries.push(app_entry("apps/dialer.elf", 4096, 0x5a));
let hash = manifest_hash(&first).unwrap();
let result = G8lS564Manifest::new(version, hash, &entries);
if version == 0 {
assert_eq!(result, Err(G8lS564ManifestChainError::VersionZero));
} else {
assert_eq!(
verify_chain_link(&first, &result.unwrap()),
Err(G8lS564ManifestChainError::VersionNotMonotonic)
);
}
}
let second = successor_of(&first, 5);
let lower = successor_of(&second, 4);
assert_eq!(
verify_manifest_chain(&[first, second, lower]),
Err(G8lS564ManifestChainError::VersionNotMonotonic)
);
let max = successor_of(&second, u32::MAX);
assert_eq!(
verify_manifest_chain(&[first, second, max]).map(|receipt| receipt.head_version),
Ok(u32::MAX)
);
assert_eq!(
G8lS564Manifest::new(0, S564_GENESIS_PREDECESSOR_HASH, &canonical_s545_package_entries()),
Err(G8lS564ManifestChainError::VersionZero)
);
}
#[test]
fn duplicate_path_is_rejected_in_manifest_and_observed_package() {
let mut entries = canonical_s545_package_entries().to_vec();
entries.push(app_entry("config.txt", 5, 0x11));
assert_eq!(
G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &entries),
Err(G8lS564ManifestChainError::DuplicatePath)
);
let manifest = genesis();
let mut observed = observed_for(&manifest);
observed[1] = observed[0];
assert_eq!(
verify_package_against_manifest(&manifest, &observed),
Err(G8lS564ManifestChainError::ObservedPathDuplicate)
);
}
#[test]
fn path_boundaries_characters_and_shape_are_enforced() {
let sixty_four = "a".repeat(64);
assert_eq!(G8lS564ManifestPath::new(&sixty_four).map(|p| p.len()), Ok(64));
assert_eq!(
G8lS564ManifestPath::new(&"a".repeat(65)),
Err(G8lS564ManifestChainError::PathTooLong)
);
assert_eq!(
G8lS564ManifestPath::new(""),
Err(G8lS564ManifestChainError::PathEmpty)
);
for bad in ["config txt", "kernel\0", "ünite.bin", "a:b", "a\\b"] {
assert_eq!(
G8lS564ManifestPath::new(bad),
Err(G8lS564ManifestChainError::PathInvalidCharacter),
"{bad}"
);
}
for bad in ["/boot/x", "apps/", "apps//x", "./x", "apps/../x", "..", "."] {
assert_eq!(
G8lS564ManifestPath::new(bad),
Err(G8lS564ManifestChainError::PathInvalidShape),
"{bad}"
);
}
let ok = G8lS564ManifestPath::new("apps/sub-dir/file_1.v2.elf").unwrap();
assert_eq!(ok.as_bytes(), b"apps/sub-dir/file_1.v2.elf");
assert!(!ok.is_empty());
assert!(G8lS564ManifestPath::EMPTY.is_empty());
let mut entry = app_entry("apps/x.elf", 1, 0x22);
entry.path = G8lS564ManifestPath::EMPTY;
assert_eq!(entry.validate(), Err(G8lS564ManifestChainError::PathEmpty));
}
#[test]
fn entry_count_boundaries_are_sixteen_and_header_count_is_bound_to_the_table() {
let entries: Vec<_> = (0..17u8)
.map(|i| app_entry(&format!("apps/app{i:02}.elf"), 1 + u64::from(i), i))
.collect();
let sixteen = G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &entries[..16]).unwrap();
assert_eq!(sixteen.entries().len(), 16);
assert_eq!(sixteen.validate().unwrap().app_entries, 16);
assert_eq!(
G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &entries),
Err(G8lS564ManifestChainError::EntryCountTooLarge)
);
assert_eq!(
G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &[]),
Err(G8lS564ManifestChainError::EntryCountZero)
);
let mut shrunk = sixteen;
shrunk.header.entry_count = 15;
assert_eq!(
shrunk.validate(),
Err(G8lS564ManifestChainError::EntryCountMismatch)
);
let mut inflated = genesis();
inflated.header.entry_count = 17;
assert_eq!(
inflated.validate(),
Err(G8lS564ManifestChainError::EntryCountTooLarge)
);
let mut hollow = genesis();
hollow.header.entry_count = 4;
assert_eq!(hollow.validate(), Err(G8lS564ManifestChainError::PathEmpty));
}
#[test]
fn size_limits_and_total_overflow_fail_closed() {
assert_eq!(
G8lS564ManifestEntry::new("apps/z.elf", 0, [0; 32], G8lS564EntryRole::App),
Err(G8lS564ManifestChainError::EntryBytesZero)
);
assert!(
G8lS564ManifestEntry::new("apps/z.elf", S564_MAX_ENTRY_BYTES, [0; 32], G8lS564EntryRole::App)
.is_ok()
);
assert_eq!(
G8lS564ManifestEntry::new(
"apps/z.elf",
S564_MAX_ENTRY_BYTES + 1,
[0; 32],
G8lS564EntryRole::App
),
Err(G8lS564ManifestChainError::EntryBytesTooLarge)
);
let four_max: Vec<_> = (0..4u8)
.map(|i| app_entry(&format!("apps/big{i}.bin"), S564_MAX_ENTRY_BYTES, i))
.collect();
let at_limit = G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &four_max).unwrap();
assert_eq!(at_limit.validate().unwrap().total_bytes, S564_MAX_PACKAGE_BYTES);
let mut five = four_max.clone();
five.push(app_entry("apps/big4.bin", 1, 4));
assert_eq!(
G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &five),
Err(G8lS564ManifestChainError::PackageBytesTooLarge)
);
let mut overflow = at_limit;
overflow.entries[0].bytes = u64::MAX;
overflow.entries[1].bytes = 1;
assert_eq!(
overflow.validate(),
Err(G8lS564ManifestChainError::EntryBytesTooLarge)
);
let mut sum_overflow = overflow;
sum_overflow.entries[0].bytes = 1;
sum_overflow.entries[1].bytes = 1;
let forced = G8lS564ManifestSummary {
entry_count: 0,
total_bytes: u64::MAX,
kernel_entries: 0,
dtb_entries: 0,
config_entries: 0,
app_entries: 0,
encoded_len: 0,
};
assert_eq!(
forced.total_bytes.checked_add(1),
None,
"checked total arithmetic is the overflow guard"
);
assert!(sum_overflow.validate().is_ok());
}
#[test]
fn observed_package_mismatch_fails_closed_on_every_field() {
let manifest = genesis();
let baseline = observed_for(&manifest);
let mut bytes_drift = baseline.clone();
bytes_drift[0].bytes += 1;
assert_eq!(
verify_package_against_manifest(&manifest, &bytes_drift),
Err(G8lS564ManifestChainError::ObservedBytesMismatch)
);
let mut digest_drift = baseline.clone();
digest_drift[1].sha256[0] ^= 0x80;
assert_eq!(
verify_package_against_manifest(&manifest, &digest_drift),
Err(G8lS564ManifestChainError::ObservedDigestMismatch)
);
let mut renamed = baseline.clone();
renamed[2].path = G8lS564ManifestPath::new("config.bak").unwrap();
assert_eq!(
verify_package_against_manifest(&manifest, &renamed),
Err(G8lS564ManifestChainError::ObservedPathUnknown)
);
assert_eq!(
verify_package_against_manifest(&manifest, &baseline[..2]),
Err(G8lS564ManifestChainError::ObservedFileCountMismatch)
);
let mut extra = baseline.clone();
extra.push(G8lS564ObservedFile::new("extra.bin", 1, [1; 32]).unwrap());
assert_eq!(
verify_package_against_manifest(&manifest, &extra),
Err(G8lS564ManifestChainError::ObservedFileCountMismatch)
);
let mut state = G8lS564ManifestChainState::new();
assert_eq!(
service_s564_model_manifest_chain_verify(&mut state, &[manifest], &digest_drift, false),
Err(G8lS564ManifestChainError::ObservedDigestMismatch)
);
assert_eq!(state.receipt(), None);
assert_eq!(verify_package_against_manifest(&manifest, &baseline), Ok(3));
}
#[test]
fn singleton_roles_cannot_repeat_and_role_codes_round_trip() {
for role in [
G8lS564EntryRole::Kernel,
G8lS564EntryRole::Dtb,
G8lS564EntryRole::Config,
G8lS564EntryRole::App,
] {
assert_eq!(G8lS564EntryRole::from_code(role.code()), Some(role));
assert_eq!(role.is_singleton(), role != G8lS564EntryRole::App);
}
assert_eq!(G8lS564EntryRole::from_code(0), None);
assert_eq!(G8lS564EntryRole::from_code(5), None);
assert_eq!(G8lS564EntryRole::from_code(0xff), None);
let mut entries = canonical_s545_package_entries().to_vec();
entries.push(
G8lS564ManifestEntry::new("kernel-b.img", 10, [3; 32], G8lS564EntryRole::Kernel).unwrap(),
);
assert_eq!(
G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &entries),
Err(G8lS564ManifestChainError::SingletonRoleRepeated)
);
let two_apps = [app_entry("apps/a.elf", 1, 1), app_entry("apps/b.elf", 2, 2)];
assert_eq!(
G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &two_apps)
.unwrap()
.validate()
.unwrap()
.app_entries,
2
);
}
#[test]
fn signature_verification_is_out_of_scope_and_fails_closed() {
let chain = [genesis()];
let observed = observed_for(&chain[0]);
let mut state = G8lS564ManifestChainState::new();
assert_eq!(
service_s564_model_manifest_chain_verify(&mut state, &chain, &observed, true),
Err(G8lS564ManifestChainError::SignatureOutOfScope)
);
assert_eq!(state.receipt(), None);
let G8lS564ManifestChainOutcome::Published(receipt) =
service_s564_model_manifest_chain_verify(&mut state, &chain, &observed, false).unwrap()
else {
panic!("publication missing")
};
assert!(!receipt.signature_verified);
assert_eq!(receipt.signature_scope_sequence, S564_SIGNATURE_SCOPE_SEQUENCE);
assert_eq!(
service_s564_model_manifest_chain_verify(&mut state, &chain, &observed, true),
Err(G8lS564ManifestChainError::SignatureOutOfScope)
);
}
#[test]
fn manifest_encoding_is_deterministic_bounded_and_round_trips() {
let manifest = genesis();
let encoded = encode_manifest(&manifest).unwrap();
assert_eq!(encoded.len(), manifest.validate().unwrap().encoded_len);
assert_eq!(&encoded[..8], b"ASOSMF01");
assert_eq!(&encoded[8..12], &1u32.to_le_bytes());
assert_eq!(&encoded[12..44], &[0u8; 32]);
assert_eq!(encoded[44], 3);
assert_eq!(encoded[45], G8lS564EntryRole::Kernel.code());
assert_eq!(encoded[46] as usize, S545_PACKAGE_IMAGE_PATH.len());
assert_eq!(encode_manifest(&manifest).unwrap(), encoded);
assert_eq!(decode_manifest(&encoded), Ok(manifest));
assert_eq!(manifest_hash(&manifest).unwrap(), sha256_digest(&encoded));
let entries: Vec<_> = (0..16u8)
.map(|i| app_entry(&"x".repeat(64), 1, i))
.take(1)
.chain((1..16u8).map(|i| app_entry(&format!("{}{i:02}", "y".repeat(62)), 1, i)))
.collect();
let widest = G8lS564Manifest::new(1, S564_GENESIS_PREDECESSOR_HASH, &entries).unwrap();
let widest_encoded = encode_manifest(&widest).unwrap();
assert_eq!(widest_encoded.len(), S564_MANIFEST_ENCODED_MAX_LEN);
assert_eq!(decode_manifest(&widest_encoded), Ok(widest));
let mut variants = BTreeSet::new();
variants.insert(manifest_hash(&manifest).unwrap());
let mut version = manifest;
version.header.version = 2;
variants.insert(manifest_hash(&version).unwrap());
let mut bytes = manifest;
bytes.entries[2].bytes = 421;
variants.insert(manifest_hash(&bytes).unwrap());
let mut digest = manifest;
digest.entries[0].sha256[5] ^= 1;
variants.insert(manifest_hash(&digest).unwrap());
let mut role = manifest;
role.entries[2].role = G8lS564EntryRole::App;
variants.insert(manifest_hash(&role).unwrap());
let mut path = manifest;
path.entries[2].path = G8lS564ManifestPath::new("config.TXT").unwrap();
variants.insert(manifest_hash(&path).unwrap());
assert_eq!(variants.len(), 6);
}
#[test]
fn malformed_encodings_are_rejected() {
let manifest = genesis();
let encoded = encode_manifest(&manifest).unwrap();
assert_eq!(
decode_manifest(&encoded[..encoded.len() - 1]),
Err(G8lS564ManifestChainError::EncodingTruncated)
);
assert_eq!(
decode_manifest(&encoded[..20]),
Err(G8lS564ManifestChainError::EncodingTruncated)
);
let mut trailing = encoded.clone();
trailing.push(0);
assert_eq!(
decode_manifest(&trailing),
Err(G8lS564ManifestChainError::EncodingTrailingBytes)
);
let mut magic = encoded.clone();
magic[7] = b'2';
assert_eq!(
decode_manifest(&magic),
Err(G8lS564ManifestChainError::EncodingMagicMismatch)
);
let mut role = encoded.clone();
role[45] = 9;
assert_eq!(
decode_manifest(&role),
Err(G8lS564ManifestChainError::InvalidRole)
);
let mut count = encoded.clone();
count[44] = 17;
assert_eq!(
decode_manifest(&count),
Err(G8lS564ManifestChainError::EntryCountTooLarge)
);
let mut path_char = encoded.clone();
path_char[47] = b' ';
assert_eq!(
decode_manifest(&path_char),
Err(G8lS564ManifestChainError::PathInvalidCharacter)
);
let mut duplicate = encoded.clone();
let second_entry_path = 45 + 42 + 18 + 2;
duplicate[second_entry_path - 1] = S545_PACKAGE_IMAGE_PATH.len() as u8;
duplicate.splice(
second_entry_path..second_entry_path + S545_PACKAGE_DTB_PATH.len(),
S545_PACKAGE_IMAGE_PATH.bytes(),
);
assert_eq!(
decode_manifest(&duplicate),
Err(G8lS564ManifestChainError::DuplicatePath)
);
assert_eq!(
decode_manifest(&vec![0u8; S564_MANIFEST_ENCODED_MAX_LEN + 1]),
Err(G8lS564ManifestChainError::EncodingTooLong)
);
assert_eq!(
decode_manifest(&[]),
Err(G8lS564ManifestChainError::EncodingTruncated)
);
}
#[test]
fn chain_length_boundaries_are_one_and_eight() {
assert_eq!(
verify_manifest_chain(&[]),
Err(G8lS564ManifestChainError::ChainEmpty)
);
let mut state = G8lS564ManifestChainState::new();
assert_eq!(
service_s564_model_manifest_chain_verify(&mut state, &[], &[], false),
Err(G8lS564ManifestChainError::ChainEmpty)
);
let mut chain = vec![genesis()];
for version in 2..=9u32 {
let previous = chain[chain.len() - 1];
chain.push(successor_of(&previous, version));
}
assert_eq!(
verify_manifest_chain(&chain[..8]).map(|receipt| (receipt.length, receipt.head_version)),
Ok((8, 8))
);
assert_eq!(
verify_manifest_chain(&chain),
Err(G8lS564ManifestChainError::ChainTooLong)
);
let mut state = G8lS564ManifestChainState::new();
let observed = observed_for(&chain[7]);
assert!(matches!(
service_s564_model_manifest_chain_verify(&mut state, &chain[..8], &observed, false),
Ok(G8lS564ManifestChainOutcome::Published(_))
));
assert_eq!(
service_s564_model_manifest_chain_verify(&mut state, &chain, &observed, false),
Err(G8lS564ManifestChainError::ChainTooLong)
);
}
#[test]
fn sha256_message_length_guard_and_state_default_are_exact() {
let state = G8lS564ManifestChainState::default();
assert_eq!(state.receipt(), None);
let mut hasher = G8lS564Sha256::new();
hasher.update(b"abc").unwrap();
let copy = hasher;
assert_eq!(copy.finalize(), hasher.finalize());
assert_eq!(hex(&G8lS564Sha256::new().finalize()), SHA256_EMPTY);
assert_eq!(
G8lS564ManifestChainError::MessageTooLong.diagnostic_code(),
1
);
assert_eq!(
hex(&sha256_digest(S545_CONFIG)),
S545_PACKAGE_CONFIG_SHA256_HEX
);
}
snippet sha256: 3e0bdc02a57f…file sha256: 3e0bdc02a57f…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL2074–L2130
website/src/lib/operations.ts::g8l-s564-r1-update-package-manifest-hash-chain-model
{
id: "g8l-s564-r1-update-package-manifest-hash-chain-model",
date: "2026-08-30",
sequence: 564,
status: "passed",
umbrella_status: "partial",
title: "S564 · R1 güncelleme: paket manifesti ve hash zinciri modeli",
summary:
"S564 kaynak/host model kapısı PASS'tir: güncelleme paketi manifesti (path ≤ 64 bayt, bayt sayısı, 32 baytlık SHA-256 özeti, Kernel/Dtb/Config/App rolleri), manifest başlığı (monoton sürüm, öncül manifest hash'i, ≤ 16 giriş), deterministik kanonik kodlama, öncül→ardıl hash zinciri doğrulaması ve gözlenen paketin head manifeste bayt-bayt exact eşleşmesi saf Rust modeli olarak eklendi; SHA-256 modülün içinde saf ve streaming olarak implemente edildi ve \"\" ile \"abc\" bilinen vektörlerine sabitlendi. S545 candidate paket kimlikleri (aselsanos-rpi5.img 945760 B / ed1901a9…, dtb 78703 B / 40a2fbe9…, config.txt 420 B / aef848bf…) fixture olarak taşınır ve SHA256SUMS ile dondurulmuş dosyalardan yeniden hesaplanan özetlerle bayt-bayt eşleşmek zorundadır; her sapma fail-closed reddedilir. İmza doğrulaması S564 kapsamı dışıdır ve açıkça fail-closed reddedilir (S566/R2). Focused 24/24 PASS'tir. S540 ve S543 fiziksel RED immutable kalır; hiçbir SD, UART, board veya güncelleme işlemi yoktur; physical observation=0, RUNBOOK_EXECUTED_IN_S564=NO, Boot-to-UI=false ve R1 acceptance=false'dur. S565 aşamalı güncelleme apply/rollback modelidir ve bu modülün SHA-256 ve zincir makbuzlarını tüketecektir.",
evidence: [
"S564, S563'ten ayrı kaynak modülü, 24-test focused binary, proof, status bloğu ve Operations kaydına sahiptir; kernel ve simulation crate'lerinde kayıtlıdır ancak hiçbir boot, IRQ, scheduler veya driver yoluna bağlanmamıştır.",
"Dar S564 source/host model status=PASS; R1 umbrella=PARTIAL ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
"SHA-256 modülün içinde FIPS 180-4'e göre saf, no_std ve streaming (64 baytlık blok tamponu, checked 2^61 bayt mesaj sınırı) olarak implemente edilmiştir; \"\" → e3b0c442…52b855, \"abc\" → ba7816bf…f20015ad, 448-bit iki-blok vektörü ve bir milyon 'a' vektörü sabitlenmiştir; 200 baytlık mesajın her bölünmesi ve bayt-bayt streaming tek atımlık özeti yeniden üretir; S565 bu implementasyonu yeniden kullanacaktır.",
"Manifest girişi path'i ≤ 64 bayt ASCII alfasayısal + '. - _ /' ile sınırlıdır; başta/sonda '/', boş segment, '.' ve '..' segmentleri fail-closed reddedilir; giriş bayt sayısı 1..=67108864 aralığındadır ve rol kodları 1..4 dışında her değer InvalidRole verir.",
"Manifest başlığı monoton sürüm (0 reddedilir), 32 baytlık öncül manifest hash'i (all-zero yalnız genesis için) ve 1..=16 giriş sayısı taşır; tabloyla uyuşmayan başlık sayısı, tekrarlanan path ve tekil Kernel/Dtb/Config rollerinin tekrarı fail-closed reddedilir; toplam paket boyutu checked aritmetikle 268435456 bayt sınırına karşı toplanır.",
"Kanonik kodlama ASOSMF01 magic, little-endian sürüm, öncül hash, giriş sayısı ve giriş başına rol/path-uzunluğu/path/bayt/özet alanlarından oluşur; en çok 1741 bayttır, deterministiktir, decoder üzerinden round-trip eder ve SHA-256'sı manifest hash'idir; kesik, taşan, trailing-byte'lı, yanlış magic'li, geçersiz rollü ve tekrarlanan path'li kodlamalar fail-closed reddedilir.",
"Zincir doğrulaması öncül→ardıl yönünde 1..=8 uzunlukla sınırlıdır; ilk manifest genesis olmak zorundadır, her ardıl öncülünün kodlamasının exact SHA-256'sını ve kesin olarak daha büyük bir sürümü taşımak zorundadır; hash uyuşmazlığı, sıfır öncül hash'i ve monoton olmayan sürüm fail-closed reddedilir.",
"Gözlenen paket head manifest girişleriyle sayı, path, bayt ve özet olarak exact eşleşmek zorundadır; eksik, fazla, yeniden adlandırılmış, tekrarlanan, yeniden boyutlandırılmış veya yeniden hash'lenmiş her dosya fail-closed reddedilir.",
"S545 paket fixture'ı aselsanos-rpi5.img 945760 B / ed1901a991e2f9e9ae3c16f254147a2b0180686a8d70ca5d7353374fee08d467 (Kernel), bcm2712-rpi-5-b.dtb 78703 B / 40a2fbe9c29e8b9a4912cf726a943068defb779fc052ec38e457a79c58abca00 (Dtb) ve config.txt 420 B / aef848bf6e0c324148eade5054a15c71a1e8c04814a3ed2e680056f87c1f9bba (Config) kimliklerini taşır; focused test evidence/rpi5/r1/sequence-545-candidate-freeze/package/SHA256SUMS dosyasını yeniden okur ve üç dondurulmuş dosyanın özetlerini modülün kendi SHA-256'sıyla yeniden hesaplayıp fixture ile bayt-bayt eşleştirir.",
"İmza doğrulaması S564 kapsamı dışıdır: servis her imza doğrulama isteğini SignatureOutOfScope ile fail-closed reddeder, receipt'te signature_verified=false ve scope sequence=566 (S566/R2) sabitlenir.",
"Servis exact replay'de aynı receipt ile Retained döner; yayın sonrası her sapma PublishedStateDrift ile fail-closed reddedilir ve hata enum'u 35 sıfırdan farklı, benzersiz tanı kodu taşır.",
"Focused target 1 grup / 24 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 36461 B / 67360375745aa1b55c0d5886d4aa49b43f46b52df450a8b3d24740f822c3bfde; focused test 30136 B / 3e0bdc02a57f3d29c163ff36fafbca97a3f3f0b20a9c8877a6a00eba62d034d8 SHA-256'dır.",
"Proof 5835 B'dir.",
"Modül unsafe, MMIO, asm!, crate::uart, crate::arch, spin:: veya #[no_mangle] içermez; hiçbir cihaz işlemi yapılmadı ve S540/S543 yeniden koşulmadı.",
"RUNBOOK_EXECUTED_IN_S564=NO; supported-profile runtime observations=0, physical observations=0, SD/UART/power/new-raw=0/0/0/0, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S565 aşamalı güncelleme apply/rollback modelini aynı non-claim'lerle kaynak/host kapısı olarak modelleyecek ve S564'ün SHA-256 ile manifest zinciri makbuzlarını tüketecektir; SD, UART, power veya fiziksel güncelleme koşusu yetkisi vermez.",
],
commands: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model -- --test-threads=1",
],
terminalSessions: [
{
id: "s564-focused",
title: "S564 update paket manifesti ve hash zinciri focused acceptance",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model -- --test-threads=1",
],
outputLines: [
"test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s",
"S564 focused=1 group / 24 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S564 kaynak/host model kapısıdır; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw'ları ve kararları değişmez.",
limitations: [
"S564 yalnız kaynak/host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve hiçbir güncelleme SD karta yazılmamıştır.",
"S540 ve S543 fiziksel RED immutable kalır; otomatik promotion yoktur ve S546 ayrı bir kapı olarak beklemededir.",
"İmza doğrulaması modellenmemiştir ve S564'te açıkça kapsam dışıdır; S566/R2 kapsamındadır ve o zamana kadar her imza doğrulama isteği fail-closed reddedilir.",
"Boot-to-UI fiziksel olarak gözlenmedi; R1 acceptance false kalır ve modül hiçbir production boot/IRQ/scheduler/driver yoluna bağlanmamıştır.",
"S565 aşamalı güncelleme apply/rollback modeli tamamlanmadan güncelleme akışının uygulama tarafı modellenmiş sayılmaz; S565 de SD, UART, power veya fiziksel koşu yetkisi vermez.",
],
},snippet sha256: 72332b50c134…file sha256: 9726dbf00f84…
Focused test komutu
CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s564_r1_update_package_manifest_hash_chain_model -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S564-R1-Update-Package-Manifest-Hash-Chain-Model-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06