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