Skip to main content

relaygate_protocol/
frame.rs

1use std::fmt;
2
3use bytes::Bytes;
4
5use crate::{BearerToken, BindingId, Destination, PipeId, SessionId};
6
7/// Stable wire error codes whose discriminants are part of protocol version 3.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum ErrorCode {
11    InvalidArgument = 1,
12    Unauthenticated = 2,
13    PermissionDenied = 3,
14    NotFound = 4,
15    FailedPrecondition = 5,
16    Unavailable = 6,
17    DeadlineExceeded = 7,
18    /// Covers admission, capacity, queue, and size limits.
19    ResourceExhausted = 8,
20    Cancelled = 9,
21    /// A peer violated the wire protocol or state-machine contract.
22    ProtocolError = 10,
23    /// A local invariant or task failed without a peer protocol violation.
24    Internal = 11,
25    AlreadyExists = 12,
26}
27
28impl ErrorCode {
29    /// Canonical snake_case name for metric labels and structured logs.
30    #[must_use]
31    pub const fn metric_name(self) -> &'static str {
32        match self {
33            Self::InvalidArgument => "invalid_argument",
34            Self::Unauthenticated => "unauthenticated",
35            Self::PermissionDenied => "permission_denied",
36            Self::NotFound => "not_found",
37            Self::FailedPrecondition => "failed_precondition",
38            Self::Unavailable => "unavailable",
39            Self::DeadlineExceeded => "deadline_exceeded",
40            Self::ResourceExhausted => "resource_exhausted",
41            Self::Cancelled => "cancelled",
42            Self::ProtocolError => "protocol_error",
43            Self::Internal => "internal",
44            Self::AlreadyExists => "already_exists",
45        }
46    }
47
48    /// Decodes the wire byte; `None` for values outside the contract.
49    #[must_use]
50    pub fn from_wire(value: u8) -> Option<Self> {
51        match value {
52            1 => Some(Self::InvalidArgument),
53            2 => Some(Self::Unauthenticated),
54            3 => Some(Self::PermissionDenied),
55            4 => Some(Self::NotFound),
56            5 => Some(Self::FailedPrecondition),
57            6 => Some(Self::Unavailable),
58            7 => Some(Self::DeadlineExceeded),
59            8 => Some(Self::ResourceExhausted),
60            9 => Some(Self::Cancelled),
61            10 => Some(Self::ProtocolError),
62            11 => Some(Self::Internal),
63            12 => Some(Self::AlreadyExists),
64            _ => None,
65        }
66    }
67}
68
69/// Whether a peer may have observed a failed control operation.
70///
71/// This is control-operation metadata, not an application payload receipt.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[repr(u8)]
74pub enum PeerObservation {
75    /// The operation did not reach the peer's observable state.
76    NotObserved = 1,
77    /// The sender cannot determine whether the peer observed the operation.
78    MaybeObserved = 2,
79    /// The peer observed or committed the operation before failure.
80    Observed = 3,
81}
82
83impl PeerObservation {
84    /// Decodes the wire byte; `None` for values outside the contract.
85    #[must_use]
86    pub fn from_wire(value: u8) -> Option<Self> {
87        match value {
88            1 => Some(Self::NotObserved),
89            2 => Some(Self::MaybeObserved),
90            3 => Some(Self::Observed),
91            _ => None,
92        }
93    }
94}
95
96/// Wire messages exchanged within one SDK–Gateway session.
97///
98/// Credentials are operation-scoped and appear only in [`Frame::Publish`] and
99/// [`Frame::Dial`]; application data remains opaque in [`Frame::Data`].
100#[derive(Clone, PartialEq, Eq)]
101pub enum Frame {
102    Hello,
103    Welcome {
104        session_id: SessionId,
105    },
106    SessionRejected {
107        code: ErrorCode,
108        message: String,
109    },
110    Publish {
111        request_id: u64,
112        destination: Destination,
113        access_token: BearerToken,
114    },
115    Published {
116        request_id: u64,
117        binding_id: BindingId,
118    },
119    PublishFailed {
120        request_id: u64,
121        code: ErrorCode,
122        message: String,
123    },
124    Unpublish {
125        request_id: u64,
126        binding_id: BindingId,
127    },
128    Unpublished {
129        request_id: u64,
130    },
131    Dial {
132        connection_id: u64,
133        destination: Destination,
134        access_token: BearerToken,
135    },
136    /// Offers an incoming Pipe to the session that owns a selected binding.
137    Offer {
138        pipe_id: PipeId,
139        binding_id: BindingId,
140        destination: Destination,
141    },
142    OfferAccepted {
143        pipe_id: PipeId,
144    },
145    OfferRejected {
146        pipe_id: PipeId,
147        code: ErrorCode,
148        message: String,
149    },
150    /// Confirms that a Pipe is established for the dialing session.
151    Opened {
152        pipe_id: PipeId,
153    },
154    DialFailed {
155        connection_id: u64,
156        code: ErrorCode,
157        observation: PeerObservation,
158        message: String,
159    },
160    Data {
161        pipe_id: PipeId,
162        payload: Bytes,
163    },
164    /// Half-closes the sender's write direction of a Pipe.
165    Fin {
166        pipe_id: PipeId,
167    },
168    /// Closes a Pipe normally in both directions.
169    Close {
170        pipe_id: PipeId,
171    },
172    /// Terminates a Pipe with an error.
173    Reset {
174        pipe_id: PipeId,
175        code: ErrorCode,
176        message: String,
177    },
178    Ping {
179        nonce: u64,
180    },
181    Pong {
182        nonce: u64,
183    },
184    /// Cancels a pending Pipe establishment attempt.
185    Cancel {
186        pipe_id: PipeId,
187    },
188}
189
190impl fmt::Debug for Frame {
191    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192        match self {
193            Self::Hello => formatter.write_str("Hello"),
194            Self::Welcome { session_id } => formatter
195                .debug_struct("Welcome")
196                .field("session_id", session_id)
197                .finish(),
198            Self::SessionRejected { code, message } => formatter
199                .debug_struct("SessionRejected")
200                .field("code", code)
201                .field("message", message)
202                .finish(),
203            Self::Publish {
204                request_id,
205                destination,
206                access_token,
207            } => formatter
208                .debug_struct("Publish")
209                .field("request_id", request_id)
210                .field("destination", destination)
211                .field("access_token", access_token)
212                .finish(),
213            Self::Published {
214                request_id,
215                binding_id,
216            } => formatter
217                .debug_struct("Published")
218                .field("request_id", request_id)
219                .field("binding_id", binding_id)
220                .finish(),
221            Self::PublishFailed {
222                request_id,
223                code,
224                message,
225            } => formatter
226                .debug_struct("PublishFailed")
227                .field("request_id", request_id)
228                .field("code", code)
229                .field("message", message)
230                .finish(),
231            Self::Unpublish {
232                request_id,
233                binding_id,
234            } => formatter
235                .debug_struct("Unpublish")
236                .field("request_id", request_id)
237                .field("binding_id", binding_id)
238                .finish(),
239            Self::Unpublished { request_id } => formatter
240                .debug_struct("Unpublished")
241                .field("request_id", request_id)
242                .finish(),
243            Self::Dial {
244                connection_id,
245                destination,
246                access_token,
247            } => formatter
248                .debug_struct("Dial")
249                .field("connection_id", connection_id)
250                .field("destination", destination)
251                .field("access_token", access_token)
252                .finish(),
253            Self::Offer {
254                pipe_id,
255                binding_id,
256                destination,
257            } => formatter
258                .debug_struct("Offer")
259                .field("pipe_id", pipe_id)
260                .field("binding_id", binding_id)
261                .field("destination", destination)
262                .finish(),
263            Self::OfferAccepted { pipe_id } => formatter
264                .debug_struct("OfferAccepted")
265                .field("pipe_id", pipe_id)
266                .finish(),
267            Self::OfferRejected {
268                pipe_id,
269                code,
270                message,
271            } => formatter
272                .debug_struct("OfferRejected")
273                .field("pipe_id", pipe_id)
274                .field("code", code)
275                .field("message", message)
276                .finish(),
277            Self::Opened { pipe_id } => formatter
278                .debug_struct("Opened")
279                .field("pipe_id", pipe_id)
280                .finish(),
281            Self::DialFailed {
282                connection_id,
283                code,
284                observation,
285                message,
286            } => formatter
287                .debug_struct("DialFailed")
288                .field("connection_id", connection_id)
289                .field("code", code)
290                .field("observation", observation)
291                .field("message", message)
292                .finish(),
293            Self::Data { pipe_id, payload } => formatter
294                .debug_struct("Data")
295                .field("pipe_id", pipe_id)
296                .field("payload_len", &payload.len())
297                .finish(),
298            Self::Fin { pipe_id } => formatter
299                .debug_struct("Fin")
300                .field("pipe_id", pipe_id)
301                .finish(),
302            Self::Close { pipe_id } => formatter
303                .debug_struct("Close")
304                .field("pipe_id", pipe_id)
305                .finish(),
306            Self::Reset {
307                pipe_id,
308                code,
309                message,
310            } => formatter
311                .debug_struct("Reset")
312                .field("pipe_id", pipe_id)
313                .field("code", code)
314                .field("message", message)
315                .finish(),
316            Self::Ping { nonce } => formatter
317                .debug_struct("Ping")
318                .field("nonce", nonce)
319                .finish(),
320            Self::Pong { nonce } => formatter
321                .debug_struct("Pong")
322                .field("nonce", nonce)
323                .finish(),
324            Self::Cancel { pipe_id } => formatter
325                .debug_struct("Cancel")
326                .field("pipe_id", pipe_id)
327                .finish(),
328        }
329    }
330}