pub use dig_merkle::SizeBucket;
use crate::error::{DigStoreError, DigStoreResult};
const MAX_EXPONENT: u8 = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SizeVerdict {
Accept,
Discard,
}
pub struct SizeProof;
impl SizeProof {
pub fn verify(anchored: SizeBucket, actual_bytes: u64) -> SizeVerdict {
match SizeBucket::for_byte_len(actual_bytes) {
Ok(actual) if actual == anchored => SizeVerdict::Accept,
_ => SizeVerdict::Discard,
}
}
pub fn require(anchored: SizeBucket, actual_bytes: u64) -> DigStoreResult<()> {
match Self::verify(anchored, actual_bytes) {
SizeVerdict::Accept => Ok(()),
SizeVerdict::Discard => {
let actual_k = SizeBucket::for_byte_len(actual_bytes)
.map(|bucket| bucket.exponent())
.unwrap_or(MAX_EXPONENT + 1);
Err(DigStoreError::SizeProofMismatch {
anchored_k: anchored.exponent(),
actual_k,
actual_bytes,
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const MIB: u64 = 1 << 20;
const GIB: u64 = 1 << 30;
#[test]
fn size_proof_accepts_matching_bucket() {
let anchored = SizeBucket::from_exponent(7).unwrap(); assert_eq!(SizeProof::verify(anchored, 100 * MIB), SizeVerdict::Accept);
assert_eq!(SizeProof::verify(anchored, 128 * MIB), SizeVerdict::Accept);
}
#[test]
fn size_proof_discards_oversize() {
let anchored = SizeBucket::from_exponent(7).unwrap(); assert_eq!(
SizeProof::verify(anchored, 128 * MIB + 1),
SizeVerdict::Discard
);
}
#[test]
fn size_proof_discards_undersize() {
let anchored = SizeBucket::from_exponent(7).unwrap(); assert_eq!(SizeProof::verify(anchored, 64 * MIB), SizeVerdict::Discard);
}
#[test]
fn size_proof_discards_over_ceiling() {
let anchored = SizeBucket::from_exponent(10).unwrap();
assert_eq!(SizeProof::verify(anchored, GIB + 1), SizeVerdict::Discard);
}
#[test]
fn require_reports_mismatch_detail() {
let anchored = SizeBucket::from_exponent(7).unwrap();
let err = SizeProof::require(anchored, 64 * MIB).unwrap_err();
match err {
DigStoreError::SizeProofMismatch {
anchored_k,
actual_k,
actual_bytes,
} => {
assert_eq!(anchored_k, 7);
assert_eq!(actual_k, 6); assert_eq!(actual_bytes, 64 * MIB);
}
other => panic!("expected SizeProofMismatch, got {other:?}"),
}
}
#[test]
fn require_over_ceiling_reports_synthetic_bucket() {
let anchored = SizeBucket::from_exponent(10).unwrap();
let err = SizeProof::require(anchored, GIB + 1).unwrap_err();
match err {
DigStoreError::SizeProofMismatch { actual_k, .. } => assert_eq!(actual_k, 11),
other => panic!("expected SizeProofMismatch, got {other:?}"),
}
}
#[test]
fn require_accepts_matching() {
let anchored = SizeBucket::from_exponent(7).unwrap();
assert!(SizeProof::require(anchored, 100 * MIB).is_ok());
}
#[test]
fn re_exported_ladder_is_canonical() {
assert_eq!(SizeBucket::from_exponent(10).unwrap().megabytes(), 1024);
assert_eq!(SizeBucket::from_exponent(10).unwrap().byte_len(), GIB);
assert_eq!(SizeBucket::for_byte_len(MIB).unwrap().exponent(), 0);
assert_eq!(SizeBucket::for_byte_len(MIB + 1).unwrap().exponent(), 1);
}
}