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
69    /// Unknown audio source.
70    pub const GAP_BAD_SOURCE_ID: u16 = 0x1001;
71    /// Opus decoder error.
72    pub const GAP_DECODE_FAILED: u16 = 0x1002;
73    /// rtp_sequence already seen for this source.
74    pub const GAP_REPLAY_DETECTED: u16 = 0x1003;
75    /// key_phase points at a stale epoch.
76    pub const GAP_EPOCH_STALE: u16 = 0x1004;
77
78    /// content_length does not match the body length.
79    pub const GTP_BAD_LENGTH: u16 = 0x2001;
80    /// content_type is not supported by the profile.
81    pub const GTP_UNSUPPORTED_CONTENT_TYPE: u16 = 0x2002;
82    /// Duplicate (sender_id, message_id).
83    pub const GTP_DUPLICATE_MESSAGE: u16 = 0x2003;
84    /// Message rejected by the policy layer (retention / quota / …).
85    pub const GTP_POLICY_REJECTED: u16 = 0x2004;
86
87    /// Invalid args schema for the given signal_type.
88    pub const GSP_BAD_SCHEMA: u16 = 0x3001;
89    /// Sender is not authorised to issue this signal.
90    pub const GSP_UNAUTHORIZED: u16 = 0x3002;
91    /// signal_type is not in the registry.
92    pub const GSP_UNKNOWN_SIGNAL: u16 = 0x3003;
93    /// Duplicate request_id.
94    pub const GSP_DUPLICATE_REQUEST: u16 = 0x3004;
95    /// Signal contradicts the current state.
96    pub const GSP_STATE_CONFLICT: u16 = 0x3005;
97}
98
99/// Compile-time descriptor: code plus its `retryable` / `fatal` semantics.
100///
101/// Used both as a documentation registry and to populate runtime
102/// `ErrorObject` values.
103#[derive(Copy, Clone, Debug)]
104pub struct ErrorSpec {
105    /// Numeric code (see [`codes`]).
106    pub code: u16,
107    /// Class.
108    pub class: ErrorClass,
109    /// MAY be retried by the client.
110    pub retryable: bool,
111    /// Fatal — the node moves to FAILED.
112    pub fatal: bool,
113    /// Stable symbolic name for logs.
114    pub name: &'static str,
115}
116
117impl ErrorSpec {
118    /// Returns the spec for a known code, or `None` otherwise.
119    pub fn lookup(code: u16) -> Option<ErrorSpec> {
120        use codes::*;
121        Some(match code {
122            UNSUPPORTED_VERSION => spec(code, ErrorClass::Schema, false, true, "ERR_UNSUPPORTED_VERSION"),
123            UNKNOWN_GROUP => spec(code, ErrorClass::State, false, true, "ERR_UNKNOWN_GROUP"),
124            EPOCH_MISMATCH => spec(code, ErrorClass::State, true, false, "ERR_EPOCH_MISMATCH"),
125            TRANSITION_MISMATCH => spec(code, ErrorClass::State, true, false, "ERR_TRANSITION_MISMATCH"),
126            REPLAY_DETECTED => spec(code, ErrorClass::Crypto, false, false, "ERR_REPLAY_DETECTED"),
127            DECRYPT_FAILED => spec(code, ErrorClass::Crypto, true, false, "ERR_DECRYPT_FAILED"),
128            COMMIT_INVALID => spec(code, ErrorClass::Crypto, false, true, "ERR_COMMIT_INVALID"),
129            STREAM_POLICY_VIOLATION => spec(code, ErrorClass::Policy, false, false, "ERR_STREAM_POLICY_VIOLATION"),
130            TRANSITION_IN_PROGRESS => spec(code, ErrorClass::State, false, false, "ERR_TRANSITION_IN_PROGRESS"),
131            GAP_BAD_SOURCE_ID => spec(code, ErrorClass::Schema, false, false, "ERR_GAP_BAD_SOURCE_ID"),
132            GAP_DECODE_FAILED => spec(code, ErrorClass::Schema, false, false, "ERR_GAP_DECODE_FAILED"),
133            GAP_REPLAY_DETECTED => spec(code, ErrorClass::Crypto, false, false, "ERR_GAP_REPLAY_DETECTED"),
134            GAP_EPOCH_STALE => spec(code, ErrorClass::Crypto, false, false, "ERR_GAP_EPOCH_STALE"),
135            GTP_BAD_LENGTH => spec(code, ErrorClass::Schema, false, false, "ERR_GTP_BAD_LENGTH"),
136            GTP_UNSUPPORTED_CONTENT_TYPE => spec(code, ErrorClass::Schema, false, false, "ERR_GTP_UNSUPPORTED_CONTENT_TYPE"),
137            GTP_DUPLICATE_MESSAGE => spec(code, ErrorClass::Policy, false, false, "ERR_GTP_DUPLICATE_MESSAGE"),
138            GTP_POLICY_REJECTED => spec(code, ErrorClass::Policy, false, false, "ERR_GTP_POLICY_REJECTED"),
139            GSP_BAD_SCHEMA => spec(code, ErrorClass::Schema, false, false, "ERR_GSP_BAD_SCHEMA"),
140            GSP_UNAUTHORIZED => spec(code, ErrorClass::Authz, false, false, "ERR_GSP_UNAUTHORIZED"),
141            GSP_UNKNOWN_SIGNAL => spec(code, ErrorClass::Schema, false, false, "ERR_GSP_UNKNOWN_SIGNAL"),
142            GSP_DUPLICATE_REQUEST => spec(code, ErrorClass::Policy, false, false, "ERR_GSP_DUPLICATE_REQUEST"),
143            GSP_STATE_CONFLICT => spec(code, ErrorClass::State, true, false, "ERR_GSP_STATE_CONFLICT"),
144            _ => return None,
145        })
146    }
147}
148
149const fn spec(code: u16, class: ErrorClass, retryable: bool, fatal: bool, name: &'static str) -> ErrorSpec {
150    ErrorSpec { code, class, retryable, fatal, name }
151}