use crate::{MerkleError, MerkleResult};
const MAX_EXPONENT: u8 = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SizeBucket {
k: u8,
}
impl SizeBucket {
pub fn from_exponent(k: u8) -> MerkleResult<Self> {
if k > MAX_EXPONENT {
return Err(MerkleError::InvalidSize(format!(
"size-bucket exponent {k} exceeds the maximum {MAX_EXPONENT} (1 GB)"
)));
}
Ok(Self { k })
}
pub fn exponent(&self) -> u8 {
self.k
}
pub fn megabytes(&self) -> u32 {
1u32 << self.k
}
pub fn byte_len(&self) -> u64 {
1u64 << (u32::from(self.k) + 20)
}
pub fn for_byte_len(bytes: u64) -> MerkleResult<Self> {
for k in 0..=MAX_EXPONENT {
if bytes <= (1u64 << (u32::from(k) + 20)) {
return Ok(Self { k });
}
}
Err(MerkleError::InvalidSize(format!(
"size {bytes} bytes exceeds the maximum bucket 2^30 (1 GiB)"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exponent_to_megabytes_ladder() {
let expected_mb = [1u32, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024];
for (k, &mb) in expected_mb.iter().enumerate() {
let bucket = SizeBucket::from_exponent(k as u8).expect("k in range");
assert_eq!(bucket.megabytes(), mb, "k={k} megabytes");
assert_eq!(bucket.exponent(), k as u8, "k={k} exponent round-trips");
assert_eq!(bucket.byte_len(), 1u64 << (k as u32 + 20), "k={k} byte_len");
}
assert_eq!(SizeBucket::from_exponent(10).unwrap().megabytes(), 1024);
assert_eq!(SizeBucket::from_exponent(10).unwrap().byte_len(), 1 << 30);
}
#[test]
fn from_exponent_rejects_out_of_range() {
assert!(matches!(
SizeBucket::from_exponent(11),
Err(MerkleError::InvalidSize(_))
));
assert!(matches!(
SizeBucket::from_exponent(255),
Err(MerkleError::InvalidSize(_))
));
}
#[test]
fn for_byte_len_boundaries() {
const MIB: u64 = 1 << 20;
const GIB: u64 = 1 << 30;
let expect = |bytes: u64, k: u8| {
assert_eq!(
SizeBucket::for_byte_len(bytes)
.expect("in range")
.exponent(),
k,
"for_byte_len({bytes}) should be k={k}"
);
};
expect(0, 0);
expect(1, 0);
expect(MIB, 0); expect(MIB + 1, 1); expect(512 * MIB, 9); expect(512 * MIB + 1, 10);
expect(GIB, 10); assert!(
matches!(
SizeBucket::for_byte_len(GIB + 1),
Err(MerkleError::InvalidSize(_))
),
"one byte over 1 GiB has no bucket"
);
}
}