gbp_core/state.rs
1//! Finite state machines defined by the state-machine specification.
2//!
3//! Only the enum values and the transition validator live here; the side
4//! effects (timers, retries, etc.) belong in `gbp-node`.
5
6use core::fmt;
7
8/// Group node FSM.
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
10pub enum NodeState {
11 /// Initial state, before any transport is opened.
12 Idle,
13 /// QUIC / TLS handshake in progress.
14 Connecting,
15 /// MLS Welcome / ratchet tree exchange in progress.
16 EstablishingGroup,
17 /// Normal operating state.
18 Active,
19 /// `ERR_EPOCH_MISMATCH` (or equivalent) was raised; digest-based resync
20 /// is in progress.
21 Resyncing,
22 /// Fatal error; the node MUST NOT transmit application data.
23 Failed,
24 /// The node performed a graceful shutdown.
25 Closed,
26}
27
28impl NodeState {
29 /// Returns `true` if the transition `self -> next` is allowed by the
30 /// state-machine specification.
31 pub fn can_transition_to(self, next: NodeState) -> bool {
32 use NodeState::*;
33 matches!(
34 (self, next),
35 (Idle, Connecting)
36 | (Idle, Failed)
37 | (Connecting, EstablishingGroup)
38 | (Connecting, Failed)
39 | (EstablishingGroup, Active)
40 | (EstablishingGroup, Failed)
41 | (Active, Resyncing)
42 | (Active, Closed)
43 | (Active, Failed)
44 | (Resyncing, Active)
45 | (Resyncing, Failed)
46 | (_, Closed)
47 )
48 }
49}
50
51impl fmt::Display for NodeState {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.write_str(match self {
54 Self::Idle => "IDLE",
55 Self::Connecting => "CONNECTING",
56 Self::EstablishingGroup => "ESTABLISHING_GROUP",
57 Self::Active => "ACTIVE",
58 Self::Resyncing => "RESYNCING",
59 Self::Failed => "FAILED",
60 Self::Closed => "CLOSED",
61 })
62 }
63}
64
65/// Epoch transition FSM.
66#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
67pub enum TransitionState {
68 /// No pending commit.
69 TIdle,
70 /// `PREPARE_TRANSITION` was issued or received.
71 TPrepared,
72 /// MLS commit was processed and the local ratchet was applied.
73 TCommitProcessed,
74 /// Every member acknowledged with `READY_FOR_TRANSITION`.
75 TReady,
76 /// `EXECUTE_TRANSITION` has been applied; epoch was advanced.
77 TExecuted,
78 /// Transition was aborted (`ABORT_TRANSITION` or timeout).
79 TAborted,
80}
81
82impl TransitionState {
83 /// Returns `true` if the transition `self -> next` is allowed by the
84 /// state-machine specification (gbp-state-machine §4).
85 pub fn can_transition_to(self, next: TransitionState) -> bool {
86 use TransitionState::*;
87 matches!(
88 (self, next),
89 (TIdle, TPrepared)
90 | (TPrepared, TCommitProcessed)
91 | (TPrepared, TAborted)
92 | (TCommitProcessed, TReady)
93 | (TCommitProcessed, TAborted)
94 | (TReady, TExecuted)
95 | (TReady, TAborted)
96 | (TExecuted, TIdle)
97 | (TAborted, TIdle)
98 )
99 }
100}
101
102/// Timeout defaults normative for interoperable deployments (gbp-state-machine §6).
103pub mod timeouts {
104 /// Coordinator: max wait for READY quorum after issuing PREPARE_TRANSITION.
105 pub const T_PREPARE_MAX_MS: u64 = 5_000;
106 /// Member: max time to complete local commit / welcome processing.
107 pub const T_READY_MAX_MS: u64 = 5_000;
108 /// Member: max wait for EXECUTE_TRANSITION after sending READY_FOR_TRANSITION.
109 pub const T_EXECUTE_MAX_MS: u64 = 10_000;
110 /// Coordinator: extra slack before declaring quorum failure.
111 pub const T_QUORUM_GRACE_MS: u64 = 2_000;
112 /// Member: silence threshold before triggering coordinator handover.
113 pub const T_COORDINATOR_GRACE_MS: u64 = 10_000;
114 /// GAP: how long old-epoch keys are retained after an epoch transition so
115 /// that in-flight audio frames can still be accepted (gap_rfc §4).
116 pub const T_GAP_KEY_OVERLAP_MS: u64 = 10_000;
117}
118
119/// Sub-protocol activation FSM.
120#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
121pub enum SubprotocolState {
122 /// Sub-protocol is disabled.
123 Disabled,
124 /// Capability negotiation is in progress (`CAPABILITIES_ADVERTISE`).
125 Negotiating,
126 /// Sub-protocol is active.
127 Enabled,
128 /// Sub-protocol is active in degraded mode (e.g. lost FEC).
129 Degraded,
130 /// Sub-protocol is temporarily suspended (`MUTE` / `STREAM_STOP`).
131 Suspended,
132}