1use anyhow::{Result, anyhow};
5use multibase::Base;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::str::FromStr;
9
10macro_rules! define_id_newtype {
19 ($(#[$meta:meta])* $name:ident, $label:literal) => {
20 $(#[$meta])*
21 #[derive(Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
22 pub struct $name(u64);
23
24 impl $name {
25 pub fn new(id: u64) -> Self {
27 Self(id)
28 }
29
30 pub fn as_u64(&self) -> u64 {
32 self.0
33 }
34
35 pub const INVALID: $name = $name(u64::MAX);
37
38 pub fn is_invalid(&self) -> bool {
40 self.0 == u64::MAX
41 }
42
43 pub const EPHEMERAL_BIT: u64 = 1u64 << 63;
47
48 pub fn ephemeral(transient_id: u64) -> Self {
51 if transient_id >= Self::EPHEMERAL_BIT {
52 return Self::INVALID;
53 }
54 Self(Self::EPHEMERAL_BIT | transient_id)
55 }
56
57 pub fn is_ephemeral(&self) -> bool {
62 self.0 & Self::EPHEMERAL_BIT != 0 && !self.is_invalid()
63 }
64
65 pub fn transient_id(&self) -> Option<u64> {
67 self.is_ephemeral().then_some(self.0 & !Self::EPHEMERAL_BIT)
68 }
69 }
70
71 impl From<u64> for $name {
72 fn from(val: u64) -> Self {
73 Self(val)
74 }
75 }
76
77 impl From<$name> for u64 {
78 fn from(id: $name) -> Self {
79 id.0
80 }
81 }
82
83 impl Default for $name {
84 fn default() -> Self {
85 Self::INVALID
86 }
87 }
88
89 impl fmt::Debug for $name {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 if self.is_invalid() {
92 write!(f, concat!($label, "(INVALID)"))
93 } else {
94 write!(f, concat!($label, "({})"), self.0)
95 }
96 }
97 }
98
99 impl fmt::Display for $name {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 write!(f, "{}", self.0)
102 }
103 }
104
105 impl FromStr for $name {
106 type Err = anyhow::Error;
107
108 fn from_str(s: &str) -> Result<Self> {
109 let id: u64 = s
110 .parse()
111 .map_err(|e| anyhow!(concat!("Invalid ", $label, " '{}': {}"), s, e))?;
112 Ok(Self::new(id))
113 }
114 }
115 };
116}
117
118define_id_newtype!(
119 Vid,
126 "Vid"
127);
128
129define_id_newtype!(
130 Eid,
135 "Eid"
136);
137
138#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
144pub struct DenseIdx(pub u32);
145
146impl DenseIdx {
147 pub fn new(idx: u32) -> Self {
149 Self(idx)
150 }
151
152 pub fn as_usize(&self) -> usize {
154 self.0 as usize
155 }
156
157 pub fn as_u32(&self) -> u32 {
159 self.0
160 }
161
162 pub const INVALID: DenseIdx = DenseIdx(u32::MAX);
164
165 pub fn is_invalid(&self) -> bool {
167 self.0 == u32::MAX
168 }
169}
170
171impl From<u32> for DenseIdx {
172 fn from(val: u32) -> Self {
173 Self(val)
174 }
175}
176
177impl From<usize> for DenseIdx {
178 fn from(val: usize) -> Self {
179 Self(val as u32)
180 }
181}
182
183impl fmt::Display for DenseIdx {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "{}", self.0)
186 }
187}
188
189#[derive(Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
191pub struct UniId([u8; 32]);
192
193impl UniId {
194 pub fn from_bytes(bytes: [u8; 32]) -> Self {
195 Self(bytes)
196 }
197
198 pub fn from_multibase(s: &str) -> Result<Self> {
213 let (base, bytes) =
214 multibase::decode(s).map_err(|e| anyhow!("Multibase decode error: {}", e))?;
215
216 if base != Base::Base32Lower {
218 return Err(anyhow!(
219 "UniId must use Base32Lower encoding, got {:?}",
220 base
221 ));
222 }
223
224 let inner: [u8; 32] = bytes.try_into().map_err(|v: Vec<u8>| {
225 anyhow!("Invalid UniId length: expected 32 bytes, got {}", v.len())
226 })?;
227
228 Ok(Self(inner))
229 }
230
231 pub fn to_multibase(&self) -> String {
232 multibase::encode(Base::Base32Lower, self.0)
233 }
234
235 pub fn as_bytes(&self) -> &[u8; 32] {
236 &self.0
237 }
238}
239
240impl fmt::Debug for UniId {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(f, "UniId({})", self.to_multibase())
243 }
244}
245
246impl fmt::Display for UniId {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 write!(f, "{}", self.to_multibase())
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_vid_basic() {
258 let vid = Vid::new(12345);
259 assert_eq!(vid.as_u64(), 12345);
260 assert!(!vid.is_invalid());
261 }
262
263 #[test]
264 fn test_vid_invalid() {
265 let vid = Vid::INVALID;
266 assert!(vid.is_invalid());
267 assert_eq!(vid.as_u64(), u64::MAX);
268 }
269
270 #[test]
271 fn test_vid_from_str() {
272 let vid: Vid = "42".parse().unwrap();
273 assert_eq!(vid.as_u64(), 42);
274
275 let original = Vid::new(12345678);
277 let s = original.to_string();
278 let parsed: Vid = s.parse().unwrap();
279 assert_eq!(original, parsed);
280
281 assert!("invalid".parse::<Vid>().is_err());
283 assert!("".parse::<Vid>().is_err());
284 }
285
286 #[test]
287 fn test_eid_basic() {
288 let eid = Eid::new(67890);
289 assert_eq!(eid.as_u64(), 67890);
290 assert!(!eid.is_invalid());
291 }
292
293 #[test]
294 fn test_eid_invalid() {
295 let eid = Eid::INVALID;
296 assert!(eid.is_invalid());
297 assert_eq!(eid.as_u64(), u64::MAX);
298 }
299
300 #[test]
301 fn test_eid_from_str() {
302 let eid: Eid = "100".parse().unwrap();
303 assert_eq!(eid.as_u64(), 100);
304
305 let original = Eid::new(0xABCDEF);
307 let s = original.to_string();
308 let parsed: Eid = s.parse().unwrap();
309 assert_eq!(original, parsed);
310
311 assert!("invalid".parse::<Eid>().is_err());
313 }
314
315 #[test]
316 fn test_dense_idx() {
317 let idx = DenseIdx::new(100);
318 assert_eq!(idx.as_usize(), 100);
319 assert_eq!(idx.as_u32(), 100);
320 assert!(!idx.is_invalid());
321
322 let invalid = DenseIdx::INVALID;
323 assert!(invalid.is_invalid());
324 }
325
326 #[test]
327 fn test_uni_id_multibase() {
328 let bytes = [0u8; 32];
329 let uid = UniId(bytes);
330 let s = uid.to_multibase();
331 let decoded = UniId::from_multibase(&s).unwrap();
332 assert_eq!(uid, decoded);
333 }
334
335 mod security_tests {
337 use super::*;
338
339 #[test]
341 fn test_uni_id_rejects_wrong_encoding() {
342 let bytes = [0u8; 32];
344 let base58_encoded = multibase::encode(multibase::Base::Base58Btc, bytes);
345
346 let result = UniId::from_multibase(&base58_encoded);
347 assert!(result.is_err());
348 assert!(
349 result
350 .unwrap_err()
351 .to_string()
352 .contains("Base32Lower encoding")
353 );
354 }
355
356 #[test]
358 fn test_uni_id_rejects_wrong_length() {
359 let short_bytes = [0u8; 16];
361 let encoded = multibase::encode(Base::Base32Lower, short_bytes);
362
363 let result = UniId::from_multibase(&encoded);
364 assert!(result.is_err());
365 assert!(
366 result
367 .unwrap_err()
368 .to_string()
369 .contains("expected 32 bytes")
370 );
371 }
372 }
373}