use std::collections::BTreeMap;
use crate::branch::{BranchKind, BranchRefRecord, BranchShardRef};
use crate::tree::Hash;
use super::BrowserSourceError;
const RECORD_MAGIC: &[u8; 4] = b"HBL1";
const NODE_MAGIC: &[u8; 4] = b"HBN1";
pub(super) const STORE_FORMAT: &[u8] = b"HAEMATITE-BROWSER-LOCAL\n1\n";
pub(super) fn encode_record(record: &BranchRefRecord, generation: &str) -> Vec<u8> {
let mut bytes = RECORD_MAGIC.to_vec();
push_bytes(&mut bytes, generation.as_bytes());
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, usize_to_u64(record.shards.len()));
for shard in &record.shards {
push_u64(&mut bytes, usize_to_u64(shard.shard_id));
bytes.extend_from_slice(shard.fork_anchor.as_bytes());
bytes.extend_from_slice(shard.head.as_bytes());
}
push_u64(&mut bytes, usize_to_u64(record.parents.len()));
for parent in &record.parents {
bytes.extend_from_slice(parent.as_bytes());
}
push_u64(
&mut bytes,
match record.kind {
BranchKind::Work => 0,
BranchKind::Namespace => 1,
},
);
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(super) fn decode_record(bytes: &[u8]) -> Result<(String, BranchRefRecord), BrowserSourceError> {
let mut reader = Reader::new(bytes);
reader.magic(RECORD_MAGIC)?;
let generation = reader.string()?;
let name = reader.string()?;
let created = reader.u64()?;
let seq = reader.u64()?;
let timestamp = reader.u64()?;
let shard_count = reader.len()?;
let mut shards = Vec::with_capacity(shard_count);
for _ in 0..shard_count {
shards.push(BranchShardRef {
shard_id: reader.len()?,
fork_anchor: reader.hash()?,
head: reader.hash()?,
});
}
let parent_count = reader.len()?;
let mut parents = Vec::with_capacity(parent_count);
for _ in 0..parent_count {
parents.push(reader.hash()?);
}
let kind = match reader.u64()? {
0 => BranchKind::Work,
1 => BranchKind::Namespace,
value => {
return Err(BrowserSourceError::codec(format!(
"unknown branch kind {value}"
)));
}
};
let namespace_lineage = match reader.u64()? {
0 => None,
1 => Some(reader.string()?),
value => {
return Err(BrowserSourceError::codec(format!(
"unknown lineage tag {value}"
)));
}
};
reader.finish()?;
Ok((
generation,
BranchRefRecord {
name,
created,
kind,
namespace_lineage,
seq,
timestamp,
shards,
parents,
},
))
}
pub(super) fn encode_nodes(nodes: &BTreeMap<Hash, Vec<u8>>) -> Vec<u8> {
let mut bytes = NODE_MAGIC.to_vec();
push_u64(&mut bytes, usize_to_u64(nodes.len()));
for (hash, node) in nodes {
bytes.extend_from_slice(hash.as_bytes());
push_bytes(&mut bytes, node);
}
bytes
}
pub(super) fn decode_nodes(bytes: &[u8]) -> Result<BTreeMap<Hash, Vec<u8>>, BrowserSourceError> {
let mut reader = Reader::new(bytes);
reader.magic(NODE_MAGIC)?;
let count = reader.len()?;
let mut nodes = BTreeMap::new();
for _ in 0..count {
let hash = reader.hash()?;
if nodes.insert(hash, reader.bytes()?.to_vec()).is_some() {
return Err(BrowserSourceError::codec("duplicate node hash"));
}
}
reader.finish()?;
Ok(nodes)
}
pub(super) fn record_file_name(name: &str) -> String {
format!("{}.hbr", blake3::hash(name.as_bytes()).to_hex())
}
pub(super) fn generation_id(name: &str) -> String {
format!(
"{:016x}-{}",
super::core::browser_timestamp(),
blake3::hash(name.as_bytes()).to_hex()
)
}
fn push_u64(bytes: &mut Vec<u8>, value: u64) {
bytes.extend_from_slice(&value.to_le_bytes());
}
fn push_bytes(bytes: &mut Vec<u8>, value: &[u8]) {
push_u64(bytes, usize_to_u64(value.len()));
bytes.extend_from_slice(value);
}
fn usize_to_u64(value: usize) -> u64 {
u64::try_from(value).unwrap_or(u64::MAX)
}
struct Reader<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> Reader<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, offset: 0 }
}
fn take(&mut self, count: usize) -> Result<&'a [u8], BrowserSourceError> {
let end = self
.offset
.checked_add(count)
.ok_or("record length overflow")?;
let value = self.bytes.get(self.offset..end).ok_or("truncated record")?;
self.offset = end;
Ok(value)
}
fn magic(&mut self, expected: &[u8]) -> Result<(), BrowserSourceError> {
if self.take(expected.len())? == expected {
Ok(())
} else {
Err(BrowserSourceError::codec("record magic mismatch"))
}
}
fn u64(&mut self) -> Result<u64, BrowserSourceError> {
let raw: [u8; 8] = self.take(8)?.try_into().map_err(|_| "truncated integer")?;
Ok(u64::from_le_bytes(raw))
}
fn len(&mut self) -> Result<usize, BrowserSourceError> {
usize::try_from(self.u64()?).map_err(|_| BrowserSourceError::codec("length exceeds usize"))
}
fn bytes(&mut self) -> Result<&'a [u8], BrowserSourceError> {
let len = self.len()?;
self.take(len)
}
fn string(&mut self) -> Result<String, BrowserSourceError> {
String::from_utf8(self.bytes()?.to_vec())
.map_err(|error| BrowserSourceError::codec(error.to_string()))
}
fn hash(&mut self) -> Result<Hash, BrowserSourceError> {
let raw: [u8; 32] = self.take(32)?.try_into().map_err(|_| "truncated hash")?;
Ok(Hash::from_bytes(raw))
}
fn finish(self) -> Result<(), BrowserSourceError> {
if self.offset == self.bytes.len() {
Ok(())
} else {
Err(BrowserSourceError::codec("trailing record bytes"))
}
}
}