Skip to main content

qubit_codec_misc/
hex_byte_codec.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Low-level hexadecimal byte codec.
9
10use qubit_codec::Codec;
11use qubit_codec::DecodeFailure;
12use qubit_utils::nonzero;
13
14use crate::MiscCodecError;
15use crate::hex_codec::hex_digit;
16use crate::hex_codec::hex_value;
17use crate::hex_codec::invalid_hex_digit;
18use crate::misc_codec_error::map_misc_decode_failure_with_consumed;
19
20/// Encodes and decodes one byte as two ASCII hexadecimal units.
21///
22/// `HexByteCodec` is the low-level [`Codec`] implementation for streaming or
23/// generic codec call sites. It does not understand whole-string prefixes,
24/// per-byte prefixes, separators, or whitespace. Use [`crate::HexCodec`] for
25/// owned byte-slice helpers with those formatting options.
26///
27/// # Examples
28///
29/// ```
30/// use qubit_codec::Codec;
31/// use qubit_codec_misc::HexByteCodec;
32///
33/// let mut codec = HexByteCodec::upper();
34/// let mut output = [0_u8; 2];
35/// let written = unsafe { Codec::encode(&mut codec, &0xaf, &mut output, 0) }?;
36///
37/// assert_eq!(2, written);
38/// assert_eq!(b"AF", &output);
39/// # Ok::<(), qubit_codec_misc::MiscCodecError>(())
40/// ```
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
42pub struct HexByteCodec {
43    /// Controls whether encoded hexadecimal digits use uppercase letters.
44    uppercase: bool,
45}
46
47impl HexByteCodec {
48    /// Creates a lowercase single-byte hexadecimal codec.
49    #[must_use]
50    #[inline]
51    pub const fn new() -> Self {
52        Self { uppercase: false }
53    }
54
55    /// Creates an uppercase single-byte hexadecimal codec.
56    #[must_use]
57    #[inline]
58    pub const fn upper() -> Self {
59        Self { uppercase: true }
60    }
61
62    /// Returns whether this byte codec emits uppercase hexadecimal digits.
63    #[must_use]
64    #[inline]
65    pub const fn is_uppercase(self) -> bool {
66        self.uppercase
67    }
68
69    /// Sets whether encoded digits should be uppercase.
70    #[must_use]
71    #[inline]
72    pub const fn with_uppercase(mut self, uppercase: bool) -> Self {
73        self.uppercase = uppercase;
74        self
75    }
76}
77
78impl Codec for HexByteCodec {
79    type Value = u8;
80    type Unit = u8;
81    type DecodeError = MiscCodecError;
82    type EncodeError = MiscCodecError;
83
84    const MIN_UNITS_PER_VALUE: usize = 2;
85    const MAX_ENCODE_UNITS_PER_VALUE: usize = 2;
86    const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
87
88    /// Decodes one byte from two ASCII hexadecimal digits.
89    ///
90    /// # Safety
91    /// The caller must provide two readable units at `input_index`.
92    #[inline]
93    unsafe fn decode(
94        &mut self,
95        input: &[u8],
96        input_index: usize,
97    ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
98        debug_assert!(input_index + 2 <= input.len());
99
100        let high_char = char::from(input[input_index]);
101        let low_char = char::from(input[input_index + 1]);
102        let high = hex_value(high_char)
103            .ok_or_else(|| invalid_hex_digit(input_index, high_char))
104            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(2)))?;
105        let low = hex_value(low_char)
106            .ok_or_else(|| invalid_hex_digit(input_index + 1, low_char))
107            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(2)))?;
108        Ok(((high << 4) | low, nonzero(2)))
109    }
110
111    /// Encodes one byte as two ASCII hexadecimal digits.
112    ///
113    /// # Safety
114    /// The caller must provide two writable output units at `output_index`.
115    #[inline]
116    unsafe fn encode(
117        &mut self,
118        value: &u8,
119        output: &mut [u8],
120        output_index: usize,
121    ) -> Result<usize, Self::EncodeError> {
122        debug_assert!(output_index + 2 <= output.len());
123
124        output[output_index] = hex_digit(*value >> 4, self.uppercase) as u8;
125        output[output_index + 1] = hex_digit(*value & 0x0f, self.uppercase) as u8;
126        Ok(2)
127    }
128}