use std::path::Path;
use super::{BranchRefError, BranchRefRecord, BranchShardRef};
use crate::branch::persist::{Reader, push_bytes, push_u64};
use crate::branch::policy::BranchKind;
const REF_MAGIC: &[u8; 4] = b"HBR1";
const KIND_EXTENSION_MAGIC: &[u8; 4] = b"HBK1";
const KIND_WORK: u64 = 0;
const KIND_NAMESPACE: u64 = 1;
pub const REF_EXTENSION: &str = "ref";
pub 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 fn corrupt_in_file(path: &Path, error: &BranchRefError) -> BranchRefError {
BranchRefError::Corrupt(format!("{}: {error}", path.display()))
}
pub 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.extend_from_slice(KIND_EXTENSION_MAGIC);
push_u64(
&mut bytes,
match record.kind {
BranchKind::Work => KIND_WORK,
BranchKind::Namespace => KIND_NAMESPACE,
},
);
match &record.namespace_lineage {
Some(lineage) => {
push_u64(&mut bytes, 1);
push_bytes(&mut bytes, lineage.as_bytes());
}
None => push_u64(&mut bytes, 0),
}
bytes
}
pub 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.min(reader.remaining() / 72));
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.min(reader.remaining() / 32));
for _ in 0..parent_count {
parents.push(reader.read_hash()?);
}
let (kind, namespace_lineage) = if reader.remaining() == 0 {
(BranchKind::Work, None)
} else {
reader.expect_magic(*KIND_EXTENSION_MAGIC)?;
let kind = match reader.read_u64()? {
KIND_WORK => BranchKind::Work,
KIND_NAMESPACE => BranchKind::Namespace,
_ => return Err(BranchRefError::Corrupt("unknown branch kind".into())),
};
let lineage_tag = reader.read_u64()?;
if kind == BranchKind::Namespace && lineage_tag == 1 {
return Err(BranchRefError::Corrupt(
"HBK1 record has kind=Namespace with lineage_tag=1".into(),
));
}
let namespace_lineage = match lineage_tag {
0 => None,
1 => Some(String::from_utf8(reader.read_bytes()?).map_err(|_error| {
BranchRefError::Corrupt("namespace lineage is not valid UTF-8".into())
})?),
_ => {
return Err(BranchRefError::Corrupt(
"unknown namespace lineage tag".into(),
));
}
};
(kind, namespace_lineage)
};
reader.finish()?;
Ok(BranchRefRecord {
name,
created,
kind,
namespace_lineage,
seq,
timestamp,
shards,
parents,
})
}