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