1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3use std::num::{NonZeroU64, NonZeroU128};
4use std::str::FromStr;
5
6use serde::de;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9macro_rules! opaque_u128_id {
10 ($name:ident, $description:literal) => {
11 #[doc = $description]
12 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13 pub struct $name(NonZeroU128);
14
15 impl $name {
16 pub fn new(value: u128) -> Result<Self, IdentityError> {
17 NonZeroU128::new(value).map(Self).ok_or(IdentityError::Zero {
18 kind: stringify!($name),
19 })
20 }
21
22 pub fn from_be_bytes(bytes: [u8; 16]) -> Result<Self, IdentityError> {
23 Self::new(u128::from_be_bytes(bytes))
24 }
25
26 #[must_use]
27 pub const fn get(self) -> u128 {
28 self.0.get()
29 }
30
31 #[must_use]
32 pub const fn to_be_bytes(self) -> [u8; 16] {
33 self.get().to_be_bytes()
34 }
35
36 #[must_use]
37 pub fn checked_next(self) -> Option<Self> {
38 self.get().checked_add(1).and_then(NonZeroU128::new).map(Self)
39 }
40 }
41
42 impl Display for $name {
43 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
44 write!(formatter, "{:032x}", self.get())
45 }
46 }
47
48 impl FromStr for $name {
49 type Err = IdentityError;
50
51 fn from_str(value: &str) -> Result<Self, Self::Err> {
52 parse_hex_u128(stringify!($name), value).and_then(Self::new)
53 }
54 }
55
56 impl Serialize for $name {
57 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58 where
59 S: Serializer,
60 {
61 serializer.collect_str(self)
62 }
63 }
64
65 impl<'de> Deserialize<'de> for $name {
66 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67 where
68 D: Deserializer<'de>,
69 {
70 let value = String::deserialize(deserializer)?;
71 value.parse().map_err(de::Error::custom)
72 }
73 }
74 };
75}
76
77opaque_u128_id!(
78 CollectionId,
79 "An immutable nonzero collection namespace encoded as 32 lower-case hexadecimal characters."
80);
81opaque_u128_id!(
82 IndexId,
83 "An immutable nonzero index-incarnation identity encoded as 32 lower-case hexadecimal characters."
84);
85opaque_u128_id!(
86 ObjectIncarnation,
87 "A never-reused nonzero physical-object identity within one backend authority."
88);
89opaque_u128_id!(
90 ProtectionSnapshotId,
91 "A never-reused semantic protection-snapshot identity derived from a burned control-authority allocator value."
92);
93opaque_u128_id!(
94 RecoveryAttemptId,
95 "A stable nonzero identity reused by every retry of one offline total-voter recovery attempt."
96);
97#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
100pub struct ClusterStorageId([u8; 16]);
101
102impl ClusterStorageId {
103 const DERIVATION_DOMAIN: &'static [u8] = b"lodestone:cluster-storage-id:v1\0";
104
105 pub fn derive_from_genesis(cluster_identity: ClusterIdentity) -> Result<Self, IdentityError> {
106 if cluster_identity.recovery_generation() != RecoveryGeneration::genesis() {
107 return Err(IdentityError::ClusterStorageIdNonGenesis(
108 cluster_identity.recovery_generation().get(),
109 ));
110 }
111 let mut hasher = blake3::Hasher::new();
112 hasher.update(Self::DERIVATION_DOMAIN);
113 hasher.update(&cluster_identity.recovery_era().to_be_bytes());
114 let digest = hasher.finalize();
115 let mut bytes = [0_u8; 16];
116 bytes.copy_from_slice(&digest.as_bytes()[..16]);
117 Ok(Self::from_be_bytes(bytes))
118 }
119
120 #[must_use]
122 pub const fn from_be_bytes(bytes: [u8; 16]) -> Self {
123 Self(bytes)
124 }
125
126 #[must_use]
127 pub const fn to_be_bytes(self) -> [u8; 16] {
128 self.0
129 }
130}
131
132impl Display for ClusterStorageId {
133 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
134 write!(formatter, "{:032x}", u128::from_be_bytes(self.0))
135 }
136}
137
138impl FromStr for ClusterStorageId {
139 type Err = IdentityError;
140
141 fn from_str(value: &str) -> Result<Self, Self::Err> {
142 let value = parse_hex_u128("ClusterStorageId", value)?;
143 Ok(Self::from_be_bytes(value.to_be_bytes()))
144 }
145}
146
147impl Serialize for ClusterStorageId {
148 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
149 where
150 S: Serializer,
151 {
152 serializer.collect_str(self)
153 }
154}
155
156impl<'de> Deserialize<'de> for ClusterStorageId {
157 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
158 where
159 D: Deserializer<'de>,
160 {
161 String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
162 }
163}
164
165#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
167pub struct NodeIncarnation(NonZeroU64);
168
169impl NodeIncarnation {
170 pub fn new(value: u64) -> Result<Self, IdentityError> {
171 NonZeroU64::new(value).map(Self).ok_or(IdentityError::Zero {
172 kind: "NodeIncarnation",
173 })
174 }
175
176 #[must_use]
177 pub const fn get(self) -> u64 {
178 self.0.get()
179 }
180
181 #[must_use]
182 pub fn checked_next(self) -> Option<Self> {
183 self.get().checked_add(1).and_then(NonZeroU64::new).map(Self)
184 }
185}
186
187impl Display for NodeIncarnation {
188 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
189 Display::fmt(&self.get(), formatter)
190 }
191}
192
193impl Serialize for NodeIncarnation {
194 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195 where
196 S: Serializer,
197 {
198 serializer.serialize_u64(self.get())
199 }
200}
201
202impl<'de> Deserialize<'de> for NodeIncarnation {
203 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204 where
205 D: Deserializer<'de>,
206 {
207 Self::new(u64::deserialize(deserializer)?).map_err(de::Error::custom)
208 }
209}
210
211#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
218pub struct RecoveryEra(NonZeroU128);
219
220impl RecoveryEra {
221 pub fn new(value: u128) -> Result<Self, IdentityError> {
222 NonZeroU128::new(value)
223 .map(Self)
224 .ok_or(IdentityError::Zero { kind: "RecoveryEra" })
225 }
226
227 pub fn from_be_bytes(bytes: [u8; 16]) -> Result<Self, IdentityError> {
228 Self::new(u128::from_be_bytes(bytes))
229 }
230
231 #[must_use]
232 pub const fn to_be_bytes(self) -> [u8; 16] {
233 self.0.get().to_be_bytes()
234 }
235}
236
237impl Display for RecoveryEra {
238 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
239 write!(formatter, "{:032x}", self.0.get())
240 }
241}
242
243impl FromStr for RecoveryEra {
244 type Err = IdentityError;
245
246 fn from_str(value: &str) -> Result<Self, Self::Err> {
247 parse_hex_u128("RecoveryEra", value).and_then(Self::new)
248 }
249}
250
251impl Serialize for RecoveryEra {
252 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
253 where
254 S: Serializer,
255 {
256 serializer.collect_str(self)
257 }
258}
259
260impl<'de> Deserialize<'de> for RecoveryEra {
261 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262 where
263 D: Deserializer<'de>,
264 {
265 String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
266 }
267}
268
269fn parse_hex_u128(kind: &'static str, value: &str) -> Result<u128, IdentityError> {
270 if value.len() != 32 {
271 return Err(IdentityError::HexLength {
272 kind,
273 actual: value.len(),
274 });
275 }
276 if !value
277 .bytes()
278 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
279 {
280 return Err(IdentityError::LowerHex { kind });
281 }
282 u128::from_str_radix(value, 16).map_err(|_| IdentityError::LowerHex { kind })
283}
284
285#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
287#[serde(transparent)]
288pub struct IndexName(String);
289
290impl IndexName {
291 pub fn new(value: impl Into<String>) -> Result<Self, IdentityError> {
292 let value = value.into();
293 let length = value.len();
294 if !(1..=255).contains(&length) {
295 return Err(IdentityError::IndexNameLength(length));
296 }
297 if value.bytes().any(|byte| byte <= 0x1f || byte == 0x7f) {
298 return Err(IdentityError::IndexNameControl);
299 }
300 Ok(Self(value))
301 }
302
303 #[must_use]
304 pub fn as_str(&self) -> &str {
305 &self.0
306 }
307
308 #[must_use]
309 pub fn into_inner(self) -> String {
310 self.0
311 }
312
313 #[must_use]
316 pub fn to_binary(&self) -> Vec<u8> {
317 let length = self.0.len() as u16;
318 let mut bytes = Vec::with_capacity(2 + usize::from(length));
319 bytes.extend_from_slice(&length.to_be_bytes());
320 bytes.extend_from_slice(self.0.as_bytes());
321 bytes
322 }
323
324 pub fn from_binary(bytes: &[u8]) -> Result<Self, IdentityError> {
326 let Some(length_bytes) = bytes.get(..2) else {
327 return Err(IdentityError::BinaryLength {
328 kind: "IndexName",
329 expected: 2,
330 actual: bytes.len(),
331 });
332 };
333 let length = usize::from(u16::from_be_bytes([length_bytes[0], length_bytes[1]]));
334 if bytes.len() != 2 + length {
335 return Err(IdentityError::BinaryLength {
336 kind: "IndexName",
337 expected: 2 + length,
338 actual: bytes.len(),
339 });
340 }
341 let value = std::str::from_utf8(&bytes[2..]).map_err(|_| IdentityError::IndexNameUtf8)?;
342 Self::new(value)
343 }
344}
345
346impl Display for IndexName {
347 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
348 formatter.write_str(self.as_str())
349 }
350}
351
352impl FromStr for IndexName {
353 type Err = IdentityError;
354
355 fn from_str(value: &str) -> Result<Self, Self::Err> {
356 Self::new(value)
357 }
358}
359
360impl<'de> Deserialize<'de> for IndexName {
361 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
362 where
363 D: Deserializer<'de>,
364 {
365 String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
366 }
367}
368
369#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
371#[serde(transparent)]
372pub struct BackendAuthorityId(String);
373
374impl BackendAuthorityId {
375 pub fn new(value: impl Into<String>) -> Result<Self, IdentityError> {
376 let value = value.into();
377 let bytes = value.as_bytes();
378 if !(1..=64).contains(&bytes.len())
379 || !bytes.first().is_some_and(u8::is_ascii_lowercase)
380 || !bytes
381 .iter()
382 .skip(1)
383 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'_' | b'-'))
384 {
385 return Err(IdentityError::BackendAuthority(value));
386 }
387 Ok(Self(value))
388 }
389
390 #[must_use]
391 pub fn as_str(&self) -> &str {
392 &self.0
393 }
394
395 #[must_use]
398 pub fn to_binary(&self) -> Vec<u8> {
399 let length = self.0.len() as u8;
400 let mut bytes = Vec::with_capacity(1 + usize::from(length));
401 bytes.push(length);
402 bytes.extend_from_slice(self.0.as_bytes());
403 bytes
404 }
405
406 pub fn from_binary(bytes: &[u8]) -> Result<Self, IdentityError> {
408 let Some(length) = bytes.first().copied() else {
409 return Err(IdentityError::BinaryLength {
410 kind: "BackendAuthorityId",
411 expected: 1,
412 actual: 0,
413 });
414 };
415 let expected = 1 + usize::from(length);
416 if bytes.len() != expected {
417 return Err(IdentityError::BinaryLength {
418 kind: "BackendAuthorityId",
419 expected,
420 actual: bytes.len(),
421 });
422 }
423 let value = std::str::from_utf8(&bytes[1..]).map_err(|_| IdentityError::BackendAuthorityUtf8)?;
424 Self::new(value)
425 }
426}
427
428impl Display for BackendAuthorityId {
429 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
430 formatter.write_str(self.as_str())
431 }
432}
433
434impl FromStr for BackendAuthorityId {
435 type Err = IdentityError;
436
437 fn from_str(value: &str) -> Result<Self, Self::Err> {
438 Self::new(value)
439 }
440}
441
442impl<'de> Deserialize<'de> for BackendAuthorityId {
443 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
444 where
445 D: Deserializer<'de>,
446 {
447 String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
448 }
449}
450
451#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
453#[serde(deny_unknown_fields)]
454pub struct CollectionIndexRef {
455 collection_id: CollectionId,
456 index_id: IndexId,
457}
458
459impl CollectionIndexRef {
460 #[must_use]
461 pub const fn new(collection_id: CollectionId, index_id: IndexId) -> Self {
462 Self {
463 collection_id,
464 index_id,
465 }
466 }
467
468 #[must_use]
469 pub const fn collection_id(self) -> CollectionId {
470 self.collection_id
471 }
472
473 #[must_use]
474 pub const fn index_id(self) -> IndexId {
475 self.index_id
476 }
477}
478
479impl Display for CollectionIndexRef {
480 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
481 write!(formatter, "{}/{}", self.collection_id, self.index_id)
482 }
483}
484
485#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
487#[serde(deny_unknown_fields)]
488pub struct IndexAddress {
489 collection_id: CollectionId,
490 index_name: IndexName,
491}
492
493impl IndexAddress {
494 #[must_use]
495 pub const fn new(collection_id: CollectionId, index_name: IndexName) -> Self {
496 Self {
497 collection_id,
498 index_name,
499 }
500 }
501
502 #[must_use]
503 pub const fn collection_id(&self) -> CollectionId {
504 self.collection_id
505 }
506
507 #[must_use]
508 pub fn index_name(&self) -> &IndexName {
509 &self.index_name
510 }
511}
512
513#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
515#[serde(deny_unknown_fields)]
516pub struct ShardKey {
517 collection_index_ref: CollectionIndexRef,
518 shard_id: crate::ShardId,
519}
520
521impl ShardKey {
522 #[must_use]
523 pub const fn new(collection_index_ref: CollectionIndexRef, shard_id: crate::ShardId) -> Self {
524 Self {
525 collection_index_ref,
526 shard_id,
527 }
528 }
529
530 #[must_use]
531 pub const fn collection_index_ref(self) -> CollectionIndexRef {
532 self.collection_index_ref
533 }
534
535 #[must_use]
536 pub const fn shard_id(self) -> crate::ShardId {
537 self.shard_id
538 }
539}
540
541#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
543#[serde(deny_unknown_fields)]
544pub struct BackendAuthorityPair {
545 wal: BackendAuthorityId,
546 data: BackendAuthorityId,
547}
548
549impl BackendAuthorityPair {
550 #[must_use]
551 pub const fn new(wal: BackendAuthorityId, data: BackendAuthorityId) -> Self {
552 Self { wal, data }
553 }
554
555 #[must_use]
556 pub fn wal(&self) -> &BackendAuthorityId {
557 &self.wal
558 }
559
560 #[must_use]
561 pub fn data(&self) -> &BackendAuthorityId {
562 &self.data
563 }
564}
565
566#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
568pub struct RecoveryGeneration(u32);
569
570impl RecoveryGeneration {
571 pub const MAX: u32 = 0x7fff_ffff;
572
573 pub fn new(value: u32) -> Result<Self, IdentityError> {
574 if value <= Self::MAX {
575 Ok(Self(value))
576 } else {
577 Err(IdentityError::RecoveryGeneration(value))
578 }
579 }
580
581 #[must_use]
582 pub const fn genesis() -> Self {
583 Self(0)
584 }
585
586 #[must_use]
587 pub const fn get(self) -> u32 {
588 self.0
589 }
590
591 #[must_use]
592 pub fn checked_next(self) -> Option<Self> {
593 self.0.checked_add(1).filter(|value| *value <= Self::MAX).map(Self)
594 }
595}
596
597impl Serialize for RecoveryGeneration {
598 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
599 where
600 S: Serializer,
601 {
602 serializer.serialize_u32(self.get())
603 }
604}
605
606impl<'de> Deserialize<'de> for RecoveryGeneration {
607 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
608 where
609 D: Deserializer<'de>,
610 {
611 Self::new(u32::deserialize(deserializer)?).map_err(de::Error::custom)
612 }
613}
614
615impl Display for RecoveryGeneration {
616 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
617 Display::fmt(&self.get(), formatter)
618 }
619}
620
621#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
623pub struct PrimaryTerm(u64);
624
625impl PrimaryTerm {
626 pub fn new(value: u64) -> Result<Self, IdentityError> {
627 if value == 0 || value > i64::MAX as u64 || value as u32 == 0 {
628 Err(IdentityError::PrimaryTerm(value))
629 } else {
630 Ok(Self(value))
631 }
632 }
633
634 pub fn from_parts(recovery_generation: RecoveryGeneration, epoch: crate::Epoch) -> Result<Self, IdentityError> {
635 if epoch.get() == 0 {
636 return Err(IdentityError::ZeroEpoch);
637 }
638 let value = (u64::from(recovery_generation.get()) << 32) | u64::from(epoch.get());
639 Self::new(value)
640 }
641
642 #[must_use]
645 pub const fn from_genesis_epoch(epoch: crate::Epoch) -> Self {
646 Self(epoch.get() as u64)
647 }
648
649 #[must_use]
650 pub const fn get(self) -> u64 {
651 self.0
652 }
653
654 #[must_use]
655 pub const fn epoch_component(self) -> crate::Epoch {
656 crate::Epoch::new(self.0 as u32)
657 }
658
659 #[must_use]
660 pub const fn recovery_generation_component(self) -> RecoveryGeneration {
661 RecoveryGeneration((self.0 >> 32) as u32)
662 }
663}
664
665impl Serialize for PrimaryTerm {
666 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
667 where
668 S: Serializer,
669 {
670 serializer.serialize_u64(self.get())
671 }
672}
673
674impl<'de> Deserialize<'de> for PrimaryTerm {
675 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
676 where
677 D: Deserializer<'de>,
678 {
679 Self::new(u64::deserialize(deserializer)?).map_err(de::Error::custom)
680 }
681}
682
683impl Display for PrimaryTerm {
684 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
685 Display::fmt(&self.get(), formatter)
686 }
687}
688
689#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
691#[serde(deny_unknown_fields)]
692pub struct FenceToken {
693 recovery_era: RecoveryEra,
694 epoch: crate::Epoch,
695}
696
697impl FenceToken {
698 pub fn new(recovery_era: RecoveryEra, epoch: crate::Epoch) -> Result<Self, IdentityError> {
699 if epoch.get() == 0 {
700 return Err(IdentityError::ZeroEpoch);
701 }
702 Ok(Self { recovery_era, epoch })
703 }
704
705 #[must_use]
706 pub const fn recovery_era(self) -> RecoveryEra {
707 self.recovery_era
708 }
709
710 #[must_use]
711 pub const fn epoch(self) -> crate::Epoch {
712 self.epoch
713 }
714
715 #[must_use]
718 pub fn to_binary(self) -> [u8; 20] {
719 let mut bytes = [0_u8; 20];
720 bytes[..16].copy_from_slice(&self.recovery_era.to_be_bytes());
721 bytes[16..].copy_from_slice(&self.epoch.get().to_be_bytes());
722 bytes
723 }
724
725 pub fn from_binary(bytes: &[u8]) -> Result<Self, IdentityError> {
727 if bytes.len() != 20 {
728 return Err(IdentityError::BinaryLength {
729 kind: "FenceToken",
730 expected: 20,
731 actual: bytes.len(),
732 });
733 }
734 let mut era_bytes = [0_u8; 16];
735 era_bytes.copy_from_slice(&bytes[..16]);
736 let recovery_era = RecoveryEra::from_be_bytes(era_bytes)?;
737 let epoch = crate::Epoch::new(u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]));
738 Self::new(recovery_era, epoch)
739 }
740}
741
742impl<'de> Deserialize<'de> for FenceToken {
743 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
744 where
745 D: Deserializer<'de>,
746 {
747 #[derive(Deserialize)]
748 #[serde(deny_unknown_fields)]
749 struct Wire {
750 recovery_era: RecoveryEra,
751 epoch: crate::Epoch,
752 }
753
754 let wire = Wire::deserialize(deserializer)?;
755 Self::new(wire.recovery_era, wire.epoch).map_err(de::Error::custom)
756 }
757}
758
759#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
761#[serde(deny_unknown_fields)]
762pub struct ClusterIdentity {
763 recovery_era: RecoveryEra,
764 recovery_generation: RecoveryGeneration,
765}
766
767impl ClusterIdentity {
768 #[must_use]
769 pub const fn new(recovery_era: RecoveryEra, recovery_generation: RecoveryGeneration) -> Self {
770 Self {
771 recovery_era,
772 recovery_generation,
773 }
774 }
775
776 #[must_use]
777 pub const fn recovery_era(self) -> RecoveryEra {
778 self.recovery_era
779 }
780
781 #[must_use]
782 pub const fn recovery_generation(self) -> RecoveryGeneration {
783 self.recovery_generation
784 }
785}
786
787#[derive(Clone, Debug, Eq, PartialEq)]
789pub enum IdentityError {
790 Zero {
791 kind: &'static str,
792 },
793 HexLength {
794 kind: &'static str,
795 actual: usize,
796 },
797 LowerHex {
798 kind: &'static str,
799 },
800 BinaryLength {
801 kind: &'static str,
802 expected: usize,
803 actual: usize,
804 },
805 IndexNameLength(usize),
806 IndexNameControl,
807 IndexNameUtf8,
808 BackendAuthority(String),
809 BackendAuthorityUtf8,
810 ClusterStorageIdNonGenesis(u32),
811 RecoveryGeneration(u32),
812 ZeroEpoch,
813 PrimaryTerm(u64),
814}
815
816impl Display for IdentityError {
817 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
818 match self {
819 Self::Zero { kind } => write!(formatter, "{kind} must be nonzero"),
820 Self::HexLength { kind, actual } => {
821 write!(
822 formatter,
823 "{kind} must contain exactly 32 lower-case hexadecimal bytes; got {actual}"
824 )
825 }
826 Self::LowerHex { kind } => {
827 write!(
828 formatter,
829 "{kind} must contain exactly 32 lower-case hexadecimal characters"
830 )
831 }
832 Self::BinaryLength { kind, expected, actual } => write!(
833 formatter,
834 "{kind} binary encoding requires exactly {expected} bytes; got {actual}"
835 ),
836 Self::IndexNameLength(length) => {
837 write!(formatter, "index name is {length} bytes; expected 1..=255")
838 }
839 Self::IndexNameControl => formatter.write_str("index name contains an ASCII control character"),
840 Self::IndexNameUtf8 => formatter.write_str("index name binary encoding is not valid UTF-8"),
841 Self::BackendAuthority(value) => write!(
842 formatter,
843 "backend authority '{value}' must match [a-z][a-z0-9_-]{{0,63}}"
844 ),
845 Self::BackendAuthorityUtf8 => formatter.write_str("backend authority binary encoding is not valid UTF-8"),
846 Self::ClusterStorageIdNonGenesis(generation) => write!(
847 formatter,
848 "cluster storage identity can be derived only from recovery generation zero; got {generation}"
849 ),
850 Self::RecoveryGeneration(value) => {
851 write!(
852 formatter,
853 "recovery generation {value} exceeds the checked 31-bit domain"
854 )
855 }
856 Self::ZeroEpoch => formatter.write_str("a fenced epoch or primary term epoch component must be nonzero"),
857 Self::PrimaryTerm(value) => {
858 write!(
859 formatter,
860 "primary term {value} must be in 1..=i64::MAX with a nonzero low epoch component"
861 )
862 }
863 }
864 }
865}
866
867impl Error for IdentityError {}
868
869#[cfg(test)]
870mod tests {
871 use std::error::Error;
872
873 use super::{
874 BackendAuthorityId, ClusterIdentity, ClusterStorageId, CollectionId, CollectionIndexRef, FenceToken,
875 IndexAddress, IndexId, IndexName, NodeIncarnation, ObjectIncarnation, PrimaryTerm, ProtectionSnapshotId,
876 RecoveryAttemptId, RecoveryEra, RecoveryGeneration, ShardKey,
877 };
878 use crate::{Epoch, ShardId};
879
880 #[test]
881 fn opaque_ids_are_nonzero_fixed_lower_hex_and_big_endian() {
882 let collection = CollectionId::new(0x102).ok();
883 assert_eq!(
884 collection.and_then(|value| serde_json::to_string(&value).ok()),
885 Some("\"00000000000000000000000000000102\"".to_owned())
886 );
887 assert_eq!(
888 collection.map(CollectionId::to_be_bytes),
889 Some(0x102_u128.to_be_bytes())
890 );
891 assert!(CollectionId::new(0).is_err());
892 assert!(serde_json::from_str::<CollectionId>("\"00000000000000000000000000000000\"").is_err());
893 assert!(serde_json::from_str::<CollectionId>("\"000000000000000000000000000000AF\"").is_err());
894 assert!(serde_json::from_str::<CollectionId>("1").is_err());
895 }
896
897 #[test]
898 fn node_incarnation_is_nonzero_monotonic_and_numeric_on_the_wire() -> Result<(), Box<dyn Error>> {
899 let incarnation = NodeIncarnation::new(7)?;
900 assert_eq!(incarnation.get(), 7);
901 assert_eq!(incarnation.checked_next().map(|value| value.get()), Some(8));
902 assert_eq!(serde_json::to_string(&incarnation)?, "7");
903 assert_eq!(serde_json::from_str::<NodeIncarnation>("7")?, incarnation);
904 assert!(NodeIncarnation::new(0).is_err());
905 assert!(serde_json::from_str::<NodeIncarnation>("0").is_err());
906 Ok(())
907 }
908
909 #[test]
910 fn recovery_attempt_identity_uses_the_opaque_hex_contract() -> Result<(), Box<dyn Error>> {
911 let identity = RecoveryAttemptId::new(9)?;
912 assert_eq!(identity.to_string(), "00000000000000000000000000000009");
913 assert_eq!(identity.to_string().parse::<RecoveryAttemptId>()?, identity);
914 let protection = ProtectionSnapshotId::new(11)?;
915 assert_eq!(protection.to_string(), "0000000000000000000000000000000b");
916 assert_eq!(protection.to_string().parse::<ProtectionSnapshotId>()?, protection);
917 assert!(ProtectionSnapshotId::new(0).is_err());
918 Ok(())
919 }
920
921 #[test]
922 fn names_and_backend_authorities_enforce_catalog_bounds() {
923 let name = IndexName::new("products");
924 assert!(name.is_ok());
925 if let Ok(value) = &name {
926 assert_eq!(IndexName::from_binary(&value.to_binary()).as_ref(), Ok(value));
927 }
928 assert!(IndexName::new("").is_err());
929 assert!(IndexName::new("bad\u{7f}name").is_err());
930 assert!(IndexName::new("x".repeat(256)).is_err());
931 let authority = BackendAuthorityId::new("standard_aps1");
932 if let Ok(value) = &authority {
933 assert_eq!(BackendAuthorityId::from_binary(&value.to_binary()).as_ref(), Ok(value));
934 }
935 assert!(BackendAuthorityId::new("Express").is_err());
936 assert!(BackendAuthorityId::new("-leading").is_err());
937 assert!(BackendAuthorityId::from_binary(&[3, b'a', b'b']).is_err());
938 }
939
940 #[test]
941 fn scopes_and_addresses_round_trip_without_unknown_fields() -> Result<(), Box<dyn Error>> {
942 let collection = CollectionId::new(1)?;
943 let index = IndexId::new(2)?;
944 let name = IndexName::new("events")?;
945 let scope = CollectionIndexRef::new(collection, index);
946 let address = IndexAddress::new(collection, name);
947 let shard = ShardKey::new(scope, ShardId::new(7));
948 let encoded_address = serde_json::to_vec(&address)?;
949 assert_eq!(serde_json::from_slice::<IndexAddress>(&encoded_address)?, address);
950 let encoded_shard = serde_json::to_vec(&shard)?;
951 assert_eq!(serde_json::from_slice::<ShardKey>(&encoded_shard)?, shard);
952 assert!(serde_json::from_str::<CollectionIndexRef>(
953 r#"{"collection_id":"00000000000000000000000000000001","index_id":"00000000000000000000000000000002","extra":1}"#
954 )
955 .is_err());
956 Ok(())
957 }
958
959 #[test]
960 fn recovery_generation_projects_a_positive_signed_primary_term() -> Result<(), Box<dyn Error>> {
961 let era = RecoveryEra::new(9)?;
962 let generation = RecoveryGeneration::new(1)?;
963 let term = PrimaryTerm::from_parts(generation, Epoch::new(7))?;
964 assert_eq!(term.get(), (1_u64 << 32) | 7);
965 assert_eq!(term.recovery_generation_component(), generation);
966 assert_eq!(term.epoch_component(), Epoch::new(7));
967 assert!(PrimaryTerm::from_parts(generation, Epoch::new(0)).is_err());
968 assert!(PrimaryTerm::new(1_u64 << 32).is_err());
969 assert!(RecoveryGeneration::new(RecoveryGeneration::MAX + 1).is_err());
970 assert!(
971 RecoveryGeneration::new(RecoveryGeneration::MAX)
972 .ok()
973 .and_then(RecoveryGeneration::checked_next)
974 .is_none()
975 );
976 let fence = FenceToken::new(era, Epoch::new(7))?;
977 assert_eq!(FenceToken::from_binary(&fence.to_binary())?, fence);
978 assert!(FenceToken::from_binary(&[0; 20]).is_err());
979 assert!(
980 serde_json::from_str::<FenceToken>(r#"{"recovery_era":"00000000000000000000000000000009","epoch":0}"#)
981 .is_err()
982 );
983 assert_eq!(ClusterIdentity::new(era, generation).recovery_generation(), generation);
984 Ok(())
985 }
986
987 #[test]
988 fn cluster_storage_identity_is_genesis_derived_and_stable() -> Result<(), Box<dyn Error>> {
989 let genesis = ClusterIdentity::new(RecoveryEra::new(9)?, RecoveryGeneration::genesis());
990 let storage_id = ClusterStorageId::derive_from_genesis(genesis)?;
991 assert_eq!(storage_id.to_string().len(), 32);
992 assert_eq!(storage_id.to_string().parse::<ClusterStorageId>()?, storage_id);
993 assert_eq!(ClusterStorageId::from_be_bytes(storage_id.to_be_bytes()), storage_id);
994 assert_eq!(
995 serde_json::from_str::<ClusterStorageId>(&serde_json::to_string(&storage_id)?)?,
996 storage_id
997 );
998 assert_eq!(
999 ClusterStorageId::derive_from_genesis(genesis)?,
1000 ClusterStorageId::derive_from_genesis(genesis)?
1001 );
1002 let successor = ClusterIdentity::new(RecoveryEra::new(10)?, RecoveryGeneration::new(1)?);
1003 assert_eq!(
1004 ClusterStorageId::derive_from_genesis(successor),
1005 Err(super::IdentityError::ClusterStorageIdNonGenesis(1))
1006 );
1007 assert_eq!(ClusterStorageId::from_be_bytes([0; 16]).to_be_bytes(), [0; 16]);
1008 Ok(())
1009 }
1010
1011 #[test]
1012 fn every_nonzero_physical_identity_uses_the_same_codec_contract() -> Result<(), Box<dyn Error>> {
1013 let index = IndexId::new(u128::MAX)?;
1014 let object = ObjectIncarnation::new(u128::MAX)?;
1015 assert_eq!(index.to_string(), object.to_string());
1016 assert!(ObjectIncarnation::from_be_bytes([0; 16]).is_err());
1017 Ok(())
1018 }
1019}