use std::io::{self, Read, Write};
#[must_use]
pub fn padme_length(len: u64) -> u64 {
if len < 2 {
return len;
}
let e: u32 = 63 - len.leading_zeros();
let s: u32 = 32 - e.leading_zeros();
let last_bits = e - s;
let bit_mask: u64 = (1u64 << last_bits) - 1;
len.checked_add(bit_mask)
.map_or(u64::MAX, |sum| sum & !bit_mask)
}
pub struct PadmeReader<R: Read> {
inner: R,
inner_done: bool,
padding_remaining: u64,
}
impl<R: Read> PadmeReader<R> {
#[must_use]
pub fn new(inner: R, real_len: u64) -> Self {
let padded_len = padme_length(real_len);
Self {
inner,
inner_done: false,
padding_remaining: padded_len.saturating_sub(real_len),
}
}
}
impl<R: Read> Read for PadmeReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
if !self.inner_done {
let n = self.inner.read(buf)?;
if n > 0 {
return Ok(n);
}
self.inner_done = true;
}
if self.padding_remaining == 0 {
return Ok(0);
}
let n = (buf.len() as u64).min(self.padding_remaining) as usize;
buf[..n].fill(0);
self.padding_remaining -= n as u64;
Ok(n)
}
}
pub struct TruncatingWriter<W: Write> {
inner: W,
remaining: Option<u64>,
}
impl<W: Write> TruncatingWriter<W> {
#[must_use]
pub fn new(inner: W, limit: u64) -> Self {
Self {
inner,
remaining: if limit == 0 { None } else { Some(limit) },
}
}
#[must_use]
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W: Write> Write for TruncatingWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let Some(remaining) = self.remaining else {
return self.inner.write(buf);
};
if remaining == 0 {
return Ok(buf.len());
}
let take = (buf.len() as u64).min(remaining) as usize;
let written = self.inner.write(&buf[..take])?;
self.remaining = Some(remaining - written as u64);
if written < take {
return Ok(written);
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn padme_length_edge_cases() {
assert_eq!(padme_length(0), 0);
assert_eq!(padme_length(1), 1);
}
#[test]
fn padme_length_known_values() {
assert_eq!(padme_length(2), 2);
assert_eq!(padme_length(3), 3);
assert_eq!(padme_length(4), 4);
assert_eq!(padme_length(7), 7);
assert_eq!(padme_length(8), 8);
assert_eq!(padme_length(9), 10);
assert_eq!(padme_length(10), 10);
assert_eq!(padme_length(11), 12);
assert_eq!(padme_length(12), 12);
assert_eq!(padme_length(13), 14);
assert_eq!(padme_length(14), 14);
assert_eq!(padme_length(15), 16);
}
#[test]
fn padme_length_saturates_near_u64_max() {
assert_eq!(padme_length(u64::MAX), u64::MAX);
assert_eq!(padme_length(u64::MAX - 1), u64::MAX);
for len in [u64::MAX / 2, u64::MAX - (1 << 57), u64::MAX] {
assert!(padme_length(len) >= len, "len={len}");
}
}
#[test]
fn padme_length_never_shrinks() {
for len in 0..100_000u64 {
assert!(padme_length(len) >= len, "len={len}");
}
for len in (100_000..5_000_000u64).step_by(997) {
assert!(padme_length(len) >= len, "len={len}");
}
}
#[test]
fn padme_length_monotonic() {
let mut prev = padme_length(0);
for len in 1..200_000u64 {
let cur = padme_length(len);
assert!(cur >= prev, "padme_length must not decrease: len={len}");
prev = cur;
}
}
#[test]
fn padme_length_overhead_bounded() {
for len in (16..2_000_000u64).step_by(101) {
let padded = padme_length(len);
let overhead = (padded - len) as f64 / len as f64;
assert!(
overhead <= 0.15,
"len={len} padded={padded} overhead={overhead:.3}"
);
}
}
#[test]
fn padme_reader_yields_padded_length_and_correct_prefix() {
let real: Vec<u8> = (0u8..=255).cycle().take(1000).collect();
let mut reader = PadmeReader::new(real.as_slice(), real.len() as u64);
let mut out = Vec::new();
io::Read::read_to_end(&mut reader, &mut out).unwrap();
let expected_len = padme_length(real.len() as u64) as usize;
assert_eq!(out.len(), expected_len);
assert_eq!(&out[..real.len()], real.as_slice());
assert!(out[real.len()..].iter().all(|&b| b == 0));
}
#[test]
fn padme_reader_empty_input_is_noop() {
let mut reader = PadmeReader::new([].as_slice(), 0);
let mut out = Vec::new();
io::Read::read_to_end(&mut reader, &mut out).unwrap();
assert!(out.is_empty());
}
#[test]
fn padme_reader_small_reads_still_reach_padded_length() {
let real = vec![0xABu8; 9]; let mut reader = PadmeReader::new(real.as_slice(), real.len() as u64);
let mut out = Vec::new();
let mut buf = [0u8; 1];
loop {
let n = io::Read::read(&mut reader, &mut buf).unwrap();
if n == 0 {
break;
}
out.push(buf[0]);
}
assert_eq!(out.len(), 10);
assert_eq!(&out[..9], real.as_slice());
assert_eq!(out[9], 0);
}
#[test]
fn truncating_writer_strips_padding_tail() {
let mut sink = Vec::new();
{
let mut w = TruncatingWriter::new(&mut sink, 5);
w.write_all(b"hello padding tail").unwrap();
}
assert_eq!(sink, b"hello");
}
#[test]
fn truncating_writer_zero_limit_passes_everything() {
let mut sink = Vec::new();
{
let mut w = TruncatingWriter::new(&mut sink, 0);
w.write_all(b"unbounded").unwrap();
}
assert_eq!(sink, b"unbounded");
}
#[test]
fn truncating_writer_exact_length_is_noop() {
let mut sink = Vec::new();
{
let mut w = TruncatingWriter::new(&mut sink, 5);
w.write_all(b"hello").unwrap();
}
assert_eq!(sink, b"hello");
}
#[test]
fn truncating_writer_across_multiple_writes() {
let mut sink = Vec::new();
{
let mut w = TruncatingWriter::new(&mut sink, 4);
w.write_all(b"ab").unwrap();
w.write_all(b"cd").unwrap();
w.write_all(b"ef").unwrap();
}
assert_eq!(sink, b"abcd");
}
struct ShortWriter(Vec<u8>);
impl Write for ShortWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = buf.len().min(3);
self.0.extend_from_slice(&buf[..n]);
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn truncating_writer_survives_short_writes() {
let mut w = TruncatingWriter::new(ShortWriter(Vec::new()), 6);
w.write_all(b"ABCDEFpppp").unwrap();
assert_eq!(w.into_inner().0, b"ABCDEF");
}
#[test]
fn truncating_writer_short_writes_within_limit() {
let mut w = TruncatingWriter::new(ShortWriter(Vec::new()), 100);
w.write_all(b"hello world").unwrap();
assert_eq!(w.into_inner().0, b"hello world");
}
#[test]
fn truncating_writer_into_inner() {
let w = TruncatingWriter::new(Vec::new(), 3);
let mut w = w;
w.write_all(b"abcdef").unwrap();
assert_eq!(w.into_inner(), b"abc");
}
}