ecksport_core/
frame.rs

1//! Frame data types for describing the wire format abstractly.
2//!
3//! In this context, we use "client" to refer to the initiator that makes an
4//! outgoing connection and "server" to refer to the responder that receives
5//! an incoming connection.  This doesn't have to map onto high-level protocol
6//! concepts in the consumer, naturally.
7
8use std::io;
9
10use borsh::{BorshDeserialize, BorshSerialize};
11use num_enum::{IntoPrimitive, TryFromPrimitive};
12
13use crate::topic;
14
15#[derive(Clone, Debug)]
16pub struct Frame {
17    header: FrameHeader,
18    body: FrameBody,
19}
20
21impl Frame {
22    pub fn with_body(body: FrameBody) -> Self {
23        Self {
24            header: FrameHeader {
25                // TODO determine how we specify flags
26                flags: 0,
27            },
28            body,
29        }
30    }
31
32    /// Gets the frame body.
33    pub fn body(&self) -> &FrameBody {
34        &self.body
35    }
36
37    /// Gets the frame type.
38    pub fn ty(&self) -> FrameType {
39        self.body.ty()
40    }
41}
42
43#[derive(Clone, Debug)]
44pub struct FrameHeader {
45    flags: u16,
46}
47
48/// Flags sent along with a message payload to signal various conditions.
49#[derive(Copy, Clone, Debug, Eq, PartialEq, BorshDeserialize, BorshSerialize)]
50pub struct MsgFlags {
51    /// If this closes the sending side of the channel (two close messages have
52    /// to be sent to fully close it).
53    pub close: bool,
54
55    /// If this data is an unusual error condition.
56    pub err: bool,
57}
58
59impl MsgFlags {
60    /// New flagset with all unset.
61    pub fn none() -> Self {
62        Self {
63            close: false,
64            err: false,
65        }
66    }
67
68    /// New flagset with only close set.
69    pub fn close() -> Self {
70        let mut this = Self::default();
71        this.close = true;
72        this
73    }
74
75    /// New flagset with just close set to a particular value.
76    pub fn with_close(close: bool) -> Self {
77        Self { close, err: false }
78    }
79}
80
81impl Default for MsgFlags {
82    fn default() -> Self {
83        Self::none()
84    }
85}
86
87#[derive(Copy, Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
88#[repr(u8)]
89pub enum FrameType {
90    /// Hello used to announce client to server.
91    ClientHello = 0,
92
93    /// Hello used to exchange protocols.
94    ServerHello = 1,
95
96    /// Finished authentication, picking a protocol.
97    ClientFinish = 2,
98
99    /// Open a new channel on a topic with some payload and possibly closing it.
100    OpenChan = 3,
101
102    /// Push data on a channel, also possibly closing it.
103    PushChan = 4,
104
105    /// Close a channel without sending any data.
106    CloseChan = 5,
107
108    /// Unconditional message that does not open a channel but does have a
109    /// topic, which may or may not be able to handle it.  Can be used to send
110    /// one-off messages that don't need the overhead of initing a new channel
111    /// only to close it and require the other side ack the closure.
112    Notification = 6,
113
114    /// Graceful goodbye with some reason.
115    Goodbye = 7,
116
117    /// Queries the protocols a server reports.
118    ClientQuery = 16,
119
120    /// Response with the protocols the server reports.
121    ServerDump = 17,
122
123    /// Ping with data.
124    Ping = 252,
125
126    /// Pong with data.
127    Pong = 253,
128}
129
130/// Roughly parsed frame body.
131#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
132pub enum FrameBody {
133    ClientHello(ClientHelloData),
134    ServerHello(ServerHelloData),
135    ClientFinish(ClientFinishData),
136    OpenChan(OpenData),
137    PushChan(PushData),
138    CloseChan(CloseData),
139    Notification(NotificationData),
140    Goodbye(GoodbyeData),
141    ClientQuery(ClientQueryData),
142    ServerDump(ServerDumpData),
143    Ping(Vec<u8>),
144    Pong(Vec<u8>),
145}
146
147impl FrameBody {
148    /// Decodes an instance from a buf.
149    pub fn from_buf(buf: &[u8]) -> Result<Self, io::Error> {
150        borsh::from_slice(buf)
151    }
152
153    /// Encodes self into an existing vec.
154    pub fn into_vec(&self, dest: &mut Vec<u8>) -> Result<(), io::Error> {
155        borsh::to_writer(dest, self)
156    }
157
158    /// Encodes self to a new vec.
159    pub fn to_vec(&self) -> Result<Vec<u8>, io::Error> {
160        borsh::to_vec(self)
161    }
162
163    pub fn ty(&self) -> FrameType {
164        match self {
165            Self::ClientHello(_) => FrameType::ClientHello,
166            Self::ServerHello(_) => FrameType::ServerHello,
167            Self::ClientFinish(_) => FrameType::ClientFinish,
168            Self::OpenChan(_) => FrameType::OpenChan,
169            Self::PushChan(_) => FrameType::PushChan,
170            Self::CloseChan(_) => FrameType::CloseChan,
171            Self::Notification(_) => FrameType::Notification,
172            Self::Goodbye(_) => FrameType::Goodbye,
173            Self::ClientQuery(_) => FrameType::ClientQuery,
174            Self::ServerDump(_) => FrameType::ServerDump,
175            Self::Ping(_) => FrameType::Ping,
176            Self::Pong(_) => FrameType::Pong,
177        }
178    }
179}
180
181/// Initial message client sends to server to begin identifying itself and pick
182/// the protocol.
183#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
184pub struct ClientHelloData {
185    /// Client brand.
186    agent: String,
187
188    /// Protocol we're picking.
189    protocol: topic::Topic,
190
191    /// If we want an identity challenge.  This also indicates that the client
192    /// wants to identify itself too.
193    server_challenge: Option<ChallengeData>,
194}
195
196impl ClientHelloData {
197    pub fn new(
198        agent: String,
199        protocol: topic::Topic,
200        server_challenge: Option<ChallengeData>,
201    ) -> Self {
202        Self {
203            agent,
204            protocol,
205            server_challenge,
206        }
207    }
208
209    pub fn agent(&self) -> &str {
210        &self.agent
211    }
212
213    pub fn protocol(&self) -> topic::Topic {
214        self.protocol
215    }
216
217    pub fn challenge(&self) -> Option<&ChallengeData> {
218        self.server_challenge.as_ref()
219    }
220}
221
222/// Acknowledgement of the `ClientHello` and the choice of protocol.  Includes
223/// challenges and responses as necessary.
224#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
225pub struct ServerHelloData {
226    /// Server brand.
227    agent: String,
228
229    /// Response if the client asked for it and the server is giving it.
230    response: Option<ResponseData>,
231
232    /// Challenge if `ClientHello`'s `want_challenge` is set.
233    challenge: Option<ChallengeData>,
234}
235
236impl ServerHelloData {
237    pub fn new(
238        agent: String,
239        response: Option<ResponseData>,
240        challenge: Option<ChallengeData>,
241    ) -> Self {
242        Self {
243            agent,
244            response,
245            challenge,
246        }
247    }
248
249    pub fn agent(&self) -> &str {
250        &self.agent
251    }
252
253    pub fn response(&self) -> Option<&ResponseData> {
254        self.response.as_ref()
255    }
256
257    pub fn challenge(&self) -> Option<&ChallengeData> {
258        self.challenge.as_ref()
259    }
260}
261
262/// Finishes the client's side of the handshake if we're identifying ourselves.
263/// This is omitted if we're not doing the full self-identification handshake.
264#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
265pub struct ClientFinishData {
266    response: Option<ResponseData>,
267}
268
269impl ClientFinishData {
270    pub fn new(response: Option<ResponseData>) -> Self {
271        Self { response }
272    }
273
274    pub fn response(&self) -> Option<&ResponseData> {
275        self.response.as_ref()
276    }
277}
278
279/// Data passed when opening a new channel.  Channel ID is implicit.
280#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
281pub struct OpenData {
282    /// Topic of the new channel we're opening.
283    topic: topic::Topic,
284
285    /// Payload flags.
286    flags: MsgFlags,
287
288    /// Initial message.
289    payload: Vec<u8>,
290}
291
292impl OpenData {
293    pub fn new(topic: topic::Topic, flags: MsgFlags, payload: Vec<u8>) -> Self {
294        Self {
295            topic,
296            flags,
297            payload,
298        }
299    }
300
301    pub fn topic(&self) -> topic::Topic {
302        self.topic
303    }
304
305    pub fn flags(&self) -> &MsgFlags {
306        &self.flags
307    }
308
309    pub fn close(&self) -> bool {
310        self.flags.close
311    }
312
313    pub fn payload(&self) -> &[u8] {
314        &self.payload
315    }
316
317    pub fn into_payload(self) -> Vec<u8> {
318        self.payload
319    }
320}
321
322/// Data passed when pushing data on a channel.
323#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
324pub struct PushData {
325    /// Channel ID.
326    chan_id: u32,
327
328    /// Payload flags.
329    flags: MsgFlags,
330
331    /// Initial message.
332    payload: Vec<u8>,
333}
334
335impl PushData {
336    pub fn new(chan_id: u32, flags: MsgFlags, payload: Vec<u8>) -> Self {
337        Self {
338            chan_id,
339            flags,
340            payload,
341        }
342    }
343
344    pub fn chan_id(&self) -> u32 {
345        self.chan_id
346    }
347
348    pub fn flags(&self) -> &MsgFlags {
349        &self.flags
350    }
351
352    pub fn close(&self) -> bool {
353        self.flags.close
354    }
355
356    pub fn payload(&self) -> &[u8] {
357        &self.payload
358    }
359
360    pub fn into_payload(self) -> Vec<u8> {
361        self.payload
362    }
363}
364
365#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
366pub struct CloseData {
367    chan_id: u32,
368}
369
370impl CloseData {
371    pub fn new(chan_id: u32) -> Self {
372        Self { chan_id }
373    }
374
375    pub fn chan_id(&self) -> u32 {
376        self.chan_id
377    }
378}
379
380#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
381pub struct NotificationData {
382    topic: topic::Topic,
383    payload: Vec<u8>,
384}
385
386impl NotificationData {
387    pub fn new(topic: topic::Topic, payload: Vec<u8>) -> Self {
388        Self { topic, payload }
389    }
390
391    pub fn topic(&self) -> topic::Topic {
392        self.topic
393    }
394
395    pub fn payload(&self) -> &[u8] {
396        &self.payload
397    }
398
399    pub fn into_payload(self) -> Vec<u8> {
400        self.payload
401    }
402}
403
404/// Sent before closing a connection.
405#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
406pub struct GoodbyeData {
407    /// Code, 0 is ok.  Any other value is an application-defined error code.
408    code: i16,
409
410    /// Human-readable message.
411    msg: String,
412}
413
414impl GoodbyeData {
415    pub fn new(code: i16, msg: String) -> Self {
416        Self { code, msg }
417    }
418
419    pub fn code(&self) -> i16 {
420        self.code
421    }
422
423    pub fn msg(&self) -> &str {
424        &self.msg
425    }
426}
427
428/// Challenge data used when identifying self.  This includes a nonce that
429/// we'll use with another nonce for the challenge.
430#[derive(Copy, Clone, Debug, BorshDeserialize, BorshSerialize)]
431pub struct ChallengeData {
432    nonce: [u8; 16],
433}
434
435impl ChallengeData {
436    pub fn from_nonce_buf(nonce: [u8; 16]) -> Self {
437        Self { nonce }
438    }
439
440    pub fn nonce(&self) -> &[u8; 16] {
441        &self.nonce
442    }
443}
444
445/// Protocol-specific response message, which we expect to be a signature and
446/// maybe the also the pubkey we're identifying ourselves as.
447#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
448pub struct ResponseData {
449    data: Vec<u8>,
450}
451
452impl ResponseData {
453    pub fn new(data: Vec<u8>) -> Self {
454        Self { data }
455    }
456
457    pub fn from_buf(buf: &[u8]) -> Self {
458        Self::new(buf.to_vec())
459    }
460
461    pub fn data(&self) -> &[u8] {
462        &self.data
463    }
464}
465
466/// Query for the protocols the server supports.
467#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
468pub struct ClientQueryData {
469    /// Client brand.
470    agent: String,
471}
472
473impl ClientQueryData {
474    pub fn new(agent: String) -> Self {
475        Self { agent }
476    }
477}
478
479/// Response to the query of the protocols the server supports.
480#[derive(Clone, Debug, BorshDeserialize, BorshSerialize)]
481pub struct ServerDumpData {
482    /// Server brand.
483    agent: String,
484
485    /// Protocols the server supports.
486    protocols: Vec<topic::Topic>,
487}
488
489impl ServerDumpData {
490    pub fn new(agent: String, protocols: Vec<topic::Topic>) -> Self {
491        Self { agent, protocols }
492    }
493
494    pub fn agent(&self) -> &str {
495        &self.agent
496    }
497
498    pub fn protocols(&self) -> &[topic::Topic] {
499        &self.protocols
500    }
501}
502
503/// Viewpoint-agnostic side of a channel, where client is generically the
504/// initator and server is generically the acceptor.
505#[derive(Copy, Clone, Debug, Eq, PartialEq)]
506pub enum Side {
507    Client,
508    Server,
509}
510
511/// Describes what auth relationship we want to set up with the server.  This
512/// ignores any authentication that may be provided by the underlying transport.
513#[derive(Copy, Clone, Debug, Eq, PartialEq)]
514pub enum AuthIntent {
515    /// Neither side is doing any authentication.
516    Neither,
517
518    /// Only the client (initiator) authenticates to the server.
519    ClientOnly,
520
521    /// Only the server (acceptor) authenticates to the client.
522    ServerOnly,
523
524    /// Both sides of the connection authenticate with each other.
525    Mutual,
526}
527
528impl AuthIntent {
529    /// Gets if the side in question should sign, given the signing intent.
530    pub fn should_sign(&self, side: Side) -> bool {
531        match (self, side) {
532            (Self::ClientOnly | Self::Mutual, Side::Client) => true,
533            (Self::ServerOnly | Self::Mutual, Side::Server) => true,
534            _ => false,
535        }
536    }
537
538    pub fn should_exchange_chals(&self) -> bool {
539        !matches!(self, Self::Neither)
540    }
541}