Skip to main content

name/
lib.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Data structures and functions relevant to `fuchsia.io` name processing.
6//!
7//! These names may be used to designate the location of a node as it
8//! appears in a directory.
9//!
10//! These should be aligned with the library comments in sdk/fidl/fuchsia.io/io.fidl.
11
12use fidl_fuchsia_io as fio;
13use static_assertions::const_assert_eq;
14use std::borrow::Borrow;
15use std::fmt::Display;
16use std::ops::Deref;
17use thiserror::Error;
18use zx_status::Status;
19
20mod repr;
21use repr::Repr;
22
23/// The maximum length, in bytes, of a single filesystem component.
24pub const MAX_NAME_LENGTH: usize = fio::MAX_NAME_LENGTH as usize;
25const_assert_eq!(MAX_NAME_LENGTH as u64, fio::MAX_NAME_LENGTH);
26
27/// The type for the name of a node, i.e. a single path component, e.g. `foo`.
28///
29/// ## Invariants
30///
31/// A valid node name must meet the following criteria:
32///
33/// * It cannot be longer than [MAX_NAME_LENGTH].
34/// * It cannot be empty.
35/// * It cannot be ".." (dot-dot).
36/// * It cannot be "." (single dot).
37/// * It cannot contain "/".
38/// * It cannot contain embedded NUL.
39#[derive(Clone)]
40pub struct Name(Repr);
41
42const_assert_eq!(std::mem::size_of::<Name>(), 16);
43const_assert_eq!(std::mem::align_of::<Name>(), 8);
44const_assert_eq!(std::mem::size_of::<Option<Name>>(), 16);
45const_assert_eq!(std::mem::size_of::<Result<Name, ParseNameError>>(), 16);
46
47impl Name {
48    /// Returns a shared reference to the underlying string slice.
49    pub fn as_str(&self) -> &str {
50        self.0.as_str()
51    }
52
53    /// Constructs a `Name` from a static string slice.
54    ///
55    /// # Panics
56    ///
57    /// Panics if the name is invalid according to [validate_name].
58    pub fn from_static(name: &'static str) -> Self {
59        validate_name(name).expect("Invalid name");
60        Self(Repr::from_static_str(name))
61    }
62}
63
64impl PartialEq for Name {
65    fn eq(&self, other: &Self) -> bool {
66        self.as_str() == other.as_str()
67    }
68}
69
70impl Eq for Name {}
71
72impl PartialOrd for Name {
73    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
74        Some(self.cmp(other))
75    }
76}
77
78impl Ord for Name {
79    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
80        self.as_str().cmp(other.as_str())
81    }
82}
83
84impl std::hash::Hash for Name {
85    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
86        self.as_str().hash(state);
87    }
88}
89
90impl std::fmt::Debug for Name {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_tuple("Name").field(&self.as_str()).finish()
93    }
94}
95
96impl Display for Name {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.write_str(self.as_str())
99    }
100}
101
102impl TryFrom<String> for Name {
103    type Error = ParseNameError;
104
105    fn try_from(value: String) -> Result<Name, ParseNameError> {
106        validate_name(&value)?;
107        Ok(Self(Repr::from_string(value)))
108    }
109}
110
111impl TryFrom<&String> for Name {
112    type Error = ParseNameError;
113
114    fn try_from(value: &String) -> Result<Name, ParseNameError> {
115        Self::try_from(value.as_str())
116    }
117}
118
119impl<'a> TryFrom<&'a str> for Name {
120    type Error = ParseNameError;
121
122    fn try_from(value: &'a str) -> Result<Name, ParseNameError> {
123        validate_name(value)?;
124        Ok(Self(Repr::from_str(value)))
125    }
126}
127
128impl Deref for Name {
129    type Target = str;
130
131    fn deref(&self) -> &Self::Target {
132        self.as_str()
133    }
134}
135
136impl Borrow<str> for Name {
137    fn borrow(&self) -> &str {
138        &*self
139    }
140}
141
142impl From<Name> for String {
143    fn from(value: Name) -> Self {
144        value.0.into()
145    }
146}
147
148#[derive(Error, Debug, Clone, PartialEq, Eq)]
149pub enum ParseNameError {
150    #[error("name is too long")]
151    TooLong,
152
153    #[error("name cannot be empty")]
154    Empty,
155
156    #[error("name cannot be `.`")]
157    Dot,
158
159    #[error("name cannot be `..`")]
160    DotDot,
161
162    #[error("name cannot contain `/`")]
163    Slash,
164
165    #[error("name cannot contain embedded NUL")]
166    EmbeddedNul,
167}
168
169impl From<ParseNameError> for Status {
170    fn from(value: ParseNameError) -> Self {
171        match value {
172            ParseNameError::TooLong => Status::BAD_PATH,
173            _ => Status::INVALID_ARGS,
174        }
175    }
176}
177
178// This lets methods take `name: impl TryInto<Name, Error: Into<ParseNameError>>` as an argument and
179// return a `Result` with an error type of either `ParseNameError` or `Status`. If a Name is passed
180// to the method, the `try_into` call will return a `Result<Name, Infallible>` and `Infallible`
181// needs to be convertible to the error type returned by the method even though it will never
182// happen.
183impl From<std::convert::Infallible> for ParseNameError {
184    fn from(value: std::convert::Infallible) -> Self {
185        match value {}
186    }
187}
188
189/// Validates whether a string slice is a valid node name.
190///
191/// A valid node name must meet the following criteria:
192/// * It cannot be longer than [MAX_NAME_LENGTH] (255 bytes).
193/// * It cannot be empty.
194/// * It cannot be "." (single dot) or ".." (dot-dot).
195/// * It cannot contain "/" (slash) or embedded NUL (`\0`) characters.
196pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
197    let len = name.len();
198    if len > MAX_NAME_LENGTH {
199        return Err(ParseNameError::TooLong);
200    }
201    if len == 0 {
202        return Err(ParseNameError::Empty);
203    }
204    let bytes = name.as_bytes();
205    if bytes[0] == b'.' {
206        if len == 1 {
207            return Err(ParseNameError::Dot);
208        }
209        if len == 2 && bytes[1] == b'.' {
210            return Err(ParseNameError::DotDot);
211        }
212    }
213    if let Some(idx) = memchr::memchr2(b'/', 0, name.as_bytes()) {
214        if name.as_bytes()[idx] == 0 {
215            return Err(ParseNameError::EmbeddedNul);
216        } else {
217            return Err(ParseNameError::Slash);
218        }
219    }
220    Ok(())
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226    use assert_matches::assert_matches;
227
228    #[test]
229    fn test_validate_name() {
230        assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong));
231        assert_matches!(
232            validate_name(
233                std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1]).unwrap()
234            ),
235            Err(ParseNameError::TooLong)
236        );
237        assert_matches!(
238            validate_name(std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap()),
239            Ok(())
240        );
241        assert_matches!(validate_name(""), Err(ParseNameError::Empty));
242        assert_matches!(validate_name("."), Err(ParseNameError::Dot));
243        assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
244        assert_matches!(validate_name(".a"), Ok(()));
245        assert_matches!(validate_name("..a"), Ok(()));
246        assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
247        assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
248        assert_matches!(validate_name("abc"), Ok(()));
249    }
250
251    #[test]
252    fn test_try_from() {
253        assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong));
254        assert_matches!(Name::try_from("abc".to_string()), Ok(name) if &*name == "abc");
255    }
256
257    #[test]
258    fn test_into() {
259        let name = Name::try_from("a".to_string()).unwrap();
260        let name: String = name.into();
261        assert_eq!(name, "a".to_string());
262    }
263
264    #[test]
265    fn test_deref() {
266        let name = Name::try_from("a".to_string()).unwrap();
267        let name: &str = &name;
268        assert_eq!(name, "a");
269    }
270
271    #[test]
272    fn test_inline() {
273        let name = Name::try_from("123456789012345").unwrap();
274        assert_eq!(name.as_str(), "123456789012345");
275
276        let name_string = Name::try_from("123456789012345".to_string()).unwrap();
277        assert_eq!(name_string.as_str(), "123456789012345");
278    }
279
280    #[test]
281    fn test_heap() {
282        let name = Name::try_from("1234567890123456").unwrap();
283        assert_eq!(name.as_str(), "1234567890123456");
284
285        let name_string = Name::try_from("1234567890123456".to_string()).unwrap();
286        assert_eq!(name_string.as_str(), "1234567890123456");
287    }
288
289    #[test]
290    fn test_static_borrow() {
291        let name = Name::from_static("static_string");
292        assert_eq!(name.as_str(), "static_string");
293
294        let name_large = Name::from_static("a_very_large_static_string_that_exceeds_15_bytes");
295        assert_eq!(name_large.as_str(), "a_very_large_static_string_that_exceeds_15_bytes");
296    }
297
298    #[test]
299    fn test_clone() {
300        let inline = Name::try_from("inline").unwrap();
301        let inline_clone = inline.clone();
302        assert_eq!(inline.as_str(), inline_clone.as_str());
303
304        let heap = Name::try_from("heap_allocated_name_large").unwrap();
305        let heap_clone = heap.clone();
306        assert_eq!(heap.as_str(), heap_clone.as_str());
307
308        let static_borrow = Name::from_static("static_borrow_large_name");
309        let static_borrow_clone = static_borrow.clone();
310        assert_eq!(static_borrow.as_str(), static_borrow_clone.as_str());
311    }
312
313    #[test]
314    fn test_equivalence() {
315        use std::collections::hash_map::DefaultHasher;
316        use std::hash::{Hash, Hasher};
317
318        let s = "a_very_large_string_that_exceeds_15_bytes";
319
320        let static_name = Name::from_static(s);
321        let heap_name = Name::try_from(s.to_string()).unwrap();
322        let borrowed_name = Name::try_from(s).unwrap();
323
324        // They must all be equal
325        assert_eq!(static_name, heap_name);
326        assert_eq!(static_name, borrowed_name);
327        assert_eq!(heap_name, borrowed_name);
328
329        // They must have the same hash
330        fn calculate_hash<T: Hash>(t: &T) -> u64 {
331            let mut s = DefaultHasher::new();
332            t.hash(&mut s);
333            s.finish()
334        }
335
336        assert_eq!(calculate_hash(&static_name), calculate_hash(&heap_name));
337        assert_eq!(calculate_hash(&static_name), calculate_hash(&borrowed_name));
338
339        // Different names are not equal and should have different hashes
340        let s2 = "another_large_string_that_is_different";
341        let other_name = Name::from_static(s2);
342        assert_ne!(static_name, other_name);
343        assert_ne!(calculate_hash(&static_name), calculate_hash(&other_name));
344    }
345}