Skip to main content

iceoryx2_bb_container/
semantic_string.rs

1// Copyright (c) 2023 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
13//! The [`SemanticString`](crate::semantic_string::SemanticString) is a trait for
14//! [`StaticString`](crate::string::StaticString) to create
15//! strong string types with semantic content contracts. They can be created
16//! with the help of the [`semantic_string`](crate::semantic_string!) macro.
17//!
18//! # Example, create a string that can contain a posix group name
19//!
20//! ```
21//! extern crate alloc;
22//!
23//! pub use iceoryx2_bb_container::semantic_string::SemanticString;
24//! use iceoryx2_bb_derive_macros::ZeroCopySend;
25//! use iceoryx2_bb_elementary_traits::zero_copy_send::ZeroCopySend;
26//!
27//! use iceoryx2_bb_container::semantic_string;
28//!
29//! const GROUP_NAME_LENGTH: usize = 31;
30//! semantic_string! {
31//!   // Name of the type
32//!   name: GroupName,
33//!   // The underlying capacity of the StaticString
34//!   capacity: GROUP_NAME_LENGTH,
35//!   // Callable that shall return true when the provided string contains invalid content
36//!   invalid_content: |string: &[u8]| {
37//!     if string.is_empty() {
38//!         // group names are not allowed to be empty
39//!         return true;
40//!     }
41//!
42//!     // group names are not allowed to start with a number or -
43//!     matches!(string[0], b'-' | b'0'..=b'9')
44//!   },
45//!   // Callable that shall return true when the provided string contains invalid characters
46//!   invalid_characters: |string: &[u8]| {
47//!     for value in string {
48//!         match value {
49//!             // only non-capital letters, numbers and - is allowed
50//!             b'a'..=b'z' | b'0'..=b'9' | b'-' => (),
51//!             _ => return true,
52//!         }
53//!     }
54//!
55//!     false
56//!   },
57//!   // When a SemanticString has multiple representations of the same semantic content, this
58//!   // callable shall convert the content to a uniform representation.
59//!   // Example: The path to `/tmp` can be also expressed as `/tmp/` or `////tmp////`
60//!   normalize: |this: &GroupName| {
61//!       this.clone()
62//!   }
63//! }
64//! ```
65
66use crate::string::*;
67use core::fmt::{Debug, Display};
68use core::hash::Hash;
69use core::ops::Deref;
70use iceoryx2_log::fail;
71
72/// Failures that can occur when a [`SemanticString`] is created or modified
73#[derive(Debug, Clone, Copy, Eq, PartialEq)]
74pub enum SemanticStringError {
75    /// The modification would lead to a [`SemanticString`] with invalid content.
76    InvalidContent,
77    /// The added content would exceed the maximum capacity of the [`SemanticString`]
78    ExceedsMaximumLength,
79}
80
81impl From<StringModificationError> for SemanticStringError {
82    fn from(value: StringModificationError) -> Self {
83        match value {
84            StringModificationError::InsertWouldExceedCapacity => {
85                SemanticStringError::ExceedsMaximumLength
86            }
87            StringModificationError::InvalidCharacter => SemanticStringError::InvalidContent,
88        }
89    }
90}
91
92impl core::fmt::Display for SemanticStringError {
93    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94        core::write!(f, "SemanticStringError::{self:?}")
95    }
96}
97
98impl core::error::Error for SemanticStringError {}
99
100#[doc(hidden)]
101pub mod internal {
102    use super::*;
103
104    pub trait SemanticStringAccessor<const CAPACITY: usize> {
105        unsafe fn new_empty() -> Self;
106        unsafe fn get_mut_string(&mut self) -> &mut StaticString<CAPACITY>;
107        fn is_invalid_content(string: &[u8]) -> bool;
108        fn does_contain_invalid_characters(string: &[u8]) -> bool;
109    }
110}
111
112/// Trait that defines the methods a [`StaticString`] with context semantics, a
113/// [`SemanticString`] shares. A new [`SemanticString`] can be created with the [`crate::semantic_string!`]
114/// macro. For the usage, see [`mod@crate::semantic_string`].
115pub trait SemanticString<const CAPACITY: usize>:
116    internal::SemanticStringAccessor<CAPACITY>
117    + Debug
118    + Display
119    + Sized
120    + Deref<Target = [u8]>
121    + PartialEq
122    + Eq
123    + Hash
124    + Clone
125    + Copy
126{
127    /// Returns a reference to the underlying [`StaticString`]
128    fn as_string(&self) -> &StaticString<CAPACITY>;
129
130    /// Creates a new content. If it contains invalid characters or exceeds the maximum supported
131    /// length of the system or contains illegal strings it fails.
132    fn new(value: &[u8]) -> Result<Self, SemanticStringError> {
133        let msg = "Unable to create SemanticString";
134        let origin = "SemanticString::new()";
135
136        let mut new_self =
137            unsafe { <Self as internal::SemanticStringAccessor<CAPACITY>>::new_empty() };
138        fail!(from origin, when new_self.push_bytes(value),
139            "{} due to an invalid value \"{}\".", msg, as_escaped_string(value));
140
141        Ok(new_self)
142    }
143
144    /// Creates a new content but does not verify that it does not contain invalid characters.
145    ///
146    /// # Safety
147    ///
148    ///   * The slice must contain only valid characters.
149    ///   * The slice must have a length that is less or equal CAPACITY
150    ///   * The slice must not contain invalid UTF-8 characters
151    ///
152    unsafe fn new_unchecked(bytes: &[u8]) -> Self;
153
154    /// Creates a new content from a given ptr. The user has to ensure that it is null-terminated.
155    ///
156    /// # Safety
157    ///
158    ///   * The pointer must be '\0' (null) terminated
159    ///   * The pointer must be valid and non-null
160    ///   * The contents must have a length that is less or equal CAPACITY
161    ///   * The contents must not contain invalid UTF-8 characters
162    ///
163    unsafe fn from_c_str(ptr: *const core::ffi::c_char) -> Result<Self, SemanticStringError> {
164        unsafe {
165            Self::new(core::slice::from_raw_parts(
166                ptr.cast(),
167                strnlen(ptr, CAPACITY + 1),
168            ))
169        }
170    }
171
172    /// Returns the contents as a slice
173    fn as_bytes(&self) -> &[u8] {
174        self.as_string().as_bytes()
175    }
176
177    /// Returns a zero terminated slice of the underlying bytes
178    fn as_c_str(&self) -> *const core::ffi::c_char {
179        self.as_string().as_c_str()
180    }
181
182    /// Returns the capacity of the file system type
183    fn capacity(&self) -> usize {
184        CAPACITY
185    }
186
187    /// Finds the first occurrence of a  byte string in the given string. If the byte string was
188    /// found the start position of the byte string is returned, otherwise [`None`].
189    fn find(&self, bytes: &[u8]) -> Option<usize> {
190        self.as_string().find(bytes)
191    }
192
193    /// Finds the last occurrence of a byte string in the given string. If the byte string was
194    /// found the start position of the byte string is returned, otherwise [`None`].
195    fn rfind(&self, bytes: &[u8]) -> Option<usize> {
196        self.as_string().find(bytes)
197    }
198
199    /// Returns true when the string is full, otherwise false
200    fn is_full(&self) -> bool {
201        self.as_string().is_full()
202    }
203
204    /// Returns true when the string is empty, otherwise false
205    fn is_empty(&self) -> bool {
206        self.as_string().is_empty()
207    }
208
209    /// Returns the length of the string
210    fn len(&self) -> usize {
211        self.as_string().len()
212    }
213
214    /// Inserts a single byte at a specific position. When the capacity is exceeded, the byte is an
215    /// illegal character or the content would result in an illegal content it fails.
216    fn insert(&mut self, idx: usize, byte: u8) -> Result<(), SemanticStringError> {
217        self.insert_bytes(idx, &[byte; 1])
218    }
219
220    /// Inserts a byte slice at a specific position. When the capacity is exceeded, the byte slice contains
221    /// illegal characters or the content would result in an illegal content it fails.
222    fn insert_bytes(&mut self, idx: usize, bytes: &[u8]) -> Result<(), SemanticStringError> {
223        let msg = "Unable to insert byte string";
224        fail!(from self, when unsafe { self.get_mut_string().insert_bytes(idx, bytes) },
225                with SemanticStringError::ExceedsMaximumLength,
226                    "{} \"{}\" since it would exceed the maximum allowed length of {}.",
227                        msg, as_escaped_string(bytes), CAPACITY);
228
229        if Self::is_invalid_content(self.as_bytes()) {
230            unsafe { self.get_mut_string().remove_range(idx, bytes.len()) };
231            fail!(from self, with SemanticStringError::InvalidContent,
232                "{} \"{}\" since it would result in an illegal content.",
233                msg, as_escaped_string(bytes));
234        }
235
236        Ok(())
237    }
238
239    /// Adds bytes to the string without checking if they only contain valid characters or
240    /// would result in a valid result.
241    ///
242    /// # Safety
243    ///
244    ///   * The user must ensure that the bytes contain only valid characters.
245    ///   * The user must ensure that the result, after the bytes were added, is valid.
246    ///   * The slice must have a length that is less or equal CAPACITY
247    ///   * The slice is not contain invalid UTF-8 characters
248    ///
249    unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]);
250
251    /// Normalizes the string. This function is used as basis for [`core::hash::Hash`] and
252    /// [`PartialEq`]. Normalizing a [`SemanticString`] means to bring it to some format so that it
253    /// contains still the same semantic content but in an uniform way so that strings, with the
254    /// same semantic content but different representation compare as equal.
255    fn normalize(&self) -> Self;
256
257    /// Removes the last character. If the string is empty it returns [`None`].
258    /// If the removal would create an illegal content it fails.
259    fn pop(&mut self) -> Result<Option<u8>, SemanticStringError> {
260        if self.len() == 0 {
261            return Ok(None);
262        }
263
264        self.remove(self.len() - 1)
265    }
266
267    /// Adds a single byte at the end. When the capacity is exceeded, the byte is an
268    /// illegal character or the content would result in an illegal content it fails.
269    fn push(&mut self, byte: u8) -> Result<(), SemanticStringError> {
270        self.insert(self.len(), byte)
271    }
272
273    /// Adds a byte slice at the end. When the capacity is exceeded, the byte slice contains
274    /// illegal characters or the content would result in an illegal content it fails.
275    fn push_bytes(&mut self, bytes: &[u8]) -> Result<(), SemanticStringError> {
276        self.insert_bytes(self.len(), bytes)
277    }
278
279    /// Removes a byte at a specific position and returns it.
280    /// If the removal would create an illegal content it fails.
281    fn remove(&mut self, idx: usize) -> Result<Option<u8>, SemanticStringError> {
282        let mut temp = *self.as_string();
283        let value = temp.remove(idx);
284
285        if Self::is_invalid_content(temp.as_bytes()) {
286            fail!(from self, with SemanticStringError::InvalidContent,
287                "Unable to remove character at position {} since it would result in an illegal content.",
288                idx);
289        }
290
291        unsafe { *self.get_mut_string() = temp };
292        Ok(value)
293    }
294
295    /// Removes a range.
296    /// If the removal would create an illegal content it fails.
297    fn remove_range(&mut self, idx: usize, len: usize) -> Result<(), SemanticStringError> {
298        let mut temp = *self.as_string();
299        temp.remove_range(idx, len);
300        if Self::is_invalid_content(temp.as_bytes()) {
301            fail!(from self, with SemanticStringError::InvalidContent,
302                "Unable to remove range from {} with length {} since it would result in the illegal content \"{}\".",
303                    idx, len, temp);
304        }
305
306        unsafe { self.get_mut_string().remove_range(idx, len) };
307        Ok(())
308    }
309
310    /// Removes all bytes which satisfy the provided clojure f.
311    /// If the removal would create an illegal content it fails.
312    fn retain<F: FnMut(u8) -> bool>(&mut self, f: F) -> Result<(), SemanticStringError> {
313        let mut temp = *self.as_string();
314        temp.retain(f);
315
316        if Self::is_invalid_content(temp.as_bytes()) {
317            fail!(from self, with SemanticStringError::InvalidContent,
318                "Unable to retain characters from string since it would result in the illegal content \"{}\".",
319                temp);
320        }
321
322        unsafe { *self.get_mut_string() = temp };
323
324        Ok(())
325    }
326
327    /// Removes a prefix. If the prefix does not exist it returns false. If the removal would lead
328    /// to an invalid string content it fails and returns [`SemanticStringError::InvalidContent`].
329    /// After a successful removal it returns true.
330    fn strip_prefix(&mut self, bytes: &[u8]) -> Result<bool, SemanticStringError> {
331        let mut temp = *self.as_string();
332        if !temp.strip_prefix(bytes) {
333            return Ok(false);
334        }
335
336        if Self::is_invalid_content(temp.as_bytes()) {
337            let mut prefix = StaticString::<123>::new();
338            unsafe { prefix.insert_bytes_unchecked(0, bytes) };
339            fail!(from self, with SemanticStringError::InvalidContent,
340                "Unable to strip prefix \"{}\" from string since it would result in the illegal content \"{}\".",
341                prefix, temp);
342        }
343
344        unsafe { self.get_mut_string().strip_prefix(bytes) };
345
346        Ok(true)
347    }
348
349    /// Removes a suffix. If the suffix does not exist it returns false. If the removal would lead
350    /// to an invalid string content it fails and returns [`SemanticStringError::InvalidContent`].
351    /// After a successful removal it returns true.
352    fn strip_suffix(&mut self, bytes: &[u8]) -> Result<bool, SemanticStringError> {
353        let mut temp = *self.as_string();
354        if !temp.strip_suffix(bytes) {
355            return Ok(false);
356        }
357
358        if Self::is_invalid_content(temp.as_bytes()) {
359            let mut prefix = StaticString::<123>::new();
360            unsafe { prefix.insert_bytes_unchecked(0, bytes) };
361            fail!(from self, with SemanticStringError::InvalidContent,
362                "Unable to strip prefix \"{}\" from string since it would result in the illegal content \"{}\".",
363                prefix, temp);
364        }
365
366        unsafe { self.get_mut_string().strip_suffix(bytes) };
367
368        Ok(true)
369    }
370
371    /// Truncates the string to new_len.
372    fn truncate(&mut self, new_len: usize) -> Result<(), SemanticStringError> {
373        let mut temp = *self.as_string();
374        temp.truncate(new_len);
375
376        if Self::is_invalid_content(temp.as_bytes()) {
377            fail!(from self, with SemanticStringError::InvalidContent,
378                "Unable to truncate characters to {} since it would result in the illegal content \"{}\".",
379                new_len, temp);
380        }
381
382        unsafe { self.get_mut_string().truncate(new_len) };
383        Ok(())
384    }
385}
386
387/// Helper macro to create a new [`SemanticString`]. Usage example can be found here:
388/// [`mod@crate::semantic_string`].
389#[macro_export(local_inner_macros)]
390macro_rules! semantic_string {
391    {
392        $(#[$documentation:meta])*
393        // Name of the struct
394        name: $string_name:ident,
395        // Capacity of the underlying StaticString
396        capacity: $capacity:expr,
397        // Callable that gets a [`&[u8]`] as input and shall return true when the slice contains
398        // invalid content.
399        invalid_content: $invalid_content:expr,
400        // Callable that gets a [`&[u8]`] as input and shall return true when the slice contains
401        // invalid characters.
402        invalid_characters: $invalid_characters:expr,
403        // Normalizes the content. Required when the same semantical content has multiple
404        // representations like paths for instance (`/tmp` == `/tmp/`)
405        normalize: $normalize:expr
406    } => {
407        $(#[$documentation])*
408        #[repr(C)]
409        #[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord, ZeroCopySend)]
410        pub struct $string_name {
411            value: iceoryx2_bb_container::string::StaticString<$capacity>
412        }
413
414        // BEGIN: serde
415        pub(crate) mod semantic_string_visitor_type {
416            pub(crate) struct $string_name;
417        }
418
419        impl<'de> serde::de::Visitor<'de> for semantic_string_visitor_type::$string_name {
420            type Value = $string_name;
421
422            fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
423                formatter.write_str("a string containing the service name")
424            }
425
426            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
427            where
428                E: serde::de::Error,
429            {
430                match $string_name::new(v.as_bytes()) {
431                    Ok(v) => Ok(v),
432                    Err(v) => Err(E::custom(alloc::format!("invalid {} provided {:?}.", core::stringify!($string_name), v))),
433                }
434            }
435        }
436
437        impl<'de> serde::Deserialize<'de> for $string_name {
438            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
439            where
440                D: serde::Deserializer<'de>,
441            {
442                deserializer.deserialize_str(semantic_string_visitor_type::$string_name)
443            }
444        }
445
446        impl serde::Serialize for $string_name {
447            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
448            where
449                S: serde::Serializer,
450            {
451                serializer.serialize_str(core::str::from_utf8(self.as_bytes()).unwrap())
452            }
453        }
454        // END: serde
455
456        impl iceoryx2_bb_container::semantic_string::SemanticString<$capacity> for $string_name {
457            fn as_string(&self) -> &iceoryx2_bb_container::string::StaticString<$capacity> {
458                &self.value
459            }
460
461            fn normalize(&self) -> Self {
462                $normalize(self)
463            }
464
465            unsafe fn new_unchecked(bytes: &[u8]) -> Self {
466                Self {
467                    value: unsafe { iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(bytes) },
468                }
469            }
470
471            unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]) {
472                use iceoryx2_bb_container::string::String;
473                unsafe {
474                    self.value.insert_bytes_unchecked(idx, bytes);
475                }
476            }
477        }
478
479        impl $string_name {
480            /// Creates a new instance.
481            ///
482            /// # Safety
483            ///
484            /// * The provided slice must have a length smaller or equal to the capacity. `value.len() < Self::max_len()`
485            /// * The contents of the slice must follow the content contract
486            ///
487            pub const unsafe fn new_unchecked_const(value: &[u8]) -> $string_name {
488                core::debug_assert!(value.len() <= $capacity);
489                $string_name {
490                    value: unsafe { iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(value) },
491                }
492            }
493
494            /// Returns the maximum supported length
495            pub const fn max_len() -> usize {
496                $capacity
497            }
498
499            /// Returns a slice to the underlying bytes
500            pub const fn as_bytes_const(&self) -> &[u8] {
501                self.value.as_bytes_const()
502            }
503        }
504
505        impl core::fmt::Display for $string_name {
506            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
507                core::write!(f, "{}", self.value)
508            }
509        }
510
511        impl core::hash::Hash for $string_name {
512            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
513                self.normalize().as_bytes().hash(state)
514            }
515        }
516
517        impl From<$string_name> for alloc::string::String {
518            fn from(value: $string_name) -> alloc::string::String {
519                // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
520                unsafe { alloc::string::String::from_utf8_unchecked(value.as_bytes().to_vec()) }
521            }
522        }
523
524        impl From<&$string_name> for alloc::string::String {
525            fn from(value: &$string_name) -> alloc::string::String {
526                // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
527                unsafe { alloc::string::String::from_utf8_unchecked(value.as_bytes().to_vec()) }
528            }
529        }
530
531        impl core::convert::TryFrom<&str> for $string_name {
532            type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
533
534            fn try_from(value: &str) -> Result<Self, Self::Error> {
535                Self::new(value.as_bytes())
536            }
537        }
538
539        impl PartialEq<$string_name> for $string_name {
540            fn eq(&self, other: &$string_name) -> bool {
541                *self.normalize().as_bytes() == *other.normalize().as_bytes()
542            }
543        }
544
545        impl PartialEq<&[u8]> for $string_name {
546            fn eq(&self, other: &&[u8]) -> bool {
547                let other = match $string_name::new(other) {
548                    Ok(other) => other,
549                    Err(_) => return false,
550                };
551
552                *self == other
553            }
554        }
555
556        impl PartialEq<&[u8]> for &$string_name {
557            fn eq(&self, other: &&[u8]) -> bool {
558                let other = match $string_name::new(other) {
559                    Ok(other) => other,
560                    Err(_) => return false,
561                };
562
563                **self == other
564            }
565        }
566
567        impl<const CAPACITY: usize> PartialEq<[u8; CAPACITY]> for $string_name {
568            fn eq(&self, other: &[u8; CAPACITY]) -> bool {
569                let other = match $string_name::new(other) {
570                    Ok(other) => other,
571                    Err(_) => return false,
572                };
573
574                *self == other
575            }
576        }
577
578        impl<const CAPACITY: usize> PartialEq<&[u8; CAPACITY]> for $string_name {
579            fn eq(&self, other: &&[u8; CAPACITY]) -> bool {
580                let other = match $string_name::new(*other) {
581                    Ok(other) => other,
582                    Err(_) => return false,
583                };
584
585                *self == other
586            }
587        }
588
589        impl PartialEq<&str> for &$string_name {
590            fn eq(&self, other: &&str) -> bool {
591                let other = match $string_name::new(other.as_bytes()) {
592                    Ok(other) => other,
593                    Err(_) => return false,
594                };
595
596                **self == other
597            }
598        }
599
600        impl core::ops::Deref for $string_name {
601            type Target = [u8];
602
603            fn deref(&self) -> &Self::Target {
604                use iceoryx2_bb_container::string::String;
605                self.value.as_bytes()
606            }
607        }
608
609        impl iceoryx2_bb_container::semantic_string::internal::SemanticStringAccessor<$capacity> for $string_name {
610            unsafe fn new_empty() -> Self {
611                Self {
612                    value: iceoryx2_bb_container::string::StaticString::new(),
613                }
614            }
615
616            unsafe fn get_mut_string(&mut self) -> &mut iceoryx2_bb_container::string::StaticString<$capacity> {
617                &mut self.value
618            }
619
620            fn is_invalid_content(string: &[u8]) -> bool {
621                if Self::does_contain_invalid_characters(string) {
622                    return true;
623                }
624
625                $invalid_content(string)
626            }
627
628            fn does_contain_invalid_characters(string: &[u8]) -> bool {
629                if core::str::from_utf8(string).is_err() {
630                    return true;
631                }
632
633                $invalid_characters(string)
634            }
635        }
636    }
637}