diff --git a/lib/CHANGELOG.md b/lib/CHANGELOG.md index 263eb41..4980a9e 100644 --- a/lib/CHANGELOG.md +++ b/lib/CHANGELOG.md @@ -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 diff --git a/lib/fuzz/Cargo.toml b/lib/fuzz/Cargo.toml index 077ecdc..ff9e982 100644 --- a/lib/fuzz/Cargo.toml +++ b/lib/fuzz/Cargo.toml @@ -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 diff --git a/lib/fuzz/fuzz_targets/encode_write.rs b/lib/fuzz/fuzz_targets/encode_write.rs new file mode 100644 index 0000000..16d91d5 --- /dev/null +++ b/lib/fuzz/fuzz_targets/encode_write.rs @@ -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); +}); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b185ec3..f393690 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -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; @@ -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 @@ -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 diff --git a/lib/tests/lib.rs b/lib/tests/lib.rs index deb708c..91f86ba 100644 --- a/lib/tests/lib.rs +++ b/lib/tests/lib.rs @@ -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]