Skip to main content

simple_someip/
capacity.rs

1//! Names the fixed-capacity internal structures whose exhaustion is
2//! reportable through the `Capacity` variant of `client::Error` and
3//! `server::Error`.
4//!
5//! Those are deliberately not intra-doc links: both modules are behind
6//! features, and this one is compiled unconditionally, so linking them
7//! fails the `--no-default-features` and single-feature doc builds.
8//!
9//! This lives at the crate root rather than under `client` or `server`
10//! because both report through it, and a consumer handling capacity
11//! exhaustion should not have to learn two different vocabularies for the
12//! same condition. Not every kind is reachable from every error type —
13//! the server currently only produces [`CapacityKind::UdpBuffer`] — and
14//! that asymmetry is deliberate: one shared kind keeps the handling code
15//! uniform as the server grows others.
16
17use core::fmt;
18
19/// Which fixed-capacity internal structure overflowed.
20///
21/// Each variant names the compile-time constant that governs it, so the
22/// bound is reachable from the type rather than by grepping for a string
23/// tag.
24///
25/// `#[non_exhaustive]`: new kinds appear as internal structures gain
26/// bounds, and that should not break a downstream `match`.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[non_exhaustive]
29pub enum CapacityKind {
30    /// Bound by `UNICAST_SOCKETS_CAP`. The client cannot bind a new
31    /// ephemeral / requested-port unicast socket because the per-client
32    /// cap is exhausted.
33    UnicastSockets,
34
35    /// Bound by [`crate::UDP_BUFFER_SIZE`]. An outgoing message was
36    /// rejected because the encoded form exceeds the application-level
37    /// UDP cap.
38    ///
39    /// With E2E protect configured for the destination key, the
40    /// post-protect payload may add up to the protect profile's overhead
41    /// bytes (Profile 1: 4, Profile 4: 16). The pre-encode check uses the
42    /// raw size; the post-protect re-check inside the spawned send loop
43    /// produces this kind if the protected datagram would overflow the
44    /// cap.
45    ///
46    /// The only kind the server currently produces, where it means a
47    /// stack send buffer smaller than the outgoing message.
48    UdpBuffer,
49
50    /// Bound by `PENDING_RESPONSES_CAP`. A request was enqueued but the
51    /// in-flight response table is full; the request was dropped.
52    PendingResponses,
53
54    /// Bound by `REQUEST_QUEUE_CAP`. The client's internal control-message
55    /// queue overflowed during a multi-pass `push_front` re-enqueue (e.g.
56    /// an auto-bind path).
57    ///
58    /// Public callers normally hit the bounded(4) control channel first
59    /// and either backpressure or fail with `Shutdown`; this kind fires
60    /// only in the narrow re-enqueue overflow window.
61    RequestQueue,
62
63    /// Bound by `SERVICE_REGISTRY_CAP`. A new service-instance endpoint
64    /// cannot be registered because the registry is full.
65    ServiceRegistry,
66}
67
68impl CapacityKind {
69    /// The `snake_case` tag for this kind.
70    ///
71    /// These are the exact strings the pre-0.12.0 `Capacity(&'static str)`
72    /// variant carried, so log output and anything scraping it are
73    /// unchanged by the move to a typed kind.
74    #[must_use]
75    pub const fn as_str(self) -> &'static str {
76        match self {
77            Self::UnicastSockets => "unicast_sockets",
78            Self::UdpBuffer => "udp_buffer",
79            Self::PendingResponses => "pending_responses",
80            Self::RequestQueue => "request_queue",
81            Self::ServiceRegistry => "service_registry",
82        }
83    }
84}
85
86impl fmt::Display for CapacityKind {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.write_str(self.as_str())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    /// The whole point of `as_str` is that the `Display` output of a
97    /// `Capacity` error is byte-identical to what the `&'static str`
98    /// variant produced. Pin the exact strings — a rename here is a
99    /// silent behavior change for anyone matching on log text, which is
100    /// precisely the fragile pattern this type exists to let them stop
101    /// doing.
102    #[test]
103    fn tags_match_the_pre_0_11_string_literals() {
104        assert_eq!(CapacityKind::UnicastSockets.as_str(), "unicast_sockets");
105        assert_eq!(CapacityKind::UdpBuffer.as_str(), "udp_buffer");
106        assert_eq!(CapacityKind::PendingResponses.as_str(), "pending_responses");
107        assert_eq!(CapacityKind::RequestQueue.as_str(), "request_queue");
108        assert_eq!(CapacityKind::ServiceRegistry.as_str(), "service_registry");
109    }
110
111    /// `Display` must delegate to `as_str` verbatim: both error enums
112    /// format the variant as `"internal capacity exceeded: {0}"`, so any
113    /// divergence changes the rendered error.
114    ///
115    /// Formats through [`core::fmt::Write`] into a fixed buffer rather
116    /// than `to_string()`, so this test runs in the `--no-default-features`
117    /// configuration too — where there is no allocator.
118    #[test]
119    fn display_delegates_to_as_str() {
120        use core::fmt::Write as _;
121
122        /// Writes into a fixed buffer; the longest tag is
123        /// `"pending_responses"` at 17 bytes.
124        struct Buf {
125            bytes: [u8; 32],
126            len: usize,
127        }
128
129        impl core::fmt::Write for Buf {
130            fn write_str(&mut self, s: &str) -> core::fmt::Result {
131                let end = self.len + s.len();
132                if end > self.bytes.len() {
133                    return Err(core::fmt::Error);
134                }
135                self.bytes[self.len..end].copy_from_slice(s.as_bytes());
136                self.len = end;
137                Ok(())
138            }
139        }
140
141        for kind in [
142            CapacityKind::UnicastSockets,
143            CapacityKind::UdpBuffer,
144            CapacityKind::PendingResponses,
145            CapacityKind::RequestQueue,
146            CapacityKind::ServiceRegistry,
147        ] {
148            let mut buf = Buf {
149                bytes: [0; 32],
150                len: 0,
151            };
152            write!(buf, "{kind}").expect("tag must fit the buffer");
153            let rendered = core::str::from_utf8(&buf.bytes[..buf.len]).expect("tags are ASCII");
154            assert_eq!(rendered, kind.as_str());
155        }
156    }
157}