1use prikk_error::{PrikkError, Result};
4
5use crate::canonical::{WireType, is_strictly_sorted};
6use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[repr(u16)]
11pub enum RefKind {
12 Branch = 1,
14 Tag = 2,
16}
17
18impl RefKind {
19 #[must_use]
21 pub const fn code(self) -> u16 {
22 self as u16
23 }
24
25 pub fn from_code(code: u32) -> Result<Self> {
27 match code {
28 1 => Ok(Self::Branch),
29 2 => Ok(Self::Tag),
30 other => Err(PrikkError::MalformedData(format!(
31 "unknown ref kind code: {other}"
32 ))),
33 }
34 }
35}
36
37pub const REF_STATE_CLOSED_SCHEMA: u32 = 2;
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct RefStatePayload {
44 pub ref_name: String,
46 pub kind: RefKind,
48 pub target_object_id: ObjectId,
50 pub update_seq: u64,
52 pub previous_ref_state_id: Option<ObjectId>,
54 pub required_attestation_ids: Vec<ObjectId>,
56 pub closed: bool,
63}
64
65impl RefStatePayload {
66 pub fn decode_canonical(bytes: &[u8], schema_version: u32) -> Result<Self> {
70 let mut cursor = CanonicalCursor::new(bytes);
71 let mut ref_name = None;
72 let mut kind = None;
73 let mut target_object_id = None;
74 let mut update_seq = None;
75 let mut previous_ref_state_id = None;
76 let mut required_attestation_ids = Vec::new();
77 let mut closed = false;
78 while let Some(field) = cursor.next_field()? {
79 match field.tag {
80 1 => ref_name = Some(field.read_string()?),
81 2 => target_object_id = Some(field.read_object_id()?),
82 3 => update_seq = Some(field.read_u64()?),
83 4 => previous_ref_state_id = Some(field.read_object_id()?),
84 5 => required_attestation_ids.push(field.read_object_id()?),
85 6 => kind = Some(RefKind::from_code(u32::from(field.read_enum_u16()?))?),
86 7 => {
87 if schema_version < REF_STATE_CLOSED_SCHEMA {
88 return Err(PrikkError::MalformedData(format!(
89 "RefState schema {schema_version} must not carry a closed field; \
90 requires schema {REF_STATE_CLOSED_SCHEMA}"
91 )));
92 }
93 let value = field.read_bool()?;
94 if !value {
95 return Err(PrikkError::MalformedData(
96 "RefState closed field must be absent when open, never encoded as \
97 false"
98 .to_string(),
99 ));
100 }
101 closed = true;
102 }
103 other => {
104 return Err(PrikkError::MalformedData(format!(
105 "unknown RefState field tag: {other}"
106 )));
107 }
108 }
109 }
110 let payload = Self {
111 ref_name: ref_name.ok_or_else(|| {
112 PrikkError::MalformedData("RefState missing ref_name".to_string())
113 })?,
114 kind: kind
115 .ok_or_else(|| PrikkError::MalformedData("RefState missing kind".to_string()))?,
116 target_object_id: target_object_id.ok_or_else(|| {
117 PrikkError::MalformedData("RefState missing target_object_id".to_string())
118 })?,
119 update_seq: update_seq.ok_or_else(|| {
120 PrikkError::MalformedData("RefState missing update_seq".to_string())
121 })?,
122 previous_ref_state_id,
123 required_attestation_ids,
124 closed,
125 };
126 if !is_strictly_sorted(&payload.required_attestation_ids) {
127 return Err(PrikkError::MalformedData(
128 "RefState attestation IDs are not sorted and unique".to_string(),
129 ));
130 }
131 Ok(payload)
132 }
133}
134
135impl CanonicalEncode for RefStatePayload {
136 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
137 if !is_strictly_sorted(&self.required_attestation_ids) {
138 return Err(PrikkError::CanonicalEncoding(
139 "required_attestation_ids must be sorted and unique".to_string(),
140 ));
141 }
142 writer.field_string(1, &self.ref_name)?;
143 writer.field_object_id(2, &self.target_object_id)?;
144 writer.field_u64(3, self.update_seq)?;
145 if let Some(previous) = self.previous_ref_state_id {
146 writer.field_object_id(4, &previous)?;
147 }
148 writer.repeated_object_id(5, &self.required_attestation_ids)?;
149 writer.field_enum_u16(6, self.kind.code())?;
150 if self.closed {
151 writer.field_bool(7, true)?;
152 }
153 Ok(())
154 }
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct RefUpdatePayload {
160 pub ref_name: String,
162 pub old_ref_state_id: Option<ObjectId>,
164 pub new_ref_state_id: ObjectId,
166 pub new_target_object_id: ObjectId,
168 pub update_seq: u64,
170 pub created_at: u64,
173 pub author_key_id: String,
175}
176
177impl CanonicalEncode for RefUpdatePayload {
178 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
179 writer.field_string(1, &self.ref_name)?;
180 if let Some(old) = self.old_ref_state_id {
181 writer.field_object_id(2, &old)?;
182 }
183 writer.field_object_id(3, &self.new_ref_state_id)?;
184 writer.field_object_id(4, &self.new_target_object_id)?;
185 writer.field_u64(5, self.update_seq)?;
186 writer.field_u64(6, self.created_at)?;
187 writer.field_string(7, &self.author_key_id)?;
188 Ok(())
189 }
190}
191
192impl RefUpdatePayload {
193 pub fn decode_canonical(bytes: &[u8]) -> Result<Self> {
195 let mut cursor = CanonicalCursor::new(bytes);
196 let mut ref_name = None;
197 let mut old_ref_state_id = None;
198 let mut new_ref_state_id = None;
199 let mut new_target_object_id = None;
200 let mut update_seq = None;
201 let mut created_at = None;
202 let mut author_key_id = None;
203 while let Some(field) = cursor.next_field()? {
204 match field.tag {
205 1 => ref_name = Some(field.read_string()?),
206 2 => old_ref_state_id = Some(field.read_object_id()?),
207 3 => new_ref_state_id = Some(field.read_object_id()?),
208 4 => new_target_object_id = Some(field.read_object_id()?),
209 5 => update_seq = Some(field.read_u64()?),
210 6 => created_at = Some(field.read_u64()?),
211 7 => author_key_id = Some(field.read_string()?),
212 other => {
213 return Err(PrikkError::MalformedData(format!(
214 "unknown RefUpdate field tag: {other}"
215 )));
216 }
217 }
218 }
219 Ok(Self {
220 ref_name: ref_name.ok_or_else(|| {
221 PrikkError::MalformedData("RefUpdate missing ref_name".to_string())
222 })?,
223 old_ref_state_id,
224 new_ref_state_id: new_ref_state_id.ok_or_else(|| {
225 PrikkError::MalformedData("RefUpdate missing new_ref_state_id".to_string())
226 })?,
227 new_target_object_id: new_target_object_id.ok_or_else(|| {
228 PrikkError::MalformedData("RefUpdate missing new_target_object_id".to_string())
229 })?,
230 update_seq: update_seq.ok_or_else(|| {
231 PrikkError::MalformedData("RefUpdate missing update_seq".to_string())
232 })?,
233 created_at: created_at.ok_or_else(|| {
234 PrikkError::MalformedData("RefUpdate missing created_at".to_string())
235 })?,
236 author_key_id: author_key_id.ok_or_else(|| {
237 PrikkError::MalformedData("RefUpdate missing author_key_id".to_string())
238 })?,
239 })
240 }
241}
242
243struct CanonicalCursor<'a> {
244 bytes: &'a [u8],
245 pos: usize,
246 last_tag: Option<u16>,
247}
248
249impl<'a> CanonicalCursor<'a> {
250 const fn new(bytes: &'a [u8]) -> Self {
251 Self {
252 bytes,
253 pos: 0,
254 last_tag: None,
255 }
256 }
257
258 fn next_field(&mut self) -> Result<Option<CanonicalField<'a>>> {
259 if self.pos == self.bytes.len() {
260 return Ok(None);
261 }
262 let tag = u16::from_be_bytes(self.read_array::<2>()?);
263 if tag == 0 {
264 return Err(PrikkError::MalformedData(
265 "field tag 0 is reserved".to_string(),
266 ));
267 }
268 if let Some(last) = self.last_tag {
269 if tag < last {
270 return Err(PrikkError::MalformedData(format!(
271 "field tag order violation: {tag} after {last}"
272 )));
273 }
274 }
275 self.last_tag = Some(tag);
276 let wire_type = self.read_u8()?;
277 let len = usize::try_from(u64::from_be_bytes(self.read_array::<8>()?)).map_err(|_| {
278 PrikkError::MalformedData("canonical field length does not fit usize".to_string())
279 })?;
280 let value = self.read_exact(len)?;
281 Ok(Some(CanonicalField {
282 tag,
283 wire_type,
284 value,
285 }))
286 }
287
288 fn read_u8(&mut self) -> Result<u8> {
289 let value = self.read_exact(1)?;
290 let Some(byte) = value.first() else {
291 return Err(PrikkError::MalformedData(
292 "unexpected empty byte".to_string(),
293 ));
294 };
295 Ok(*byte)
296 }
297
298 fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
299 let bytes = self.read_exact(N)?;
300 let mut out = [0_u8; N];
301 out.copy_from_slice(bytes);
302 Ok(out)
303 }
304
305 fn read_exact(&mut self, len: usize) -> Result<&'a [u8]> {
306 let end = self
307 .pos
308 .checked_add(len)
309 .ok_or_else(|| PrikkError::MalformedData("canonical range overflow".to_string()))?;
310 let Some(slice) = self.bytes.get(self.pos..end) else {
311 return Err(PrikkError::MalformedData(
312 "unexpected end of canonical payload".to_string(),
313 ));
314 };
315 self.pos = end;
316 Ok(slice)
317 }
318}
319
320struct CanonicalField<'a> {
321 tag: u16,
322 wire_type: u8,
323 value: &'a [u8],
324}
325
326impl<'a> CanonicalField<'a> {
327 fn read_string(&self) -> Result<String> {
328 self.require_wire(WireType::String)?;
329 String::from_utf8(self.value.to_vec())
330 .map_err(|err| PrikkError::MalformedData(format!("invalid UTF-8 string: {err}")))
331 }
332
333 fn read_u64(&self) -> Result<u64> {
334 self.require_wire(WireType::U64)?;
335 Ok(u64::from_be_bytes(self.read_array::<8>()?))
336 }
337
338 fn read_object_id(&self) -> Result<ObjectId> {
339 self.require_wire(WireType::ObjectId)?;
340 Ok(ObjectId::from_bytes(self.read_array::<32>()?))
341 }
342
343 fn read_enum_u16(&self) -> Result<u16> {
344 self.require_wire(WireType::EnumU16)?;
345 Ok(u16::from_be_bytes(self.read_array::<2>()?))
346 }
347
348 fn read_bool(&self) -> Result<bool> {
349 self.require_wire(WireType::Bool)?;
350 match self.read_array::<1>()?[0] {
351 0 => Ok(false),
352 1 => Ok(true),
353 other => Err(PrikkError::MalformedData(format!(
354 "field {} has invalid bool byte: {other}",
355 self.tag
356 ))),
357 }
358 }
359
360 fn require_wire(&self, expected: WireType) -> Result<()> {
361 if self.wire_type == expected as u8 {
362 return Ok(());
363 }
364 Err(PrikkError::MalformedData(format!(
365 "field {} has wrong wire type: expected {}, got {}",
366 self.tag, expected as u8, self.wire_type
367 )))
368 }
369
370 fn read_array<const N: usize>(&self) -> Result<[u8; N]> {
371 if self.value.len() != N {
372 return Err(PrikkError::MalformedData(format!(
373 "field {} expected {N} bytes, got {}",
374 self.tag,
375 self.value.len()
376 )));
377 }
378 let mut out = [0_u8; N];
379 out.copy_from_slice(self.value);
380 Ok(out)
381 }
382}