iceoryx2_bb_system_types/
file_name.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//! Relocatable (inter-process shared memory compatible) [`semantic_string::SemanticString`] implementation for
14//! [`FileName`]. All modification operations ensure that never an
15//! invalid file or path name can be generated. All strings have a fixed size so that the maximum
16//! path or file name length the system supports can be stored.
17//!
18//! # Example
19//!
20//! ```
21//! # extern crate iceoryx2_loggers;
22//!
23//! use iceoryx2_bb_container::semantic_string::SemanticString;
24//! use iceoryx2_bb_system_types::file_name::*;
25//!
26//! let name = FileName::new(b"some_file.txt");
27//!
28//! let invalid_name = FileName::new(b"no/path/allowed.txt");
29//! assert!(invalid_name.is_err());
30//! ```
31
32pub use iceoryx2_bb_container::semantic_string::SemanticString;
33
34use core::hash::{Hash, Hasher};
35
36use alloc::string::String;
37
38use iceoryx2_bb_container::semantic_string;
39use iceoryx2_bb_derive_macros::ZeroCopySend;
40use iceoryx2_bb_elementary::static_assert::{static_assert_ge, static_assert_le};
41use iceoryx2_bb_elementary_traits::zero_copy_send::ZeroCopySend;
42use iceoryx2_pal_configuration::FILENAME_LENGTH;
43
44fn invalid_characters(value: &[u8]) -> bool {
45    for c in value {
46        match c {
47            // linux & windows
48            0 => return true,
49            b'/' => return true,
50            // windows only
51            1..=31 => return true,
52            #[cfg(target_os = "windows")]
53            b':' => return true,
54            b'\\' => return true,
55            b'<' => return true,
56            b'>' => return true,
57            b'"' => return true,
58            b'|' => return true,
59            b'?' => return true,
60            b'*' => return true,
61            _ => (),
62        }
63    }
64    false
65}
66
67fn invalid_content(value: &[u8]) -> bool {
68    matches!(value, b"" | b"." | b"..")
69}
70
71fn normalize(this: &FileName) -> FileName {
72    *this
73}
74
75semantic_string! {
76  /// Represents a file name. The restriction are choosen in a way that it is platform independent.
77  /// This means characters/strings which would be legal on some platforms are forbidden as well.
78  name: FileName,
79  capacity: FILENAME_LENGTH,
80  invalid_content: invalid_content,
81  invalid_characters: invalid_characters,
82  normalize: normalize
83}
84
85#[derive(Debug, Clone, Copy, Eq, Ord, PartialOrd, ZeroCopySend)]
86#[repr(C)]
87pub struct RestrictedFileName<const CAPACITY: usize> {
88    value: iceoryx2_bb_container::string::StaticString<CAPACITY>,
89}
90
91impl<const CAPACITY: usize>
92    iceoryx2_bb_container::semantic_string::internal::SemanticStringAccessor<CAPACITY>
93    for RestrictedFileName<CAPACITY>
94{
95    unsafe fn new_empty() -> Self {
96        static_assert_le::<{ CAPACITY }, { FILENAME_LENGTH }>();
97        static_assert_ge::<{ CAPACITY }, 1>();
98
99        Self {
100            value: iceoryx2_bb_container::string::StaticString::new(),
101        }
102    }
103
104    unsafe fn get_mut_string(
105        &mut self,
106    ) -> &mut iceoryx2_bb_container::string::StaticString<CAPACITY> {
107        &mut self.value
108    }
109
110    fn is_invalid_content(string: &[u8]) -> bool {
111        if Self::does_contain_invalid_characters(string) {
112            return true;
113        }
114
115        invalid_content(string)
116    }
117
118    fn does_contain_invalid_characters(string: &[u8]) -> bool {
119        if core::str::from_utf8(string).is_err() {
120            return true;
121        }
122
123        invalid_characters(string)
124    }
125}
126
127// BEGIN: serde
128pub(crate) mod visitor_type {
129    pub(crate) struct RestrictedFileName<const CAPACITY: usize>;
130}
131
132impl<const CAPACITY: usize> serde::de::Visitor<'_> for visitor_type::RestrictedFileName<CAPACITY> {
133    type Value = RestrictedFileName<CAPACITY>;
134
135    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
136        formatter.write_str("a string containing the service name")
137    }
138
139    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
140    where
141        E: serde::de::Error,
142    {
143        RestrictedFileName::<CAPACITY>::new(v.as_bytes()).map_err(|e| {
144            E::custom(alloc::format!(
145                "invalid RestrictedFileName<{CAPACITY}> provided {v:?} ({e:?})."
146            ))
147        })
148    }
149}
150
151impl<'de, const CAPACITY: usize> serde::Deserialize<'de> for RestrictedFileName<CAPACITY> {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: serde::Deserializer<'de>,
155    {
156        deserializer.deserialize_str(visitor_type::RestrictedFileName::<CAPACITY>)
157    }
158}
159
160impl<const CAPACITY: usize> serde::Serialize for RestrictedFileName<CAPACITY> {
161    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162    where
163        S: serde::Serializer,
164    {
165        serializer.serialize_str(core::str::from_utf8(self.as_bytes()).unwrap())
166    }
167}
168// END: serde
169
170impl<const CAPACITY: usize> iceoryx2_bb_container::semantic_string::SemanticString<CAPACITY>
171    for RestrictedFileName<CAPACITY>
172{
173    fn as_string(&self) -> &iceoryx2_bb_container::string::StaticString<CAPACITY> {
174        &self.value
175    }
176
177    fn normalize(&self) -> Self {
178        *self
179    }
180
181    unsafe fn new_unchecked(bytes: &[u8]) -> Self {
182        debug_assert!(bytes.len() <= CAPACITY);
183
184        Self {
185            value: iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(bytes),
186        }
187    }
188
189    unsafe fn insert_bytes_unchecked(&mut self, idx: usize, bytes: &[u8]) {
190        debug_assert!(bytes.len() + self.len() <= CAPACITY);
191        use iceoryx2_bb_container::string::String;
192        self.value.insert_bytes_unchecked(idx, bytes);
193    }
194}
195
196impl<const CAPACITY: usize> RestrictedFileName<CAPACITY> {
197    /// Creates a new instance.
198    ///
199    /// # Safety
200    ///
201    /// * The provided slice must have a length smaller or equal to the capacity. `value.len() < Self::max_len()`
202    /// * The contents of the slice must follow the content contract
203    ///
204    pub const unsafe fn new_unchecked_const(value: &[u8]) -> RestrictedFileName<CAPACITY> {
205        debug_assert!(value.len() <= CAPACITY);
206        Self {
207            value: iceoryx2_bb_container::string::StaticString::from_bytes_unchecked(value),
208        }
209    }
210
211    /// Returns the maximum length of the [`RestrictedFileName`]
212    pub const fn max_len() -> usize {
213        CAPACITY
214    }
215}
216
217impl<const CAPACITY: usize> core::fmt::Display for RestrictedFileName<CAPACITY> {
218    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
219        write!(f, "{}", self.value)
220    }
221}
222
223impl<const CAPACITY: usize> Hash for RestrictedFileName<CAPACITY> {
224    fn hash<H: Hasher>(&self, state: &mut H) {
225        self.normalize().as_bytes().hash(state)
226    }
227}
228
229impl<const CAPACITY: usize> From<RestrictedFileName<CAPACITY>> for FileName {
230    fn from(value: RestrictedFileName<CAPACITY>) -> FileName {
231        // SAFETY: It is ensured that the RestrictedFileName contains always a valid FileName, just
232        // with less capacity
233        unsafe { FileName::new_unchecked(value.as_bytes()) }
234    }
235}
236
237impl<const CAPACITY: usize> From<RestrictedFileName<CAPACITY>> for String {
238    fn from(value: RestrictedFileName<CAPACITY>) -> String {
239        // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
240        unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
241    }
242}
243
244impl<const CAPACITY: usize> From<&RestrictedFileName<CAPACITY>> for String {
245    fn from(value: &RestrictedFileName<CAPACITY>) -> String {
246        // SAFETY: It is ensured that the semantic string contains only valid utf-8 strings
247        unsafe { String::from_utf8_unchecked(value.as_bytes().to_vec()) }
248    }
249}
250
251impl<const CAPACITY: usize> core::convert::TryFrom<&str> for RestrictedFileName<CAPACITY> {
252    type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
253
254    fn try_from(value: &str) -> Result<Self, Self::Error> {
255        Self::new(value.as_bytes())
256    }
257}
258
259impl<const CAPACITY: usize> core::convert::TryFrom<&FileName> for RestrictedFileName<CAPACITY> {
260    type Error = iceoryx2_bb_container::semantic_string::SemanticStringError;
261
262    fn try_from(value: &FileName) -> Result<Self, Self::Error> {
263        Self::new(value.as_bytes())
264    }
265}
266
267impl<const CAPACITY: usize> PartialEq<RestrictedFileName<CAPACITY>>
268    for RestrictedFileName<CAPACITY>
269{
270    fn eq(&self, other: &RestrictedFileName<CAPACITY>) -> bool {
271        *self.normalize().as_bytes() == *other.normalize().as_bytes()
272    }
273}
274
275impl<const CAPACITY: usize> PartialEq<&[u8]> for RestrictedFileName<CAPACITY> {
276    fn eq(&self, other: &&[u8]) -> bool {
277        let other = match RestrictedFileName::<CAPACITY>::new(other) {
278            Ok(other) => other,
279            Err(_) => return false,
280        };
281
282        *self == other
283    }
284}
285
286impl<const CAPACITY: usize> PartialEq<&[u8]> for &RestrictedFileName<CAPACITY> {
287    fn eq(&self, other: &&[u8]) -> bool {
288        let other = match RestrictedFileName::<CAPACITY>::new(other) {
289            Ok(other) => other,
290            Err(_) => return false,
291        };
292
293        **self == other
294    }
295}
296
297impl<const CAPACITY: usize> PartialEq<[u8; CAPACITY]> for RestrictedFileName<CAPACITY> {
298    fn eq(&self, other: &[u8; CAPACITY]) -> bool {
299        let other = match RestrictedFileName::<CAPACITY>::new(other) {
300            Ok(other) => other,
301            Err(_) => return false,
302        };
303
304        *self == other
305    }
306}
307
308impl<const CAPACITY: usize> PartialEq<&[u8; CAPACITY]> for RestrictedFileName<CAPACITY> {
309    fn eq(&self, other: &&[u8; CAPACITY]) -> bool {
310        // TODO: false positive from clippy
311        #[allow(clippy::explicit_auto_deref)]
312        let other = match RestrictedFileName::<CAPACITY>::new(*other) {
313            Ok(other) => other,
314            Err(_) => return false,
315        };
316
317        *self == other
318    }
319}
320
321impl<const CAPACITY: usize> core::ops::Deref for RestrictedFileName<CAPACITY> {
322    type Target = [u8];
323
324    fn deref(&self) -> &Self::Target {
325        use iceoryx2_bb_container::string::String;
326        self.value.as_bytes()
327    }
328}