Skip to main content

reddb_server/
transport.rs

1//! Transport listener descriptors and readiness state.
2//!
3//! These contracts are shared by process bootstrap and the HTTP health
4//! surface. They intentionally live outside both so neither layer imports
5//! the other's implementation details.
6
7pub(crate) mod execution_admission;
8pub(crate) mod overload;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum TransportKind {
12    Router,
13    Http,
14    Https,
15    Grpc,
16    GrpcTls,
17    Wire,
18    WireTls,
19    Postgres,
20}
21
22impl TransportKind {
23    pub const fn as_str(self) -> &'static str {
24        match self {
25            Self::Router => "router",
26            Self::Http => "http",
27            Self::Https => "https",
28            Self::Grpc => "grpc",
29            Self::GrpcTls => "grpc-tls",
30            Self::Wire => "wire",
31            Self::WireTls => "wire-tls",
32            Self::Postgres => "pg-wire",
33        }
34    }
35}
36
37/// What a listener's exit means for the node that hosts it.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum TransportRole {
40    /// The shape's own transport. Losing its bind ends the boot and its
41    /// accept loop returning ends the process.
42    Primary,
43    /// A listener attached to another shape's node. It is spawned and
44    /// forgotten: its exit is logged and the node keeps serving.
45    Secondary,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct TransportDescriptor {
50    pub kind: TransportKind,
51    pub bind_addr: String,
52    pub explicit: bool,
53    pub role: TransportRole,
54}
55
56impl TransportDescriptor {
57    pub fn new(
58        kind: TransportKind,
59        bind_addr: String,
60        explicit: bool,
61        role: TransportRole,
62    ) -> Self {
63        Self {
64            kind,
65            bind_addr,
66            explicit,
67            role,
68        }
69    }
70}
71
72#[derive(Debug, Clone, Default, PartialEq, Eq)]
73pub struct TransportSet {
74    descriptors: Vec<TransportDescriptor>,
75}
76
77impl TransportSet {
78    pub fn new(descriptors: Vec<TransportDescriptor>) -> Self {
79        Self { descriptors }
80    }
81
82    pub fn descriptors(&self) -> &[TransportDescriptor] {
83        &self.descriptors
84    }
85
86    pub fn is_empty(&self) -> bool {
87        self.descriptors.is_empty()
88    }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct TransportListenerState {
93    pub transport: String,
94    pub bind_addr: String,
95    pub explicit: bool,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct TransportListenerFailure {
100    pub transport: String,
101    pub bind_addr: String,
102    pub explicit: bool,
103    pub reason: String,
104}
105
106#[derive(Debug, Clone, Default, PartialEq, Eq)]
107pub struct TransportReadiness {
108    pub active: Vec<TransportListenerState>,
109    pub failed: Vec<TransportListenerFailure>,
110}
111
112impl TransportReadiness {
113    pub(crate) fn active(&mut self, transport: &str, bind_addr: &str, explicit: bool) {
114        self.active.push(TransportListenerState {
115            transport: transport.to_string(),
116            bind_addr: bind_addr.to_string(),
117            explicit,
118        });
119    }
120
121    pub(crate) fn failed(
122        &mut self,
123        transport: &str,
124        bind_addr: &str,
125        explicit: bool,
126        reason: String,
127    ) {
128        self.failed.push(TransportListenerFailure {
129            transport: transport.to_string(),
130            bind_addr: bind_addr.to_string(),
131            explicit,
132            reason,
133        });
134    }
135}
136
137/// Whether `bind_addr` ("host:port", "[v6]:port" or "localhost:port")
138/// names a loopback host. Shared by the PG-wire trust-auth rule and the
139/// plaintext-listener warning at boot.
140pub(crate) fn is_loopback_bind(bind_addr: &str) -> bool {
141    let host = bind_addr
142        .rsplit_once(':')
143        .map(|(host, _)| host.trim_matches(['[', ']']))
144        .unwrap_or(bind_addr);
145    if host.eq_ignore_ascii_case("localhost") {
146        return true;
147    }
148    host.parse::<std::net::IpAddr>()
149        .map(|addr| addr.is_loopback())
150        .unwrap_or(false)
151}