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