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