use bao_tree::io::outboard::PreOrderMemOutboard;
use decdn_cache::range_pull::{
IROH_BLOCK_SIZE, RangeVerifyError, align_range, bao_encoded_size, encode_verified_range,
};
const GROUP: u64 = 1u64 << (IROH_BLOCK_SIZE.chunk_log() + 10);
fn make_blob(len: usize) -> Vec<u8> {
let mut v = vec![0u8; len];
let mut x: u32 = 0x9e37_79b9;
for b in &mut v {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*b = x.to_le_bytes().first().copied().unwrap_or(0);
}
v
}
fn sub(data: &[u8], start: u64, end: u64) -> anyhow::Result<Vec<u8>> {
let s = usize::try_from(start)?;
let e = usize::try_from(end)?;
data.get(s..e)
.map(<[u8]>::to_vec)
.ok_or_else(|| anyhow::anyhow!("range [{start}, {end}) out of bounds"))
}
fn err_of<T, E>(r: Result<T, E>) -> anyhow::Result<E> {
match r {
Ok(_) => anyhow::bail!("expected Err, got Ok"),
Err(e) => Ok(e),
}
}
fn strip_size_header(combined: &[u8]) -> anyhow::Result<u64> {
u64::try_from(combined.len())?
.checked_sub(8)
.ok_or_else(|| anyhow::anyhow!("combined encoding shorter than its 8-byte header"))
}
#[test]
fn align_range_snaps_to_chunk_group_boundaries() -> anyhow::Result<()> {
let a = align_range(20 * 1024, 20 * 1024, 200 * 1024)?;
anyhow::ensure!(
a.fetch_start() == GROUP,
"floor to 16 KiB, got {}",
a.fetch_start()
);
anyhow::ensure!(
a.fetch_end() == 3 * GROUP,
"ceil to 48 KiB, got {}",
a.fetch_end()
);
Ok(())
}
#[test]
fn align_range_already_aligned_does_not_over_widen() -> anyhow::Result<()> {
let a = align_range(GROUP, GROUP, 200 * 1024)?;
anyhow::ensure!(
a.fetch_start() == GROUP && a.fetch_end() == 2 * GROUP,
"no over-widen"
);
let whole = align_range(0, 0, 200 * 1024)?;
anyhow::ensure!(whole.fetch_start() == 0 && whole.fetch_end() == 200 * 1024);
Ok(())
}
#[test]
fn align_range_zero_len_means_to_end() -> anyhow::Result<()> {
let blob_size = 200 * 1024;
let a = align_range(64 * 1024, 0, blob_size)?;
anyhow::ensure!(a.fetch_start() == 64 * 1024);
anyhow::ensure!(a.fetch_end() == blob_size, "clamp to blob end");
Ok(())
}
#[test]
fn align_range_rejects_offset_past_end() -> anyhow::Result<()> {
let err = err_of(align_range(300 * 1024, 1024, 200 * 1024))?;
anyhow::ensure!(
matches!(err, RangeVerifyError::RangeOutOfBounds { .. }),
"{err:?}"
);
Ok(())
}
#[test]
fn align_range_rejects_overflowing_len() -> anyhow::Result<()> {
let err = err_of(align_range(200 * 1024 - 1, u64::MAX, 200 * 1024))?;
anyhow::ensure!(
matches!(err, RangeVerifyError::RangeOutOfBounds { .. }),
"{err:?}"
);
Ok(())
}
#[test]
fn align_range_rejects_len_past_end_but_accepts_exact_fit() -> anyhow::Result<()> {
let blob_size: u64 = 200 * 1024;
let over = err_of(align_range(64 * 1024, blob_size - 64 * 1024 + 1, blob_size))?;
anyhow::ensure!(
matches!(over, RangeVerifyError::RangeOutOfBounds { .. }),
"{over:?}"
);
let exact = align_range(64 * 1024, blob_size - 64 * 1024, blob_size)?;
anyhow::ensure!(exact.fetch_end() == blob_size);
Ok(())
}
#[test]
fn align_range_empty_blob_serves_as_empty_whole() -> anyhow::Result<()> {
let a = align_range(0, 0, 0)?;
anyhow::ensure!(
a.fetch_start() == 0 && a.fetch_end() == 0,
"empty blob has an empty fetch span, got [{}, {})",
a.fetch_start(),
a.fetch_end()
);
anyhow::ensure!(a.chunk_ranges().is_empty(), "no chunk groups for 0 bytes");
anyhow::ensure!(a.wire_len() == 0, "zero wire bytes, got {}", a.wire_len());
anyhow::ensure!(a.blob_size() == 0);
Ok(())
}
#[test]
fn align_range_empty_blob_rejects_positive_offset_or_len() -> anyhow::Result<()> {
let off = err_of(align_range(3, 0, 0))?;
anyhow::ensure!(
matches!(off, RangeVerifyError::RangeOutOfBounds { .. }),
"{off:?}"
);
let len = err_of(align_range(0, 5, 0))?;
anyhow::ensure!(
matches!(len, RangeVerifyError::RangeOutOfBounds { .. }),
"{len:?}"
);
Ok(())
}
#[test]
fn encode_verified_range_roundtrips_for_honest_bytes() -> anyhow::Result<()> {
let blob = make_blob(200 * 1024);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let aligned = align_range(64 * 1024, 32 * 1024, blob_size)?;
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let encoded = encode_verified_range(root, &aligned, &data, ob.data.clone().into())?;
anyhow::ensure!(!encoded.is_empty());
Ok(())
}
#[test]
fn encode_verified_range_rejects_tampered_data() -> anyhow::Result<()> {
let blob = make_blob(200 * 1024);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let aligned = align_range(64 * 1024, 32 * 1024, blob_size)?;
let mut data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
if let Some(b) = data.get_mut(100) {
*b ^= 0xff; }
let err = err_of(encode_verified_range(
root,
&aligned,
&data,
ob.data.clone().into(),
))?;
anyhow::ensure!(
matches!(err, RangeVerifyError::Verification { .. }),
"{err:?}"
);
Ok(())
}
#[test]
fn encode_verified_range_rejects_tampered_outboard() -> anyhow::Result<()> {
let blob = make_blob(200 * 1024);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let aligned = align_range(64 * 1024, 32 * 1024, blob_size)?;
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let mut bad_outboard = ob.data.clone();
if let Some(b) = bad_outboard.get_mut(40) {
*b ^= 0xff;
}
let err = err_of(encode_verified_range(
root,
&aligned,
&data,
bad_outboard.into(),
))?;
anyhow::ensure!(
matches!(err, RangeVerifyError::Verification { .. }),
"{err:?}"
);
Ok(())
}
#[test]
fn encode_verified_range_rejects_wrong_root() -> anyhow::Result<()> {
let blob = make_blob(200 * 1024);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let mut root = *ob.root.as_bytes();
if let Some(b) = root.get_mut(0) {
*b ^= 0xff; }
let blob_size = u64::try_from(blob.len())?;
let aligned = align_range(64 * 1024, 32 * 1024, blob_size)?;
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let err = err_of(encode_verified_range(
root,
&aligned,
&data,
ob.data.clone().into(),
))?;
anyhow::ensure!(
matches!(err, RangeVerifyError::Verification { .. }),
"{err:?}"
);
Ok(())
}
#[test]
fn encode_verified_range_rejects_wrong_length_outboard() -> anyhow::Result<()> {
let blob = make_blob(200 * 1024);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let aligned = align_range(64 * 1024, 32 * 1024, blob_size)?;
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let mut short = ob.data.clone();
short.truncate(short.len() - 8);
let err = err_of(encode_verified_range(root, &aligned, &data, short.into()))?;
anyhow::ensure!(
matches!(err, RangeVerifyError::OutboardSize { .. }),
"{err:?}"
);
Ok(())
}
#[tokio::test]
async fn widened_import_serves_back_the_requested_subrange() -> anyhow::Result<()> {
use iroh_blobs::store::mem::MemStore;
let blob = make_blob(200 * 1024);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let hash = iroh_blobs::Hash::from_bytes(root);
let (req_start, req_end) = (20 * 1024, 40 * 1024);
let aligned = align_range(req_start, req_end - req_start, blob_size)?;
anyhow::ensure!(aligned.fetch_start() == GROUP && aligned.fetch_end() == 3 * GROUP);
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let encoded = encode_verified_range(root, &aligned, &data, ob.data.clone().into())?;
let store = MemStore::new();
store
.blobs()
.import_bao_bytes(hash, aligned.chunk_ranges().clone(), encoded)
.await?;
let exported = store
.blobs()
.export_ranges(hash, req_start..req_end)
.concatenate()
.await?;
anyhow::ensure!(
exported == sub(&blob, req_start, req_end)?,
"requested sub-range mismatch"
);
Ok(())
}
#[tokio::test]
async fn non_group_aligned_blob_tail_roundtrips() -> anyhow::Result<()> {
use iroh_blobs::store::mem::MemStore;
let blob = make_blob(200 * 1024 + 1234); let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let hash = iroh_blobs::Hash::from_bytes(root);
let req_start = blob_size - 4096;
let aligned = align_range(req_start, 0, blob_size)?;
anyhow::ensure!(
aligned.fetch_end() == blob_size,
"tail clamps to blob end, not past it"
);
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let encoded = encode_verified_range(root, &aligned, &data, ob.data.clone().into())?;
let store = MemStore::new();
store
.blobs()
.import_bao_bytes(hash, aligned.chunk_ranges().clone(), encoded)
.await?;
let exported = store
.blobs()
.export_ranges(hash, req_start..blob_size)
.concatenate()
.await?;
anyhow::ensure!(
exported == sub(&blob, req_start, blob_size)?,
"tail sub-range mismatch"
);
Ok(())
}
fn assert_encoded_size_matches(blob_len: usize, offset: u64, len: u64) -> anyhow::Result<()> {
let blob = make_blob(blob_len);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let blob_size = u64::try_from(blob.len())?;
let aligned = align_range(offset, len, blob_size)?;
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let combined = encode_verified_range(root, &aligned, &data, ob.data.clone().into())?;
let wire_len = strip_size_header(&combined)?;
let predicted = bao_encoded_size(blob_size, aligned.chunk_ranges());
anyhow::ensure!(
predicted == wire_len,
"bao_encoded_size {predicted} != actual wire length {wire_len} \
(blob {blob_len}, offset {offset}, len {len})"
);
anyhow::ensure!(predicted >= aligned.fetch_len(), "wire must cover the data");
Ok(())
}
#[test]
fn bao_encoded_size_matches_actual_wire_length() -> anyhow::Result<()> {
assert_encoded_size_matches(200 * 1024, 0, 0)?;
assert_encoded_size_matches(200 * 1024, 64 * 1024, 32 * 1024)?;
assert_encoded_size_matches(200 * 1024, 32 * 1024, 16 * 1024)?;
assert_encoded_size_matches(200 * 1024 + 1234, 196 * 1024, 0)?;
assert_encoded_size_matches(500, 0, 0)?;
Ok(())
}
#[test]
fn bao_encoded_size_whole_blob_1_5_mib_is_golden() -> anyhow::Result<()> {
const BLOB_SIZE: u64 = 1_572_864; const GOLDEN_WIRE: u64 = 1_578_944;
let aligned = align_range(0, 0, BLOB_SIZE)?;
let predicted = bao_encoded_size(BLOB_SIZE, aligned.chunk_ranges());
anyhow::ensure!(
predicted == GOLDEN_WIRE,
"1.5 MiB whole-blob wire size changed: got {predicted}, expected {GOLDEN_WIRE} \
(a bao-tree encoding-shape change — see ADR 038 §Risks)"
);
let blob = make_blob(usize::try_from(BLOB_SIZE)?);
let ob = PreOrderMemOutboard::create(&blob, IROH_BLOCK_SIZE);
let root = *ob.root.as_bytes();
let data = sub(&blob, aligned.fetch_start(), aligned.fetch_end())?;
let combined = encode_verified_range(root, &aligned, &data, ob.data.clone().into())?;
let actual_wire = strip_size_header(&combined)?;
anyhow::ensure!(
actual_wire == GOLDEN_WIRE,
"actual emitted wire {actual_wire} != golden {GOLDEN_WIRE}"
);
Ok(())
}
#[test]
fn bao_encoded_size_zero_blob_or_empty_ranges_is_zero() -> anyhow::Result<()> {
anyhow::ensure!(
bao_encoded_size(0, &bao_tree::ChunkRanges::all()) == 0,
"0-byte blob over all() ranges is 0 wire bytes"
);
anyhow::ensure!(
bao_encoded_size(0, &bao_tree::ChunkRanges::empty()) == 0,
"0-byte blob over empty ranges is 0 wire bytes"
);
anyhow::ensure!(
bao_encoded_size(200 * 1024, &bao_tree::ChunkRanges::empty()) == 0,
"empty range set over a non-empty blob is 0 wire bytes"
);
Ok(())
}