Skip to main content

prikk_object/payload/
block.rs

1//! Block payload types.
2
3use prikk_error::{PrikkError, Result};
4
5use crate::canonical::is_strictly_sorted;
6use crate::payload::common::MerkleRoot;
7use crate::{CanonicalEncode, CanonicalWriter, ObjectId};
8
9/// Block kind.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[repr(u16)]
12pub enum BlockKind {
13    /// Root block.
14    Root = 1,
15    /// Normal block.
16    Normal = 2,
17    /// Merge block.
18    Merge = 3,
19    /// Repair block.
20    Repair = 4,
21    /// Import block.
22    Import = 5,
23}
24
25impl BlockKind {
26    /// Stable code.
27    #[must_use]
28    pub const fn code(self) -> u16 {
29        self as u16
30    }
31
32    /// Parse a stable block-kind code.
33    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/// Block payload. Block summaries are intentionally not identity-bearing.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct BlockPayload {
50    /// Parent block IDs, sorted unless a later design adds semantic parent roles.
51    pub parent_block_ids: Vec<ObjectId>,
52    /// Block kind.
53    pub kind: BlockKind,
54    /// Patch IDs in canonical block patch order.
55    pub patch_ids: Vec<ObjectId>,
56    /// State Merkle root.
57    pub state_merkle_root: MerkleRoot,
58    /// Optional full snapshot blob reference.
59    pub snapshot_blob_ref: Option<ObjectId>,
60}
61
62impl BlockPayload {
63    /// Decode a block payload from Prikk canonical TLV bytes.
64    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}