Skip to main content

codec_hex/
encode.rs

1// SPDX-FileCopyrightText: 2026 ReallyMe LLC
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use crate::HexError;
6
7const LOWER_HEX: &[u8; 16] = b"0123456789abcdef";
8
9/// Encode bytes as canonical lowercase hexadecimal.
10pub fn bytes_to_lower_hex(bytes: &[u8]) -> String {
11    // A valid slice is at most isize::MAX bytes, so doubling its length fits
12    // in usize on every supported target. Reserve once to avoid leaving
13    // secret-bearing prefixes in freed String allocations.
14    let Some(capacity) = bytes.len().checked_mul(2) else {
15        return String::new();
16    };
17    let mut output = String::with_capacity(capacity);
18    append_lower_hex(bytes, &mut output);
19    output
20}
21
22/// Append canonical lowercase hexadecimal to an existing string buffer.
23pub fn write_lower_hex(bytes: &[u8], output: &mut String) -> Result<(), HexError> {
24    let additional = bytes
25        .len()
26        .checked_mul(2)
27        .ok_or(HexError::OutputCapacityExceeded)?;
28    let _total = output
29        .len()
30        .checked_add(additional)
31        .ok_or(HexError::OutputCapacityExceeded)?;
32    // Reserve before writing so growth cannot leave earlier encoded prefixes
33    // in discarded allocations when the caller supplied a short buffer.
34    output
35        .try_reserve_exact(additional)
36        .map_err(|_| HexError::OutputCapacityExceeded)?;
37    append_lower_hex(bytes, output);
38    Ok(())
39}
40
41fn append_lower_hex(bytes: &[u8], output: &mut String) {
42    for byte in bytes {
43        let high = usize::from(byte >> 4);
44        let low = usize::from(byte & 0x0f);
45        output.push(char::from(LOWER_HEX[high]));
46        output.push(char::from(LOWER_HEX[low]));
47    }
48}