use std::collections::TryReserveError;
use liminal::durability::StoredEntry;
const KEY_PREFIX: &[u8] = b"liminal:routed-stream:v1:";
const HEAD_PREFIX: &[u8; 5] = b"LRSH\x01";
const ROW_PREFIX: &[u8; 5] = b"LRSR\x01";
const HEAD_BYTES: usize = 13;
const ROW_HEADER_BYTES: usize = 29;
#[derive(Debug, thiserror::Error)]
pub enum CodecError {
#[error("routed stream identity is empty")]
EmptyStream,
#[error("routed stream identity exceeds its encoded width")]
StreamTooLong,
#[error("routed stream framing length overflow")]
LengthOverflow,
#[error("invalid routed stream key framing")]
InvalidKey,
#[error("invalid routed stream head framing")]
InvalidHead,
#[error("invalid routed stream row framing")]
InvalidRow,
#[error("routed stream row sequence mismatch: expected {expected}, actual {actual}")]
SequenceMismatch { expected: u64, actual: u64 },
#[error("routed stream framing allocation refused")]
Allocation(#[source] TryReserveError),
}
#[derive(Debug, PartialEq, Eq)]
pub(super) enum StreamKey<'a> {
Head { stream: &'a str },
Row { stream: &'a str, sequence: u64 },
}
pub(super) fn head_key(stream: &str) -> Result<Vec<u8>, CodecError> {
let mut key = key_prefix(stream, 1)?;
key.push(0);
Ok(key)
}
pub(super) fn row_key(stream: &str, sequence: u64) -> Result<Vec<u8>, CodecError> {
let mut key = key_prefix(stream, 9)?;
key.push(1);
key.extend_from_slice(&sequence.to_be_bytes());
Ok(key)
}
pub(super) fn stream_window(stream: &str) -> Result<(Vec<u8>, Vec<u8>), CodecError> {
let mut from = key_prefix(stream, 1)?;
let mut to = from.clone();
from.push(0);
to.push(2);
Ok((from, to))
}
fn key_prefix(stream: &str, suffix: usize) -> Result<Vec<u8>, CodecError> {
if stream.is_empty() {
return Err(CodecError::EmptyStream);
}
let length = u32::try_from(stream.len()).map_err(|_| CodecError::StreamTooLong)?;
let capacity = KEY_PREFIX
.len()
.checked_add(4)
.and_then(|value| value.checked_add(stream.len()))
.and_then(|value| value.checked_add(suffix))
.ok_or(CodecError::LengthOverflow)?;
let mut key = Vec::new();
key.try_reserve_exact(capacity)
.map_err(CodecError::Allocation)?;
key.extend_from_slice(KEY_PREFIX);
key.extend_from_slice(&length.to_be_bytes());
key.extend_from_slice(stream.as_bytes());
Ok(key)
}
#[cfg(test)]
pub(super) fn row_stream(key: &[u8]) -> Option<&str> {
match decode_key(key) {
Ok(StreamKey::Row { stream, .. }) => Some(stream),
Ok(StreamKey::Head { .. }) | Err(_) => None,
}
}
pub(super) fn decode_key(bytes: &[u8]) -> Result<StreamKey<'_>, CodecError> {
if !bytes.starts_with(KEY_PREFIX) {
return Err(CodecError::InvalidKey);
}
let length_end = KEY_PREFIX
.len()
.checked_add(4)
.ok_or(CodecError::LengthOverflow)?;
let length_bytes: [u8; 4] = bytes
.get(KEY_PREFIX.len()..length_end)
.ok_or(CodecError::InvalidKey)?
.try_into()
.map_err(|_| CodecError::InvalidKey)?;
let length = usize::try_from(u32::from_be_bytes(length_bytes))
.map_err(|_| CodecError::LengthOverflow)?;
if length == 0 {
return Err(CodecError::EmptyStream);
}
let stream_end = length_end
.checked_add(length)
.ok_or(CodecError::LengthOverflow)?;
let stream = std::str::from_utf8(
bytes
.get(length_end..stream_end)
.ok_or(CodecError::InvalidKey)?,
)
.map_err(|_| CodecError::InvalidKey)?;
match bytes.get(stream_end..).ok_or(CodecError::InvalidKey)? {
[0] => Ok(StreamKey::Head { stream }),
[1, sequence @ ..] if sequence.len() == 8 => Ok(StreamKey::Row {
stream,
sequence: decode_integer(sequence, CodecError::InvalidKey)?,
}),
_ => Err(CodecError::InvalidKey),
}
}
#[must_use]
pub(super) fn encode_head(next_sequence: u64) -> [u8; HEAD_BYTES] {
let mut bytes = [0; HEAD_BYTES];
bytes[..5].copy_from_slice(HEAD_PREFIX);
bytes[5..].copy_from_slice(&next_sequence.to_be_bytes());
bytes
}
pub(super) fn decode_head(bytes: &[u8]) -> Result<u64, CodecError> {
if bytes.len() != HEAD_BYTES || !bytes.starts_with(HEAD_PREFIX) {
return Err(CodecError::InvalidHead);
}
decode_integer(
bytes.get(5..).ok_or(CodecError::InvalidHead)?,
CodecError::InvalidHead,
)
}
pub(super) fn encode_row(entry: &StoredEntry) -> Result<Vec<u8>, CodecError> {
let length = u64::try_from(entry.payload.len()).map_err(|_| CodecError::LengthOverflow)?;
let capacity = ROW_HEADER_BYTES
.checked_add(entry.payload.len())
.ok_or(CodecError::LengthOverflow)?;
let mut bytes = Vec::new();
bytes
.try_reserve_exact(capacity)
.map_err(CodecError::Allocation)?;
bytes.extend_from_slice(ROW_PREFIX);
bytes.extend_from_slice(&entry.sequence.to_be_bytes());
bytes.extend_from_slice(&entry.timestamp.to_be_bytes());
bytes.extend_from_slice(&length.to_be_bytes());
bytes.extend_from_slice(&entry.payload);
Ok(bytes)
}
pub(super) fn decode_row(bytes: &[u8], expected_sequence: u64) -> Result<StoredEntry, CodecError> {
let (timestamp, bytes) = decode_row_payload(bytes, expected_sequence)?;
let mut payload = Vec::new();
payload
.try_reserve_exact(bytes.len())
.map_err(CodecError::Allocation)?;
payload.extend_from_slice(bytes);
Ok(StoredEntry {
sequence: expected_sequence,
timestamp,
payload,
})
}
pub(super) fn decode_row_payload(
bytes: &[u8],
expected_sequence: u64,
) -> Result<(u64, &[u8]), CodecError> {
if bytes.len() < ROW_HEADER_BYTES || !bytes.starts_with(ROW_PREFIX) {
return Err(CodecError::InvalidRow);
}
let sequence = decode_integer(&bytes[5..13], CodecError::InvalidRow)?;
if sequence != expected_sequence {
return Err(CodecError::SequenceMismatch {
expected: expected_sequence,
actual: sequence,
});
}
let timestamp = decode_integer(&bytes[13..21], CodecError::InvalidRow)?;
let length = usize::try_from(decode_integer(&bytes[21..29], CodecError::InvalidRow)?)
.map_err(|_| CodecError::LengthOverflow)?;
let end = ROW_HEADER_BYTES
.checked_add(length)
.ok_or(CodecError::LengthOverflow)?;
if end != bytes.len() {
return Err(CodecError::InvalidRow);
}
Ok((timestamp, &bytes[ROW_HEADER_BYTES..]))
}
fn decode_integer(bytes: &[u8], error: CodecError) -> Result<u64, CodecError> {
let encoded = bytes.try_into().map_err(|_| error)?;
Ok(u64::from_be_bytes(encoded))
}
#[cfg(test)]
mod tests;