Skip to main content

zerodds_foundation/
buffer.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Fixed-Capacity Stack-Buffer fuer Hot-Path-Allokationen.
4//!
5//! `PoolBuffer<CAP>` ist ein on-stack array-basierter Buffer mit
6//! fester Kapazitaet `CAP`. Append-Operationen sind O(1) ohne
7//! Heap-Touch; Overflow wird als `PoolBufferError::Overflow`
8//! signalisiert statt zu panicen. `no_std`-tauglich.
9//!
10//! ## Spec-Bezug
11//!
12//! Dieses Modul ist kein OMG-Spec-Mapping — es ist ein internes
13//! Performance-Primitive fuer den Hot-Path
14//! `Writer::write`/`Reader::take`, der ohne Pro-Sample-Realloc
15//! arbeiten soll.
16
17#![allow(clippy::module_name_repetitions)]
18
19// ============================================================================
20// PoolBuffer<CAP> — fixed-capacity byte buffer
21// ============================================================================
22
23/// Fixed-Capacity Byte-Buffer, der wie `Vec<u8>` befuellt werden kann
24/// — aber keinen Heap-Realloc macht. Ueberlauf wird per
25/// [`Result`] gemeldet, nicht per `panic!`.
26///
27/// Layout: `[u8; CAP]` + `len: u16` (max 65 535 Bytes pro Buffer).
28/// Fuer DDS-Hot-Path-Samples bis 1.5 kB ist das genug; groessere
29/// Samples laufen weiter ueber den `alloc`-Pfad.
30#[derive(Debug)]
31pub struct PoolBuffer<const CAP: usize> {
32    bytes: [u8; CAP],
33    len: u16,
34}
35
36impl<const CAP: usize> PoolBuffer<CAP> {
37    /// Erzeugt einen leeren Buffer.
38    ///
39    /// `CAP` muss `<= u16::MAX as usize` sein — bei groesseren
40    /// Werten lehnt jede Mutation mit [`PoolBufferError::CapacityTooLarge`] ab.
41    #[must_use]
42    pub const fn new() -> Self {
43        Self {
44            bytes: [0u8; CAP],
45            len: 0,
46        }
47    }
48
49    /// Aktuelle Laenge (Bytes geschrieben).
50    #[must_use]
51    pub const fn len(&self) -> usize {
52        self.len as usize
53    }
54
55    /// `true` wenn keine Bytes geschrieben sind.
56    #[must_use]
57    pub const fn is_empty(&self) -> bool {
58        self.len == 0
59    }
60
61    /// Statische Kapazitaet.
62    #[must_use]
63    pub const fn capacity(&self) -> usize {
64        CAP
65    }
66
67    /// Lesender Slice ueber die geschriebenen Bytes.
68    #[must_use]
69    pub fn as_slice(&self) -> &[u8] {
70        // `len` ist immer <= CAP per Konstruktion — alle Mutations-APIs
71        // cappen ihn.
72        self.bytes.get(..self.len()).unwrap_or(&[])
73    }
74
75    /// Mutabler Slice ueber den uninitialisierten Tail (Capacity-len).
76    #[must_use]
77    pub fn spare_capacity_mut(&mut self) -> &mut [u8] {
78        let start = self.len();
79        self.bytes.get_mut(start..).unwrap_or(&mut [])
80    }
81
82    /// Setzt den Inhalt zurueck auf `len = 0`. O(1), kein Memzero.
83    pub fn clear(&mut self) {
84        self.len = 0;
85    }
86
87    /// Haengt `data` ans Ende. Fehler wenn der Buffer voll waere.
88    ///
89    /// # Errors
90    /// [`PoolBufferError::Overflow`] wenn `self.len() + data.len() > CAP`.
91    /// [`PoolBufferError::CapacityTooLarge`] wenn `CAP > u16::MAX`.
92    pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), PoolBufferError> {
93        if CAP > u16::MAX as usize {
94            return Err(PoolBufferError::CapacityTooLarge);
95        }
96        let needed = self
97            .len()
98            .checked_add(data.len())
99            .ok_or(PoolBufferError::Overflow)?;
100        if needed > CAP {
101            return Err(PoolBufferError::Overflow);
102        }
103        let start = self.len();
104        let dst = self
105            .bytes
106            .get_mut(start..needed)
107            .ok_or(PoolBufferError::Overflow)?;
108        dst.copy_from_slice(data);
109        // needed <= CAP <= u16::MAX, kein truncate-Risiko.
110        self.len = needed as u16;
111        Ok(())
112    }
113
114    /// Schreibt ein einzelnes Byte. Fehler bei Vollheit.
115    ///
116    /// # Errors
117    /// [`PoolBufferError::Overflow`] wenn der Buffer voll ist.
118    pub fn push(&mut self, byte: u8) -> Result<(), PoolBufferError> {
119        self.extend_from_slice(&[byte])
120    }
121
122    /// Setzt die Laenge explizit. Genutzt nach einem `spare_capacity_mut`-
123    /// Schreibzugriff durch ein Codec-Backend.
124    ///
125    /// # Errors
126    /// [`PoolBufferError::Overflow`] wenn `new_len > CAP`.
127    pub fn set_len(&mut self, new_len: usize) -> Result<(), PoolBufferError> {
128        if new_len > CAP || CAP > u16::MAX as usize {
129            return Err(PoolBufferError::Overflow);
130        }
131        // Cast ist nach der Range-Validation safe.
132        self.len = new_len as u16;
133        Ok(())
134    }
135}
136
137impl<const CAP: usize> Default for PoolBuffer<CAP> {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143impl<const CAP: usize> AsRef<[u8]> for PoolBuffer<CAP> {
144    fn as_ref(&self) -> &[u8] {
145        self.as_slice()
146    }
147}
148
149// ============================================================================
150// Error
151// ============================================================================
152
153/// Fehler-Familie fuer `PoolBuffer`.
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub enum PoolBufferError {
156    /// Schreibende Operation wuerde die statische Kapazitaet
157    /// ueberschreiten.
158    Overflow,
159    /// `CAP > u16::MAX`. Diese Variante existiert weil `len` als
160    /// `u16` modelliert ist.
161    CapacityTooLarge,
162}
163
164impl core::fmt::Display for PoolBufferError {
165    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166        match self {
167            Self::Overflow => f.write_str("pool buffer overflow"),
168            Self::CapacityTooLarge => f.write_str("CAP exceeds u16::MAX"),
169        }
170    }
171}
172
173#[cfg(feature = "std")]
174impl std::error::Error for PoolBufferError {}
175
176// ============================================================================
177// ============================================================================
178// Tests
179// ============================================================================
180
181#[cfg(test)]
182#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn empty_buffer_has_len_zero() {
188        let b: PoolBuffer<128> = PoolBuffer::new();
189        assert_eq!(b.len(), 0);
190        assert!(b.is_empty());
191        assert_eq!(b.capacity(), 128);
192        assert_eq!(b.as_slice(), &[]);
193    }
194
195    #[test]
196    fn extend_appends_and_tracks_len() {
197        let mut b: PoolBuffer<16> = PoolBuffer::new();
198        b.extend_from_slice(b"hello").unwrap();
199        assert_eq!(b.len(), 5);
200        assert_eq!(b.as_slice(), b"hello");
201        b.extend_from_slice(b" world").unwrap();
202        assert_eq!(b.as_slice(), b"hello world");
203    }
204
205    #[test]
206    fn extend_overflow_returns_error_no_partial_write() {
207        let mut b: PoolBuffer<8> = PoolBuffer::new();
208        b.extend_from_slice(b"1234").unwrap();
209        let err = b.extend_from_slice(b"56789").unwrap_err();
210        assert_eq!(err, PoolBufferError::Overflow);
211        assert_eq!(b.as_slice(), b"1234");
212    }
213
214    #[test]
215    fn push_byte_works() {
216        let mut b: PoolBuffer<4> = PoolBuffer::new();
217        b.push(0xCA).unwrap();
218        b.push(0xFE).unwrap();
219        assert_eq!(b.as_slice(), &[0xCA, 0xFE]);
220    }
221
222    #[test]
223    fn push_overflow_errors() {
224        let mut b: PoolBuffer<2> = PoolBuffer::new();
225        b.push(1).unwrap();
226        b.push(2).unwrap();
227        assert_eq!(b.push(3).unwrap_err(), PoolBufferError::Overflow);
228    }
229
230    #[test]
231    fn clear_resets_len_only() {
232        let mut b: PoolBuffer<16> = PoolBuffer::new();
233        b.extend_from_slice(b"abc").unwrap();
234        b.clear();
235        assert_eq!(b.len(), 0);
236        assert_eq!(b.as_slice(), &[]);
237        b.extend_from_slice(b"xyz").unwrap();
238        assert_eq!(b.as_slice(), b"xyz");
239    }
240
241    #[test]
242    fn spare_capacity_then_set_len() {
243        let mut b: PoolBuffer<32> = PoolBuffer::new();
244        let spare = b.spare_capacity_mut();
245        assert_eq!(spare.len(), 32);
246        spare[0..3].copy_from_slice(&[1, 2, 3]);
247        b.set_len(3).unwrap();
248        assert_eq!(b.as_slice(), &[1, 2, 3]);
249    }
250
251    #[test]
252    fn set_len_overflow_errors() {
253        let mut b: PoolBuffer<8> = PoolBuffer::new();
254        assert_eq!(b.set_len(9).unwrap_err(), PoolBufferError::Overflow);
255    }
256
257    #[test]
258    fn cap_above_u16_max_extend_errors() {
259        let mut b: PoolBuffer<{ u16::MAX as usize + 1 }> = PoolBuffer::new();
260        let err = b.extend_from_slice(&[0u8]).unwrap_err();
261        assert_eq!(err, PoolBufferError::CapacityTooLarge);
262    }
263
264    #[test]
265    fn error_display_strings() {
266        let s = std::format!("{}", PoolBufferError::Overflow);
267        assert!(s.contains("overflow"));
268        let s = std::format!("{}", PoolBufferError::CapacityTooLarge);
269        assert!(s.contains("u16"));
270    }
271}