Skip to main content

use_hex/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum HexCase {
6    Lower,
7    Upper,
8}
9
10#[must_use]
11pub fn hex_encode(input: &[u8]) -> String {
12    encode_hex(input, HexCase::Lower)
13}
14
15#[must_use]
16pub fn hex_encode_upper(input: &[u8]) -> String {
17    encode_hex(input, HexCase::Upper)
18}
19
20pub fn hex_decode(input: &str) -> Option<Vec<u8>> {
21    let value = strip_hex_prefix(input);
22    if !value.len().is_multiple_of(2) || !value.chars().all(is_hex_char) {
23        return None;
24    }
25
26    let mut output = Vec::with_capacity(value.len() / 2);
27    let bytes = value.as_bytes();
28    let mut index = 0;
29
30    while index < bytes.len() {
31        let high = decode_nibble(bytes[index])?;
32        let low = decode_nibble(bytes[index + 1])?;
33        output.push((high << 4) | low);
34        index += 2;
35    }
36
37    Some(output)
38}
39
40#[must_use]
41pub fn is_hex(input: &str) -> bool {
42    let value = strip_hex_prefix(input);
43    value.len().is_multiple_of(2) && value.chars().all(is_hex_char)
44}
45
46#[must_use]
47pub fn is_hex_char(c: char) -> bool {
48    c.is_ascii_hexdigit()
49}
50
51#[must_use]
52pub fn strip_hex_prefix(input: &str) -> &str {
53    input
54        .strip_prefix("0x")
55        .or_else(|| input.strip_prefix("0X"))
56        .or_else(|| input.strip_prefix('#'))
57        .unwrap_or(input)
58}
59
60#[must_use]
61pub fn ensure_hex_prefix(input: &str) -> String {
62    format!("0x{}", strip_hex_prefix(input))
63}
64
65pub fn normalize_hex(input: &str, case: HexCase) -> Option<String> {
66    let value = strip_hex_prefix(input);
67    if !value.chars().all(is_hex_char) {
68        return None;
69    }
70
71    Some(match case {
72        HexCase::Lower => value.to_ascii_lowercase(),
73        HexCase::Upper => value.to_ascii_uppercase(),
74    })
75}
76
77fn encode_hex(input: &[u8], case: HexCase) -> String {
78    let digits = match case {
79        HexCase::Lower => b"0123456789abcdef",
80        HexCase::Upper => b"0123456789ABCDEF",
81    };
82    let mut output = String::with_capacity(input.len() * 2);
83
84    for byte in input {
85        output.push(digits[(byte >> 4) as usize] as char);
86        output.push(digits[(byte & 0x0f) as usize] as char);
87    }
88
89    output
90}
91
92fn decode_nibble(byte: u8) -> Option<u8> {
93    match byte {
94        b'0'..=b'9' => Some(byte - b'0'),
95        b'a'..=b'f' => Some(byte - b'a' + 10),
96        b'A'..=b'F' => Some(byte - b'A' + 10),
97        _ => None,
98    }
99}