thornode_pulse_wire/protocol.rs
1//! Stable, non-payload parts of the Pulse wire-v2 contract.
2//!
3//! Keeping these values beside the frame codec prevents the server, SDKs and
4//! documentation from independently redefining protocol behavior.
5
6/// TLS ALPN negotiated by every Pulse QUIC connection.
7pub const ALPN: &[u8] = b"pulse";
8
9/// The only wire version implemented by this release.
10pub const WIRE_VERSION: u32 = 2;
11
12/// The first control message was malformed or otherwise invalid.
13pub const CLOSE_INVALID_CONTROL: u32 = 1;
14/// Authentication failed, credentials were revoked, or required credentials
15/// were not supplied.
16pub const CLOSE_UNAUTHENTICATED: u32 = 2;
17/// Admission is temporarily unavailable (for example, all subscription slots
18/// are currently in use). Retry with bounded, jittered backoff.
19pub const CLOSE_QUOTA_EXCEEDED: u32 = 3;
20/// The first control message did not negotiate a supported wire version.
21pub const CLOSE_UNSUPPORTED_VERSION: u32 = 4;
22/// The authenticated tier can never use the requested Pulse entitlement or
23/// subscription shape. Retrying the same request cannot succeed.
24pub const CLOSE_TIER_NOT_ENTITLED: u32 = 5;
25
26/// What a client should do after a terminal application close.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum RetryClass {
29 /// The peer closed normally. Reconnect only when the application intends
30 /// to continue consuming the feed.
31 Normal,
32 /// The same request is invalid or permanently unsupported.
33 NonRetryable,
34 /// Obtain or refresh credentials before opening another connection.
35 CredentialsRequired,
36 /// Retry with bounded, jittered backoff.
37 Transient,
38 /// A future or private close code not known to this SDK version.
39 Unknown,
40}
41
42/// Classifies a Pulse application close code without guessing about unknown
43/// codes. In particular, unknown codes are not automatically retryable.
44pub const fn classify_close_code(code: u64) -> RetryClass {
45 match code {
46 0 => RetryClass::Normal,
47 1 | 4 | 5 => RetryClass::NonRetryable,
48 2 => RetryClass::CredentialsRequired,
49 3 => RetryClass::Transient,
50 _ => RetryClass::Unknown,
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn public_close_codes_have_stable_retry_semantics() {
60 assert_eq!(classify_close_code(0), RetryClass::Normal);
61 assert_eq!(classify_close_code(1), RetryClass::NonRetryable);
62 assert_eq!(classify_close_code(2), RetryClass::CredentialsRequired);
63 assert_eq!(classify_close_code(3), RetryClass::Transient);
64 assert_eq!(classify_close_code(4), RetryClass::NonRetryable);
65 assert_eq!(classify_close_code(5), RetryClass::NonRetryable);
66 assert_eq!(classify_close_code(99), RetryClass::Unknown);
67 }
68}