use prikk_error::{PrikkError, Result};
use crate::canonical::{WireType, is_strictly_sorted};
use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum RefKind {
Branch = 1,
Tag = 2,
}
impl RefKind {
#[must_use]
pub const fn code(self) -> u16 {
self as u16
}
pub fn from_code(code: u32) -> Result<Self> {
match code {
1 => Ok(Self::Branch),
2 => Ok(Self::Tag),
other => Err(PrikkError::MalformedData(format!(
"unknown ref kind code: {other}"
))),
}
}
}
pub const REF_STATE_CLOSED_SCHEMA: u32 = 2;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefStatePayload {
pub ref_name: String,
pub kind: RefKind,
pub target_object_id: ObjectId,
pub update_seq: u64,
pub previous_ref_state_id: Option<ObjectId>,
pub required_attestation_ids: Vec<ObjectId>,
pub closed: bool,
}
impl RefStatePayload {
pub fn decode_canonical(bytes: &[u8], schema_version: u32) -> Result<Self> {
let mut cursor = CanonicalCursor::new(bytes);
let mut ref_name = None;
let mut kind = None;
let mut target_object_id = None;
let mut update_seq = None;
let mut previous_ref_state_id = None;
let mut required_attestation_ids = Vec::new();
let mut closed = false;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => ref_name = Some(field.read_string()?),
2 => target_object_id = Some(field.read_object_id()?),
3 => update_seq = Some(field.read_u64()?),
4 => previous_ref_state_id = Some(field.read_object_id()?),
5 => required_attestation_ids.push(field.read_object_id()?),
6 => kind = Some(RefKind::from_code(u32::from(field.read_enum_u16()?))?),
7 => {
if schema_version < REF_STATE_CLOSED_SCHEMA {
return Err(PrikkError::MalformedData(format!(
"RefState schema {schema_version} must not carry a closed field; \
requires schema {REF_STATE_CLOSED_SCHEMA}"
)));
}
let value = field.read_bool()?;
if !value {
return Err(PrikkError::MalformedData(
"RefState closed field must be absent when open, never encoded as \
false"
.to_string(),
));
}
closed = true;
}
other => {
return Err(PrikkError::MalformedData(format!(
"unknown RefState field tag: {other}"
)));
}
}
}
let payload = Self {
ref_name: ref_name.ok_or_else(|| {
PrikkError::MalformedData("RefState missing ref_name".to_string())
})?,
kind: kind
.ok_or_else(|| PrikkError::MalformedData("RefState missing kind".to_string()))?,
target_object_id: target_object_id.ok_or_else(|| {
PrikkError::MalformedData("RefState missing target_object_id".to_string())
})?,
update_seq: update_seq.ok_or_else(|| {
PrikkError::MalformedData("RefState missing update_seq".to_string())
})?,
previous_ref_state_id,
required_attestation_ids,
closed,
};
if !is_strictly_sorted(&payload.required_attestation_ids) {
return Err(PrikkError::MalformedData(
"RefState attestation IDs are not sorted and unique".to_string(),
));
}
Ok(payload)
}
}
impl CanonicalEncode for RefStatePayload {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
if !is_strictly_sorted(&self.required_attestation_ids) {
return Err(PrikkError::CanonicalEncoding(
"required_attestation_ids must be sorted and unique".to_string(),
));
}
writer.field_string(1, &self.ref_name)?;
writer.field_object_id(2, &self.target_object_id)?;
writer.field_u64(3, self.update_seq)?;
if let Some(previous) = self.previous_ref_state_id {
writer.field_object_id(4, &previous)?;
}
writer.repeated_object_id(5, &self.required_attestation_ids)?;
writer.field_enum_u16(6, self.kind.code())?;
if self.closed {
writer.field_bool(7, true)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RefUpdatePayload {
pub ref_name: String,
pub old_ref_state_id: Option<ObjectId>,
pub new_ref_state_id: ObjectId,
pub new_target_object_id: ObjectId,
pub update_seq: u64,
pub created_at: u64,
pub author_key_id: String,
}
impl CanonicalEncode for RefUpdatePayload {
fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
writer.field_string(1, &self.ref_name)?;
if let Some(old) = self.old_ref_state_id {
writer.field_object_id(2, &old)?;
}
writer.field_object_id(3, &self.new_ref_state_id)?;
writer.field_object_id(4, &self.new_target_object_id)?;
writer.field_u64(5, self.update_seq)?;
writer.field_u64(6, self.created_at)?;
writer.field_string(7, &self.author_key_id)?;
Ok(())
}
}
impl RefUpdatePayload {
pub fn decode_canonical(bytes: &[u8]) -> Result<Self> {
let mut cursor = CanonicalCursor::new(bytes);
let mut ref_name = None;
let mut old_ref_state_id = None;
let mut new_ref_state_id = None;
let mut new_target_object_id = None;
let mut update_seq = None;
let mut created_at = None;
let mut author_key_id = None;
while let Some(field) = cursor.next_field()? {
match field.tag {
1 => ref_name = Some(field.read_string()?),
2 => old_ref_state_id = Some(field.read_object_id()?),
3 => new_ref_state_id = Some(field.read_object_id()?),
4 => new_target_object_id = Some(field.read_object_id()?),
5 => update_seq = Some(field.read_u64()?),
6 => created_at = Some(field.read_u64()?),
7 => author_key_id = Some(field.read_string()?),
other => {
return Err(PrikkError::MalformedData(format!(
"unknown RefUpdate field tag: {other}"
)));
}
}
}
Ok(Self {
ref_name: ref_name.ok_or_else(|| {
PrikkError::MalformedData("RefUpdate missing ref_name".to_string())
})?,
old_ref_state_id,
new_ref_state_id: new_ref_state_id.ok_or_else(|| {
PrikkError::MalformedData("RefUpdate missing new_ref_state_id".to_string())
})?,
new_target_object_id: new_target_object_id.ok_or_else(|| {
PrikkError::MalformedData("RefUpdate missing new_target_object_id".to_string())
})?,
update_seq: update_seq.ok_or_else(|| {
PrikkError::MalformedData("RefUpdate missing update_seq".to_string())
})?,
created_at: created_at.ok_or_else(|| {
PrikkError::MalformedData("RefUpdate missing created_at".to_string())
})?,
author_key_id: author_key_id.ok_or_else(|| {
PrikkError::MalformedData("RefUpdate missing author_key_id".to_string())
})?,
})
}
}
struct CanonicalCursor<'a> {
bytes: &'a [u8],
pos: usize,
last_tag: Option<u16>,
}
impl<'a> CanonicalCursor<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
pos: 0,
last_tag: None,
}
}
fn next_field(&mut self) -> Result<Option<CanonicalField<'a>>> {
if self.pos == self.bytes.len() {
return Ok(None);
}
let tag = u16::from_be_bytes(self.read_array::<2>()?);
if tag == 0 {
return Err(PrikkError::MalformedData(
"field tag 0 is reserved".to_string(),
));
}
if let Some(last) = self.last_tag {
if tag < last {
return Err(PrikkError::MalformedData(format!(
"field tag order violation: {tag} after {last}"
)));
}
}
self.last_tag = Some(tag);
let wire_type = self.read_u8()?;
let len = usize::try_from(u64::from_be_bytes(self.read_array::<8>()?)).map_err(|_| {
PrikkError::MalformedData("canonical field length does not fit usize".to_string())
})?;
let value = self.read_exact(len)?;
Ok(Some(CanonicalField {
tag,
wire_type,
value,
}))
}
fn read_u8(&mut self) -> Result<u8> {
let value = self.read_exact(1)?;
let Some(byte) = value.first() else {
return Err(PrikkError::MalformedData(
"unexpected empty byte".to_string(),
));
};
Ok(*byte)
}
fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
let bytes = self.read_exact(N)?;
let mut out = [0_u8; N];
out.copy_from_slice(bytes);
Ok(out)
}
fn read_exact(&mut self, len: usize) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(len)
.ok_or_else(|| PrikkError::MalformedData("canonical range overflow".to_string()))?;
let Some(slice) = self.bytes.get(self.pos..end) else {
return Err(PrikkError::MalformedData(
"unexpected end of canonical payload".to_string(),
));
};
self.pos = end;
Ok(slice)
}
}
struct CanonicalField<'a> {
tag: u16,
wire_type: u8,
value: &'a [u8],
}
impl<'a> CanonicalField<'a> {
fn read_string(&self) -> Result<String> {
self.require_wire(WireType::String)?;
String::from_utf8(self.value.to_vec())
.map_err(|err| PrikkError::MalformedData(format!("invalid UTF-8 string: {err}")))
}
fn read_u64(&self) -> Result<u64> {
self.require_wire(WireType::U64)?;
Ok(u64::from_be_bytes(self.read_array::<8>()?))
}
fn read_object_id(&self) -> Result<ObjectId> {
self.require_wire(WireType::ObjectId)?;
Ok(ObjectId::from_bytes(self.read_array::<32>()?))
}
fn read_enum_u16(&self) -> Result<u16> {
self.require_wire(WireType::EnumU16)?;
Ok(u16::from_be_bytes(self.read_array::<2>()?))
}
fn read_bool(&self) -> Result<bool> {
self.require_wire(WireType::Bool)?;
match self.read_array::<1>()?[0] {
0 => Ok(false),
1 => Ok(true),
other => Err(PrikkError::MalformedData(format!(
"field {} has invalid bool byte: {other}",
self.tag
))),
}
}
fn require_wire(&self, expected: WireType) -> Result<()> {
if self.wire_type == expected as u8 {
return Ok(());
}
Err(PrikkError::MalformedData(format!(
"field {} has wrong wire type: expected {}, got {}",
self.tag, expected as u8, self.wire_type
)))
}
fn read_array<const N: usize>(&self) -> Result<[u8; N]> {
if self.value.len() != N {
return Err(PrikkError::MalformedData(format!(
"field {} expected {N} bytes, got {}",
self.tag,
self.value.len()
)));
}
let mut out = [0_u8; N];
out.copy_from_slice(self.value);
Ok(out)
}
}