jj_lib/
hex_util.rs

1// Copyright 2023 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Hex string helpers.
16
17const FORWARD_HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
18const REVERSE_HEX_CHARS: &[u8; 16] = b"zyxwvutsrqponmlk";
19
20fn forward_hex_value(b: u8) -> Option<u8> {
21    match b {
22        b'0'..=b'9' => Some(b - b'0'),
23        b'a'..=b'f' => Some(b - b'a' + 10),
24        b'A'..=b'F' => Some(b - b'A' + 10),
25        _ => None,
26    }
27}
28
29fn reverse_hex_value(b: u8) -> Option<u8> {
30    match b {
31        b'k'..=b'z' => Some(b'z' - b),
32        b'K'..=b'Z' => Some(b'Z' - b),
33        _ => None,
34    }
35}
36
37/// Decodes `hex` as normal hex string.
38pub fn decode_hex(hex: impl AsRef<[u8]>) -> Option<Vec<u8>> {
39    decode_hex_inner(hex.as_ref(), forward_hex_value)
40}
41
42/// Decodes `hex` as normal hex string prefix. The output may have odd-length
43/// byte. Returns `(bytes, has_odd_byte)`.
44pub fn decode_hex_prefix(hex: impl AsRef<[u8]>) -> Option<(Vec<u8>, bool)> {
45    decode_hex_prefix_inner(hex.as_ref(), forward_hex_value)
46}
47
48/// Decodes `reverse_hex` as hex string using `z-k` "digits".
49pub fn decode_reverse_hex(reverse_hex: impl AsRef<[u8]>) -> Option<Vec<u8>> {
50    decode_hex_inner(reverse_hex.as_ref(), reverse_hex_value)
51}
52
53/// Decodes `reverse_hex` as hex string prefix using `z-k` "digits". The output
54/// may have odd-length byte. Returns `(bytes, has_odd_byte)`.
55pub fn decode_reverse_hex_prefix(reverse_hex: impl AsRef<[u8]>) -> Option<(Vec<u8>, bool)> {
56    decode_hex_prefix_inner(reverse_hex.as_ref(), reverse_hex_value)
57}
58
59fn decode_hex_inner(reverse_hex: &[u8], hex_value: impl Fn(u8) -> Option<u8>) -> Option<Vec<u8>> {
60    if reverse_hex.len() % 2 != 0 {
61        return None;
62    }
63    let (decoded, _) = decode_hex_prefix_inner(reverse_hex, hex_value)?;
64    Some(decoded)
65}
66
67fn decode_hex_prefix_inner(
68    reverse_hex: &[u8],
69    hex_value: impl Fn(u8) -> Option<u8>,
70) -> Option<(Vec<u8>, bool)> {
71    let mut decoded = Vec::with_capacity(usize::div_ceil(reverse_hex.len(), 2));
72    let mut chunks = reverse_hex.chunks_exact(2);
73    for chunk in &mut chunks {
74        let [hi, lo] = chunk.try_into().unwrap();
75        decoded.push(hex_value(hi)? << 4 | hex_value(lo)?);
76    }
77    if let &[hi] = chunks.remainder() {
78        decoded.push(hex_value(hi)? << 4);
79        Some((decoded, true))
80    } else {
81        Some((decoded, false))
82    }
83}
84
85/// Encodes `data` as normal hex string.
86pub fn encode_hex(data: &[u8]) -> String {
87    encode_hex_inner(data, FORWARD_HEX_CHARS)
88}
89
90/// Encodes `data` as hex string using `z-k` "digits".
91pub fn encode_reverse_hex(data: &[u8]) -> String {
92    encode_hex_inner(data, REVERSE_HEX_CHARS)
93}
94
95fn encode_hex_inner(data: &[u8], chars: &[u8; 16]) -> String {
96    let encoded = data
97        .iter()
98        .flat_map(|b| [chars[usize::from(b >> 4)], chars[usize::from(b & 0xf)]])
99        .collect();
100    String::from_utf8(encoded).unwrap()
101}
102
103/// Calculates common prefix length of two byte sequences. The length
104/// to be returned is a number of hexadecimal digits.
105pub fn common_hex_len(bytes_a: &[u8], bytes_b: &[u8]) -> usize {
106    std::iter::zip(bytes_a, bytes_b)
107        .enumerate()
108        .find_map(|(i, (a, b))| match a ^ b {
109            0 => None,
110            d if d & 0xf0 == 0 => Some(i * 2 + 1),
111            _ => Some(i * 2),
112        })
113        .unwrap_or_else(|| bytes_a.len().min(bytes_b.len()) * 2)
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_forward_hex() {
122        // Empty string
123        assert_eq!(decode_hex(""), Some(vec![]));
124        assert_eq!(decode_hex_prefix(""), Some((vec![], false)));
125        assert_eq!(encode_hex(b""), "".to_string());
126
127        // Single digit
128        assert_eq!(decode_hex("0"), None);
129        assert_eq!(decode_hex_prefix("f"), Some((vec![0xf0], true)));
130
131        // All digits
132        assert_eq!(
133            decode_hex("0123456789abcDEF"),
134            Some(b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec())
135        );
136        assert_eq!(
137            decode_hex_prefix("0123456789ABCdef"),
138            Some((b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec(), false))
139        );
140        assert_eq!(
141            encode_hex(b"\x01\x23\x45\x67\x89\xab\xcd\xef"),
142            "0123456789abcdef".to_string()
143        );
144
145        // Invalid digit
146        assert_eq!(decode_hex("gg"), None);
147        assert_eq!(decode_hex_prefix("gg"), None);
148    }
149
150    #[test]
151    fn test_reverse_hex() {
152        // Empty string
153        assert_eq!(decode_reverse_hex(""), Some(vec![]));
154        assert_eq!(decode_reverse_hex_prefix(""), Some((vec![], false)));
155        assert_eq!(encode_reverse_hex(b""), "".to_string());
156
157        // Single digit
158        assert_eq!(decode_reverse_hex("z"), None);
159        assert_eq!(decode_reverse_hex_prefix("k"), Some((vec![0xf0], true)));
160
161        // All digits
162        assert_eq!(
163            decode_reverse_hex("zyxwvutsRQPONMLK"),
164            Some(b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec())
165        );
166        assert_eq!(
167            decode_reverse_hex_prefix("ZYXWVUTSrqponmlk"),
168            Some((b"\x01\x23\x45\x67\x89\xab\xcd\xef".to_vec(), false))
169        );
170        assert_eq!(
171            encode_reverse_hex(b"\x01\x23\x45\x67\x89\xab\xcd\xef"),
172            "zyxwvutsrqponmlk".to_string()
173        );
174
175        // Invalid digit
176        assert_eq!(decode_reverse_hex("jj"), None);
177        assert_eq!(decode_reverse_hex_prefix("jj"), None);
178    }
179}