1use 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
23pub 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#[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 pub fn as_str(&self) -> &str {
50 self.0.as_str()
51 }
52
53 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
178impl From<std::convert::Infallible> for ParseNameError {
184 fn from(value: std::convert::Infallible) -> Self {
185 match value {}
186 }
187}
188
189pub 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 assert_eq!(static_name, heap_name);
326 assert_eq!(static_name, borrowed_name);
327 assert_eq!(heap_name, borrowed_name);
328
329 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 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}