#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
use bytes::Bytes;
use decdn_bao_range::{AlignedRange, CHUNK_GROUP_BYTES, IROH_BLOCK_SIZE, RangedStore};
use decdn_client::ClientRangedStore;
use std::assert_matches;
const GROUP: u64 = CHUNK_GROUP_BYTES;
fn synth_blob(len: usize) -> ([u8; 32], Vec<u8>, Bytes) {
let mut plaintext = vec![0u8; len];
let mut x: u32 = 0x9e37_79b9;
for b in &mut plaintext {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*b = x.to_le_bytes().first().copied().unwrap_or(0);
}
let ob = bao_tree::io::outboard::PreOrderMemOutboard::create(&plaintext, IROH_BLOCK_SIZE);
let root: [u8; 32] = *ob.root.as_bytes();
(root, plaintext, Bytes::from(ob.data))
}
fn bao_for(root: [u8; 32], plaintext: &[u8], outboard: Bytes, aligned: &AlignedRange) -> Bytes {
let s = usize::try_from(aligned.fetch_start()).expect("fits usize");
let e = usize::try_from(aligned.fetch_end()).expect("fits usize");
let data = plaintext.get(s..e).expect("aligned span in bounds");
decdn_bao_range::encode_verified_range(root, aligned, data, outboard).expect("verifies")
}
#[tokio::test]
async fn resume_trusts_record_without_rehashing() {
let total = 3 * GROUP;
let (root, plaintext, outboard) = synth_blob(usize::try_from(total).expect("fits"));
let dir = tempfile::tempdir().expect("tmp dir");
let k = GROUP; {
let store = ClientRangedStore::create(dir.path(), "blob", root, total).expect("create");
let aligned = decdn_bao_range::align_range(0, k, total).expect("align prefix");
let bao_bytes = bao_for(root, &plaintext, outboard.clone(), &aligned);
store.admit(aligned, bao_bytes).await.expect("admit prefix");
}
let partial_path = dir.path().join("blob.partial");
{
use std::io::{Seek, SeekFrom, Write};
let mut f = std::fs::OpenOptions::new()
.write(true)
.open(&partial_path)
.expect("open partial data file");
f.seek(SeekFrom::Start(0)).expect("seek");
let zeros = vec![0u8; usize::try_from(k).expect("fits")];
f.write_all(&zeros).expect("corrupt prefix");
}
let reopened =
ClientRangedStore::open(dir.path(), "blob", root).expect("open after corruption");
let present = reopened.present_ranges().await.expect("present_ranges");
let expected_prefix = decdn_bao_range::align_range(0, k, total)
.expect("align prefix")
.chunk_ranges()
.clone();
assert_eq!(
present, expected_prefix,
"open must trust the persisted record, not re-hash the (now corrupt) data"
);
let missing = reopened.missing_ranges(0, 0).await.expect("missing_ranges");
let expected_missing = decdn_bao_range::align_range(0, 0, total)
.expect("align whole blob")
.chunk_ranges()
.clone()
- &expected_prefix;
assert_eq!(missing, expected_missing);
let rest_aligned = decdn_bao_range::align_range(k, total - k, total).expect("align rest");
let rest_bao = bao_for(root, &plaintext, outboard, &rest_aligned);
reopened
.admit(rest_aligned, rest_bao)
.await
.expect("admit rest");
assert!(
reopened.is_complete().await.expect("is_complete"),
"record claims every group present"
);
let err = reopened
.finalize()
.await
.expect_err("finalize's whole-file hash must catch the corrupted prefix");
assert_matches!(
err,
decdn_bao_range::RangedStoreError::Backend(_),
"{err:?}"
);
assert!(partial_path.exists(), "a failed finalize keeps the partial");
}
#[tokio::test]
async fn resume_continues_from_record_without_refetching_prefix() {
let total = 3 * GROUP;
let (root, plaintext, outboard) = synth_blob(usize::try_from(total).expect("fits"));
let dir = tempfile::tempdir().expect("tmp dir");
let k = GROUP;
{
let store = ClientRangedStore::create(dir.path(), "blob", root, total).expect("create");
let aligned = decdn_bao_range::align_range(0, k, total).expect("align prefix");
let bao_bytes = bao_for(root, &plaintext, outboard.clone(), &aligned);
store.admit(aligned, bao_bytes).await.expect("admit prefix");
}
let reopened = ClientRangedStore::open(dir.path(), "blob", root).expect("open");
let missing = reopened.missing_ranges(0, 0).await.expect("missing_ranges");
let expected_missing = decdn_bao_range::align_range(0, 0, total)
.expect("align whole blob")
.chunk_ranges()
.clone()
- decdn_bao_range::align_range(0, k, total)
.expect("align prefix")
.chunk_ranges();
assert_eq!(
missing, expected_missing,
"held prefix must not be re-listed as missing"
);
let rest_aligned = decdn_bao_range::align_range(k, total - k, total).expect("align rest");
let rest_bao = bao_for(root, &plaintext, outboard, &rest_aligned);
reopened
.admit(rest_aligned, rest_bao)
.await
.expect("admit rest");
reopened.finalize().await.expect("finalize promotes");
let final_path = dir.path().join("blob");
let on_disk = std::fs::read(&final_path).expect("read promoted final file");
assert_eq!(
on_disk, plaintext,
"promoted file must hold the exact full plaintext"
);
}