iceoryx2_bb_container/string/
mod.rs

1// Copyright (c) 2025 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13use core::mem::MaybeUninit;
14use core::{
15    fmt::Debug,
16    fmt::Display,
17    hash::Hash,
18    ops::{Deref, DerefMut},
19};
20use iceoryx2_log::{fail, fatal_panic};
21
22/// Runtime fixed-capacity string where the user can provide a stateful allocator.
23pub mod polymorphic_string;
24
25/// Compile-time fixed-capacity string variant that is shared-memory compatible.
26pub mod static_string;
27
28/// Runtime fixed-capacity shared-memory compatible string
29pub mod relocatable_string;
30
31/// String helper functions
32pub mod utils;
33
34pub use polymorphic_string::*;
35pub use relocatable_string::*;
36pub use static_string::*;
37pub use utils::*;
38
39/// Error which can occur when a [`String`] is modified.
40#[derive(Debug, PartialEq, Eq, Clone, Copy)]
41pub enum StringModificationError {
42    /// A string with unsupported unicode code points greater or equal 128 (U+0080) was provided
43    InvalidCharacter,
44    /// The content that shall be added would exceed the maximum capacity of the
45    /// [`String`].
46    InsertWouldExceedCapacity,
47}
48
49impl core::fmt::Display for StringModificationError {
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        write!(f, "StringModificationError::{self:?}")
52    }
53}
54
55impl core::error::Error for StringModificationError {}
56
57#[doc(hidden)]
58pub(crate) mod internal {
59    use super::*;
60
61    #[doc(hidden)]
62    pub trait StringView {
63        fn data(&self) -> &[MaybeUninit<u8>];
64
65        /// # Safety
66        ///
67        /// * user must ensure that any modification keeps the initialized data contiguous
68        /// * user must update len with [`StringView::set_len()`] when adding/removing elements
69        unsafe fn data_mut(&mut self) -> &mut [MaybeUninit<u8>];
70
71        /// # Safety
72        ///
73        /// * user must ensure that the len defines the number of initialized contiguous
74        ///   elements in [`StringView::data_mut()`] and [`StringView::data()`]
75        unsafe fn set_len(&mut self, len: u64);
76    }
77}
78
79/// A UTF-8 string trait.
80/// The string class uses Unicode (ISO/IEC 10646) terminology throughout its interface. In particular:
81///   - A code point is the numerical index assigned to a character in the Unicode standard.
82///   - A code unit is the basic component of a character encoding system. For UTF-8, the code unit has a size of 8-bits
83///
84/// For example, the code point U+0041 represents the letter 'A' and can be encoded in a single 8-bit code unit in
85/// UTF-8. The code point U+1F4A9 requires four 8-bit code units in the UTF-8 encoding.
86///
87/// The NUL code point (U+0000) is not allowed anywhere in the string.
88///
89/// ## Note
90///
91/// Currently only Unicode code points less than 128 (U+0080) are supported.
92/// This restricts the valid contents of a string to those UTF8 strings
93/// that are also valid 7-bit ASCII strings. Full Unicode support will get added later.
94pub trait String:
95    internal::StringView
96    + Debug
97    + Display
98    + PartialOrd
99    + Ord
100    + Hash
101    + Deref<Target = [u8]>
102    + DerefMut
103    + PartialEq
104    + Eq
105{
106    /// Returns a slice to the underlying bytes
107    fn as_bytes(&self) -> &[u8] {
108        unsafe { core::slice::from_raw_parts(self.data().as_ptr() as *const u8, self.len()) }
109    }
110
111    /// Returns a null-terminated slice to the underlying bytes
112    fn as_bytes_with_nul(&self) -> &[u8] {
113        unsafe { core::slice::from_raw_parts(self.data().as_ptr() as *const u8, self.len() + 1) }
114    }
115
116    /// Returns a zero terminated slice of the underlying bytes
117    fn as_c_str(&self) -> *const core::ffi::c_char {
118        self.data().as_ptr() as *const core::ffi::c_char
119    }
120
121    /// Returns a mutable slice to the underlying bytes
122    fn as_mut_bytes(&mut self) -> &mut [u8] {
123        unsafe {
124            core::slice::from_raw_parts_mut(self.data_mut().as_mut_ptr() as *mut u8, self.len())
125        }
126    }
127
128    /// Returns the content as a string slice if the bytes are valid UTF-8
129    fn as_str(&self) -> &str {
130        unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
131    }
132
133    /// Returns the capacity of the string
134    fn capacity(&self) -> usize;
135
136    /// Removes all bytes from the string and set the len to zero
137    fn clear(&mut self) {
138        unsafe { self.set_len(0) };
139        unsafe { self.data_mut()[0].write(0) };
140    }
141
142    /// Finds the first occurrence of a  byte string in the given string. If the byte string was
143    /// found the start position of the byte string is returned, otherwise [`None`].
144    fn find(&self, bytes: &[u8]) -> Option<usize> {
145        if self.len() < bytes.len() {
146            return None;
147        }
148
149        for i in 0..self.len() - bytes.len() + 1 {
150            let mut has_found = true;
151            for (n, byte) in bytes.iter().enumerate() {
152                if unsafe { *self.data()[i + n].as_ptr() } != *byte {
153                    has_found = false;
154                    break;
155                }
156            }
157
158            if has_found {
159                return Some(i);
160            }
161        }
162
163        None
164    }
165
166    /// True if the string is empty, otherwise false
167    fn is_empty(&self) -> bool {
168        self.len() == 0
169    }
170
171    /// True if the string is full, otherwise false.
172    fn is_full(&self) -> bool {
173        self.len() == self.capacity()
174    }
175
176    /// Inserts a byte at a provided index. If the index is out of bounds it panics.
177    /// If the string has no more capacity left it fails otherwise it succeeds.
178    ///
179    /// ```
180    /// # extern crate iceoryx2_loggers;
181    ///
182    /// use iceoryx2_bb_container::string::*;
183    ///
184    /// const STRING_CAPACITY: usize = 123;
185    ///
186    /// let mut some_string = StaticString::<STRING_CAPACITY>::from_bytes(b"helo").unwrap();
187    /// some_string.insert(3, 'l' as u8).unwrap();
188    /// assert!(some_string == b"hello");
189    /// ```
190    fn insert(&mut self, idx: usize, byte: u8) -> Result<(), StringModificationError> {
191        self.insert_bytes(idx, &[byte; 1])
192    }
193
194    /// Inserts a byte array at a provided index. If the index is out of bounds it panics.
195    /// If the string has no more capacity left it fails otherwise it succeeds.
196    ///
197    /// ```
198    /// # extern crate iceoryx2_loggers;
199    ///
200    /// use iceoryx2_bb_container::string::*;
201    ///
202    /// const STRING_CAPACITY: usize = 123;
203    ///
204    /// let mut some_string = StaticString::<STRING_CAPACITY>::from_bytes(b"ho").unwrap();
205    /// some_string.insert_bytes(1, b"ell").unwrap();
206    /// assert!(some_string == b"hello");
207    /// ```
208    fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> Result<(), StringModificationError> {
209        let msg = "Unable to insert byte string";
210        if self.len() < idx {
211            fatal_panic!(from self, "{} \"{}\" since the index {} is out of bounds.",
212                msg, as_escaped_string(bytes) , idx);
213        }
214
215        if self.capacity() < self.len() + bytes.len() {
216            fail!(from self, with StringModificationError::InsertWouldExceedCapacity,
217                "{} \"{}\" since it would exceed the maximum capacity of {}.",
218                msg, as_escaped_string(bytes), self.capacity());
219        }
220
221        for byte in bytes {
222            if 128 <= *byte || 0 == *byte {
223                fail!(from self, with StringModificationError::InvalidCharacter,
224                    "{} \"{}\" since it contains unsupported unicode points. Only unicode points less than 128 (U+0080) are supported",
225                    msg, as_escaped_string(bytes));
226            }
227        }
228
229        unsafe { self.insert_bytes_unchecked(idx, bytes) };
230
231        Ok(())
232    }
233
234    /// Inserts a byte array at a provided index.
235    ///
236    /// # Safety
237    ///
238    ///  * The 'idx' must by less than [`String::len()`].
239    ///  * The 'bytes.len()' must be less or equal than [`String::capacity()`] -
240    ///    [`String::len()`]
241    ///
242    unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]) {
243        let data = unsafe { self.data_mut() };
244        let ptr = data.as_mut_ptr();
245        unsafe {
246            core::ptr::copy(ptr.add(idx), ptr.add(idx + bytes.len()), self.len() - idx);
247        }
248
249        for (i, byte) in bytes.iter().enumerate() {
250            self.data_mut()[idx + i].write(*byte);
251        }
252
253        let new_len = self.len() + bytes.len();
254        self.set_len(new_len as u64);
255        if new_len < self.capacity() {
256            self.data_mut()[new_len].write(0);
257        }
258    }
259
260    /// Returns the length of the string
261    fn len(&self) -> usize;
262
263    /// Removes the last character from the string and returns it. If the string is empty it
264    /// returns none.
265    /// ```
266    /// # extern crate iceoryx2_loggers;
267    ///
268    /// use iceoryx2_bb_container::string::*;
269    ///
270    /// const STRING_CAPACITY: usize = 123;
271    ///
272    /// let mut some_string = StaticString::<STRING_CAPACITY>::from_bytes(b"hello!").unwrap();
273    /// let char = some_string.pop().unwrap();
274    ///
275    /// assert!(char == '!' as u8);
276    /// assert!(some_string == b"hello");
277    /// ```
278    fn pop(&mut self) -> Option<u8> {
279        if self.is_empty() {
280            return None;
281        }
282
283        self.remove(self.len() - 1)
284    }
285
286    /// Adds a byte at the end of the string. If there is no more space left it fails, otherwise
287    /// it succeeds.
288    fn push(&mut self, byte: u8) -> Result<(), StringModificationError> {
289        self.insert(self.len(), byte)
290    }
291
292    /// Adds a byte array at the end of the string. If there is no more space left it fails, otherwise
293    /// it succeeds.
294    fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), StringModificationError> {
295        self.insert_bytes(self.len(), bytes)
296    }
297
298    /// Removes a character at the provided index and returns it.
299    fn remove(&mut self, idx: usize) -> Option<u8> {
300        if self.len() < idx {
301            return None;
302        }
303
304        let removed_byte = unsafe { *self.data()[idx].as_ptr() };
305
306        self.remove_range(idx, 1);
307
308        Some(removed_byte)
309    }
310
311    /// Removes a range beginning from idx.
312    fn remove_range(&mut self, idx: usize, len: usize) -> bool {
313        if self.len() < idx + len {
314            return false;
315        }
316
317        if self.len() != idx + len {
318            let data = unsafe { self.data_mut() };
319            let ptr = data.as_mut_ptr();
320            unsafe {
321                core::ptr::copy(ptr.add(idx + len), ptr.add(idx), self.len() - (idx + len));
322            }
323        }
324
325        let new_len = self.len() - len;
326        unsafe { self.data_mut()[new_len].write(0) };
327        unsafe { self.set_len(new_len as u64) };
328
329        true
330    }
331
332    /// Removes all characters where f(c) returns false.
333    fn retain<F: FnMut(u8) -> bool>(&mut self, mut f: F) {
334        let len = self.len();
335        for idx in (0..len).rev() {
336            if f(unsafe { *self.data()[idx].as_ptr() }) {
337                self.remove(idx);
338            }
339        }
340    }
341
342    /// Finds the last occurrence of a byte string in the given string. If the byte string was
343    /// found the start position of the byte string is returned, otherwise [`None`].
344    fn rfind(&self, bytes: &[u8]) -> Option<usize> {
345        if self.len() < bytes.len() {
346            return None;
347        }
348
349        for i in (0..self.len() - bytes.len() + 1).rev() {
350            let mut has_found = true;
351            for (n, byte) in bytes.iter().enumerate() {
352                if unsafe { *self.data()[i + n].as_ptr() } != *byte {
353                    has_found = false;
354                    break;
355                }
356            }
357
358            if has_found {
359                return Some(i);
360            }
361        }
362
363        None
364    }
365
366    /// Removes a given prefix from the string. If the prefix was not found it returns false,
367    /// otherwise the prefix is removed and the function returns true.
368    fn strip_prefix(&mut self, bytes: &[u8]) -> bool {
369        match self.find(bytes) {
370            Some(0) => {
371                self.remove_range(0, bytes.len());
372                true
373            }
374            _ => false,
375        }
376    }
377
378    /// Removes a given suffix from the string. If the suffix was not found it returns false,
379    /// otherwise the suffix is removed and the function returns true.
380    fn strip_suffix(&mut self, bytes: &[u8]) -> bool {
381        if self.len() < bytes.len() {
382            return false;
383        }
384
385        let pos = self.len() - bytes.len();
386        match self.rfind(bytes) {
387            Some(v) => {
388                if v != pos {
389                    return false;
390                }
391                self.remove_range(pos, bytes.len())
392            }
393            None => false,
394        }
395    }
396
397    /// Truncates the string to new_len.
398    fn truncate(&mut self, new_len: usize) {
399        if self.len() < new_len {
400            return;
401        }
402
403        if new_len < self.capacity() {
404            unsafe { self.data_mut()[new_len].write(0u8) };
405        }
406        unsafe { self.set_len(new_len as u64) };
407    }
408}