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