prikk_object/payload/
block.rs1use prikk_error::{PrikkError, Result};
4
5use crate::canonical::is_strictly_sorted;
6use crate::payload::common::MerkleRoot;
7use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(u16)]
12pub enum BlockKind {
13 Root = 1,
15 Normal = 2,
17 Merge = 3,
19 Repair = 4,
21 Import = 5,
23}
24
25impl BlockKind {
26 #[must_use]
28 pub const fn code(self) -> u16 {
29 self as u16
30 }
31
32 pub fn from_code(code: u32) -> Result<Self> {
34 match code {
35 1 => Ok(Self::Root),
36 2 => Ok(Self::Normal),
37 3 => Ok(Self::Merge),
38 4 => Ok(Self::Repair),
39 5 => Ok(Self::Import),
40 other => Err(PrikkError::MalformedData(format!(
41 "unknown block kind code: {other}"
42 ))),
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct BlockPayload {
50 pub parent_block_ids: Vec<ObjectId>,
52 pub kind: BlockKind,
54 pub patch_ids: Vec<ObjectId>,
56 pub state_merkle_root: MerkleRoot,
58 pub snapshot_blob_ref: Option<ObjectId>,
60}
61
62impl BlockPayload {
63 pub fn decode_canonical(bytes: &[u8]) -> Result<Self> {
65 let mut cursor = BlockCanonicalCursor::new(bytes);
66 let mut parent_block_ids = Vec::new();
67 let mut kind = None;
68 let mut patch_ids = Vec::new();
69 let mut state_merkle_root = None;
70 let mut snapshot_blob_ref = None;
71 while let Some(field) = cursor.next_field()? {
72 match field.tag {
73 1 => parent_block_ids.push(field.read_object_id()?),
74 2 => kind = Some(BlockKind::from_code(u32::from(field.read_enum_u16()?))?),
75 3 => patch_ids.push(field.read_object_id()?),
76 4 => state_merkle_root = Some(MerkleRoot(field.read_array::<32>()?)),
77 5 => snapshot_blob_ref = Some(field.read_object_id()?),
78 other => {
79 return Err(PrikkError::MalformedData(format!(
80 "unknown Block field tag: {other}"
81 )));
82 }
83 }
84 }
85 let payload = Self {
86 parent_block_ids,
87 kind: kind
88 .ok_or_else(|| PrikkError::MalformedData("Block missing kind".to_string()))?,
89 patch_ids,
90 state_merkle_root: state_merkle_root.ok_or_else(|| {
91 PrikkError::MalformedData("Block missing state_merkle_root".to_string())
92 })?,
93 snapshot_blob_ref,
94 };
95 if !is_strictly_sorted(&payload.parent_block_ids) {
96 return Err(PrikkError::MalformedData(
97 "Block parent IDs are not sorted and unique".to_string(),
98 ));
99 }
100 Ok(payload)
101 }
102}
103
104struct BlockCanonicalCursor<'a> {
105 bytes: &'a [u8],
106 pos: usize,
107 last_tag: Option<u16>,
108}
109
110impl<'a> BlockCanonicalCursor<'a> {
111 const fn new(bytes: &'a [u8]) -> Self {
112 Self {
113 bytes,
114 pos: 0,
115 last_tag: None,
116 }
117 }
118
119 fn next_field(&mut self) -> Result<Option<BlockCanonicalField<'a>>> {
120 if self.pos == self.bytes.len() {
121 return Ok(None);
122 }
123 let tag = u16::from_be_bytes(self.read_array::<2>()?);
124 if tag == 0 {
125 return Err(PrikkError::MalformedData(
126 "field tag 0 is reserved".to_string(),
127 ));
128 }
129 if let Some(last) = self.last_tag {
130 if tag < last {
131 return Err(PrikkError::MalformedData(format!(
132 "field tag order violation: {tag} after {last}"
133 )));
134 }
135 }
136 self.last_tag = Some(tag);
137 let wire_type = self.read_u8()?;
138 let len = usize::try_from(u64::from_be_bytes(self.read_array::<8>()?)).map_err(|_| {
139 PrikkError::MalformedData("canonical field length does not fit usize".to_string())
140 })?;
141 let value = self.read_exact(len)?;
142 Ok(Some(BlockCanonicalField {
143 tag,
144 wire_type,
145 value,
146 }))
147 }
148
149 fn read_u8(&mut self) -> Result<u8> {
150 let value = self.read_exact(1)?;
151 let Some(byte) = value.first() else {
152 return Err(PrikkError::MalformedData(
153 "unexpected empty byte".to_string(),
154 ));
155 };
156 Ok(*byte)
157 }
158
159 fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
160 let bytes = self.read_exact(N)?;
161 let mut out = [0_u8; N];
162 out.copy_from_slice(bytes);
163 Ok(out)
164 }
165
166 fn read_exact(&mut self, len: usize) -> Result<&'a [u8]> {
167 let end = self
168 .pos
169 .checked_add(len)
170 .ok_or_else(|| PrikkError::MalformedData("canonical range overflow".to_string()))?;
171 let Some(slice) = self.bytes.get(self.pos..end) else {
172 return Err(PrikkError::MalformedData(
173 "unexpected end of canonical payload".to_string(),
174 ));
175 };
176 self.pos = end;
177 Ok(slice)
178 }
179}
180
181struct BlockCanonicalField<'a> {
182 tag: u16,
183 wire_type: u8,
184 value: &'a [u8],
185}
186
187impl<'a> BlockCanonicalField<'a> {
188 fn read_object_id(&self) -> Result<ObjectId> {
189 self.require_wire(crate::canonical::WireType::ObjectId)?;
190 Ok(ObjectId::from_bytes(self.read_array::<32>()?))
191 }
192
193 fn read_enum_u16(&self) -> Result<u16> {
194 self.require_wire(crate::canonical::WireType::EnumU16)?;
195 Ok(u16::from_be_bytes(self.read_array::<2>()?))
196 }
197
198 fn require_wire(&self, expected: crate::canonical::WireType) -> Result<()> {
199 if self.wire_type == expected as u8 {
200 return Ok(());
201 }
202 Err(PrikkError::MalformedData(format!(
203 "field {} has wrong wire type: expected {}, got {}",
204 self.tag, expected as u8, self.wire_type
205 )))
206 }
207
208 fn read_array<const N: usize>(&self) -> Result<[u8; N]> {
209 if self.value.len() != N {
210 return Err(PrikkError::MalformedData(format!(
211 "field {} expected {N} bytes, got {}",
212 self.tag,
213 self.value.len()
214 )));
215 }
216 let mut out = [0_u8; N];
217 out.copy_from_slice(self.value);
218 Ok(out)
219 }
220}
221
222impl CanonicalEncode for BlockPayload {
223 fn encode_canonical(&self, writer: &mut CanonicalWriter) -> Result<()> {
224 if !is_strictly_sorted(&self.parent_block_ids) {
225 return Err(PrikkError::CanonicalEncoding(
226 "parent_block_ids must be sorted and unique".to_string(),
227 ));
228 }
229 writer.repeated_object_id(1, &self.parent_block_ids)?;
230 writer.field_enum_u16(2, self.kind.code())?;
231 writer.repeated_object_id(3, &self.patch_ids)?;
232 writer.field_bytes(4, &self.state_merkle_root.0)?;
233 if let Some(snapshot) = self.snapshot_blob_ref {
234 writer.field_object_id(5, &snapshot)?;
235 }
236 Ok(())
237 }
238}