use std::fmt;
pub use kcode_k1_access_types::{Authority, GroupId, TxId, UserId};
pub use kcode_k1_kmap_format::NodeId;
pub const PROJECTION_HEADER: &[u8; 8] = b"K1LNV3\0\0";
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TargetName(String);
impl TargetName {
pub fn new(value: String) -> Result<Self, String> {
let scalars = value.chars().count();
if !(1..=80).contains(&scalars)
|| value.len() > 320
|| value.chars().all(char::is_whitespace)
|| value.chars().any(char::is_control)
{
return Err("invalid target name".to_owned());
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct TargetId {
authority: Authority,
target: TargetName,
}
impl TargetId {
pub fn new(authority: Authority, target: TargetName) -> Self {
Self { authority, target }
}
pub fn authority(&self) -> Authority {
self.authority
}
pub fn target(&self) -> &TargetName {
&self.target
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SetAction {
operation_id: [u8; 16],
target: TargetId,
node: NodeId,
}
impl SetAction {
pub fn new(operation_id: [u8; 16], target: TargetId, node: NodeId) -> Self {
Self {
operation_id,
target,
node,
}
}
pub fn operation_id(&self) -> &[u8; 16] {
&self.operation_id
}
pub fn target(&self) -> &TargetId {
&self.target
}
pub fn node(&self) -> &NodeId {
&self.node
}
pub fn encode(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(64);
bytes.extend_from_slice(&[1, 1]);
bytes.extend_from_slice(&self.operation_id);
encode_target(&mut bytes, &self.target);
bytes.extend_from_slice(&self.node.0);
bytes
}
pub fn decode(bytes: &[u8]) -> Result<Self, CodecError> {
let mut decoder = Decoder::new(bytes);
if decoder.byte()? != 1 {
return Err(CodecError::UnknownVersion);
}
if decoder.byte()? != 1 {
return Err(CodecError::UnknownAction);
}
let operation_id = decoder.array()?;
let target = decoder.target()?;
let node = NodeId(decoder.array()?);
if decoder.remaining() != 0 {
return Err(CodecError::TrailingBytes);
}
Ok(Self::new(operation_id, target, node))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProjectionRecord {
callback: TxId,
target: TargetId,
node: NodeId,
}
impl ProjectionRecord {
pub fn new(callback: TxId, target: TargetId, node: NodeId) -> Self {
Self {
callback,
target,
node,
}
}
pub fn callback(&self) -> &TxId {
&self.callback
}
pub fn target(&self) -> &TargetId {
&self.target
}
pub fn node(&self) -> &NodeId {
&self.node
}
pub fn encode(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(64);
bytes.extend_from_slice(self.callback.as_bytes());
encode_target(&mut bytes, &self.target);
bytes.extend_from_slice(&self.node.0);
let checksum = checksum(&bytes);
bytes.extend_from_slice(&checksum.to_le_bytes());
bytes
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProjectionDecode {
Incomplete,
Complete {
record: ProjectionRecord,
consumed: usize,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CodecError {
UnknownVersion,
UnknownAction,
InvalidAuthority,
InvalidId,
InvalidUtf8,
InvalidTargetName,
Truncated,
TrailingBytes,
ChecksumMismatch,
}
impl fmt::Display for CodecError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{self:?}")
}
}
impl std::error::Error for CodecError {}
pub fn decode_projection_record(bytes: &[u8]) -> Result<ProjectionDecode, CodecError> {
if bytes.len() < 27 {
return Ok(ProjectionDecode::Incomplete);
}
let target_length = u16::from_le_bytes([bytes[25], bytes[26]]) as usize;
if target_length > 320 {
return Err(CodecError::InvalidTargetName);
}
let record_length = 43 + target_length;
if bytes.len() < record_length {
return Ok(ProjectionDecode::Incomplete);
}
let body_length = record_length - 4;
let stored = u32::from_le_bytes([
bytes[body_length],
bytes[body_length + 1],
bytes[body_length + 2],
bytes[body_length + 3],
]);
if checksum(&bytes[..body_length]) != stored {
return Err(CodecError::ChecksumMismatch);
}
let mut decoder = Decoder::new(&bytes[..body_length]);
let callback = decoder.tx_id()?;
let target = decoder.target()?;
let node = NodeId(decoder.array()?);
Ok(ProjectionDecode::Complete {
record: ProjectionRecord::new(callback, target, node),
consumed: record_length,
})
}
fn encode_target(bytes: &mut Vec<u8>, target: &TargetId) {
match target.authority() {
Authority::User(id) => {
bytes.push(1);
bytes.extend_from_slice(id.as_tx_id().as_bytes());
}
Authority::Group(id) => {
bytes.push(2);
bytes.extend_from_slice(id.txid().as_bytes());
}
}
let value = target.target().as_str().as_bytes();
bytes.extend_from_slice(&(value.len() as u16).to_le_bytes());
bytes.extend_from_slice(value);
}
fn checksum(bytes: &[u8]) -> u32 {
bytes.iter().fold(0x811c_9dc5, |value, byte| {
(value ^ u32::from(*byte)).wrapping_mul(0x0100_0193)
})
}
struct Decoder<'a> {
bytes: &'a [u8],
position: usize,
}
impl<'a> Decoder<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self { bytes, position: 0 }
}
fn byte(&mut self) -> Result<u8, CodecError> {
Ok(self.array::<1>()?[0])
}
fn array<const N: usize>(&mut self) -> Result<[u8; N], CodecError> {
if self.remaining() < N {
return Err(CodecError::Truncated);
}
let mut value = [0; N];
value.copy_from_slice(&self.bytes[self.position..self.position + N]);
self.position += N;
Ok(value)
}
fn tx_id(&mut self) -> Result<TxId, CodecError> {
Ok(TxId::from_bytes(self.array()?))
}
fn target(&mut self) -> Result<TargetId, CodecError> {
let tag = self.byte()?;
let authority = match tag {
1 => Authority::User(UserId::from_tx_id(self.tx_id()?)),
2 => Authority::Group(GroupId::new(self.tx_id()?)),
_ => return Err(CodecError::InvalidAuthority),
};
let length = u16::from_le_bytes(self.array()?) as usize;
let value = self.slice(length)?;
let text = std::str::from_utf8(value).map_err(|_| CodecError::InvalidUtf8)?;
let target = TargetName::new(text.to_owned()).map_err(|_| CodecError::InvalidTargetName)?;
Ok(TargetId::new(authority, target))
}
fn slice(&mut self, length: usize) -> Result<&'a [u8], CodecError> {
if self.remaining() < length {
return Err(CodecError::Truncated);
}
let value = &self.bytes[self.position..self.position + length];
self.position += length;
Ok(value)
}
fn remaining(&self) -> usize {
self.bytes.len() - self.position
}
}
#[cfg(test)]
mod tests {
use super::*;
fn action_bytes(authority_tag: u8) -> Vec<u8> {
let mut bytes = vec![1, 1];
bytes.extend_from_slice(&[7; 16]);
bytes.push(authority_tag);
bytes.extend_from_slice(&[1; 12]);
bytes.extend_from_slice(&3_u16.to_le_bytes());
bytes.extend_from_slice(b"a/b");
bytes.extend_from_slice(&[2; 12]);
bytes
}
fn record_bytes() -> Vec<u8> {
let mut bytes = vec![3; 12];
bytes.push(1);
bytes.extend_from_slice(&[1; 12]);
bytes.extend_from_slice(&3_u16.to_le_bytes());
bytes.extend_from_slice(b"a/b");
bytes.extend_from_slice(&[2; 12]);
let sum = bytes.iter().fold(0x811c_9dc5_u32, |value, byte| {
(value ^ u32::from(*byte)).wrapping_mul(0x0100_0193)
});
bytes.extend_from_slice(&sum.to_le_bytes());
bytes
}
#[test]
fn action_has_exact_bytes_and_roundtrips() {
let bytes = action_bytes(1);
let action = SetAction::decode(&bytes).unwrap();
assert_eq!(action.operation_id(), &[7; 16]);
assert_eq!(action.target().target().as_str(), "a/b");
assert_eq!(action.encode(), bytes);
}
#[test]
fn user_and_group_authorities_stay_separate() {
let user = SetAction::decode(&action_bytes(1)).unwrap();
let group = SetAction::decode(&action_bytes(2)).unwrap();
assert!(matches!(user.target().authority(), Authority::User(_)));
assert!(matches!(group.target().authority(), Authority::Group(_)));
assert_ne!(user.encode(), group.encode());
}
#[test]
fn target_validation_preserves_accepted_text() {
assert!(TargetName::new(String::new()).is_err());
assert!(TargetName::new(" \u{2003}".to_owned()).is_err());
assert!(TargetName::new("a\n".to_owned()).is_err());
assert!(TargetName::new("a".repeat(81)).is_err());
assert!(TargetName::new("🦀".repeat(80)).is_ok());
assert_eq!("🦀".repeat(80).len(), 320);
assert!(TargetName::new("🦀".repeat(80) + "a").is_err());
let exact = " / Case / ".to_owned();
assert_eq!(TargetName::new(exact.clone()).unwrap().into_string(), exact);
}
#[test]
fn action_rejects_unknown_truncated_and_trailing_input() {
let bytes = action_bytes(1);
for length in 0..bytes.len() {
assert_eq!(
SetAction::decode(&bytes[..length]),
Err(CodecError::Truncated)
);
}
let mut version = bytes.clone();
version[0] = 2;
assert_eq!(SetAction::decode(&version), Err(CodecError::UnknownVersion));
let mut tag = bytes.clone();
tag[1] = 2;
assert_eq!(SetAction::decode(&tag), Err(CodecError::UnknownAction));
let mut trailing = bytes;
trailing.push(0);
assert_eq!(SetAction::decode(&trailing), Err(CodecError::TrailingBytes));
}
#[test]
fn projection_has_exact_bytes_and_roundtrips() {
let bytes = record_bytes();
let ProjectionDecode::Complete { record, consumed } =
decode_projection_record(&bytes).unwrap()
else {
panic!("complete record expected");
};
assert_eq!(consumed, bytes.len());
assert_eq!(record.target().target().as_str(), "a/b");
assert_eq!(record.encode(), bytes);
}
#[test]
fn projection_distinguishes_incomplete_boundaries() {
let bytes = record_bytes();
for length in [0, 1, 11, 12, 26, 27, bytes.len() - 1] {
assert_eq!(
decode_projection_record(&bytes[..length]),
Ok(ProjectionDecode::Incomplete)
);
}
}
#[test]
fn projection_rejects_checksum_corruption() {
let mut bytes = record_bytes();
bytes[30] ^= 1;
assert_eq!(
decode_projection_record(&bytes),
Err(CodecError::ChecksumMismatch)
);
}
#[test]
fn complete_package_stays_below_context_ceiling() {
let files = [
include_str!("../Cargo.toml"),
include_str!("../Documentation.md"),
include_str!("lib.rs"),
];
let count = files
.iter()
.flat_map(|file| file.lines())
.filter(|line| !line.trim().is_empty())
.count();
assert!(count < 500, "complete nonblank line count: {count}");
}
}