use super::{BTreeSet, DurableTransition, Error, ReadVersion, Result, validate_user_named_bucket};
pub(super) const RESERVED: &str = "\u{1}trine-branch\u{1}";
const ENCODED_DATA_RESERVED: &str = "\u{1}trine-branch-data-v1\u{1}";
const REGISTRY_ENTRY_MAGIC: &[u8; 8] = b"TRNBRG01";
pub(super) const SEP: char = '\u{1}';
const MAX_BRANCH_NAME_BYTES: usize = 1_024;
pub(super) fn registry_bucket() -> String {
format!("{RESERVED}registry")
}
pub(super) fn encode_name_component(value: &str) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut encoded = String::with_capacity(value.len().saturating_mul(2));
for byte in value.bytes() {
encoded.push(char::from(HEX[usize::from(byte >> 4)]));
encoded.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
encoded
}
pub(super) fn validate_branch_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(Error::invalid_options("branch name cannot be empty"));
}
if name.len() > MAX_BRANCH_NAME_BYTES {
return Err(Error::invalid_options(
"branch name exceeds the 1024-byte limit",
));
}
Ok(())
}
pub(super) fn data_bucket(branch: &str, user_bucket: &str) -> String {
format!(
"{ENCODED_DATA_RESERVED}{}{SEP}{}",
encode_name_component(branch),
encode_name_component(user_bucket)
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BranchInfo {
fork: ReadVersion,
parent: Option<String>,
}
impl BranchInfo {
pub(super) fn from_active(entry: RegistryEntry) -> Option<Self> {
(entry.lifecycle == BranchLifecycle::Active).then_some(Self {
fork: entry.fork,
parent: entry.parent,
})
}
#[must_use]
pub const fn fork(&self) -> ReadVersion {
self.fork
}
#[must_use]
pub fn parent(&self) -> Option<&str> {
self.parent.as_deref()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum BranchLifecycle {
Creating,
Active,
Deleting,
}
impl BranchLifecycle {
pub(super) fn require_active(self) -> Result<()> {
match self {
Self::Active => Ok(()),
Self::Creating => Err(Error::runtime_busy("branch creation is in progress")),
Self::Deleting => Err(Error::invalid_options("branch deletion is in progress")),
}
}
pub(super) fn begin_delete(self) -> DurableTransition<Self> {
match self {
Self::Creating | Self::Active => DurableTransition::Apply(Self::Deleting),
Self::Deleting => DurableTransition::AlreadyApplied(Self::Deleting),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct RegistryEntry {
pub(super) fork: ReadVersion,
pub(super) parent: Option<String>,
pub(super) written_buckets: BTreeSet<String>,
pub(super) lifecycle: BranchLifecycle,
pub(super) generation: [u8; 16],
}
fn put_str(out: &mut Vec<u8>, value: &str) -> Result<()> {
let len = u32::try_from(value.len())
.map_err(|_| Error::invalid_options("branch registry string exceeds u32::MAX bytes"))?;
out.extend_from_slice(&len.to_le_bytes());
out.extend_from_slice(value.as_bytes());
Ok(())
}
fn take_registry_bytes<'a>(
bytes: &'a [u8],
position: &mut usize,
length: usize,
) -> Result<&'a [u8]> {
let end = position
.checked_add(length)
.ok_or_else(|| Error::Corruption {
message: "branch registry field offset overflow".to_owned(),
})?;
let value = bytes.get(*position..end).ok_or_else(|| Error::Corruption {
message: "malformed branch registry entry".to_owned(),
})?;
*position = end;
Ok(value)
}
impl RegistryEntry {
pub(super) fn require_generation(&self, generation: [u8; 16]) -> Result<()> {
self.lifecycle.require_active()?;
if self.generation != generation {
return Err(Error::invalid_options(
"branch handle belongs to a replaced branch generation",
));
}
Ok(())
}
pub(super) fn require_identity(
&self,
generation: [u8; 16],
fork: ReadVersion,
parent: Option<&str>,
) -> Result<()> {
self.lifecycle.require_active()?;
if self.generation != generation || self.fork != fork || self.parent.as_deref() != parent {
return Err(Error::invalid_options(
"branch handle belongs to a replaced branch generation",
));
}
Ok(())
}
pub(super) fn encode(&self) -> Result<Vec<u8>> {
if let Some(parent) = &self.parent {
validate_branch_name(parent)?;
}
for bucket in &self.written_buckets {
validate_user_named_bucket(bucket)?;
}
let mut out = Vec::new();
out.extend_from_slice(REGISTRY_ENTRY_MAGIC);
out.extend_from_slice(&self.fork.as_u64().to_le_bytes());
let count = u32::try_from(self.written_buckets.len())
.map_err(|_| Error::invalid_options("branch registry bucket count exceeds u32::MAX"))?;
out.extend_from_slice(&count.to_le_bytes());
for bucket in &self.written_buckets {
put_str(&mut out, bucket)?;
}
match &self.parent {
Some(parent) => {
out.push(1);
put_str(&mut out, parent)?;
}
None => out.push(0),
}
out.push(match self.lifecycle {
BranchLifecycle::Active => 0,
BranchLifecycle::Deleting => 1,
BranchLifecycle::Creating => 2,
});
out.extend_from_slice(&self.generation);
Ok(out)
}
pub(super) fn decode(bytes: &[u8]) -> Result<Self> {
let corrupt = || Error::Corruption {
message: "malformed branch registry entry".to_owned(),
};
if bytes.get(..REGISTRY_ENTRY_MAGIC.len()) != Some(REGISTRY_ENTRY_MAGIC) {
return Err(corrupt());
}
let mut pos = REGISTRY_ENTRY_MAGIC.len();
let take_u32 = |pos: &mut usize| -> Result<u32> {
let raw: [u8; 4] = take_registry_bytes(bytes, pos, 4)?.try_into().expect("4");
Ok(u32::from_le_bytes(raw))
};
let fork_bytes: [u8; 8] = take_registry_bytes(bytes, &mut pos, 8)?
.try_into()
.expect("8");
let fork = ReadVersion::from_u64(u64::from_le_bytes(fork_bytes));
let count = take_u32(&mut pos)?;
let mut written_buckets = BTreeSet::new();
for _ in 0..count {
let len = take_u32(&mut pos)? as usize;
let name = take_registry_bytes(bytes, &mut pos, len)?;
let name = String::from_utf8(name.to_vec()).map_err(|_| corrupt())?;
validate_user_named_bucket(&name).map_err(|_| corrupt())?;
if !written_buckets.insert(name) {
return Err(corrupt());
}
}
let parent = match bytes.get(pos) {
Some(&0) => {
pos += 1;
None
}
Some(&1) => {
pos += 1;
let len = take_u32(&mut pos)? as usize;
let name = take_registry_bytes(bytes, &mut pos, len)?;
let name = String::from_utf8(name.to_vec()).map_err(|_| corrupt())?;
validate_branch_name(&name).map_err(|_| corrupt())?;
Some(name)
}
Some(_) | None => return Err(corrupt()),
};
let lifecycle = match bytes.get(pos) {
Some(&0) => {
pos += 1;
BranchLifecycle::Active
}
Some(&1) => {
pos += 1;
BranchLifecycle::Deleting
}
Some(&2) => {
pos += 1;
BranchLifecycle::Creating
}
Some(_) | None => return Err(corrupt()),
};
let generation = bytes
.get(pos..)
.filter(|raw| raw.len() == 16)
.ok_or_else(corrupt)?
.try_into()
.expect("checked generation length");
Ok(Self {
fork,
parent,
written_buckets,
lifecycle,
generation,
})
}
}
pub(super) fn decode_registry_name(bytes: Vec<u8>) -> Result<String> {
let name = String::from_utf8(bytes).map_err(|_| Error::Corruption {
message: "branch registry holds a non-utf8 name".to_owned(),
})?;
validate_branch_name(&name).map_err(|_| Error::Corruption {
message: "branch registry holds an invalid branch name".to_owned(),
})?;
Ok(name)
}