1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8 
9 use crate::GuidFromStrError;
10 
byte_to_ascii_hex_lower(byte: u8) -> (u8, u8)11 pub(crate) const fn byte_to_ascii_hex_lower(byte: u8) -> (u8, u8) {
12     let mut l = byte & 0xf;
13     let mut h = byte >> 4;
14     if l <= 9 {
15         l += b'0';
16     } else {
17         l += b'a' - 10;
18     }
19     if h <= 9 {
20         h += b'0';
21     } else {
22         h += b'a' - 10;
23     }
24     (h, l)
25 }
26 
27 /// Parse a hexadecimal ASCII character as a `u8`.
parse_byte_from_ascii_char(c: u8) -> Option<u8>28 const fn parse_byte_from_ascii_char(c: u8) -> Option<u8> {
29     match c {
30         b'0' => Some(0x0),
31         b'1' => Some(0x1),
32         b'2' => Some(0x2),
33         b'3' => Some(0x3),
34         b'4' => Some(0x4),
35         b'5' => Some(0x5),
36         b'6' => Some(0x6),
37         b'7' => Some(0x7),
38         b'8' => Some(0x8),
39         b'9' => Some(0x9),
40         b'a' | b'A' => Some(0xa),
41         b'b' | b'B' => Some(0xb),
42         b'c' | b'C' => Some(0xc),
43         b'd' | b'D' => Some(0xd),
44         b'e' | b'E' => Some(0xe),
45         b'f' | b'F' => Some(0xf),
46         _ => None,
47     }
48 }
49 
50 /// Parse a pair of hexadecimal ASCII characters as a `u8`. For example,
51 /// `(b'1', b'a')` is parsed as `0x1a`.
parse_byte_from_ascii_char_pair(a: u8, b: u8) -> Option<u8>52 const fn parse_byte_from_ascii_char_pair(a: u8, b: u8) -> Option<u8> {
53     let Some(a) = parse_byte_from_ascii_char(a) else {
54         return None;
55     };
56 
57     let Some(b) = parse_byte_from_ascii_char(b) else {
58         return None;
59     };
60 
61     Some(a << 4 | b)
62 }
63 
64 /// Parse a pair of hexadecimal ASCII characters at position `start` as
65 /// a `u8`.
parse_byte_from_ascii_str_at( s: &[u8], start: u8, ) -> Result<u8, GuidFromStrError>66 pub(crate) const fn parse_byte_from_ascii_str_at(
67     s: &[u8],
68     start: u8,
69 ) -> Result<u8, GuidFromStrError> {
70     // This `as` conversion is needed because this is a const
71     // function. It is always valid since `usize` is always bigger than
72     // a u8.
73     #![allow(clippy::as_conversions)]
74     let start_usize = start as usize;
75 
76     if let Some(byte) =
77         parse_byte_from_ascii_char_pair(s[start_usize], s[start_usize + 1])
78     {
79         Ok(byte)
80     } else {
81         Err(GuidFromStrError::Hex(start))
82     }
83 }
84 
85 #[cfg(test)]
86 mod tests {
87     use super::*;
88 
89     #[test]
test_to_ascii()90     fn test_to_ascii() {
91         assert_eq!(byte_to_ascii_hex_lower(0x1f), (b'1', b'f'));
92         assert_eq!(byte_to_ascii_hex_lower(0xf1), (b'f', b'1'));
93     }
94 
95     #[test]
test_parse()96     fn test_parse() {
97         assert_eq!(parse_byte_from_ascii_char_pair(b'1', b'a'), Some(0x1a));
98         assert_eq!(parse_byte_from_ascii_char_pair(b'8', b'f'), Some(0x8f));
99 
100         assert_eq!(parse_byte_from_ascii_char_pair(b'g', b'a'), None);
101         assert_eq!(parse_byte_from_ascii_char_pair(b'a', b'g'), None);
102     }
103 }
104