Skip to content

Commit 40d1066

Browse files
committed
Process multiple stream decoder quads
1 parent d7a5976 commit 40d1066

3 files changed

Lines changed: 94 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
pending quantum continues through the current slice when buffer capacity
1313
allows, including preserving final 1-2 byte tails instead of forcing an
1414
early short write.
15+
- Hardened `stream::Decoder::write` so direct writes process multiple complete
16+
Base64 quads per call, continue after completing pending input, and preserve
17+
final partial quads as pending input when those bytes are accepted.
1518

1619
## 0.12.0 - 2026-05-17
1720

src/lib.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,25 +1347,57 @@ pub mod stream {
13471347
}
13481348
self.clear_pending();
13491349
consumed += needed;
1350-
return Ok(consumed);
1350+
1351+
if self.finished {
1352+
return Ok(consumed);
1353+
}
13511354
}
13521355

1353-
let remaining = &input[consumed..];
1354-
let full_len = remaining.len() / 4 * 4;
1355-
if full_len > 0 {
1356-
let quad = [remaining[0], remaining[1], remaining[2], remaining[3]];
1356+
while input.len() - consumed >= 4 {
1357+
if self.output.available_capacity() < 3 {
1358+
return Ok(consumed);
1359+
}
1360+
1361+
let quad = [
1362+
input[consumed],
1363+
input[consumed + 1],
1364+
input[consumed + 2],
1365+
input[consumed + 3],
1366+
];
13571367
let mut quad = quad;
1358-
let result = self.queue_full_quad(quad);
1368+
let mut decoded = [0u8; 3];
1369+
let written = match self.engine.decode_slice(&quad, &mut decoded) {
1370+
Ok(written) => written,
1371+
Err(err) => {
1372+
crate::wipe_bytes(&mut quad);
1373+
crate::wipe_bytes(&mut decoded);
1374+
if consumed > 0 {
1375+
return Ok(consumed);
1376+
}
1377+
1378+
self.failed = true;
1379+
return Err(decode_error_to_io(err));
1380+
}
1381+
};
1382+
1383+
let result = self.output.push_slice(&decoded[..written]);
13591384
crate::wipe_bytes(&mut quad);
1385+
crate::wipe_bytes(&mut decoded);
13601386
result?;
1361-
return Ok(consumed + 4);
1387+
consumed += 4;
1388+
1389+
if written < 3 {
1390+
self.finished = true;
1391+
return Ok(consumed);
1392+
}
13621393
}
13631394

1364-
let tail = &remaining[full_len..];
1395+
let tail = &input[consumed..];
13651396
self.pending[..tail.len()].copy_from_slice(tail);
13661397
self.pending_len = tail.len();
1398+
consumed += tail.len();
13671399

1368-
Ok(input.len())
1400+
Ok(consumed)
13691401
}
13701402

13711403
fn flush(&mut self) -> io::Result<()> {

tests/rfc4648.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3399,6 +3399,56 @@ fn stream_decoder_write_failure_preserves_pending_input() {
33993399
assert_eq!(decoder.get_ref().output, b"hi");
34003400
}
34013401

3402+
#[cfg(feature = "stream")]
3403+
#[test]
3404+
fn stream_decoder_direct_write_processes_multiple_quads() {
3405+
let mut decoder = Decoder::new(Vec::new(), STANDARD);
3406+
3407+
let written = decoder.write(b"aGVsbG8=").unwrap();
3408+
assert_eq!(written, 8);
3409+
assert_eq!(decoder.buffered_output_len(), 5);
3410+
assert!(decoder.has_terminal_padding());
3411+
3412+
let decoded = decoder.finish().unwrap();
3413+
assert_eq!(decoded, b"hello");
3414+
}
3415+
3416+
#[cfg(feature = "stream")]
3417+
#[test]
3418+
fn stream_decoder_direct_write_continues_after_pending_quad() {
3419+
let mut decoder = Decoder::new(Vec::new(), STANDARD);
3420+
3421+
assert_eq!(decoder.write(b"a").unwrap(), 1);
3422+
assert_eq!(decoder.pending_len(), 1);
3423+
3424+
let written = decoder.write(b"GVsbG8=").unwrap();
3425+
assert_eq!(written, 7);
3426+
assert_eq!(decoder.pending_len(), 0);
3427+
assert_eq!(decoder.buffered_output_len(), 5);
3428+
assert!(decoder.has_terminal_padding());
3429+
3430+
let decoded = decoder.finish().unwrap();
3431+
assert_eq!(decoded, b"hello");
3432+
}
3433+
3434+
#[cfg(feature = "stream")]
3435+
#[test]
3436+
fn stream_decoder_direct_write_reports_partial_progress_for_large_input() {
3437+
let input = vec![b'a'; 1500];
3438+
let encoded = STANDARD.encode_vec(&input).unwrap();
3439+
let mut decoder = Decoder::new(Vec::new(), STANDARD);
3440+
3441+
let written = decoder.write(&encoded).unwrap();
3442+
assert_eq!(written, 1364);
3443+
assert_eq!(decoder.buffered_output_len(), 1023);
3444+
assert_eq!(decoder.pending_len(), 0);
3445+
assert!(decoder.has_buffered_output());
3446+
3447+
decoder.write_all(&encoded[written..]).unwrap();
3448+
let decoded = decoder.finish().unwrap();
3449+
assert_eq!(decoded, input);
3450+
}
3451+
34023452
#[cfg(feature = "stream")]
34033453
#[test]
34043454
fn stream_decoder_direct_write_reports_partial_progress() {

0 commit comments

Comments
 (0)