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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
27pub struct HexByteCodec {
28    uppercase: bool,
29}
30
31impl HexByteCodec {
32    /// Creates a lowercase single-byte hexadecimal codec.
33    #[must_use]
34    #[inline]
35    pub const fn new() -> Self {
36        Self { uppercase: false }
37    }
38
39    /// Creates an uppercase single-byte hexadecimal codec.
40    #[must_use]
41    #[inline]
42    pub const fn upper() -> Self {
43        Self { uppercase: true }
44    }
45
46    /// Sets whether encoded digits should be uppercase.
47    #[must_use]
48    #[inline]
49    pub const fn with_uppercase(mut self, uppercase: bool) -> Self {
50        self.uppercase = uppercase;
51        self
52    }
53
54    /// Returns whether this byte codec emits uppercase hexadecimal digits.
55    #[must_use]
56    #[inline]
57    pub const fn is_uppercase(self) -> bool {
58        self.uppercase
59    }
60}
61
62impl Codec for HexByteCodec {
63    type Value = u8;
64    type Unit = u8;
65    type DecodeError = MiscCodecError;
66    type EncodeError = MiscCodecError;
67
68    const MIN_UNITS_PER_VALUE: usize = 2;
69    const MAX_ENCODE_UNITS_PER_VALUE: usize = 2;
70    const MAX_DECODE_UNITS_PER_VALUE: usize = 2;
71
72    /// Decodes one byte from two ASCII hexadecimal digits.
73    ///
74    /// # Safety
75    /// The caller must provide two readable units at `input_index`.
76    #[inline]
77    unsafe fn decode(
78        &mut self,
79        input: &[u8],
80        input_index: usize,
81    ) -> Result<(u8, core::num::NonZeroUsize), DecodeFailure<Self::DecodeError>>
82    {
83        debug_assert!(input_index + 2 <= input.len());
84
85        let high_char = char::from(input[input_index]);
86        let low_char = char::from(input[input_index + 1]);
87        let high = hex_value(high_char)
88            .ok_or_else(|| invalid_hex_digit(input_index, high_char))
89            .map_err(|error| {
90                map_misc_decode_failure_with_consumed(error, nonzero(2))
91            })?;
92        let low = hex_value(low_char)
93            .ok_or_else(|| invalid_hex_digit(input_index + 1, low_char))
94            .map_err(|error| {
95                map_misc_decode_failure_with_consumed(error, nonzero(2))
96            })?;
97        Ok(((high << 4) | low, nonzero(2)))
98    }
99
100    /// Encodes one byte as two ASCII hexadecimal digits.
101    ///
102    /// # Safety
103    /// The caller must provide two writable output units at `output_index`.
104    #[inline]
105    unsafe fn encode(
106        &mut self,
107        value: &u8,
108        output: &mut [u8],
109        output_index: usize,
110    ) -> Result<usize, Self::EncodeError> {
111        debug_assert!(output_index + 2 <= output.len());
112
113        output[output_index] = hex_digit(*value >> 4, self.uppercase) as u8;
114        output[output_index + 1] =
115            hex_digit(*value & 0x0f, self.uppercase) as u8;
116        Ok(2)
117    }
118}