Skip to content

Commit

Permalink
Merge #121: Encapsulate unsafe code inside the table module
Browse files Browse the repository at this point in the history
6cd4e45 Encapsulate unsafe code inside the table module (Leo Nash)

Pull request description:

  The table module now returns only `[char; 2]` or `&str`, at the caller's choice.

  Once `Table::byte_to_str` is called, the destination buffer cannot be written to until the scope of the returned reference ends.

ACKs for top commit:
  apoelstra:
    ACK 6cd4e45; successfully ran local tests
  tcharding:
    ACK 6cd4e45

Tree-SHA512: 256a40e7f9c3226224d124e2eda52de20d8bb88c768c5e8840b1641e13eaaa5ff6aa17beebfc0298931f4f1a2758fa0aa189072c7fa0f1ebe0883430be175a44
  • Loading branch information
apoelstra committed Oct 16, 2024
2 parents 7139164 + 6cd4e45 commit 0dfa2ff
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 27 deletions.
5 changes: 2 additions & 3 deletions src/buf_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ impl<const CAP: usize> BufEncoder<CAP> {
#[inline]
#[track_caller]
pub fn put_byte(&mut self, byte: u8, case: Case) {
let hex_chars: [u8; 2] = case.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) };
let mut hex_chars = [0u8; 2];
let hex_str = case.table().byte_to_str(&mut hex_chars, byte);
self.buf.push_str(hex_str);
}

Expand Down
7 changes: 3 additions & 4 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ fn internal_display(bytes: &[u8], f: &mut fmt::Formatter, case: Case) -> fmt::Re
Some(max) if bytes.len() > max / 2 => {
write!(f, "{}", bytes[..(max / 2)].as_hex())?;
if max % 2 == 1 {
f.write_char(char::from(case.table().byte_to_hex(bytes[max / 2])[0]))?;
f.write_char(case.table().byte_to_chars(bytes[max / 2])[0])?;
}
}
Some(_) | None => {
Expand Down Expand Up @@ -569,9 +569,8 @@ where
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
let mut n = 0;
for byte in buf {
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) };
let mut hex_chars = [0u8; 2];
let hex_str = self.table.byte_to_str(&mut hex_chars, *byte);
if self.writer.write_str(hex_str).is_err() {
break;
}
Expand Down
41 changes: 26 additions & 15 deletions src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ where
Some(c)
}
None => self.iter.next().map(|b| {
let [high, low] = self.table.byte_to_hex(*b.borrow());
self.low = Some(char::from(low));
char::from(high)
let [high, low] = self.table.byte_to_chars(*b.borrow());
self.low = Some(low);
high
}),
}
}
Expand Down Expand Up @@ -264,9 +264,9 @@ where
Some(c)
}
None => self.iter.next_back().map(|b| {
let [high, low] = self.table.byte_to_hex(*b.borrow());
self.low = Some(char::from(low));
char::from(high)
let [high, low] = self.table.byte_to_chars(*b.borrow());
self.low = Some(low);
high
}),
}
}
Expand Down Expand Up @@ -294,15 +294,26 @@ mod tests {

#[test]
fn encode_byte() {
assert_eq!(Table::LOWER.byte_to_hex(0x00), [b'0', b'0']);
assert_eq!(Table::LOWER.byte_to_hex(0x0a), [b'0', b'a']);
assert_eq!(Table::LOWER.byte_to_hex(0xad), [b'a', b'd']);
assert_eq!(Table::LOWER.byte_to_hex(0xff), [b'f', b'f']);

assert_eq!(Table::UPPER.byte_to_hex(0x00), [b'0', b'0']);
assert_eq!(Table::UPPER.byte_to_hex(0x0a), [b'0', b'A']);
assert_eq!(Table::UPPER.byte_to_hex(0xad), [b'A', b'D']);
assert_eq!(Table::UPPER.byte_to_hex(0xff), [b'F', b'F']);
assert_eq!(Table::LOWER.byte_to_chars(0x00), ['0', '0']);
assert_eq!(Table::LOWER.byte_to_chars(0x0a), ['0', 'a']);
assert_eq!(Table::LOWER.byte_to_chars(0xad), ['a', 'd']);
assert_eq!(Table::LOWER.byte_to_chars(0xff), ['f', 'f']);

assert_eq!(Table::UPPER.byte_to_chars(0x00), ['0', '0']);
assert_eq!(Table::UPPER.byte_to_chars(0x0a), ['0', 'A']);
assert_eq!(Table::UPPER.byte_to_chars(0xad), ['A', 'D']);
assert_eq!(Table::UPPER.byte_to_chars(0xff), ['F', 'F']);

let mut buf = [0u8; 2];
assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0x00), "00");
assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0x0a), "0a");
assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0xad), "ad");
assert_eq!(Table::LOWER.byte_to_str(&mut buf, 0xff), "ff");

assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0x00), "00");
assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0x0a), "0A");
assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0xad), "AD");
assert_eq!(Table::UPPER.byte_to_str(&mut buf, 0xff), "FF");
}

#[test]
Expand Down
20 changes: 15 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,24 @@ mod table {
/// Encodes single byte as two ASCII chars using the given table.
///
/// The function guarantees only returning values from the provided table.
///
/// The function returns a `[u8; 2]` to give callers the freedom to interpret the return
/// value as a `&str` via `str::from_utf8` or a collection of `char`'s.
#[inline]
pub(crate) fn byte_to_hex(&self, byte: u8) -> [u8; 2] {
pub(crate) fn byte_to_chars(&self, byte: u8) -> [char; 2] {
let left = self.0[usize::from(byte >> 4)];
let right = self.0[usize::from(byte & 0x0F)];
[left, right]
[char::from(left), char::from(right)]
}

/// Writes the single byte as two ASCII chars in the provided buffer, and returns a `&str`
/// to that buffer.
///
/// The function guarantees only returning values from the provided table.
#[inline]
pub(crate) fn byte_to_str<'a>(&self, dest: &'a mut [u8; 2], byte: u8) -> &'a str {
dest[0] = self.0[usize::from(byte >> 4)];
dest[1] = self.0[usize::from(byte & 0x0F)];
// SAFETY: Table inner array contains only valid ascii
let hex_str = unsafe { core::str::from_utf8_unchecked(dest) };
hex_str
}
}
}
Expand Down

0 comments on commit 0dfa2ff

Please sign in to comment.