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 /// The digits are read from `input` starting at `input_index`. Invalid
91 /// digits are reported with their position in the input.
92 ///
93 /// # Parameters
94 ///
95 /// - `input`: The byte units containing the hexadecimal digits.
96 /// - `input_index`: The index of the first digit.
97 ///
98 /// # Returns
99 ///
100 /// The decoded byte and the nonzero number of input units consumed (two).
101 ///
102 /// # Errors
103 ///
104 /// Returns a decode failure when either input unit is not an ASCII
105 /// hexadecimal digit. The failure identifies the invalid digit and its
106 /// input position.
107 ///
108 /// # Safety
109 /// `input` must contain at least two readable units starting at
110 /// `input_index`.
111 #[inline]
112 unsafe fn decode(
113 &mut self,
114 input: &[u8],
115 input_index: usize,
116 ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>> {
117 debug_assert!(input_index + 2 <= input.len());
118
119 let high_char = char::from(input[input_index]);
120 let low_char = char::from(input[input_index + 1]);
121 let high = hex_value(high_char)
122 .ok_or_else(|| invalid_hex_digit(input_index, high_char))
123 .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(2)))?;
124 let low = hex_value(low_char)
125 .ok_or_else(|| invalid_hex_digit(input_index + 1, low_char))
126 .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(2)))?;
127 Ok(((high << 4) | low, nonzero(2)))
128 }
129
130 /// Encodes one byte as two ASCII hexadecimal digits.
131 ///
132 /// # Parameters
133 ///
134 /// - `value`: The byte to encode.
135 /// - `output`: The buffer that receives the encoded digits.
136 /// - `output_index`: The index of the first output unit to write.
137 ///
138 /// # Returns
139 ///
140 /// The number of output units written (two).
141 ///
142 /// # Errors
143 ///
144 /// This implementation does not return an encoding error; the result is
145 /// retained to satisfy the [`Codec`] interface.
146 ///
147 /// # Safety
148 /// `output` must contain at least two writable units starting at
149 /// `output_index`.
150 #[inline]
151 unsafe fn encode(
152 &mut self,
153 value: &u8,
154 output: &mut [u8],
155 output_index: usize,
156 ) -> Result<usize, Self::EncodeError> {
157 debug_assert!(output_index + 2 <= output.len());
158
159 output[output_index] = hex_digit(*value >> 4, self.uppercase) as u8;
160 output[output_index + 1] = hex_digit(*value & 0x0f, self.uppercase) as u8;
161 Ok(2)
162 }
163}