liminal_protocol/wire/primitives.rs
1/// Stable conversation identifier.
2pub type ConversationId = u64;
3
4/// Permanent participant identifier and base identity index.
5pub type ParticipantId = u64;
6
7/// Permanent participant index used by participant-scoped progress accounting.
8pub type ParticipantIndex = u64;
9
10/// Nonzero participant capability or retired generation.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Generation(core::num::NonZeroU64);
13
14impl Generation {
15 /// Canonical first participant generation.
16 pub const ONE: Self = Self(core::num::NonZeroU64::MIN);
17
18 /// Creates a generation, returning `None` for the forbidden zero value.
19 #[must_use]
20 pub const fn new(value: u64) -> Option<Self> {
21 match core::num::NonZeroU64::new(value) {
22 Some(value) => Some(Self(value)),
23 None => None,
24 }
25 }
26
27 /// Returns the nonzero wire value.
28 #[must_use]
29 pub const fn get(self) -> u64 {
30 self.0.get()
31 }
32}
33
34/// Conversation delivery sequence.
35pub type DeliverySeq = u64;
36
37/// Serialized conversation transaction order.
38pub type TransactionOrder = u64;
39
40/// Observer refusal/progress epoch.
41pub type ObserverEpoch = u64;
42
43/// Marker-settlement refusal/clearing epoch.
44///
45/// Deliberately distinct from [`ObserverEpoch`]: the settlement family is
46/// cleared by another participant's record admission or by boot drain, never by
47/// observer progress, so the two epochs are never comparable even though both
48/// serialize as `u64` (participant contract ยง0.16 condition 2).
49pub type SettlementEpoch = u64;
50
51/// Participant protocol version carried in every inner frame prefix.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct ProtocolVersion {
54 /// Major version.
55 pub major: u16,
56 /// Minor version.
57 pub minor: u16,
58}
59
60impl ProtocolVersion {
61 /// Participant protocol v1.0.
62 pub const V1: Self = Self { major: 1, minor: 0 };
63
64 /// Creates a protocol version.
65 #[must_use]
66 pub const fn new(major: u16, minor: u16) -> Self {
67 Self { major, minor }
68 }
69}
70
71/// Server and connection identity for one accepted connection incarnation.
72#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub struct ConnectionIncarnation {
74 /// Durable server incarnation.
75 pub server_incarnation: u64,
76 /// Connection ordinal within that server incarnation.
77 pub connection_ordinal: u64,
78}
79
80impl ConnectionIncarnation {
81 /// Creates a connection incarnation.
82 #[must_use]
83 pub const fn new(server_incarnation: u64, connection_ordinal: u64) -> Self {
84 Self {
85 server_incarnation,
86 connection_ordinal,
87 }
88 }
89}
90
91/// Immutable participant binding epoch.
92///
93/// It has no order. A participant can rebind on an earlier connection at a
94/// higher capability generation, so no order over both fields means "later".
95/// "Later" compares `capability_generation`; a sort or an ordered collection
96/// keys by [`Self::canonical_sort_key`].
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
98pub struct BindingEpoch {
99 /// Connection that owns the binding.
100 pub connection_incarnation: ConnectionIncarnation,
101 /// Capability generation captured by the binding commit.
102 pub capability_generation: Generation,
103}
104
105impl BindingEpoch {
106 /// Creates a binding epoch.
107 #[must_use]
108 pub const fn new(
109 connection_incarnation: ConnectionIncarnation,
110 capability_generation: Generation,
111 ) -> Self {
112 Self {
113 connection_incarnation,
114 capability_generation,
115 }
116 }
117
118 /// A total order for canonical sorting and ordered-collection keys only:
119 /// connection first, then generation. It does not mean "later"; compare
120 /// `capability_generation` for that.
121 #[must_use]
122 pub const fn canonical_sort_key(self) -> (ConnectionIncarnation, Generation) {
123 (self.connection_incarnation, self.capability_generation)
124 }
125}
126
127macro_rules! fixed_credential {
128 ($(#[$meta:meta])* $name:ident, $length:expr) => {
129 $(#[$meta])*
130 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
131 pub struct $name([u8; $length]);
132
133 /// Names the type and its length, never its bytes: a credential
134 /// reaches logs and error text through `{:?}`.
135 impl core::fmt::Debug for $name {
136 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
137 write!(formatter, "{}({} bytes)", stringify!($name), $length)
138 }
139 }
140
141 impl $name {
142 /// Creates the fixed-width value from its canonical bytes.
143 #[must_use]
144 pub const fn new(bytes: [u8; $length]) -> Self {
145 Self(bytes)
146 }
147
148 /// Returns the canonical fixed-width bytes.
149 #[must_use]
150 pub const fn into_bytes(self) -> [u8; $length] {
151 self.0
152 }
153
154 /// Borrows the canonical fixed-width bytes.
155 #[must_use]
156 pub const fn as_bytes(&self) -> &[u8; $length] {
157 &self.0
158 }
159 }
160 };
161}
162
163fixed_credential!(
164 /// Single-purpose enrollment attempt token.
165 EnrollmentToken,
166 16
167);
168fixed_credential!(
169 /// Single-purpose credential-attach attempt token.
170 AttachAttemptToken,
171 16
172);
173fixed_credential!(
174 /// Single-purpose explicit-detach attempt token.
175 DetachAttemptToken,
176 16
177);
178fixed_credential!(
179 /// Single-purpose terminal Leave attempt token.
180 LeaveAttemptToken,
181 16
182);
183fixed_credential!(
184 /// Single-purpose ordinary record-admission attempt token.
185 RecordAdmissionAttemptToken,
186 16
187);
188fixed_credential!(
189 /// Participant attach secret.
190 AttachSecret,
191 32
192);