Skip to main content

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}