use std::path::Path;
use super::{BranchRefError, BranchRefRecord, BranchShardRef};
use crate::branch::persist::{Reader, push_bytes, push_u64};
const REF_MAGIC: &[u8; 4] = b"HBR1";
pub(super) const REF_EXTENSION: &str = "ref";
pub(super) fn ref_file_name(name: &str) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let digest = blake3::hash(name.as_bytes());
let mut file_name = String::with_capacity(36);
for byte in &digest.as_bytes()[..16] {
file_name.push(char::from(HEX[usize::from(byte >> 4)]));
file_name.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
file_name.push('.');
file_name.push_str(REF_EXTENSION);
file_name
}
pub(super) fn corrupt_in_file(path: &Path, error: &BranchRefError) -> BranchRefError {
BranchRefError::Corrupt(format!("{}: {error}", path.display()))
}
pub(super) fn encode_record(record: &BranchRefRecord) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(REF_MAGIC);
push_bytes(&mut bytes, record.name.as_bytes());
push_u64(&mut bytes, record.created);
push_u64(&mut bytes, record.seq);
push_u64(&mut bytes, record.timestamp);
push_u64(&mut bytes, record.shards.len() as u64);
for shard in &record.shards {
push_u64(&mut bytes, shard.shard_id as u64);
bytes.extend_from_slice(shard.fork_anchor.as_bytes());
bytes.extend_from_slice(shard.head.as_bytes());
}
push_u64(&mut bytes, record.parents.len() as u64);
for parent in &record.parents {
bytes.extend_from_slice(parent.as_bytes());
}
bytes
}
pub(super) fn decode_record(bytes: &[u8]) -> Result<BranchRefRecord, BranchRefError> {
let mut reader = Reader::new(bytes);
reader.expect_magic(*REF_MAGIC)?;
let name = String::from_utf8(reader.read_bytes()?)
.map_err(|_error| BranchRefError::Corrupt("branch name is not valid UTF-8".into()))?;
let created = reader.read_u64()?;
let seq = reader.read_u64()?;
let timestamp = reader.read_u64()?;
let shard_count = reader.read_usize()?;
let mut shards: Vec<BranchShardRef> = Vec::with_capacity(shard_count);
for _ in 0..shard_count {
let shard_id = reader.read_usize()?;
if shards
.last()
.is_some_and(|previous| previous.shard_id >= shard_id)
{
return Err(BranchRefError::Corrupt(
"shard ids are not strictly ascending".into(),
));
}
let fork_anchor = reader.read_hash()?;
let head = reader.read_hash()?;
shards.push(BranchShardRef {
shard_id,
fork_anchor,
head,
});
}
let parent_count = reader.read_usize()?;
let mut parents = Vec::with_capacity(parent_count);
for _ in 0..parent_count {
parents.push(reader.read_hash()?);
}
reader.finish()?;
Ok(BranchRefRecord {
name,
created,
seq,
timestamp,
shards,
parents,
})
}