Skip to main content

nnrp_core/
enums.rs

1use crate::NnrpError;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4#[repr(u8)]
5pub enum MessageType {
6    ClientHello = 0x01,
7    ServerHelloAck = 0x02,
8    SessionPatch = 0x03,
9    SessionPatchAck = 0x04,
10    Close = 0x05,
11    Error = 0x06,
12    SessionOpen = 0x07,
13    SessionOpenAck = 0x08,
14    SessionClose = 0x09,
15    SessionCloseAck = 0x0a,
16    FrameSubmit = 0x10,
17    FrameCancel = 0x11,
18    ResultPush = 0x12,
19    ResultDrop = 0x13,
20    CachePut = 0x14,
21    CacheAck = 0x15,
22    CacheInvalidate = 0x16,
23    FlowUpdate = 0x17,
24    ResultHint = 0x18,
25    TransportProbe = 0x19,
26    TransportProbeAck = 0x1a,
27    SessionMigrate = 0x1b,
28    SessionMigrateAck = 0x1c,
29    Ping = 0x20,
30    Pong = 0x21,
31}
32
33impl MessageType {
34    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
35        let message_type = match value {
36            0x01 => Self::ClientHello,
37            0x02 => Self::ServerHelloAck,
38            0x03 => Self::SessionPatch,
39            0x04 => Self::SessionPatchAck,
40            0x05 => Self::Close,
41            0x06 => Self::Error,
42            0x07 => Self::SessionOpen,
43            0x08 => Self::SessionOpenAck,
44            0x09 => Self::SessionClose,
45            0x0a => Self::SessionCloseAck,
46            0x10 => Self::FrameSubmit,
47            0x11 => Self::FrameCancel,
48            0x12 => Self::ResultPush,
49            0x13 => Self::ResultDrop,
50            0x14 => Self::CachePut,
51            0x15 => Self::CacheAck,
52            0x16 => Self::CacheInvalidate,
53            0x17 => Self::FlowUpdate,
54            0x18 => Self::ResultHint,
55            0x19 => Self::TransportProbe,
56            0x1a => Self::TransportProbeAck,
57            0x1b => Self::SessionMigrate,
58            0x1c => Self::SessionMigrateAck,
59            0x20 => Self::Ping,
60            0x21 => Self::Pong,
61            _ => return Err(NnrpError::UnknownMessageType(value)),
62        };
63
64        Ok(message_type)
65    }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69#[repr(u8)]
70pub enum SessionPriorityClass {
71    Interactive = 0,
72    Balanced = 1,
73    Background = 2,
74}
75
76impl SessionPriorityClass {
77    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
78        match value {
79            0 => Ok(Self::Interactive),
80            1 => Ok(Self::Balanced),
81            2 => Ok(Self::Background),
82            _ => Err(NnrpError::UnknownEnumValue {
83                enum_name: "session_priority_class",
84                value: value as u64,
85            }),
86        }
87    }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[repr(u8)]
92pub enum SessionStatus {
93    Opened = 0,
94    Rejected = 1,
95    RetryLater = 2,
96    Resumed = 3,
97}
98
99impl SessionStatus {
100    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
101        match value {
102            0 => Ok(Self::Opened),
103            1 => Ok(Self::Rejected),
104            2 => Ok(Self::RetryLater),
105            3 => Ok(Self::Resumed),
106            _ => Err(NnrpError::UnknownEnumValue {
107                enum_name: "session_status",
108                value: value as u64,
109            }),
110        }
111    }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[repr(u16)]
116pub enum SessionCloseReason {
117    Normal = 0,
118    ClientShutdown = 1,
119    ServerShutdown = 2,
120    IdleTimeout = 3,
121    ProtocolError = 4,
122    AuthRevoked = 5,
123}
124
125impl SessionCloseReason {
126    pub fn try_from_u16(value: u16) -> Result<Self, NnrpError> {
127        match value {
128            0 => Ok(Self::Normal),
129            1 => Ok(Self::ClientShutdown),
130            2 => Ok(Self::ServerShutdown),
131            3 => Ok(Self::IdleTimeout),
132            4 => Ok(Self::ProtocolError),
133            5 => Ok(Self::AuthRevoked),
134            _ => Err(NnrpError::UnknownEnumValue {
135                enum_name: "close_reason",
136                value: value as u64,
137            }),
138        }
139    }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[repr(u8)]
144pub enum InFlightPolicy {
145    Drain = 0,
146    Abort = 1,
147}
148
149impl InFlightPolicy {
150    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
151        match value {
152            0 => Ok(Self::Drain),
153            1 => Ok(Self::Abort),
154            _ => Err(NnrpError::UnknownEnumValue {
155                enum_name: "in_flight_policy",
156                value: value as u64,
157            }),
158        }
159    }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163#[repr(u8)]
164pub enum SessionCloseStatus {
165    Acknowledged = 0,
166    Draining = 1,
167    Closed = 2,
168    Rejected = 3,
169}
170
171impl SessionCloseStatus {
172    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
173        match value {
174            0 => Ok(Self::Acknowledged),
175            1 => Ok(Self::Draining),
176            2 => Ok(Self::Closed),
177            3 => Ok(Self::Rejected),
178            _ => Err(NnrpError::UnknownEnumValue {
179                enum_name: "close_status",
180                value: value as u64,
181            }),
182        }
183    }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187#[repr(u8)]
188pub enum OperationState {
189    Accepted = 0,
190    Running = 1,
191    Partial = 2,
192    WaitingTool = 3,
193    Superseded = 4,
194    Cancelled = 5,
195    Failed = 6,
196    Completed = 7,
197}
198
199impl OperationState {
200    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
201        match value {
202            0 => Ok(Self::Accepted),
203            1 => Ok(Self::Running),
204            2 => Ok(Self::Partial),
205            3 => Ok(Self::WaitingTool),
206            4 => Ok(Self::Superseded),
207            5 => Ok(Self::Cancelled),
208            6 => Ok(Self::Failed),
209            7 => Ok(Self::Completed),
210            _ => Err(NnrpError::UnknownEnumValue {
211                enum_name: "operation_state",
212                value: value as u64,
213            }),
214        }
215    }
216
217    pub fn is_terminal(self) -> bool {
218        matches!(
219            self,
220            Self::Superseded | Self::Cancelled | Self::Failed | Self::Completed
221        )
222    }
223
224    pub fn can_transition_to(self, next: Self) -> bool {
225        if self.is_terminal() {
226            return false;
227        }
228
229        matches!(
230            (self, next),
231            (Self::Accepted, Self::Running)
232                | (Self::Accepted, Self::Cancelled)
233                | (Self::Accepted, Self::Failed)
234                | (Self::Accepted, Self::Superseded)
235                | (Self::Running, Self::Partial)
236                | (Self::Running, Self::WaitingTool)
237                | (Self::Running, Self::Cancelled)
238                | (Self::Running, Self::Failed)
239                | (Self::Running, Self::Completed)
240                | (Self::Running, Self::Superseded)
241                | (Self::Partial, Self::Partial)
242                | (Self::Partial, Self::WaitingTool)
243                | (Self::Partial, Self::Cancelled)
244                | (Self::Partial, Self::Failed)
245                | (Self::Partial, Self::Completed)
246                | (Self::Partial, Self::Superseded)
247                | (Self::WaitingTool, Self::Running)
248                | (Self::WaitingTool, Self::Cancelled)
249                | (Self::WaitingTool, Self::Failed)
250                | (Self::WaitingTool, Self::Superseded)
251        )
252    }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256#[repr(u8)]
257pub enum CancelScope {
258    Operation = 0,
259    Subtree = 1,
260    Group = 2,
261    Session = 3,
262}
263
264impl CancelScope {
265    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
266        match value {
267            0 => Ok(Self::Operation),
268            1 => Ok(Self::Subtree),
269            2 => Ok(Self::Group),
270            3 => Ok(Self::Session),
271            _ => Err(NnrpError::UnknownEnumValue {
272                enum_name: "cancel_scope",
273                value: value as u64,
274            }),
275        }
276    }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280#[repr(u8)]
281pub enum FlowScopeKind {
282    Connection = 0,
283    Session = 1,
284    Operation = 2,
285}
286
287impl FlowScopeKind {
288    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
289        match value {
290            0 => Ok(Self::Connection),
291            1 => Ok(Self::Session),
292            2 => Ok(Self::Operation),
293            _ => Err(NnrpError::UnknownEnumValue {
294                enum_name: "scope_kind",
295                value: value as u64,
296            }),
297        }
298    }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302#[repr(u8)]
303pub enum FlowUpdateReason {
304    Grant = 0,
305    Reduce = 1,
306    Pause = 2,
307    Resume = 3,
308    Congestion = 4,
309}
310
311impl FlowUpdateReason {
312    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
313        match value {
314            0 => Ok(Self::Grant),
315            1 => Ok(Self::Reduce),
316            2 => Ok(Self::Pause),
317            3 => Ok(Self::Resume),
318            4 => Ok(Self::Congestion),
319            _ => Err(NnrpError::UnknownEnumValue {
320                enum_name: "update_reason",
321                value: value as u64,
322            }),
323        }
324    }
325}
326
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328#[repr(u8)]
329pub enum BackpressureLevel {
330    None = 0,
331    Soft = 1,
332    Hard = 2,
333}
334
335impl BackpressureLevel {
336    pub fn try_from_u8(value: u8) -> Result<Self, NnrpError> {
337        match value {
338            0 => Ok(Self::None),
339            1 => Ok(Self::Soft),
340            2 => Ok(Self::Hard),
341            _ => Err(NnrpError::UnknownEnumValue {
342                enum_name: "backpressure_level",
343                value: value as u64,
344            }),
345        }
346    }
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub struct HeaderFlags(pub u32);
351
352impl HeaderFlags {
353    pub const NONE: Self = Self(0);
354    pub const ACK_REQUIRED: Self = Self(0x0000_0001);
355    pub const CAN_DROP: Self = Self(0x0000_0002);
356    pub const STALE: Self = Self(0x0000_0004);
357    pub const EOS: Self = Self(0x0000_0008);
358    pub const RETRANSMIT: Self = Self(0x0000_0010);
359    pub const KEYFRAME: Self = Self(0x0000_0020);
360    pub const KNOWN_MASK: u32 = Self::ACK_REQUIRED.0
361        | Self::CAN_DROP.0
362        | Self::STALE.0
363        | Self::EOS.0
364        | Self::RETRANSMIT.0
365        | Self::KEYFRAME.0;
366
367    pub fn validate_known(self) -> Result<(), NnrpError> {
368        if self.0 & !Self::KNOWN_MASK != 0 {
369            return Err(NnrpError::ReservedBitsSet {
370                value: self.0 as u64,
371                allowed: Self::KNOWN_MASK as u64,
372            });
373        }
374
375        Ok(())
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use crate::NnrpError;
382
383    use super::{
384        BackpressureLevel, CancelScope, FlowScopeKind, FlowUpdateReason, HeaderFlags,
385        InFlightPolicy, MessageType, OperationState, SessionCloseReason, SessionCloseStatus,
386        SessionPriorityClass, SessionStatus,
387    };
388
389    #[test]
390    fn preview3_message_type_assignments_are_frozen() {
391        let assignments = [
392            (0x01, MessageType::ClientHello),
393            (0x02, MessageType::ServerHelloAck),
394            (0x03, MessageType::SessionPatch),
395            (0x04, MessageType::SessionPatchAck),
396            (0x05, MessageType::Close),
397            (0x06, MessageType::Error),
398            (0x07, MessageType::SessionOpen),
399            (0x08, MessageType::SessionOpenAck),
400            (0x09, MessageType::SessionClose),
401            (0x0a, MessageType::SessionCloseAck),
402            (0x10, MessageType::FrameSubmit),
403            (0x11, MessageType::FrameCancel),
404            (0x12, MessageType::ResultPush),
405            (0x13, MessageType::ResultDrop),
406            (0x14, MessageType::CachePut),
407            (0x15, MessageType::CacheAck),
408            (0x16, MessageType::CacheInvalidate),
409            (0x17, MessageType::FlowUpdate),
410            (0x18, MessageType::ResultHint),
411            (0x19, MessageType::TransportProbe),
412            (0x1a, MessageType::TransportProbeAck),
413            (0x1b, MessageType::SessionMigrate),
414            (0x1c, MessageType::SessionMigrateAck),
415            (0x20, MessageType::Ping),
416            (0x21, MessageType::Pong),
417        ];
418
419        for (wire_value, message_type) in assignments {
420            assert_eq!(MessageType::try_from_u8(wire_value), Ok(message_type));
421            assert_eq!(message_type as u8, wire_value);
422        }
423    }
424
425    #[test]
426    fn message_type_rejects_unknown_values() {
427        assert_eq!(
428            MessageType::try_from_u8(0xff),
429            Err(NnrpError::UnknownMessageType(0xff))
430        );
431    }
432
433    #[test]
434    fn header_flags_accept_known_bits_and_reject_reserved_bits() {
435        let all_known = HeaderFlags(
436            HeaderFlags::NONE.0
437                | HeaderFlags::ACK_REQUIRED.0
438                | HeaderFlags::CAN_DROP.0
439                | HeaderFlags::STALE.0
440                | HeaderFlags::EOS.0
441                | HeaderFlags::RETRANSMIT.0
442                | HeaderFlags::KEYFRAME.0,
443        );
444
445        assert_eq!(HeaderFlags::KNOWN_MASK, 0x0000_003f);
446        assert_eq!(all_known.validate_known(), Ok(()));
447        assert_eq!(
448            HeaderFlags(0x0000_0040).validate_known(),
449            Err(NnrpError::ReservedBitsSet {
450                value: 0x0000_0040,
451                allowed: 0x0000_003f
452            })
453        );
454    }
455
456    #[test]
457    fn preview3_session_and_operation_enums_are_frozen() {
458        assert_enum_u8(
459            "session_priority_class",
460            SessionPriorityClass::try_from_u8,
461            0,
462            2,
463        );
464        assert_eq!(
465            SessionPriorityClass::try_from_u8(3),
466            Err(NnrpError::UnknownEnumValue {
467                enum_name: "session_priority_class",
468                value: 3
469            })
470        );
471
472        assert_enum_u8("session_status", SessionStatus::try_from_u8, 0, 3);
473        assert_enum_u16("close_reason", SessionCloseReason::try_from_u16, 0, 5);
474        assert_enum_u8("in_flight_policy", InFlightPolicy::try_from_u8, 0, 1);
475        assert_enum_u8("close_status", SessionCloseStatus::try_from_u8, 0, 3);
476        assert_enum_u8("operation_state", OperationState::try_from_u8, 0, 7);
477        assert_enum_u8("cancel_scope", CancelScope::try_from_u8, 0, 3);
478    }
479
480    #[test]
481    fn preview3_flow_enums_are_frozen() {
482        assert_enum_u8("scope_kind", FlowScopeKind::try_from_u8, 0, 2);
483        assert_enum_u8("update_reason", FlowUpdateReason::try_from_u8, 0, 4);
484        assert_enum_u8("backpressure_level", BackpressureLevel::try_from_u8, 0, 2);
485    }
486
487    #[test]
488    fn operation_state_terminal_and_transition_rules_are_stable() {
489        assert!(!OperationState::Accepted.is_terminal());
490        assert!(OperationState::Completed.is_terminal());
491        assert!(OperationState::Running.can_transition_to(OperationState::Partial));
492        assert!(OperationState::Partial.can_transition_to(OperationState::Completed));
493        assert!(OperationState::WaitingTool.can_transition_to(OperationState::Running));
494        assert!(!OperationState::Completed.can_transition_to(OperationState::Running));
495        assert!(!OperationState::Accepted.can_transition_to(OperationState::Partial));
496    }
497
498    fn assert_enum_u8<T: Copy>(
499        enum_name: &'static str,
500        parse: fn(u8) -> Result<T, NnrpError>,
501        first: u8,
502        last: u8,
503    ) {
504        for value in first..=last {
505            assert!(
506                parse(value).is_ok(),
507                "{enum_name} value {value} should parse"
508            );
509        }
510        assert_eq!(
511            parse(last + 1).map(|_| ()),
512            Err(NnrpError::UnknownEnumValue {
513                enum_name,
514                value: (last + 1) as u64
515            })
516        );
517    }
518
519    fn assert_enum_u16<T: Copy>(
520        enum_name: &'static str,
521        parse: fn(u16) -> Result<T, NnrpError>,
522        first: u16,
523        last: u16,
524    ) {
525        for value in first..=last {
526            assert!(
527                parse(value).is_ok(),
528                "{enum_name} value {value} should parse"
529            );
530        }
531        assert_eq!(
532            parse(last + 1).map(|_| ()),
533            Err(NnrpError::UnknownEnumValue {
534                enum_name,
535                value: (last + 1) as u64
536            })
537        );
538    }
539}