uguid/
util.rs

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
9use crate::GuidFromStrError;
10
11pub(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`.
28const 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`.
52const 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`.
66pub(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)]
86mod tests {
87    use super::*;
88
89    #[test]
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]
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}