Skip to main content

gbp_core/
errors.rs

1//! Error class enum and the registry of canonical error codes.
2//!
3//! The wire-serialisable `ErrorObject` lives in the `gbp` crate (base layer).
4//! Only the classification and the code constants live here so that any layer
5//! can refer to them without taking a serde dependency.
6
7/// Coarse classification of an error. Single byte on the wire.
8#[repr(u8)]
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
10pub enum ErrorClass {
11    /// 0x01 — transport (QUIC / TLS).
12    Transport = 0x01,
13    /// 0x02 — cryptography (AEAD / MLS).
14    Crypto = 0x02,
15    /// 0x03 — state (epoch / transition).
16    State = 0x03,
17    /// 0x04 — policy (replay / quota).
18    Policy = 0x04,
19    /// 0x05 — schema (CBOR shape, length validation).
20    Schema = 0x05,
21    /// 0x06 — authorisation (roles, GSP).
22    Authz = 0x06,
23}
24
25impl TryFrom<u8> for ErrorClass {
26    type Error = u8;
27    fn try_from(v: u8) -> Result<Self, u8> {
28        use ErrorClass::*;
29        Ok(match v {
30            0x01 => Transport,
31            0x02 => Crypto,
32            0x03 => State,
33            0x04 => Policy,
34            0x05 => Schema,
35            0x06 => Authz,
36            other => return Err(other),
37        })
38    }
39}
40
41/// All canonical error codes.
42///
43/// Allocation:
44/// * `0x0000`–`0x0FFF` — GBP base layer
45/// * `0x1000`–`0x1FFF` — GAP
46/// * `0x2000`–`0x2FFF` — GTP
47/// * `0x3000`–`0x3FFF` — GSP
48/// * `0xF000`–`0xFFFF` — private extensions
49pub mod codes {
50    /// Unsupported protocol version.
51    pub const UNSUPPORTED_VERSION: u16 = 0x0001;
52    /// Unknown group_id.
53    pub const UNKNOWN_GROUP: u16 = 0x0002;
54    /// Frame epoch does not match the receiver's current epoch.
55    pub const EPOCH_MISMATCH: u16 = 0x0003;
56    /// Frame transition_id does not match the current transition.
57    pub const TRANSITION_MISMATCH: u16 = 0x0004;
58    /// Sequence number was already used — replay detected.
59    pub const REPLAY_DETECTED: u16 = 0x0005;
60    /// AEAD authentication failed.
61    pub const DECRYPT_FAILED: u16 = 0x0006;
62    /// MLS commit was rejected.
63    pub const COMMIT_INVALID: u16 = 0x0007;
64    /// Stream class used outside its allowed policy.
65    pub const STREAM_POLICY_VIOLATION: u16 = 0x0008;
66    /// A second commit arrived while a pending transition is in progress.
67    pub const TRANSITION_IN_PROGRESS: u16 = 0x0009;
68    /// Coordinator timed out waiting for READY quorum.
69    pub const PREPARE_TIMEOUT: u16 = 0x0010;
70    /// Member timed out before completing local commit processing.
71    pub const READY_TIMEOUT: u16 = 0x0011;
72    /// Member timed out waiting for EXECUTE_TRANSITION after READY.
73    pub const EXECUTE_TIMEOUT: u16 = 0x0012;
74    /// Coordinator transport lost; handover required.
75    pub const COORDINATOR_GONE: u16 = 0x0013;
76    /// member_set_root_hash mismatch on Resync.
77    pub const DIGEST_MISMATCH: u16 = 0x0014;
78
79    /// Unknown audio source.
80    pub const GAP_BAD_SOURCE_ID: u16 = 0x1001;
81    /// Opus decoder error.
82    pub const GAP_DECODE_FAILED: u16 = 0x1002;
83    /// AEAD authentication failed at the GAP layer.
84    pub const GAP_AUTH_FAILED: u16 = 0x1003;
85    /// rtp_sequence already seen for this source.
86    pub const GAP_REPLAY_DETECTED: u16 = 0x1004;
87    /// key_phase points at a stale epoch.
88    pub const GAP_EPOCH_STALE: u16 = 0x1005;
89    /// key_phase refers to an unknown epoch.
90    pub const GAP_KEY_PHASE_UNKNOWN: u16 = 0x1006;
91
92    /// content_length does not match the body length.
93    pub const GTP_BAD_LENGTH: u16 = 0x2001;
94    /// content_type is not supported by the profile.
95    pub const GTP_UNSUPPORTED_CONTENT_TYPE: u16 = 0x2002;
96    /// Duplicate (sender_id, message_id).
97    pub const GTP_DUPLICATE_MESSAGE: u16 = 0x2003;
98    /// Message rejected by the policy layer (retention / quota / …).
99    pub const GTP_POLICY_REJECTED: u16 = 0x2004;
100    /// Attachment integrity check failed.
101    pub const GTP_ATTACHMENT_INTEGRITY: u16 = 0x2005;
102    /// Request timed out.
103    pub const GTP_REQUEST_TIMEOUT: u16 = 0x2006;
104
105    /// Invalid args schema for the given signal_type.
106    pub const GSP_BAD_SCHEMA: u16 = 0x3001;
107    /// Sender is not authorised to issue this signal.
108    pub const GSP_UNAUTHORIZED: u16 = 0x3002;
109    /// signal_type is not in the registry.
110    pub const GSP_UNKNOWN_SIGNAL: u16 = 0x3003;
111    /// Duplicate request_id.
112    pub const GSP_DUPLICATE_REQUEST: u16 = 0x3004;
113    /// Signal contradicts the current state.
114    pub const GSP_STATE_CONFLICT: u16 = 0x3005;
115    /// Signal precondition not met.
116    pub const GSP_PRECONDITION_FAILED: u16 = 0x3006;
117}
118
119/// Compile-time descriptor: code plus its `retryable` / `fatal` semantics.
120///
121/// Used both as a documentation registry and to populate runtime
122/// `ErrorObject` values.
123#[derive(Copy, Clone, Debug)]
124pub struct ErrorSpec {
125    /// Numeric code (see [`codes`]).
126    pub code: u16,
127    /// Class.
128    pub class: ErrorClass,
129    /// MAY be retried by the client.
130    pub retryable: bool,
131    /// Fatal — the node moves to FAILED.
132    pub fatal: bool,
133    /// Stable symbolic name for logs.
134    pub name: &'static str,
135}
136
137impl ErrorSpec {
138    /// Returns the spec for a known code, or `None` otherwise.
139    pub fn lookup(code: u16) -> Option<ErrorSpec> {
140        use codes::*;
141        Some(match code {
142            UNSUPPORTED_VERSION => spec(
143                code,
144                ErrorClass::Schema,
145                false,
146                true,
147                "ERR_UNSUPPORTED_VERSION",
148            ),
149            UNKNOWN_GROUP => spec(code, ErrorClass::State, false, true, "ERR_UNKNOWN_GROUP"),
150            EPOCH_MISMATCH => spec(code, ErrorClass::State, true, false, "ERR_EPOCH_MISMATCH"),
151            TRANSITION_MISMATCH => spec(
152                code,
153                ErrorClass::State,
154                true,
155                false,
156                "ERR_TRANSITION_MISMATCH",
157            ),
158            REPLAY_DETECTED => spec(
159                code,
160                ErrorClass::Crypto,
161                false,
162                false,
163                "ERR_REPLAY_DETECTED",
164            ),
165            DECRYPT_FAILED => spec(code, ErrorClass::Crypto, true, false, "ERR_DECRYPT_FAILED"),
166            COMMIT_INVALID => spec(code, ErrorClass::Crypto, false, true, "ERR_COMMIT_INVALID"),
167            STREAM_POLICY_VIOLATION => spec(
168                code,
169                ErrorClass::Policy,
170                false,
171                false,
172                "ERR_STREAM_POLICY_VIOLATION",
173            ),
174            TRANSITION_IN_PROGRESS => spec(
175                code,
176                ErrorClass::State,
177                false,
178                false,
179                "ERR_TRANSITION_IN_PROGRESS",
180            ),
181            PREPARE_TIMEOUT => spec(code, ErrorClass::State, true, false, "ERR_PREPARE_TIMEOUT"),
182            READY_TIMEOUT => spec(code, ErrorClass::State, true, false, "ERR_READY_TIMEOUT"),
183            EXECUTE_TIMEOUT => spec(code, ErrorClass::State, true, false, "ERR_EXECUTE_TIMEOUT"),
184            COORDINATOR_GONE => spec(code, ErrorClass::State, true, false, "ERR_COORDINATOR_GONE"),
185            DIGEST_MISMATCH => spec(code, ErrorClass::State, false, true, "ERR_DIGEST_MISMATCH"),
186            GAP_BAD_SOURCE_ID => spec(
187                code,
188                ErrorClass::Schema,
189                false,
190                false,
191                "ERR_GAP_BAD_SOURCE_ID",
192            ),
193            GAP_DECODE_FAILED => spec(
194                code,
195                ErrorClass::Schema,
196                false,
197                false,
198                "ERR_GAP_DECODE_FAILED",
199            ),
200            GAP_AUTH_FAILED => spec(
201                code,
202                ErrorClass::Crypto,
203                false,
204                false,
205                "ERR_GAP_AUTH_FAILED",
206            ),
207            GAP_REPLAY_DETECTED => spec(
208                code,
209                ErrorClass::Crypto,
210                false,
211                false,
212                "ERR_GAP_REPLAY_DETECTED",
213            ),
214            GAP_EPOCH_STALE => spec(
215                code,
216                ErrorClass::Crypto,
217                false,
218                false,
219                "ERR_GAP_EPOCH_STALE",
220            ),
221            GAP_KEY_PHASE_UNKNOWN => spec(
222                code,
223                ErrorClass::Crypto,
224                false,
225                false,
226                "ERR_GAP_KEY_PHASE_UNKNOWN",
227            ),
228            GTP_BAD_LENGTH => spec(code, ErrorClass::Schema, false, false, "ERR_GTP_BAD_LENGTH"),
229            GTP_UNSUPPORTED_CONTENT_TYPE => spec(
230                code,
231                ErrorClass::Schema,
232                false,
233                false,
234                "ERR_GTP_UNSUPPORTED_CONTENT_TYPE",
235            ),
236            GTP_DUPLICATE_MESSAGE => spec(
237                code,
238                ErrorClass::Policy,
239                false,
240                false,
241                "ERR_GTP_DUPLICATE_MESSAGE",
242            ),
243            GTP_POLICY_REJECTED => spec(
244                code,
245                ErrorClass::Policy,
246                false,
247                false,
248                "ERR_GTP_POLICY_REJECTED",
249            ),
250            GTP_ATTACHMENT_INTEGRITY => spec(
251                code,
252                ErrorClass::Schema,
253                false,
254                false,
255                "ERR_GTP_ATTACHMENT_INTEGRITY",
256            ),
257            GTP_REQUEST_TIMEOUT => spec(
258                code,
259                ErrorClass::State,
260                true,
261                false,
262                "ERR_GTP_REQUEST_TIMEOUT",
263            ),
264            GSP_BAD_SCHEMA => spec(code, ErrorClass::Schema, false, false, "ERR_GSP_BAD_SCHEMA"),
265            GSP_UNAUTHORIZED => spec(
266                code,
267                ErrorClass::Authz,
268                false,
269                false,
270                "ERR_GSP_UNAUTHORIZED",
271            ),
272            GSP_UNKNOWN_SIGNAL => spec(
273                code,
274                ErrorClass::Schema,
275                false,
276                false,
277                "ERR_GSP_UNKNOWN_SIGNAL",
278            ),
279            GSP_DUPLICATE_REQUEST => spec(
280                code,
281                ErrorClass::Policy,
282                false,
283                false,
284                "ERR_GSP_DUPLICATE_REQUEST",
285            ),
286            GSP_STATE_CONFLICT => spec(
287                code,
288                ErrorClass::State,
289                true,
290                false,
291                "ERR_GSP_STATE_CONFLICT",
292            ),
293            GSP_PRECONDITION_FAILED => spec(
294                code,
295                ErrorClass::State,
296                false,
297                false,
298                "ERR_GSP_PRECONDITION_FAILED",
299            ),
300            _ => return None,
301        })
302    }
303}
304
305const fn spec(
306    code: u16,
307    class: ErrorClass,
308    retryable: bool,
309    fatal: bool,
310    name: &'static str,
311) -> ErrorSpec {
312    ErrorSpec {
313        code,
314        class,
315        retryable,
316        fatal,
317        name,
318    }
319}