Skip to content

Commit

Permalink
Add encode_write and encode_write_buffer (#91)
Browse files Browse the repository at this point in the history
Fixes #69
  • Loading branch information
ia0 authored Nov 15, 2023
1 parent 15c27df commit 35399a8
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 5 deletions.
1 change: 1 addition & 0 deletions lib/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Minor

- Add `Encoding::encode_write()` to encode to `core::fmt::Write` (fixes #69)
- Add `Encoder` and `Encoding::new_encoder()` for fragmented inputs (fixes #81)
- Make some functions `must_use`
- Bump MSRV from 1.47 to 1.48
Expand Down
6 changes: 6 additions & 0 deletions lib/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ name = "encoder"
path = "fuzz_targets/encoder.rs"
test = false
doc = false

[[bin]]
name = "encode_write"
path = "fuzz_targets/encode_write.rs"
test = false
doc = false
15 changes: 15 additions & 0 deletions lib/fuzz/fuzz_targets/encode_write.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#![no_main]

use data_encoding_fuzz::{generate_encoding, generate_usize};
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
let mut data = data;
let encoding = generate_encoding(&mut data);
let mut buffer = vec![0; generate_usize(&mut data, 510, 2050)];
let input = data;
let mut output = String::new();
encoding.encode_write_buffer(input, &mut output, &mut buffer).unwrap();
let expected = encoding.encode(input);
assert_eq!(output, expected);
});
51 changes: 46 additions & 5 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,15 @@ impl Encoding {
(self.0[513] & 0x7) as usize
}

/// Minimum number of input and output blocks when encoding
fn block_len(&self) -> (usize, usize) {
let bit = self.bit();
match self.wrap() {
Some((col, end)) => (col / dec(bit) * enc(bit), col + end.len()),
None => (enc(bit), dec(bit)),
}
}

fn wrap(&self) -> Option<(usize, &[u8])> {
if self.0.len() <= 515 {
return None;
Expand Down Expand Up @@ -1323,6 +1332,42 @@ impl Encoding {
Encoder::new(self, output)
}

/// Writes the encoding of `input` to `output`
///
/// This allocates a buffer of 1024 bytes on the stack. If you want to control the buffer size
/// and location, use [`Encoding::encode_write_buffer()`] instead.
///
/// # Errors
///
/// Returns an error when writing to the output fails.
pub fn encode_write(
&self, input: &[u8], output: &mut impl core::fmt::Write,
) -> core::fmt::Result {
self.encode_write_buffer(input, output, &mut [0; 1024])
}

/// Writes the encoding of `input` to `output` using a temporary `buffer`
///
/// # Panics
///
/// Panics if the buffer is shorter than 510 bytes.
///
/// # Errors
///
/// Returns an error when writing to the output fails.
pub fn encode_write_buffer(
&self, input: &[u8], output: &mut impl core::fmt::Write, buffer: &mut [u8],
) -> core::fmt::Result {
assert!(510 <= buffer.len());
let (enc, dec) = self.block_len();
for input in input.chunks(buffer.len() / dec * enc) {
let buffer = &mut buffer[.. self.encode_len(input.len())];
self.encode_mut(input, buffer);
output.write_str(unsafe { core::str::from_utf8_unchecked(buffer) })?;
}
Ok(())
}

/// Returns encoded `input`
///
/// # Examples
Expand Down Expand Up @@ -1590,12 +1635,8 @@ impl<'a> Encoder<'a> {

/// Encodes the provided input fragment and appends the result to the output
pub fn append(&mut self, mut input: &[u8]) {
let bit = self.encoding.bit();
#[allow(clippy::cast_possible_truncation)] // no truncation
let max = match self.encoding.wrap() {
Some((x, _)) => (x / dec(bit) * enc(bit)) as u8,
None => enc(bit) as u8,
};
let max = self.encoding.block_len().0 as u8;
if self.length != 0 {
let len = self.length;
#[allow(clippy::cast_possible_truncation)] // no truncation
Expand Down
14 changes: 14 additions & 0 deletions lib/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,20 @@ fn encode_append() {
test(b"fo", "ba", "baZm8=");
}

#[test]
fn encode_write() {
fn test(input: &[u8], output: &str, expected: &str) {
let mut output = output.to_string();
data_encoding::BASE64.encode_write(input, &mut output).unwrap();
assert_eq!(output, expected);
}
test(b"", "", "");
test(b"foo", "", "Zm9v");
test(b"foo", "bar", "barZm9v");
test(b"fo", "", "Zm8=");
test(b"fo", "ba", "baZm8=");
}

#[test]
fn encoder() {
#[track_caller]
Expand Down

0 comments on commit 35399a8

Please sign in to comment.