use crate::{
CodecError,
CodecResult,
Decoder,
Encoder,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HexCodec {
uppercase: bool,
prefix: Option<String>,
separator: Option<String>,
ignore_ascii_whitespace: bool,
}
impl HexCodec {
pub fn new() -> Self {
Self {
uppercase: false,
prefix: None,
separator: None,
ignore_ascii_whitespace: false,
}
}
pub fn upper() -> Self {
Self::new().with_uppercase(true)
}
pub fn with_uppercase(mut self, uppercase: bool) -> Self {
self.uppercase = uppercase;
self
}
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn with_separator(mut self, separator: impl Into<String>) -> Self {
self.separator = Some(separator.into());
self
}
pub fn with_ignored_ascii_whitespace(mut self, ignore: bool) -> Self {
self.ignore_ascii_whitespace = ignore;
self
}
pub fn encode(&self, bytes: &[u8]) -> String {
let separator_len = self.separator.as_ref().map_or(0, String::len);
let prefix_len = self.prefix.as_ref().map_or(0, String::len);
let capacity = bytes
.len()
.saturating_mul(prefix_len.saturating_add(2))
.saturating_add(bytes.len().saturating_sub(1).saturating_mul(separator_len));
let mut output = String::with_capacity(capacity);
self.encode_into(bytes, &mut output);
output
}
pub fn encode_into(&self, bytes: &[u8], output: &mut String) {
for (index, byte) in bytes.iter().enumerate() {
if index > 0
&& let Some(separator) = &self.separator
{
output.push_str(separator);
}
if let Some(prefix) = &self.prefix {
output.push_str(prefix);
}
push_hex_byte(*byte, self.uppercase, output);
}
}
pub fn decode(&self, text: &str) -> CodecResult<Vec<u8>> {
let mut output = Vec::new();
self.decode_into(text, &mut output)?;
Ok(output)
}
pub fn decode_into(&self, text: &str, output: &mut Vec<u8>) -> CodecResult<()> {
let digits = self.normalized_digits(text)?;
if digits.len() % 2 != 0 {
return Err(CodecError::OddHexLength {
digits: digits.len(),
});
}
output.reserve(digits.len() / 2);
for pair in digits.chunks_exact(2) {
let mut pair = pair.iter();
let Some(&(high_index, high_char)) = pair.next() else {
continue;
};
let Some(&(low_index, low_char)) = pair.next() else {
continue;
};
let high = hex_value(high_char).ok_or(CodecError::InvalidHexDigit {
index: high_index,
character: high_char,
})?;
let low = hex_value(low_char).ok_or(CodecError::InvalidHexDigit {
index: low_index,
character: low_char,
})?;
output.push((high << 4) | low);
}
Ok(())
}
fn normalized_digits(&self, text: &str) -> CodecResult<Vec<(usize, char)>> {
if let Some(prefix) = self.prefix.as_deref().filter(|prefix| !prefix.is_empty()) {
return self.normalized_prefixed_digits(text, prefix);
}
self.normalized_unprefixed_digits(text)
}
fn normalized_unprefixed_digits(&self, text: &str) -> CodecResult<Vec<(usize, char)>> {
let mut digits = Vec::with_capacity(text.len());
let separator = self
.separator
.as_deref()
.filter(|separator| !separator.is_empty());
let mut index = 0;
while index < text.len() {
let Some(rest) = text.get(index..) else {
break;
};
if let Some(separator) = separator
&& rest.starts_with(separator)
{
index += separator.len();
continue;
}
let Some(ch) = rest.chars().next() else {
break;
};
if self.ignore_ascii_whitespace && ch.is_ascii_whitespace() {
index += ch.len_utf8();
continue;
}
if hex_value(ch).is_some() {
digits.push((index, ch));
index += ch.len_utf8();
continue;
}
return Err(CodecError::InvalidHexDigit {
index,
character: ch,
});
}
Ok(digits)
}
fn normalized_prefixed_digits(
&self,
text: &str,
prefix: &str,
) -> CodecResult<Vec<(usize, char)>> {
let mut digits = Vec::with_capacity(text.len());
let separator = self
.separator
.as_deref()
.filter(|separator| !separator.is_empty());
let mut index = 0;
while index < text.len() {
index = self.skip_ignored(text, index, separator);
if index >= text.len() {
break;
}
let Some(rest) = text.get(index..) else {
break;
};
if !rest.starts_with(prefix) {
return Err(CodecError::MissingPrefix {
prefix: prefix.to_owned(),
});
}
index += prefix.len();
let mut digit_count = 0;
while digit_count < 2 && index < text.len() {
let Some(rest) = text.get(index..) else {
break;
};
let Some(ch) = rest.chars().next() else {
break;
};
if self.ignore_ascii_whitespace && ch.is_ascii_whitespace() {
index += ch.len_utf8();
continue;
}
if hex_value(ch).is_some() {
digits.push((index, ch));
index += ch.len_utf8();
digit_count += 1;
continue;
}
return Err(CodecError::InvalidHexDigit {
index,
character: ch,
});
}
}
Ok(digits)
}
fn skip_ignored(&self, text: &str, mut index: usize, separator: Option<&str>) -> usize {
loop {
let Some(rest) = text.get(index..) else {
return index;
};
if let Some(separator) = separator
&& rest.starts_with(separator)
{
index += separator.len();
continue;
}
let Some(ch) = rest.chars().next() else {
return index;
};
if self.ignore_ascii_whitespace && ch.is_ascii_whitespace() {
index += ch.len_utf8();
continue;
}
return index;
}
}
}
impl Default for HexCodec {
fn default() -> Self {
Self::new()
}
}
impl Encoder<[u8]> for HexCodec {
type Error = CodecError;
type Output = String;
fn encode(&self, input: &[u8]) -> Result<Self::Output, Self::Error> {
Ok(HexCodec::encode(self, input))
}
}
impl Decoder<str> for HexCodec {
type Error = CodecError;
type Output = Vec<u8>;
fn decode(&self, input: &str) -> Result<Self::Output, Self::Error> {
HexCodec::decode(self, input)
}
}
fn hex_value(ch: char) -> Option<u8> {
match ch {
'0'..='9' => Some(ch as u8 - b'0'),
'a'..='f' => Some(ch as u8 - b'a' + 10),
'A'..='F' => Some(ch as u8 - b'A' + 10),
_ => None,
}
}
fn push_hex_byte(byte: u8, uppercase: bool, output: &mut String) {
output.push(hex_digit(byte >> 4, uppercase));
output.push(hex_digit(byte & 0x0f, uppercase));
}
fn hex_digit(value: u8, uppercase: bool) -> char {
match value & 0x0f {
0x0 => '0',
0x1 => '1',
0x2 => '2',
0x3 => '3',
0x4 => '4',
0x5 => '5',
0x6 => '6',
0x7 => '7',
0x8 => '8',
0x9 => '9',
0x0a if uppercase => 'A',
0x0b if uppercase => 'B',
0x0c if uppercase => 'C',
0x0d if uppercase => 'D',
0x0e if uppercase => 'E',
0x0f if uppercase => 'F',
0x0a => 'a',
0x0b => 'b',
0x0c => 'c',
0x0d => 'd',
0x0e => 'e',
_ => 'f',
}
}