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/// For a complete safe streaming adapter example, see the [English README](https://github.com/qubit-ltd/rs-codec-misc/blob/main/README.md#safe-streaming-hex-byte-adapter)
28/// and [Chinese README](https://github.com/qubit-ltd/rs-codec-misc/blob/main/README.zh_CN.md#安全的-hex-字节流式-adapter).
29///
30/// # Examples
31///
32/// ```
33/// use qubit_codec::Codec;
34/// use qubit_codec_misc::HexByteCodec;
35///
36/// let mut codec = HexByteCodec::upper();
37/// let mut output = [0_u8; 2];
38/// let written = unsafe { Codec::encode(&mut codec, &0xaf, &mut output, 0) }?;
39///
40/// assert_eq!(2, written);
41/// assert_eq!(b"AF", &output);
42/// # Ok::<(), qubit_codec_misc::MiscCodecError>(())
43/// ```
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
45pub struct HexByteCodec {
46    /// Controls whether encoded hexadecimal digits use uppercase letters.
47    uppercase: bool,
48}
49
50impl HexByteCodec {
51    /// Creates a lowercase single-byte hexadecimal codec.
52    #[must_use]
53    #[inline]
54    pub const fn new() -> Self {
55        Self { uppercase: false }
56    }
57
58    /// Creates an uppercase single-byte hexadecimal codec.
59    #[must_use]
60    #[inline]
61    pub const fn upper() -> Self {
62        Self { uppercase: true }
63    }
64
65    /// Returns whether this byte codec emits uppercase hexadecimal digits.
66    #[must_use]
67    #[inline]
68    pub const fn is_uppercase(self) -> bool {
69        self.uppercase
70    }
71
72    /// Sets whether encoded digits should be uppercase.
73    #[must_use]
74    #[inline]
75    pub const fn with_uppercase(mut self, uppercase: bool) -> Self {
76        self.uppercase = uppercase;
77        self
78    }
79}
80
81impl Codec for HexByteCodec {
82    type Value = u8;
83    type Unit = u8;
84    type DecodeError = MiscCodecError;
85    type EncodeError = MiscCodecError;
86
87    const MIN_UNITS_PER_VALUE: usize = 2;
88    const MAX_ENCODE_UNITS_PER_VALUE: usize = 2;
89    const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
90
91    /// Decodes one byte from two ASCII hexadecimal digits.
92    ///
93    /// The digits are read from `input` starting at `input_index`. Invalid
94    /// digits are reported with their position in the input.
95    ///
96    /// # Parameters
97    ///
98    /// - `input`: The byte units containing the hexadecimal digits.
99    /// - `input_index`: The index of the first digit.
100    ///
101    /// # Returns
102    ///
103    /// The decoded byte and the nonzero number of input units consumed (two).
104    ///
105    /// # Errors
106    ///
107    /// Returns a decode failure when either input unit is not an ASCII
108    /// hexadecimal digit. The failure identifies the invalid digit and its
109    /// input position.
110    ///
111    /// # Safety
112    /// `input` must contain at least two readable units starting at
113    /// `input_index`.
114    #[inline]
115    unsafe fn decode(
116        &mut self,
117        input: &[u8],
118        input_index: usize,
119    ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
120        debug_assert!(input_index + 2 <= input.len());
121
122        let high_char = char::from(input[input_index]);
123        let low_char = char::from(input[input_index + 1]);
124        let high = hex_value(high_char)
125            .ok_or_else(|| invalid_hex_digit(input_index, high_char))
126            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(2)))?;
127        let low = hex_value(low_char)
128            .ok_or_else(|| invalid_hex_digit(input_index + 1, low_char))
129            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(2)))?;
130        Ok(((high << 4) | low, nonzero(2)))
131    }
132
133    /// Encodes one byte as two ASCII hexadecimal digits.
134    ///
135    /// # Parameters
136    ///
137    /// - `value`: The byte to encode.
138    /// - `output`: The buffer that receives the encoded digits.
139    /// - `output_index`: The index of the first output unit to write.
140    ///
141    /// # Returns
142    ///
143    /// The number of output units written (two).
144    ///
145    /// # Errors
146    ///
147    /// This implementation does not return an encoding error; the result is
148    /// retained to satisfy the [`Codec`] interface.
149    ///
150    /// # Safety
151    /// `output` must contain at least two writable units starting at
152    /// `output_index`.
153    #[inline]
154    unsafe fn encode(
155        &mut self,
156        value: &u8,
157        output: &mut [u8],
158        output_index: usize,
159    ) -> Result<usize, Self::EncodeError> {
160        debug_assert!(output_index + 2 <= output.len());
161
162        output[output_index] = hex_digit(*value >> 4, self.uppercase) as u8;
163        output[output_index + 1] = hex_digit(*value & 0x0f, self.uppercase) as u8;
164        Ok(2)
165    }
166}