Skip to content

Commit

Permalink
Restrict BufEncoder to uniform case encoding
Browse files Browse the repository at this point in the history
The API now enforces that all hex strings encoded with `BufEncoder`
contain characters of the same case.
  • Loading branch information
tankyleo committed Oct 14, 2024
1 parent d6ac222 commit 54d2520
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 40 deletions.
98 changes: 64 additions & 34 deletions src/buf_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use core::borrow::Borrow;

use arrayvec::ArrayString;

use super::Case;
use super::{Case, Table};

/// Hex-encodes bytes into the provided buffer.
///
Expand All @@ -21,47 +21,48 @@ use super::Case;
/// buffering the hex and then formatting it is significantly faster.
pub struct BufEncoder<const CAP: usize> {
buf: ArrayString<CAP>,
table: &'static Table,
}

impl<const CAP: usize> BufEncoder<CAP> {
const _CHECK_EVEN_CAPACITY: () = [(); 1][CAP % 2];

/// Creates an empty `BufEncoder`.
/// Creates an empty `BufEncoder` that will encode bytes to hex characters in the given case.
#[inline]
pub fn new() -> Self { BufEncoder { buf: ArrayString::new() } }
pub fn new(case: Case) -> Self { BufEncoder { buf: ArrayString::new(), table: case.table() } }

/// Encodes `byte` as hex in given `case` and appends it to the buffer.
/// Encodes `byte` as hex and appends it to the buffer.
///
/// ## Panics
///
/// The method panics if the buffer is full.
#[inline]
#[track_caller]
pub fn put_byte(&mut self, byte: u8, case: Case) {
let hex_chars: [u8; 2] = case.table().byte_to_hex(byte);
pub fn put_byte(&mut self, byte: u8) {
let hex_chars: [u8; 2] = self.table.byte_to_hex(byte);
// SAFETY: Table::byte_to_hex returns only valid ASCII
let hex_str = unsafe { core::str::from_utf8_unchecked(&hex_chars) };
self.buf.push_str(hex_str);
}

/// Encodes `bytes` as hex in given `case` and appends them to the buffer.
/// Encodes `bytes` as hex and appends them to the buffer.
///
/// ## Panics
///
/// The method panics if the bytes wouldn't fit the buffer.
#[inline]
#[track_caller]
pub fn put_bytes<I>(&mut self, bytes: I, case: Case)
pub fn put_bytes<I>(&mut self, bytes: I)
where
I: IntoIterator,
I::Item: Borrow<u8>,
{
self.put_bytes_inner(bytes.into_iter(), case)
self.put_bytes_inner(bytes.into_iter())
}

#[inline]
#[track_caller]
fn put_bytes_inner<I>(&mut self, bytes: I, case: Case)
fn put_bytes_inner<I>(&mut self, bytes: I)
where
I: Iterator,
I::Item: Borrow<u8>,
Expand All @@ -71,7 +72,7 @@ impl<const CAP: usize> BufEncoder<CAP> {
assert!(max <= self.space_remaining());
}
for byte in bytes {
self.put_byte(*byte.borrow(), case);
self.put_byte(*byte.borrow());
}
}

Expand All @@ -82,9 +83,9 @@ impl<const CAP: usize> BufEncoder<CAP> {
#[must_use = "this may write only part of the input buffer"]
#[inline]
#[track_caller]
pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8], case: Case) -> &'a [u8] {
pub fn put_bytes_min<'a>(&mut self, bytes: &'a [u8]) -> &'a [u8] {
let to_write = self.space_remaining().min(bytes.len());
self.put_bytes(&bytes[..to_write], case);
self.put_bytes(&bytes[..to_write]);
&bytes[to_write..]
}

Expand Down Expand Up @@ -121,7 +122,7 @@ impl<const CAP: usize> BufEncoder<CAP> {
}

impl<const CAP: usize> Default for BufEncoder<CAP> {
fn default() -> Self { Self::new() }
fn default() -> Self { Self::new(Case::Lower) }
}

#[cfg(test)]
Expand All @@ -130,72 +131,99 @@ mod tests {

#[test]
fn empty() {
let encoder = BufEncoder::<2>::new();
let encoder = BufEncoder::<2>::new(Case::Lower);
assert_eq!(encoder.as_str(), "");
assert!(!encoder.is_full());

let encoder = BufEncoder::<2>::new(Case::Upper);
assert_eq!(encoder.as_str(), "");
assert!(!encoder.is_full());
}

#[test]
fn single_byte_exact_buf() {
let mut encoder = BufEncoder::<2>::new();
let mut encoder = BufEncoder::<2>::new(Case::Lower);
assert_eq!(encoder.space_remaining(), 1);
encoder.put_byte(42, Case::Lower);
encoder.put_byte(42);
assert_eq!(encoder.as_str(), "2a");
assert_eq!(encoder.space_remaining(), 0);
assert!(encoder.is_full());
encoder.clear();
assert_eq!(encoder.space_remaining(), 1);
assert!(!encoder.is_full());
encoder.put_byte(42, Case::Upper);

let mut encoder = BufEncoder::<2>::new(Case::Upper);
assert_eq!(encoder.space_remaining(), 1);
encoder.put_byte(42);
assert_eq!(encoder.as_str(), "2A");
assert_eq!(encoder.space_remaining(), 0);
assert!(encoder.is_full());
encoder.clear();
assert_eq!(encoder.space_remaining(), 1);
assert!(!encoder.is_full());
}

#[test]
fn single_byte_oversized_buf() {
let mut encoder = BufEncoder::<4>::new();
let mut encoder = BufEncoder::<4>::new(Case::Lower);
assert_eq!(encoder.space_remaining(), 2);
encoder.put_byte(42, Case::Lower);
encoder.put_byte(42);
assert_eq!(encoder.space_remaining(), 1);
assert_eq!(encoder.as_str(), "2a");
assert!(!encoder.is_full());
encoder.clear();
assert_eq!(encoder.space_remaining(), 2);
encoder.put_byte(42, Case::Upper);
assert_eq!(encoder.as_str(), "2A");
assert!(!encoder.is_full());

let mut encoder = BufEncoder::<4>::new(Case::Upper);
assert_eq!(encoder.space_remaining(), 2);
encoder.put_byte(42);
assert_eq!(encoder.space_remaining(), 1);
assert_eq!(encoder.as_str(), "2A");
assert!(!encoder.is_full());
encoder.clear();
assert_eq!(encoder.space_remaining(), 2);
assert!(!encoder.is_full());
}

#[test]
fn two_bytes() {
let mut encoder = BufEncoder::<4>::new();
encoder.put_byte(42, Case::Lower);
let mut encoder = BufEncoder::<4>::new(Case::Lower);
assert_eq!(encoder.space_remaining(), 2);
encoder.put_byte(42);
assert_eq!(encoder.space_remaining(), 1);
encoder.put_byte(255, Case::Lower);
encoder.put_byte(255);
assert_eq!(encoder.space_remaining(), 0);
assert_eq!(encoder.as_str(), "2aff");
assert!(encoder.is_full());
encoder.clear();
assert_eq!(encoder.space_remaining(), 2);
assert!(!encoder.is_full());
encoder.put_byte(42, Case::Upper);
encoder.put_byte(255, Case::Upper);

let mut encoder = BufEncoder::<4>::new(Case::Upper);
assert_eq!(encoder.space_remaining(), 2);
encoder.put_byte(42);
assert_eq!(encoder.space_remaining(), 1);
encoder.put_byte(255);
assert_eq!(encoder.space_remaining(), 0);
assert_eq!(encoder.as_str(), "2AFF");
assert!(encoder.is_full());
encoder.clear();
assert_eq!(encoder.space_remaining(), 2);
assert!(!encoder.is_full());
}

#[test]
fn put_bytes_min() {
let mut encoder = BufEncoder::<2>::new();
let remainder = encoder.put_bytes_min(b"", Case::Lower);
let mut encoder = BufEncoder::<2>::new(Case::Lower);
let remainder = encoder.put_bytes_min(b"");
assert_eq!(remainder, b"");
assert_eq!(encoder.as_str(), "");
let remainder = encoder.put_bytes_min(b"*", Case::Lower);
let remainder = encoder.put_bytes_min(b"*");
assert_eq!(remainder, b"");
assert_eq!(encoder.as_str(), "2a");
encoder.clear();
let remainder = encoder.put_bytes_min(&[42, 255], Case::Lower);
let remainder = encoder.put_bytes_min(&[42, 255]);
assert_eq!(remainder, &[255]);
assert_eq!(encoder.as_str(), "2a");
}
Expand Down Expand Up @@ -227,18 +255,20 @@ mod tests {
}

let mut writer = Writer { buf: [0u8; 2], pos: 0 };
let mut encoder = BufEncoder::<2>::new();

let mut encoder = BufEncoder::<2>::new(Case::Lower);
for i in 0..=255 {
write!(writer, "{:02x}", i).unwrap();
encoder.put_byte(i, Case::Lower);
encoder.put_byte(i);
assert_eq!(encoder.as_str(), writer.as_str());
writer.pos = 0;
encoder.clear();
}

let mut encoder = BufEncoder::<2>::new(Case::Upper);
for i in 0..=255 {
write!(writer, "{:02X}", i).unwrap();
encoder.put_byte(i, Case::Upper);
encoder.put_byte(i);
assert_eq!(encoder.as_str(), writer.as_str());
writer.pos = 0;
encoder.clear();
Expand Down
12 changes: 6 additions & 6 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re
//
// This would complicate the code so I was too lazy to do them but feel free to send a PR!

let mut encoder = BufEncoder::<1024>::new();
let mut encoder = BufEncoder::<1024>::new(case);

let pad_right = if let Some(width) = f.width() {
let string_len = match f.precision() {
Expand Down Expand Up @@ -158,11 +158,11 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re
Some(_) | None => {
let mut chunks = bytes.chunks_exact(512);
for chunk in &mut chunks {
encoder.put_bytes(chunk, case);
encoder.put_bytes(chunk);
f.write_str(encoder.as_str())?;
encoder.clear();
}
encoder.put_bytes(chunks.remainder(), case);
encoder.put_bytes(chunks.remainder());
f.write_str(encoder.as_str())?;
}
}
Expand Down Expand Up @@ -522,15 +522,15 @@ where
I: IntoIterator,
I::Item: Borrow<u8>,
{
let mut encoder = BufEncoder::<N>::new();
let mut encoder = BufEncoder::<N>::new(case);
let encoded = match f.precision() {
Some(p) if p < N => {
let n = (p + 1) / 2;
encoder.put_bytes(bytes.into_iter().take(n), case);
encoder.put_bytes(bytes.into_iter().take(n));
&encoder.as_str()[..p]
}
_ => {
encoder.put_bytes(bytes, case);
encoder.put_bytes(bytes);
encoder.as_str()
}
};
Expand Down

0 comments on commit 54d2520

Please sign in to comment.