#![allow(clippy::expect_used)]
use std::path::{Path, PathBuf};
use prikk_hash::{sha256, to_hex};
use crate::file_codec::decode_envelope_file;
use crate::layout::ref_name_storage_key;
use crate::wal::Wal;
const FROZEN_REF_LOG_MAGIC: &[u8; 8] = b"PREFLOG1";
const FROZEN_REF_LOG_VERSION: u16 = 1;
const FROZEN_REF_LOG_HEADER_LEN: usize = 8 + 2 + 8 + 32;
struct FrozenRefLogReplay {
record_count: usize,
trailing_partial_bytes: usize,
all_checksums_verified: bool,
}
fn decode_frozen_ref_log_records(bytes: &[u8]) -> FrozenRefLogReplay {
let mut offset = 0_usize;
let mut record_count = 0_usize;
let mut all_checksums_verified = true;
loop {
let remaining = bytes.len().saturating_sub(offset);
if remaining < FROZEN_REF_LOG_HEADER_LEN {
return FrozenRefLogReplay {
record_count,
trailing_partial_bytes: remaining,
all_checksums_verified,
};
}
let header_end = offset + FROZEN_REF_LOG_HEADER_LEN;
let Some(header) = bytes.get(offset..header_end) else {
return FrozenRefLogReplay {
record_count,
trailing_partial_bytes: remaining,
all_checksums_verified,
};
};
let Some(magic) = header.get(0..8) else {
all_checksums_verified = false;
break;
};
if magic != FROZEN_REF_LOG_MAGIC {
all_checksums_verified = false;
break;
}
let version = u16::from_be_bytes(
header
.get(8..10)
.unwrap_or(&[0, 0])
.try_into()
.unwrap_or([0, 0]),
);
if version != FROZEN_REF_LOG_VERSION {
all_checksums_verified = false;
break;
}
let body_len_bytes: [u8; 8] = header
.get(10..18)
.unwrap_or(&[0; 8])
.try_into()
.unwrap_or([0; 8]);
let body_len = u64::from_be_bytes(body_len_bytes);
let Ok(body_len) = usize::try_from(body_len) else {
all_checksums_verified = false;
break;
};
let checksum: [u8; 32] = header
.get(18..50)
.unwrap_or(&[0; 32])
.try_into()
.unwrap_or([0; 32]);
let Some(body_end) = header_end.checked_add(body_len) else {
all_checksums_verified = false;
break;
};
let Some(body) = bytes.get(header_end..body_end) else {
return FrozenRefLogReplay {
record_count,
trailing_partial_bytes: remaining,
all_checksums_verified,
};
};
let mut preimage = Vec::new();
preimage.extend_from_slice(FROZEN_REF_LOG_MAGIC);
preimage.extend_from_slice(&FROZEN_REF_LOG_VERSION.to_be_bytes());
preimage.extend_from_slice(&(body_len as u64).to_be_bytes());
preimage.extend_from_slice(body);
if sha256(&preimage) != checksum {
all_checksums_verified = false;
}
record_count += 1;
offset = body_end;
}
FrozenRefLogReplay {
record_count,
trailing_partial_bytes: 0,
all_checksums_verified,
}
}
fn fixture_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../prikk-cli/tests/fixtures/dc55_pre_swap_repo/.prikk")
}
#[test]
fn ref_name_storage_key_matches_the_frozen_ref_filenames() {
let expected = "c316ccb36a95a977918874d43e722a5a7d9ef74b138f3b76078f6993c14a799f".to_string();
assert_eq!(ref_name_storage_key("heads/main"), expected);
assert_eq!(to_hex(sha256(b"heads/main").as_ref()), expected);
let root = fixture_root();
assert!(
root.join(format!("refs/by-id/{expected}.ref")).is_file(),
"frozen ref pointer filename does not match the recomputed storage key"
);
assert!(
root.join(format!("refs/logs/{expected}.log")).is_file(),
"frozen ref log filename does not match the recomputed storage key"
);
}
#[test]
fn every_frozen_object_id_matches_its_own_filename() {
let objects_dir = fixture_root().join("objects");
let mut checked = 0_usize;
for path in collect_files(&objects_dir) {
let Some(extension) = path.extension() else {
continue;
};
if extension != "pobj" {
continue;
}
let bytes = std::fs::read(&path).expect("read frozen object file");
let envelope = decode_envelope_file(&bytes).expect("decode frozen object envelope");
let expected_id = path
.file_stem()
.and_then(|stem| stem.to_str())
.expect("object filename is valid UTF-8");
assert_eq!(
envelope.object_id().to_hex(),
expected_id,
"recomputed object id does not match frozen filename at {}",
path.display()
);
checked += 1;
}
assert_eq!(
checked, 8,
"expected 8 frozen objects (2 blob, 2 block, 2 patch, 2 ref-state); found {checked} -- \
update this count deliberately if the fixture ever legitimately changes shape, never to \
paper over a decode failure"
);
}
fn collect_files(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let Ok(entries) = std::fs::read_dir(dir) else {
return out;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
out.extend(collect_files(&path));
} else {
out.push(path);
}
}
out
}
#[test]
fn frozen_ref_log_checksums_verify_and_decode_cleanly() {
let path = fixture_root()
.join("refs/logs/c316ccb36a95a977918874d43e722a5a7d9ef74b138f3b76078f6993c14a799f.log");
let bytes = std::fs::read(&path).expect("read frozen ref log");
let replay = decode_frozen_ref_log_records(&bytes);
assert!(
replay.all_checksums_verified,
"a checksum in the frozen ref log no longer verifies"
);
assert_eq!(replay.trailing_partial_bytes, 0);
assert_eq!(replay.record_count, 2);
}
#[test]
fn corrupted_object_bytes_no_longer_match_the_original_filename() {
let original_path = fixture_root().join(
"objects/blob/1b/1b05e8e870004a5852990d93a5610d80e56507b129e6bc90a82bd050c8a4f878.pobj",
);
let original_id = "1b05e8e870004a5852990d93a5610d80e56507b129e6bc90a82bd050c8a4f878";
let mut bytes = std::fs::read(&original_path).expect("read frozen object for probe copy");
let payload_byte = bytes
.get_mut(22)
.expect("frozen object payload is non-empty");
*payload_byte ^= 0x01;
let envelope =
decode_envelope_file(&bytes).expect("single-byte-flipped envelope still decodes");
assert_ne!(
envelope.object_id().to_hex(),
original_id,
"flipping a payload byte must change the recomputed object id"
);
}
#[test]
fn frozen_active_wal_is_empty_and_replays_as_empty() {
let path = fixture_root().join("active/default/queue.wal");
assert_eq!(
std::fs::metadata(&path).expect("stat frozen WAL").len(),
0,
"frozen WAL is no longer empty -- if it now carries real records, extend this test to \
assert on `replay.records`/checksums instead of just emptiness"
);
let replay = Wal::new(path).replay().expect("replay frozen WAL");
assert!(!replay.has_item_failure());
assert_eq!(replay.trailing_partial_bytes, 0);
assert!(replay.records.is_empty());
}