phoxal_runtime_contract/
identity.rs1use std::fmt;
21use std::num::NonZeroU64;
22
23use serde::{Deserialize, Deserializer, Serialize};
24
25const OPAQUE_BYTES: usize = 16;
27
28#[derive(Clone, Copy, PartialEq, Eq, Hash)]
38pub struct ExecutionId([u8; OPAQUE_BYTES]);
39
40impl ExecutionId {
41 pub const LEN: usize = OPAQUE_BYTES * 2;
43
44 pub fn mint() -> Self {
46 ExecutionId(random_bytes())
47 }
48
49 pub fn parse(value: &str) -> Result<Self, InvalidIdentity> {
52 parse_hex(value).map(ExecutionId)
53 }
54
55 pub fn as_key_segment(&self) -> String {
57 format!("x{self}")
61 }
62}
63
64impl fmt::Display for ExecutionId {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write_hex(&self.0, formatter)
67 }
68}
69
70impl fmt::Debug for ExecutionId {
71 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(formatter, "ExecutionId({self})")
73 }
74}
75
76impl Serialize for ExecutionId {
77 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
78 serializer.serialize_str(&self.to_string())
79 }
80}
81
82impl<'de> Deserialize<'de> for ExecutionId {
83 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
84 let value = String::deserialize(deserializer)?;
85 ExecutionId::parse(&value).map_err(serde::de::Error::custom)
86 }
87}
88
89#[derive(Clone, Copy, PartialEq, Eq, Hash)]
97pub struct ProducerId([u8; OPAQUE_BYTES]);
98
99impl ProducerId {
100 pub fn mint() -> Self {
102 ProducerId(random_bytes())
103 }
104
105 pub fn parse(value: &str) -> Result<Self, InvalidIdentity> {
107 parse_hex(value).map(ProducerId)
108 }
109}
110
111impl fmt::Display for ProducerId {
112 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write_hex(&self.0, formatter)
114 }
115}
116
117impl fmt::Debug for ProducerId {
118 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(formatter, "ProducerId({self})")
120 }
121}
122
123impl Serialize for ProducerId {
124 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
125 serializer.serialize_bytes(&self.0)
126 }
127}
128
129impl<'de> Deserialize<'de> for ProducerId {
130 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
131 let bytes = serde_bytes::ByteBuf::deserialize(deserializer)?;
132 <[u8; OPAQUE_BYTES]>::try_from(bytes.as_ref())
133 .map(ProducerId)
134 .map_err(|_| {
135 serde::de::Error::custom(format!(
136 "producer id must be {OPAQUE_BYTES} bytes, got {}",
137 bytes.len()
138 ))
139 })
140 }
141}
142
143#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
150#[serde(transparent)]
151pub struct TimelineId(NonZeroU64);
152
153impl TimelineId {
154 pub fn mint() -> Self {
156 let mut bytes = [0_u8; 8];
157 getrandom::fill(&mut bytes).expect("the host must provide randomness");
158 TimelineId(NonZeroU64::new(u64::from_le_bytes(bytes)).unwrap_or(NonZeroU64::MIN))
161 }
162
163 pub const fn from_raw(value: u64) -> Option<Self> {
165 match NonZeroU64::new(value) {
166 Some(value) => Some(TimelineId(value)),
167 None => None,
168 }
169 }
170
171 pub const fn get(self) -> u64 {
173 self.0.get()
174 }
175}
176
177impl fmt::Display for TimelineId {
178 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
179 write!(formatter, "t{:016x}", self.0.get())
180 }
181}
182
183impl fmt::Debug for TimelineId {
184 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(formatter, "TimelineId({self})")
186 }
187}
188
189#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
191#[error("{0}")]
192pub struct InvalidIdentity(String);
193
194fn random_bytes() -> [u8; OPAQUE_BYTES] {
195 let mut bytes = [0_u8; OPAQUE_BYTES];
196 getrandom::fill(&mut bytes).expect("the host must provide randomness");
197 bytes
198}
199
200fn write_hex(bytes: &[u8; OPAQUE_BYTES], formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
201 for byte in bytes {
202 write!(formatter, "{byte:02x}")?;
203 }
204 Ok(())
205}
206
207fn parse_hex(value: &str) -> Result<[u8; OPAQUE_BYTES], InvalidIdentity> {
208 let value = value.strip_prefix('x').unwrap_or(value);
209 if value.len() != OPAQUE_BYTES * 2 {
210 return Err(InvalidIdentity(format!(
211 "expected {} hex characters, got {}",
212 OPAQUE_BYTES * 2,
213 value.len()
214 )));
215 }
216 let mut bytes = [0_u8; OPAQUE_BYTES];
217 for (index, byte) in bytes.iter_mut().enumerate() {
218 let pair = &value[index * 2..index * 2 + 2];
219 *byte = u8::from_str_radix(pair, 16)
220 .map_err(|_| InvalidIdentity(format!("'{pair}' is not a hex byte")))?;
221 }
222 Ok(bytes)
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn execution_ids_are_opaque_unique_and_key_safe() {
231 let first = ExecutionId::mint();
232 let second = ExecutionId::mint();
233 assert_ne!(first, second);
234
235 let segment = first.as_key_segment();
236 assert_eq!(segment.len(), ExecutionId::LEN + 1);
237 assert!(segment.starts_with('x'));
238 assert!(!segment.contains('/') && !segment.contains('*'));
239 assert_eq!(ExecutionId::parse(&segment), Ok(first));
240 assert_eq!(ExecutionId::parse(&first.to_string()), Ok(first));
241 }
242
243 #[test]
244 fn a_malformed_execution_id_is_rejected_rather_than_truncated() {
245 assert!(ExecutionId::parse("").is_err());
246 assert!(ExecutionId::parse("xdeadbeef").is_err());
247 assert!(ExecutionId::parse(&"z".repeat(ExecutionId::LEN)).is_err());
248 }
249
250 #[test]
251 fn producer_ids_round_trip_through_the_wire_encoding() {
252 let producer = ProducerId::mint();
253 let encoded = rmp_serde::to_vec_named(&producer).unwrap();
254 let decoded: ProducerId = rmp_serde::from_slice(&encoded).unwrap();
255 assert_eq!(decoded, producer);
256 assert_ne!(producer, ProducerId::mint());
257 }
258
259 #[test]
260 fn timelines_have_no_zero_value_and_no_generation_order() {
261 assert_eq!(TimelineId::from_raw(0), None);
262 let timeline = TimelineId::mint();
263 assert_eq!(TimelineId::from_raw(timeline.get()), Some(timeline));
264 assert_ne!(timeline, TimelineId::mint());
268 }
269}