#![forbid(unsafe_code)]
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ChunkId(pub [u8; 32]);
impl ChunkId {
pub const ZERO: ChunkId = ChunkId([0u8; 32]);
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub const fn is_zero(&self) -> bool {
let mut i = 0;
while i < 32 {
if self.0[i] != 0 {
return false;
}
i += 1;
}
true
}
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn of(data: &[u8]) -> Self {
Self(blake3::hash(data).into())
}
pub fn from_bytes(b: &[u8]) -> Option<Self> {
b.try_into().ok().map(Self)
}
pub fn from_hex(s: &str) -> Option<Self> {
if s.len() != 64 {
return None;
}
let mut out = [0u8; 32];
for (i, chunk) in s.as_bytes().chunks_exact(2).enumerate() {
let hi = hex_val(chunk[0])?;
let lo = hex_val(chunk[1])?;
out[i] = (hi << 4) | lo;
}
Some(Self(out))
}
}
fn hex_val(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
impl fmt::Display for ChunkId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl fmt::Debug for ChunkId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ChunkId({})", hex_prefix(&self.0, 12))
}
}
fn hex_prefix(bytes: &[u8; 32], n: usize) -> String {
let mut s = String::with_capacity(n * 2 + 3);
for b in &bytes[..n] {
s.push_str(&format!("{b:02x}"));
}
s.push_str("...");
s
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Extent {
pub offset: u64,
pub length: u64,
}
impl Extent {
pub const fn new(offset: u64, length: u64) -> Self {
Self { offset, length }
}
pub const fn end(&self) -> Option<u64> {
match self.offset.checked_add(self.length) {
Some(e) => Some(e),
None => None,
}
}
pub const fn contains(&self, pos: u64) -> bool {
if let Some(end) = self.end() {
pos >= self.offset && pos < end
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunk_id_roundtrip() {
let c = ChunkId::of(b"hello world");
let hex = c.to_string();
assert_eq!(hex.len(), 64);
assert_eq!(ChunkId::from_hex(&hex), Some(c));
assert_eq!(ChunkId::from_hex(&hex.to_uppercase()), Some(c));
assert_eq!(ChunkId::from_hex("abc"), None);
assert_eq!(ChunkId::from_hex(&"z".repeat(64)), None);
assert_eq!(ChunkId::ZERO, ChunkId::ZERO);
assert!(ChunkId::ZERO.is_zero());
assert!(!c.is_zero());
}
#[test]
fn extent_arithmetic() {
let e = Extent::new(10, 5);
assert_eq!(e.end(), Some(15));
assert!(e.contains(10));
assert!(e.contains(14));
assert!(!e.contains(15));
assert!(!e.contains(9));
let big = Extent::new(u64::MAX - 1, 5);
assert_eq!(big.end(), None);
}
}