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_bb_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_bb_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 unsafe {
244 let data = self.data_mut();
245 let ptr = data.as_mut_ptr();
246
247 core::ptr::copy(ptr.add(idx), ptr.add(idx + bytes.len()), self.len() - idx);
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
261 /// Returns the length of the string
262 fn len(&self) -> usize;
263
264 /// Removes the last character from the string and returns it. If the string is empty it
265 /// returns none.
266 /// ```
267 /// # extern crate iceoryx2_bb_loggers;
268 ///
269 /// use iceoryx2_bb_container::string::*;
270 ///
271 /// const STRING_CAPACITY: usize = 123;
272 ///
273 /// let mut some_string = StaticString::<STRING_CAPACITY>::from_bytes(b"hello!").unwrap();
274 /// let char = some_string.pop().unwrap();
275 ///
276 /// assert!(char == '!' as u8);
277 /// assert!(some_string == b"hello");
278 /// ```
279 fn pop(&mut self) -> Option<u8> {
280 if self.is_empty() {
281 return None;
282 }
283
284 self.remove(self.len() - 1)
285 }
286
287 /// Adds a byte at the end of the string. If there is no more space left it fails, otherwise
288 /// it succeeds.
289 fn push(&mut self, byte: u8) -> Result<(), StringModificationError> {
290 self.insert(self.len(), byte)
291 }
292
293 /// Adds a byte array at the end of the string. If there is no more space left it fails, otherwise
294 /// it succeeds.
295 fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), StringModificationError> {
296 self.insert_bytes(self.len(), bytes)
297 }
298
299 /// Removes a character at the provided index and returns it.
300 fn remove(&mut self, idx: usize) -> Option<u8> {
301 if self.len() < idx {
302 return None;
303 }
304
305 let removed_byte = unsafe { *self.data()[idx].as_ptr() };
306
307 self.remove_range(idx, 1);
308
309 Some(removed_byte)
310 }
311
312 /// Removes a range beginning from idx.
313 fn remove_range(&mut self, idx: usize, len: usize) -> bool {
314 if self.len() < idx + len {
315 return false;
316 }
317
318 if self.len() != idx + len {
319 let data = unsafe { self.data_mut() };
320 let ptr = data.as_mut_ptr();
321 unsafe {
322 core::ptr::copy(ptr.add(idx + len), ptr.add(idx), self.len() - (idx + len));
323 }
324 }
325
326 let new_len = self.len() - len;
327 unsafe { self.data_mut()[new_len].write(0) };
328 unsafe { self.set_len(new_len as u64) };
329
330 true
331 }
332
333 /// Removes all characters where f(c) returns false.
334 fn retain<F: FnMut(u8) -> bool>(&mut self, mut f: F) {
335 let len = self.len();
336 for idx in (0..len).rev() {
337 if f(unsafe { *self.data()[idx].as_ptr() }) {
338 self.remove(idx);
339 }
340 }
341 }
342
343 /// Finds the last occurrence of a byte string in the given string. If the byte string was
344 /// found the start position of the byte string is returned, otherwise [`None`].
345 fn rfind(&self, bytes: &[u8]) -> Option<usize> {
346 if self.len() < bytes.len() {
347 return None;
348 }
349
350 for i in (0..self.len() - bytes.len() + 1).rev() {
351 let mut has_found = true;
352 for (n, byte) in bytes.iter().enumerate() {
353 if unsafe { *self.data()[i + n].as_ptr() } != *byte {
354 has_found = false;
355 break;
356 }
357 }
358
359 if has_found {
360 return Some(i);
361 }
362 }
363
364 None
365 }
366
367 /// Removes a given prefix from the string. If the prefix was not found it returns false,
368 /// otherwise the prefix is removed and the function returns true.
369 fn strip_prefix(&mut self, bytes: &[u8]) -> bool {
370 match self.find(bytes) {
371 Some(0) => {
372 self.remove_range(0, bytes.len());
373 true
374 }
375 _ => false,
376 }
377 }
378
379 /// Removes a given suffix from the string. If the suffix was not found it returns false,
380 /// otherwise the suffix is removed and the function returns true.
381 fn strip_suffix(&mut self, bytes: &[u8]) -> bool {
382 if self.len() < bytes.len() {
383 return false;
384 }
385
386 let pos = self.len() - bytes.len();
387 match self.rfind(bytes) {
388 Some(v) => {
389 if v != pos {
390 return false;
391 }
392 self.remove_range(pos, bytes.len())
393 }
394 None => false,
395 }
396 }
397
398 /// Truncates the string to new_len.
399 fn truncate(&mut self, new_len: usize) {
400 if self.len() < new_len {
401 return;
402 }
403
404 if new_len < self.capacity() {
405 unsafe { self.data_mut()[new_len].write(0u8) };
406 }
407 unsafe { self.set_len(new_len as u64) };
408 }
409}