1use core::{char, convert::TryFrom, fmt};
2
3pub const NAME_CHARS: [u8; 32] = *b".12345abcdefghijklmnopqrstuvwxyz";
5
6pub const NAME_MAX_LEN: usize = 13;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum ParseNameError {
12 BadChar(u8),
14 TooLong,
16}
17
18impl fmt::Display for ParseNameError {
19 #[inline]
20 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21 match *self {
22 Self::BadChar(c) => {
23 write!(f, "name contains invalid character '{}'", char::from(c))
24 }
25 Self::TooLong => {
26 write!(f, "name is too long, must be 13 chars or less")
27 }
28 }
29 }
30}
31
32#[inline]
56pub fn name_from_bytes<I>(mut iter: I) -> Result<u64, ParseNameError>
57where
58 I: Iterator<Item = u8>,
59{
60 let mut value = 0_u64;
61 let mut len = 0_u64;
62
63 while let Some(c) = iter.next() {
65 let v = char_to_value(c).ok_or_else(|| ParseNameError::BadChar(c))?;
66 value <<= 5;
67 value |= u64::from(v);
68 len += 1;
69
70 if len == 12 {
71 break;
72 }
73 }
74
75 if len == 0 {
76 return Ok(0);
77 }
78
79 value <<= 4 + 5 * (12 - len);
80
81 if let Some(c) = iter.next() {
83 let v = char_to_value(c).ok_or_else(|| ParseNameError::BadChar(c))?;
84
85 if v > 0x0F {
87 return Err(ParseNameError::BadChar(c));
88 }
89
90 value |= u64::from(v);
91
92 if iter.next().is_some() {
94 return Err(ParseNameError::TooLong);
95 }
96 }
97
98 Ok(value)
99}
100
101fn char_to_value(c: u8) -> Option<u8> {
103 if c == b'.' {
104 Some(0)
105 } else if c >= b'1' && c <= b'5' {
106 Some(c - b'1' + 1)
107 } else if c >= b'a' && c <= b'z' {
108 Some(c - b'a' + 6)
109 } else {
110 None
111 }
112}
113
114#[inline]
127#[must_use]
128pub fn name_to_bytes(value: u64) -> [u8; NAME_MAX_LEN] {
129 let mut chars = [b'.'; NAME_MAX_LEN];
130 if value == 0 {
131 return chars;
132 }
133
134 let mask = 0xF800_0000_0000_0000;
135 let mut v = value;
136 for (i, c) in chars.iter_mut().enumerate() {
137 let index = (v & mask) >> (if i == 12 { 60 } else { 59 });
138 let index = usize::try_from(index).unwrap_or_default();
139 if let Some(v) = NAME_CHARS.get(index) {
140 *c = *v;
141 }
142 v <<= 5;
143 }
144 chars
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use core::str;
151 use proptest::prelude::*;
152
153 #[test]
154 fn from_bytes_to_bytes() {
155 proptest!(|(input in "[[1-5][a-z]\\.]{0,12}[[1-5][a-j]\\.]{0,1}")| {
156 let name = match name_from_bytes(input.bytes()) {
157 Ok(name) => name,
158 Err(error) => panic!("Failed with input '{}': {}", input, error),
159 };
160 let bytes = name_to_bytes(name);
161 let string = str::from_utf8(&bytes).expect("Failed to convert bytes into str");
162 prop_assert_eq!(
163 string,
164 format!("{:.<13}", input),
165 "Names don't match"
166 );
167 });
168 }
169
170 #[test]
171 fn from_bytes_too_long() {
172 proptest!(|(input in "[[1-5][a-z]\\.]{12}[[1-5][a-j]\\.]{2}")| {
173 let result = name_from_bytes(input.bytes());
174 prop_assert_eq!(
175 result,
176 Err(ParseNameError::TooLong),
177 "Should've gotten TooLong error"
178 );
179 });
180 }
181
182 #[test]
183 fn from_bytes_bad_char() {
184 proptest!(|(
185 input in "[[1-5][a-z]\\.]{11}",
186 bad_char in "[^[1-5][a-z]\\.]{1}"
187 )| {
188 let input = input + &bad_char;
189 let result = name_from_bytes(input.bytes());
190 let bad_char = bad_char.bytes().next().unwrap();
191 prop_assert_eq!(
192 result,
193 Err(ParseNameError::BadChar(bad_char)),
194 "Should've gotten BadChar error with char '{}'",
195 char::from(bad_char)
196 );
197 });
198 }
199
200 #[test]
201 fn from_bytes_bad_last_char() {
202 proptest!(|(
203 input in "[[1-5][a-z]\\.]{12}",
204 bad_char in "[^[1-5][a-j]\\.]{1}"
205 )| {
206 let input = input + &bad_char;
207 let result = name_from_bytes(input.bytes());
208 let bad_char = bad_char.bytes().next().unwrap();
209 prop_assert_eq!(
210 result,
211 Err(ParseNameError::BadChar(bad_char)),
212 "Should've gotten BadChar error with char '{}'",
213 char::from(bad_char)
214 );
215 });
216 }
217
218 #[test]
219 fn to_bytes_doesnt_crash() {
220 proptest!(|(input in 0_u64..)| {
221 let _ = name_to_bytes(input);
222 });
223 }
224}