use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sha2::{Digest, Sha256};
use std::fmt;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
use std::process;
use std::str::FromStr;
pub const BLOCK_TOKENS: u32 = 256;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct CacheFormatVersion(pub u32);
pub const CURRENT_FORMAT_VERSION: CacheFormatVersion = CacheFormatVersion(1);
fn hex_encode_32(bytes: &[u8; 32]) -> String {
hex::encode(bytes)
}
fn hex_decode_32(s: &str) -> Result<[u8; 32], String> {
let v = hex::decode(s).map_err(|e| format!("hex decode: {e}"))?;
if v.len() != 32 {
return Err(format!("expected 32-byte hex, got {}", v.len()));
}
let mut out = [0u8; 32];
out.copy_from_slice(&v);
Ok(out)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BlockHash(pub [u8; 32]);
impl BlockHash {
pub fn zero() -> Self {
Self([0u8; 32])
}
}
impl fmt::Display for BlockHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex_encode_32(&self.0))
}
}
impl FromStr for BlockHash {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
hex_decode_32(s).map(Self)
}
}
impl Serialize for BlockHash {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&hex_encode_32(&self.0))
}
}
impl<'de> Deserialize<'de> for BlockHash {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
let s = String::deserialize(de)?;
hex_decode_32(&s)
.map(Self)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ParentBlockHash(pub Option<BlockHash>);
impl ParentBlockHash {
fn hash_input_bytes(&self) -> [u8; 32] {
self.0.map(|h| h.0).unwrap_or([0u8; 32])
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ModelFingerprint(pub [u8; 32]);
impl ModelFingerprint {
pub fn short_hex(&self) -> String {
hex::encode(&self.0[..8])
}
}
impl fmt::Display for ModelFingerprint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&hex_encode_32(&self.0))
}
}
impl FromStr for ModelFingerprint {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
hex_decode_32(s).map(Self)
}
}
impl Serialize for ModelFingerprint {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&hex_encode_32(&self.0))
}
}
impl<'de> Deserialize<'de> for ModelFingerprint {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
let s = String::deserialize(de)?;
hex_decode_32(&s)
.map(Self)
.map_err(serde::de::Error::custom)
}
}
pub fn compute_model_fingerprint(
repo_id: &str,
quant: &str,
producer_version: &str,
source_sha256: &str,
tokenizer_chat_template: &str,
) -> ModelFingerprint {
let mut h = Sha256::new();
h.update(repo_id.as_bytes());
h.update(b"\x00");
h.update(quant.as_bytes());
h.update(b"\x00");
h.update(producer_version.as_bytes());
h.update(b"\x00");
h.update(source_sha256.as_bytes());
h.update(b"\x00");
h.update(tokenizer_chat_template.as_bytes());
let out = h.finalize();
let mut buf = [0u8; 32];
buf.copy_from_slice(&out);
ModelFingerprint(buf)
}
pub fn compute_block_hash(
model_fp: &ModelFingerprint,
parent: &ParentBlockHash,
token_ids: &[u32],
) -> BlockHash {
let mut h = Sha256::new();
h.update(model_fp.0);
h.update(parent.hash_input_bytes());
for tok in token_ids {
h.update(tok.to_le_bytes());
}
let out = h.finalize();
let mut buf = [0u8; 32];
buf.copy_from_slice(&out);
BlockHash(buf)
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EnvelopeHeader {
pub format_version: u32,
pub model_fingerprint: ModelFingerprint,
pub block_hash: BlockHash,
pub parent_block_hash: ParentBlockHash,
pub payload_kind: String,
pub codec_version: u32,
pub n_tokens: u32,
}
pub fn write_envelope(path: &Path, header: &EnvelopeHeader, body: &[u8]) -> io::Result<u64> {
if header.format_version != CURRENT_FORMAT_VERSION.0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"EnvelopeHeader.format_version = {} but writer ships version {}",
header.format_version, CURRENT_FORMAT_VERSION.0
),
));
}
let parent = path
.parent()
.ok_or_else(|| io::Error::other(format!("path {} has no parent", path.display())))?;
if !parent.exists() {
std::fs::create_dir_all(parent)?;
}
let header_json = serde_json::to_vec(header)
.map_err(|e| io::Error::other(format!("serialize EnvelopeHeader: {e}")))?;
let pad = (8 - (header_json.len() % 8)) % 8;
let mut header_bytes = header_json;
header_bytes.extend(std::iter::repeat(b' ').take(pad));
let tmp_name = match path.file_name().and_then(|s| s.to_str()) {
Some(stem) => format!("{stem}.tmp.{}", process::id()),
None => format!("envelope.tmp.{}", process::id()),
};
let tmp_path = parent.join(tmp_name);
{
let mut f = File::create(&tmp_path)?;
let header_len = header_bytes.len() as u64;
f.write_all(&header_len.to_le_bytes())?;
f.write_all(&header_bytes)?;
f.write_all(body)?;
f.sync_all()?;
}
std::fs::rename(&tmp_path, path)?;
File::open(parent)?.sync_all()?;
let total = 8u64 + header_bytes.len() as u64 + body.len() as u64;
Ok(total)
}
fn trim_header_padding(bytes: &[u8]) -> &[u8] {
let trim_end = bytes
.iter()
.rposition(|b| *b != b' ' && *b != 0)
.map(|p| p + 1)
.unwrap_or(0);
&bytes[..trim_end]
}
fn read_envelope_header_from_file(f: &mut File) -> io::Result<EnvelopeHeader> {
let mut hlen_buf = [0u8; 8];
f.read_exact(&mut hlen_buf)
.map_err(|e| io::Error::other(format!("envelope header_len truncated: {e}")))?;
let hlen = u64::from_le_bytes(hlen_buf) as usize;
if hlen == 0 || hlen > 64 * 1024 * 1024 {
return Err(io::Error::other(format!(
"envelope header_len {hlen} out of range (0, 64 MiB]"
)));
}
let mut header_bytes = vec![0u8; hlen];
f.read_exact(&mut header_bytes)
.map_err(|e| io::Error::other(format!("envelope header truncated: {e}")))?;
let trimmed = trim_header_padding(&header_bytes);
let header: EnvelopeHeader = serde_json::from_slice(trimmed)
.map_err(|e| io::Error::other(format!("envelope header malformed: {e}")))?;
Ok(header)
}
pub fn read_envelope_header(path: &Path) -> io::Result<EnvelopeHeader> {
let mut f = File::open(path)?;
read_envelope_header_from_file(&mut f)
}
pub fn read_envelope_body(path: &Path) -> io::Result<(EnvelopeHeader, Vec<u8>)> {
let mut f = File::open(path)?;
let header = read_envelope_header_from_file(&mut f)?;
if header.format_version != CURRENT_FORMAT_VERSION.0 {
return Err(io::Error::other(format!(
"envelope format_version {} != current {}",
header.format_version, CURRENT_FORMAT_VERSION.0
)));
}
let mut body = Vec::new();
f.read_to_end(&mut body)?;
let mut h = Sha256::new();
h.update(&body);
let actual: [u8; 32] = h.finalize().into();
if actual != header.block_hash.0 {
return Err(io::Error::other(format!(
"envelope body sha256 mismatch: header.block_hash={} actual={}",
header.block_hash,
hex_encode_32(&actual)
)));
}
Ok((header, body))
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
fn temp_dir(label: &str) -> std::path::PathBuf {
static COUNTER: AtomicU32 = AtomicU32::new(0);
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let pid = process::id();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let dir = std::env::temp_dir().join(format!("hf2q-kv-fmt-{label}-{pid}-{nanos}-{n}"));
std::fs::create_dir_all(&dir).expect("temp_dir mkdir");
dir
}
fn body_and_matching_block_hash(seed: u8) -> (Vec<u8>, BlockHash) {
let body: Vec<u8> = (0..256u32).map(|i| (i as u8) ^ seed).collect();
let mut h = Sha256::new();
h.update(&body);
let bh: [u8; 32] = h.finalize().into();
(body, BlockHash(bh))
}
fn fixture_fp() -> ModelFingerprint {
compute_model_fingerprint(
"test/repo",
"Q4_0",
"hf2q-test-1.0.0",
"deadbeefcafebabe1122334455667788",
"<|im_start|>...<|im_end|>",
)
}
#[test]
fn current_format_version_is_1() {
assert_eq!(CURRENT_FORMAT_VERSION.0, 1);
}
#[test]
fn block_hash_chain_deterministic_across_calls() {
let fp = fixture_fp();
let parent = ParentBlockHash(None);
let tokens: Vec<u32> = (0..256u32).collect();
let h1 = compute_block_hash(&fp, &parent, &tokens);
let h2 = compute_block_hash(&fp, &parent, &tokens);
let h3 = compute_block_hash(&fp, &parent, &tokens);
assert_eq!(h1, h2);
assert_eq!(h2, h3);
let parent2 = ParentBlockHash(Some(h1));
let tokens2: Vec<u32> = (256..512u32).collect();
let h4a = compute_block_hash(&fp, &parent2, &tokens2);
let h4b = compute_block_hash(&fp, &parent2, &tokens2);
assert_eq!(h4a, h4b);
let mut combined: Vec<u32> = (0..256u32).collect();
combined.extend(256..512u32);
let h_one_shot = compute_block_hash(&fp, &ParentBlockHash(None), &combined);
assert_ne!(h4a, h_one_shot, "chain != one-shot under same tokens");
}
#[test]
fn block_hash_chain_genesis_vs_non_genesis() {
let fp = fixture_fp();
let tokens = vec![1u32, 2, 3, 4];
let h_none = compute_block_hash(&fp, &ParentBlockHash(None), &tokens);
let h_zero = compute_block_hash(&fp, &ParentBlockHash(Some(BlockHash::zero())), &tokens);
assert_eq!(h_none, h_zero, "None-parent ≡ zero-parent (intentional)");
let nonzero_parent = compute_block_hash(&fp, &ParentBlockHash(None), &[42u32]);
let h_nonzero = compute_block_hash(&fp, &ParentBlockHash(Some(nonzero_parent)), &tokens);
assert_ne!(h_none, h_nonzero);
}
#[test]
fn model_fingerprint_stable_across_provenance_inputs() {
let a = compute_model_fingerprint("r/m", "Q4_0", "v1", "abc", "tpl");
let b = compute_model_fingerprint("r/m", "Q4_0", "v1", "abc", "tpl");
let c = compute_model_fingerprint("r/m", "Q4_0", "v1", "abc", "tpl");
assert_eq!(a, b);
assert_eq!(b, c);
let split_a = compute_model_fingerprint("ab", "c", "v", "h", "t");
let split_b = compute_model_fingerprint("a", "bc", "v", "h", "t");
assert_ne!(
split_a, split_b,
"NUL separator must defend against component-split collisions"
);
}
#[test]
fn model_fingerprint_changes_on_input_perturbation() {
let base = compute_model_fingerprint("test/repo", "Q4_0", "v1", "deadbeef", "tpl");
let perturbations = [
("test/repo2", "Q4_0", "v1", "deadbeef", "tpl"),
("test/repo", "Q4_K_M", "v1", "deadbeef", "tpl"),
("test/repo", "Q4_0", "v2", "deadbeef", "tpl"),
("test/repo", "Q4_0", "v1", "deadbeec", "tpl"),
("test/repo", "Q4_0", "v1", "deadbeef", "tpl2"),
];
for (i, (r, q, v, s, t)) in perturbations.iter().enumerate() {
let pert = compute_model_fingerprint(r, q, v, s, t);
assert_ne!(
base, pert,
"perturbation #{i} must flip fingerprint (component={r}/{q}/{v}/{s}/{t})"
);
}
}
#[test]
fn write_then_read_envelope_round_trip() {
let dir = temp_dir("rt");
let fp = fixture_fp();
let (body, body_bh) = body_and_matching_block_hash(0xAB);
let header = EnvelopeHeader {
format_version: CURRENT_FORMAT_VERSION.0,
model_fingerprint: fp,
block_hash: body_bh,
parent_block_hash: ParentBlockHash(None),
payload_kind: "kv-dense-bf16".into(),
codec_version: 1,
n_tokens: 256,
};
let path = dir.join("rt.safetensors");
let total = write_envelope(&path, &header, &body).expect("write_envelope");
let on_disk_size = std::fs::metadata(&path).expect("stat").len();
assert_eq!(total, on_disk_size, "returned size matches actual");
let header_only = read_envelope_header(&path).expect("read_envelope_header");
assert_eq!(header_only, header);
let (h2, body2) = read_envelope_body(&path).expect("read_envelope_body");
assert_eq!(h2, header);
assert_eq!(body2, body);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn write_envelope_atomic_rename_no_visible_partial() {
let dir = temp_dir("atomic");
let fp = fixture_fp();
let (body, body_bh) = body_and_matching_block_hash(0x33);
let header = EnvelopeHeader {
format_version: CURRENT_FORMAT_VERSION.0,
model_fingerprint: fp,
block_hash: body_bh,
parent_block_hash: ParentBlockHash(None),
payload_kind: "kv-dense-bf16".into(),
codec_version: 1,
n_tokens: 256,
};
let path = dir.join("atomic.safetensors");
let _ = write_envelope(&path, &header, &body).expect("write_envelope");
let mut entries: Vec<_> = std::fs::read_dir(&dir)
.expect("read_dir")
.filter_map(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
.collect();
entries.sort();
assert!(
entries.iter().all(|n| !n.contains(".tmp.")),
"no .tmp.<pid> survivors after clean write; saw {entries:?}"
);
assert!(
entries.iter().any(|n| n == "atomic.safetensors"),
"final file present"
);
let crashed_tmp = dir.join(format!("atomic.safetensors.tmp.{}", process::id() + 1));
std::fs::write(&crashed_tmp, b"partial-bytes-from-crashed-process")
.expect("write tmp sentinel");
let (h_after, body_after) = read_envelope_body(&path).expect("read after sim crash");
assert_eq!(h_after, header);
assert_eq!(body_after, body);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn read_envelope_body_rejects_hash_mismatch() {
let dir = temp_dir("mismatch");
let fp = fixture_fp();
let (body, body_bh) = body_and_matching_block_hash(0xCC);
let header = EnvelopeHeader {
format_version: CURRENT_FORMAT_VERSION.0,
model_fingerprint: fp,
block_hash: body_bh,
parent_block_hash: ParentBlockHash(None),
payload_kind: "kv-dense-bf16".into(),
codec_version: 1,
n_tokens: 256,
};
let path = dir.join("mut.safetensors");
let _ = write_envelope(&path, &header, &body).expect("write_envelope");
let header_json = serde_json::to_vec(&header).expect("re-serialize header");
let pad = (8 - (header_json.len() % 8)) % 8;
let header_len_bytes = header_json.len() + pad;
let body_offset = 8 + header_len_bytes;
let mut full = std::fs::read(&path).expect("read full");
assert!(full.len() > body_offset, "body region present");
full[body_offset] ^= 0xFF;
std::fs::write(&path, &full).expect("write mutated");
let h_after = read_envelope_header(&path).expect("header still parses");
assert_eq!(h_after, header);
let err = read_envelope_body(&path).expect_err("must fail");
assert!(
err.to_string().contains("body sha256 mismatch"),
"expected body sha256 mismatch error, got: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn block_hash_hex_round_trip() {
let fp = fixture_fp();
let h = compute_block_hash(&fp, &ParentBlockHash(None), &[1, 2, 3, 4, 5]);
let s = h.to_string();
assert_eq!(s.len(), 64, "hex length is 64");
assert!(s
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));
let parsed: BlockHash = s.parse().expect("parse hex");
assert_eq!(parsed, h);
let bad: Result<BlockHash, _> = "not-hex".parse();
assert!(bad.is_err());
let short: Result<BlockHash, _> = "ab".parse();
assert!(short.is_err());
}
#[test]
fn envelope_header_serde_round_trip_preserves_hex() {
let fp = fixture_fp();
let parent_h = compute_block_hash(&fp, &ParentBlockHash(None), &[7]);
let block_h = compute_block_hash(&fp, &ParentBlockHash(Some(parent_h)), &[8, 9]);
let header = EnvelopeHeader {
format_version: CURRENT_FORMAT_VERSION.0,
model_fingerprint: fp,
block_hash: block_h,
parent_block_hash: ParentBlockHash(Some(parent_h)),
payload_kind: "kv-tq-packed".into(),
codec_version: 7,
n_tokens: 128,
};
let s = serde_json::to_string(&header).expect("serialize");
assert!(
s.contains(&block_h.to_string()),
"block_hash hex appears in JSON: {s}"
);
assert!(
s.contains(&parent_h.to_string()),
"parent_block_hash hex appears in JSON: {s}"
);
let back: EnvelopeHeader = serde_json::from_str(&s).expect("deserialize");
assert_eq!(back, header);
}
}