ascii_domain

Domains whose labels are only ASCII.
git clone https://git.philomathiclife.com/repos/ascii_domain
Log | Files | Refs | README

char_set.rs (21093B)


      1 use core::{
      2     error::Error,
      3     fmt::{self, Display, Formatter},
      4     str,
      5 };
      6 /// Error returned from [`AllowedAscii::try_from_unique_ascii`].
      7 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
      8 pub enum AsciiErr {
      9     /// Since `AllowedAscii` only allows unique ASCII characters and doesn't allow `b'.'`, the maximum count is
     10     /// 127. This variant is returned when the count exceeds that.
     11     CountTooLarge(usize),
     12     /// The contained `u8` is not valid ASCII (i.e., it is strictly greater than 127).
     13     InvalidByte(u8),
     14     /// `b'.'` was in the allowed ASCII. It is the only ASCII value not allowed since it is always used
     15     /// as a [`crate::dom::Label`] separator.
     16     Contains46,
     17     /// The contained ASCII appeared more than once.
     18     Duplicate(u8),
     19 }
     20 impl Display for AsciiErr {
     21     #[inline]
     22     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     23         match *self {
     24             Self::CountTooLarge(byt) => {
     25                 write!(f, "the allowed ASCII had {byt} values, but 127 is the max")
     26             }
     27             Self::InvalidByte(byt) => {
     28                 write!(f, "allowed ASCII was passed the invalid byte value {byt}")
     29             }
     30             Self::Contains46 => f.write_str("allowed ASCII contains '.'"),
     31             Self::Duplicate(byt) => {
     32                 let input = [byt];
     33                 if let Ok(val) = str::from_utf8(input.as_slice()) {
     34                     write!(f, "allowed ASCII has the duplicate value '{val}'")
     35                 } else {
     36                     write!(f, "allowed ASCII has the invalid value '{byt}'")
     37                 }
     38             }
     39         }
     40     }
     41 }
     42 impl Error for AsciiErr {}
     43 /// Container of the ASCII `u8`s that are allowed to appear in a [`crate::dom::Label`]. Note that while
     44 /// [`crate::dom::Domain`] treats ASCII uppercase letters as lowercase, it still depends on such `u8`s being
     45 /// included. For example if `b'A'` is not included, then `b'A'` is not allowed even if `b'a'` is included.
     46 ///
     47 /// It is _highly_ unlikely that non-printable ASCII nor `b'\\'` should be used since such ASCII would almost
     48 /// certainly require being escaped.
     49 pub struct AllowedAscii<T> {
     50     /// The allowed ASCII `u8`s.
     51     allowed: T,
     52 }
     53 impl<T> AllowedAscii<T> {
     54     /// Returns a reference to the contained value.
     55     ///
     56     /// # Example
     57     ///
     58     /// ```
     59     /// use ascii_domain::char_set;
     60     /// assert!(char_set::ASCII_LETTERS.as_inner().len() == 52);
     61     /// ```
     62     #[inline]
     63     pub const fn as_inner(&self) -> &T {
     64         &self.allowed
     65     }
     66     /// Returns the contained value consuming `self`.
     67     ///
     68     /// # Example
     69     ///
     70     /// ```
     71     /// use ascii_domain::char_set;
     72     /// assert!(char_set::ASCII_LETTERS.into_inner().len() == 52);
     73     /// ```
     74     #[inline]
     75     pub fn into_inner(self) -> T {
     76         self.allowed
     77     }
     78 }
     79 impl<T: AsRef<[u8]>> AllowedAscii<T> {
     80     /// Returns `true` iff `val` is an allowed ASCII value in a [`crate::dom::Label`].
     81     ///
     82     /// # Example
     83     ///
     84     /// ```
     85     /// use ascii_domain::char_set;
     86     /// assert!(char_set::ASCII_LETTERS.contains(b'a'));
     87     /// ```
     88     #[inline]
     89     #[must_use]
     90     pub fn contains(&self, val: u8) -> bool {
     91         // We sort `allowed` in `try_from_unique_ascii`, so `binary_search` is fine.
     92         self.allowed.as_ref().binary_search(&val).is_ok()
     93     }
     94     /// Returns the number of allowed ASCII characters.
     95     ///
     96     /// # Example
     97     ///
     98     /// ```
     99     /// use ascii_domain::char_set;
    100     /// assert!(char_set::ASCII_LETTERS.len() == 52);
    101     /// ```
    102     #[expect(
    103         clippy::as_conversions,
    104         clippy::cast_possible_truncation,
    105         reason = "comment justifies its correctness"
    106     )]
    107     #[inline]
    108     #[must_use]
    109     pub fn len(&self) -> u8 {
    110         // We enforce only unique non `b'.'` ASCII in `try_from_unique_ascii` which among other things means
    111         // the max count is 127 so truncation will not occur
    112         self.allowed.as_ref().len() as u8
    113     }
    114     /// Returns `true` iff `self` does not contain any ASCII.
    115     ///
    116     /// # Examples
    117     ///
    118     /// ```
    119     /// use ascii_domain::char_set::{self, AllowedAscii};
    120     /// assert!(!char_set::ASCII_LETTERS.is_empty());
    121     /// assert!(AllowedAscii::try_from_unique_ascii([]).unwrap().is_empty());
    122     /// ```
    123     #[inline]
    124     #[must_use]
    125     pub fn is_empty(&self) -> bool {
    126         self.allowed.as_ref().is_empty()
    127     }
    128 }
    129 impl<T: AsMut<[u8]>> AllowedAscii<T> {
    130     /// `allowed` must contain unique ASCII `u8`s. Note it is likely `allowed` should be a subset of
    131     /// [`PRINTABLE_ASCII`] since any other ASCII would likely require some form of escape character logic.
    132     /// Additionally, it is likely `allowed.as_mut().len()` should be greater than 0; otherwise the returned
    133     /// `AllowedAscii` will always cause [`crate::dom::Domain::try_from_bytes`] to error since `Domain` requires
    134     /// at least one non-root [`crate::dom::Label`].
    135     ///
    136     /// `allowed` is mutated such that `allowed.as_mut()` is sorted in order.
    137     ///
    138     /// # Errors
    139     ///
    140     /// Returns `AsciiError` iff `allowed` does not contain a set of unique ASCII `u8`s or contains `b'.'`.
    141     ///
    142     /// # Examples
    143     ///
    144     /// ```
    145     /// use ascii_domain::char_set::{AllowedAscii, AsciiErr};
    146     /// assert!(AllowedAscii::try_from_unique_ascii(b"asdfghjkl".to_owned()).map_or(false, |ascii| ascii.contains(b'a') && !ascii.contains(b'A')));
    147     /// assert!(AllowedAscii::try_from_unique_ascii(b"aa".to_owned()).map_or_else(|err| err == AsciiErr::Duplicate(b'a'), |_| false));
    148     /// assert!(AllowedAscii::try_from_unique_ascii([255]).map_or_else(|err| err == AsciiErr::InvalidByte(255), |_| false));
    149     /// assert!(AllowedAscii::try_from_unique_ascii([0; 128]).map_or_else(|err| err == AsciiErr::CountTooLarge(128), |_| false));
    150     /// assert!(AllowedAscii::try_from_unique_ascii([b'.']).map_or_else(|err| err == AsciiErr::Contains46, |_| false));
    151     /// ```
    152     #[inline]
    153     pub fn try_from_unique_ascii(mut allowed: T) -> Result<Self, AsciiErr> {
    154         let bytes = allowed.as_mut();
    155         if bytes.len() > 127 {
    156             Err(AsciiErr::CountTooLarge(bytes.len()))
    157         } else {
    158             bytes.sort_unstable();
    159             // Since `bytes` is sorted, we simply have to check the last value to determine if valid ASCII was
    160             // provided.
    161             if let Some(byt) = bytes.last() {
    162                 let b = *byt;
    163                 if b > 127 {
    164                     return Err(AsciiErr::InvalidByte(b));
    165                 }
    166             }
    167             bytes
    168                 .iter()
    169                 // 255 is not valid ASCII, so we can use it as an initializer.
    170                 .try_fold(255, |prev, b| {
    171                     let byt = *b;
    172                     if byt == b'.' {
    173                         Err(AsciiErr::Contains46)
    174                     } else if prev == byt {
    175                         Err(AsciiErr::Duplicate(prev))
    176                     } else {
    177                         Ok(byt)
    178                     }
    179                 })
    180                 .map(|_| Self { allowed })
    181         }
    182     }
    183 }
    184 /// Printable ASCII that should not need to be "escaped". That is to say
    185 /// printable ASCII excluding space (i.e., 32), dot (i.e. 46), and backslash (i.e., 92).
    186 /// This returns all `u8`s inclusively between 33 and 126 except 46 and 92.
    187 pub const PRINTABLE_ASCII: AllowedAscii<[u8; 92]> = AllowedAscii {
    188     allowed: [
    189         b'!', b'"', b'#', b'$', b'%', b'&', b'\'', b'(', b')', b'*', b'+', b',', b'-', b'/', b'0',
    190         b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', b'<', b'=', b'>', b'?',
    191         b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N',
    192         b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'[', b']', b'^',
    193         b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm',
    194         b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'{', b'|',
    195         b'}', b'~',
    196     ],
    197 };
    198 /// ASCII allowed in [RFC 5322 `atext`](https://www.rfc-editor.org/rfc/rfc5322#section-3.2.3).
    199 /// This contains the following `u8`s:
    200 ///
    201 /// 33, 35–39, 42–43, 45, 47–57, 61, 63, 65–90, and 94–126.
    202 pub const RFC5322_ATEXT: AllowedAscii<[u8; 81]> = AllowedAscii {
    203     allowed: [
    204         b'!', b'#', b'$', b'%', b'&', b'\'', b'*', b'+', b'-', b'/', b'0', b'1', b'2', b'3', b'4',
    205         b'5', b'6', b'7', b'8', b'9', b'=', b'?', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H',
    206         b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W',
    207         b'X', b'Y', b'Z', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i',
    208         b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x',
    209         b'y', b'z', b'{', b'|', b'}', b'~',
    210     ],
    211 };
    212 /// ASCII allowed in a domain by [Firefox](https://www.mozilla.org/en-US/firefox/)
    213 /// as of 2023-09-03T20:50+00:00.
    214 /// This contains the following `u8`s:
    215 ///
    216 /// 33, 36, 38–41, 43–45, 48–57, 59, 61, 65–90, 95–123, and 125–126.
    217 pub const ASCII_FIREFOX: AllowedAscii<[u8; 78]> = AllowedAscii {
    218     allowed: [
    219         b'!', b'$', b'&', b'\'', b'(', b')', b'+', b',', b'-', b'0', b'1', b'2', b'3', b'4', b'5',
    220         b'6', b'7', b'8', b'9', b';', b'=', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I',
    221         b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X',
    222         b'Y', b'Z', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k',
    223         b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
    224         b'{', b'}', b'~',
    225     ],
    226 };
    227 /// ASCII hyphen, digits, and letters.
    228 /// This contains 45 and all `u8`s inclusively between 48 and 57, 65 and 90, and 97 and 122.
    229 pub const ASCII_HYPHEN_DIGITS_LETTERS: AllowedAscii<[u8; 63]> = AllowedAscii {
    230     allowed: [
    231         b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
    232         b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S',
    233         b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
    234         b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w',
    235         b'x', b'y', b'z',
    236     ],
    237 };
    238 /// ASCII digits and letters.
    239 /// This contains all `u8`s inclusively between 48 and 57, 65 and 90, and 97 and 122.
    240 pub const ASCII_DIGITS_LETTERS: AllowedAscii<[u8; 62]> = AllowedAscii {
    241     allowed: [
    242         b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E',
    243         b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
    244         b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i',
    245         b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x',
    246         b'y', b'z',
    247     ],
    248 };
    249 /// ASCII letters.
    250 /// This contains all `u8`s inclusively between 65 and 90 and 97 and 122.
    251 pub const ASCII_LETTERS: AllowedAscii<[u8; 52]> = AllowedAscii {
    252     allowed: [
    253         b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O',
    254         b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd',
    255         b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
    256         b't', b'u', b'v', b'w', b'x', b'y', b'z',
    257     ],
    258 };
    259 /// ASCII hyphen, digits, and uppercase letters.
    260 /// This contains 45 and all `u8`s inclusively between 48 and 57 and 65 and 90.
    261 pub const ASCII_HYPHEN_DIGITS_UPPERCASE: AllowedAscii<[u8; 37]> = AllowedAscii {
    262     allowed: [
    263         b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D',
    264         b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S',
    265         b'T', b'U', b'V', b'W', b'X', b'Y', b'Z',
    266     ],
    267 };
    268 /// ASCII hyphen, digits, and lowercase letters.
    269 /// This contains 45 and all `u8`s inclusively between 48 and 57 and 97 and 122.
    270 pub const ASCII_HYPHEN_DIGITS_LOWERCASE: AllowedAscii<[u8; 37]> = AllowedAscii {
    271     allowed: [
    272         b'-', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd',
    273         b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
    274         b't', b'u', b'v', b'w', b'x', b'y', b'z',
    275     ],
    276 };
    277 /// ASCII digits and uppercase letters.
    278 /// This contains all `u8`s inclusively between 48 and 57 and 65 and 90.
    279 pub const ASCII_DIGITS_UPPERCASE: AllowedAscii<[u8; 36]> = AllowedAscii {
    280     allowed: [
    281         b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E',
    282         b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
    283         b'U', b'V', b'W', b'X', b'Y', b'Z',
    284     ],
    285 };
    286 /// ASCII digits and lowercase letters.
    287 /// This contains all `u8`s inclusively between 48 and 57 and 97 and 122.
    288 pub const ASCII_DIGITS_LOWERCASE: AllowedAscii<[u8; 36]> = AllowedAscii {
    289     allowed: [
    290         b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e',
    291         b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
    292         b'u', b'v', b'w', b'x', b'y', b'z',
    293     ],
    294 };
    295 /// ASCII uppercase letters.
    296 /// This contains all `u8`s inclusively between 65 and 90.
    297 pub const ASCII_UPPERCASE: AllowedAscii<[u8; 26]> = AllowedAscii {
    298     allowed: [
    299         b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O',
    300         b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z',
    301     ],
    302 };
    303 /// ASCII lowercase letters.
    304 /// This contains all `u8`s inclusively between 97 and 122.
    305 pub const ASCII_LOWERCASE: AllowedAscii<[u8; 26]> = AllowedAscii {
    306     allowed: [
    307         b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
    308         b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
    309     ],
    310 };
    311 /// ASCII digits.
    312 /// This contains all `u8`s inclusively between 48 and 57.
    313 pub const ASCII_DIGITS: AllowedAscii<[u8; 10]> = AllowedAscii {
    314     allowed: [b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9'],
    315 };
    316 #[cfg(test)]
    317 mod tests {
    318     extern crate alloc;
    319     use crate::char_set::{
    320         AllowedAscii, AsciiErr, ASCII_DIGITS, ASCII_DIGITS_LETTERS, ASCII_DIGITS_LOWERCASE,
    321         ASCII_DIGITS_UPPERCASE, ASCII_FIREFOX, ASCII_HYPHEN_DIGITS_LETTERS,
    322         ASCII_HYPHEN_DIGITS_LOWERCASE, ASCII_HYPHEN_DIGITS_UPPERCASE, ASCII_LETTERS,
    323         ASCII_LOWERCASE, ASCII_UPPERCASE, PRINTABLE_ASCII, RFC5322_ATEXT,
    324     };
    325     use alloc::{borrow::ToOwned, vec::Vec};
    326     #[test]
    327     fn try_from() {
    328         // Empty is allowed.
    329         assert!(AllowedAscii::try_from_unique_ascii([]).is_ok());
    330         // Duplicates are not allowed.
    331         assert!(AllowedAscii::try_from_unique_ascii(b"aba".to_owned())
    332             .map_or_else(|e| e == AsciiErr::Duplicate(b'a'), |_| false));
    333         // `b'.'` is not allowed.
    334         assert!(AllowedAscii::try_from_unique_ascii(b"a.c".to_owned())
    335             .map_or_else(|e| e == AsciiErr::Contains46, |_| false));
    336         // At most 127 bytes are allowed.
    337         assert!(AllowedAscii::try_from_unique_ascii([0; 128])
    338             .map_or_else(|e| e == AsciiErr::CountTooLarge(128), |_| false));
    339         let mut all_ascii = (0..b'.').collect::<Vec<u8>>();
    340         let next = b'.' + 1;
    341         all_ascii.extend(next..=127);
    342         assert!(AllowedAscii::try_from_unique_ascii(all_ascii).is_ok());
    343         // Only ASCII is allowed.
    344         assert!(AllowedAscii::try_from_unique_ascii([255])
    345             .map_or_else(|e| e == AsciiErr::InvalidByte(255), |_| false));
    346         assert!(
    347             AllowedAscii::try_from_unique_ascii(b"abcdef".to_owned()).map_or(false, |bytes| bytes
    348                 .contains(b'a')
    349                 && bytes.contains(b'b')
    350                 && bytes.contains(b'c')
    351                 && bytes.contains(b'd')
    352                 && bytes.contains(b'e')
    353                 && bytes.contains(b'f'))
    354         );
    355     }
    356     #[test]
    357     fn test_consts() {
    358         let letters = ASCII_LETTERS;
    359         assert!(letters.len() == 52);
    360         for i in b'A'..=b'Z' {
    361             assert!(letters.contains(i));
    362         }
    363         for i in b'a'..=b'z' {
    364             assert!(letters.contains(i));
    365         }
    366         let digits = ASCII_DIGITS;
    367         assert!(digits.len() == 10);
    368         for i in b'0'..=b'9' {
    369             assert!(digits.contains(i));
    370         }
    371         let lower = ASCII_LOWERCASE;
    372         assert!(lower.len() == 26);
    373         for i in b'a'..=b'z' {
    374             assert!(lower.contains(i));
    375         }
    376         let upper = ASCII_UPPERCASE;
    377         assert!(upper.len() == 26);
    378         for i in b'A'..=b'Z' {
    379             assert!(upper.contains(i));
    380         }
    381         let dig_let = ASCII_DIGITS_LETTERS;
    382         assert!(dig_let.len() == 62);
    383         for i in b'a'..=b'z' {
    384             assert!(dig_let.contains(i));
    385         }
    386         for i in b'0'..=b'9' {
    387             assert!(dig_let.contains(i));
    388         }
    389         for i in b'A'..=b'Z' {
    390             assert!(dig_let.contains(i));
    391         }
    392         let dig_lower = ASCII_DIGITS_LOWERCASE;
    393         assert!(dig_lower.len() == 36);
    394         for i in b'a'..=b'z' {
    395             assert!(dig_lower.contains(i));
    396         }
    397         for i in b'0'..=b'9' {
    398             assert!(dig_lower.contains(i));
    399         }
    400         let dig_upper = ASCII_DIGITS_UPPERCASE;
    401         assert!(dig_upper.len() == 36);
    402         for i in b'A'..=b'Z' {
    403             assert!(dig_upper.contains(i));
    404         }
    405         for i in b'0'..=b'9' {
    406             assert!(dig_upper.contains(i));
    407         }
    408         let ffox = ASCII_FIREFOX;
    409         assert!(ffox.len() == 78);
    410         for i in b'A'..=b'Z' {
    411             assert!(ffox.contains(i));
    412         }
    413         for i in b'a'..=b'z' {
    414             assert!(ffox.contains(i));
    415         }
    416         for i in b'0'..=b'9' {
    417             assert!(ffox.contains(i));
    418         }
    419         assert!(ffox.contains(b'!'));
    420         assert!(ffox.contains(b'$'));
    421         assert!(ffox.contains(b'&'));
    422         assert!(ffox.contains(b'\''));
    423         assert!(ffox.contains(b'('));
    424         assert!(ffox.contains(b')'));
    425         assert!(ffox.contains(b'+'));
    426         assert!(ffox.contains(b','));
    427         assert!(ffox.contains(b'-'));
    428         assert!(ffox.contains(b';'));
    429         assert!(ffox.contains(b'='));
    430         assert!(ffox.contains(b'_'));
    431         assert!(ffox.contains(b'`'));
    432         assert!(ffox.contains(b'{'));
    433         assert!(ffox.contains(b'}'));
    434         assert!(ffox.contains(b'~'));
    435         assert!(ASCII_HYPHEN_DIGITS_LETTERS.len() == 63);
    436         assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(b'-'));
    437         for i in b'A'..=b'Z' {
    438             assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(i));
    439         }
    440         for i in b'a'..=b'z' {
    441             assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(i));
    442         }
    443         for i in b'0'..=b'9' {
    444             assert!(ASCII_HYPHEN_DIGITS_LETTERS.contains(i));
    445         }
    446         let hyp_lower = ASCII_HYPHEN_DIGITS_LOWERCASE;
    447         assert!(hyp_lower.len() == 37);
    448         assert!(hyp_lower.contains(b'-'));
    449         for i in b'a'..=b'z' {
    450             assert!(hyp_lower.contains(i));
    451         }
    452         for i in b'0'..=b'9' {
    453             assert!(hyp_lower.contains(i));
    454         }
    455         let hyp_upper = ASCII_HYPHEN_DIGITS_UPPERCASE;
    456         assert!(hyp_upper.len() == 37);
    457         assert!(hyp_upper.contains(b'-'));
    458         for i in b'A'..=b'Z' {
    459             assert!(hyp_upper.contains(i));
    460         }
    461         for i in b'0'..=b'9' {
    462             assert!(hyp_upper.contains(i));
    463         }
    464         let printable = PRINTABLE_ASCII;
    465         assert!(printable.len() == 92);
    466         let stop = b'.' - 1;
    467         for i in 33..=stop {
    468             assert!(printable.contains(i));
    469         }
    470         let stop2 = b'\\' - 1;
    471         for i in stop + 2..=stop2 {
    472             assert!(printable.contains(i));
    473         }
    474         for i in stop2 + 2..=b'~' {
    475             assert!(printable.contains(i));
    476         }
    477         let rfc = RFC5322_ATEXT;
    478         assert!(rfc.len() == 81);
    479         for i in b'A'..=b'Z' {
    480             assert!(rfc.contains(i));
    481         }
    482         for i in b'a'..=b'z' {
    483             assert!(rfc.contains(i));
    484         }
    485         for i in b'0'..=b'9' {
    486             assert!(rfc.contains(i));
    487         }
    488         assert!(rfc.contains(b'!'));
    489         assert!(rfc.contains(b'#'));
    490         assert!(rfc.contains(b'$'));
    491         assert!(rfc.contains(b'%'));
    492         assert!(rfc.contains(b'&'));
    493         assert!(rfc.contains(b'\''));
    494         assert!(rfc.contains(b'*'));
    495         assert!(rfc.contains(b'+'));
    496         assert!(rfc.contains(b'-'));
    497         assert!(rfc.contains(b'/'));
    498         assert!(rfc.contains(b'='));
    499         assert!(rfc.contains(b'?'));
    500         assert!(rfc.contains(b'^'));
    501         assert!(rfc.contains(b'_'));
    502         assert!(rfc.contains(b'`'));
    503         assert!(rfc.contains(b'{'));
    504         assert!(rfc.contains(b'|'));
    505         assert!(rfc.contains(b'}'));
    506         assert!(rfc.contains(b'~'));
    507     }
    508 }