1use core::fmt;
4use core::str::FromStr;
5
6use prikk_error::{PrikkError, Result};
7use prikk_hash::{sha256, to_hex};
8
9pub const OBJECT_ID_DOMAIN: &[u8] = b"PRIKK-OBJECT-ID-v1";
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[repr(u16)]
15pub enum ObjectType {
16 Patch = 0x01,
18 Block = 0x02,
20 RefState = 0x03,
22 RefUpdate = 0x04,
25 Tag = 0x05,
27 Attestation = 0x06,
29 Blob = 0x07,
31 BlockSummaryCache = 0x08,
34 RecoveryNote = 0x09,
37 ProjectGenesis = 0x0A,
39 RecognitionClaim = 0x0B,
43}
44
45impl ObjectType {
46 #[must_use]
48 pub const fn code(self) -> u16 {
49 self as u16
50 }
51
52 pub fn from_code(code: u16) -> Result<Self> {
54 match code {
55 0x01 => Ok(Self::Patch),
56 0x02 => Ok(Self::Block),
57 0x03 => Ok(Self::RefState),
58 0x04 => Ok(Self::RefUpdate),
59 0x05 => Ok(Self::Tag),
60 0x06 => Ok(Self::Attestation),
61 0x07 => Ok(Self::Blob),
62 0x08 => Ok(Self::BlockSummaryCache),
63 0x09 => Ok(Self::RecoveryNote),
64 0x0A => Ok(Self::ProjectGenesis),
65 0x0B => Ok(Self::RecognitionClaim),
66 other => Err(PrikkError::MalformedData(format!(
67 "unknown object type code: {other}"
68 ))),
69 }
70 }
71
72 #[must_use]
74 pub const fn name(self) -> &'static str {
75 match self {
76 Self::Patch => "patch",
77 Self::Block => "block",
78 Self::RefState => "ref-state",
79 Self::RefUpdate => "ref-update",
80 Self::Tag => "tag",
81 Self::Attestation => "attestation",
82 Self::Blob => "blob",
83 Self::BlockSummaryCache => "block-summary-cache",
84 Self::RecoveryNote => "recovery-note",
85 Self::ProjectGenesis => "project-genesis",
86 Self::RecognitionClaim => "recognition-claim",
87 }
88 }
89}
90
91impl fmt::Display for ObjectType {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.write_str(self.name())
94 }
95}
96
97#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
99pub struct ObjectId([u8; 32]);
100
101impl ObjectId {
102 #[must_use]
104 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
105 Self(bytes)
106 }
107
108 #[must_use]
110 pub const fn as_bytes(&self) -> &[u8; 32] {
111 &self.0
112 }
113
114 #[must_use]
116 pub fn from_canonical_payload(
117 object_type: ObjectType,
118 schema_version: u32,
119 canonical_payload: &[u8],
120 ) -> Self {
121 let mut preimage =
122 Vec::with_capacity(OBJECT_ID_DOMAIN.len() + 2 + 4 + 8 + canonical_payload.len());
123 preimage.extend_from_slice(OBJECT_ID_DOMAIN);
124 preimage.extend_from_slice(&object_type.code().to_be_bytes());
125 preimage.extend_from_slice(&schema_version.to_be_bytes());
126 preimage.extend_from_slice(&(canonical_payload.len() as u64).to_be_bytes());
127 preimage.extend_from_slice(canonical_payload);
128 Self(sha256(&preimage))
129 }
130
131 #[must_use]
133 pub fn to_hex(&self) -> String {
134 to_hex(&self.0)
135 }
136}
137
138impl fmt::Debug for ObjectId {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 write!(f, "ObjectId({})", self.to_hex())
141 }
142}
143
144impl fmt::Display for ObjectId {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 f.write_str(&self.to_hex())
147 }
148}
149
150impl FromStr for ObjectId {
151 type Err = PrikkError;
152
153 fn from_str(s: &str) -> Result<Self> {
154 if s.len() != 64 {
155 return Err(PrikkError::InvalidObjectId(format!(
156 "expected 64 lowercase hex chars, got {}",
157 s.len()
158 )));
159 }
160 let mut out = [0_u8; 32];
161 for (slot, pair) in out.iter_mut().zip(s.as_bytes().chunks_exact(2)) {
162 let mut bytes = pair.iter().copied();
163 let high = bytes.next().ok_or_else(|| {
164 PrikkError::InvalidObjectId("hex pair is unexpectedly short".to_string())
165 })?;
166 let low = bytes.next().ok_or_else(|| {
167 PrikkError::InvalidObjectId("hex pair is unexpectedly short".to_string())
168 })?;
169 *slot = (hex_value(high)? << 4) | hex_value(low)?;
170 }
171 Ok(Self(out))
172 }
173}
174
175fn hex_value(byte: u8) -> Result<u8> {
176 match byte {
177 b'0'..=b'9' => Ok(byte - b'0'),
178 b'a'..=b'f' => Ok(byte - b'a' + 10),
179 _ => Err(PrikkError::InvalidObjectId(
180 "object IDs must use lowercase hex only".to_string(),
181 )),
182 }
183}
184
185#[cfg(test)]
186mod tests;