1use std::fmt;
10use std::io;
11use std::pin::Pin;
12use std::task::{Context, Poll, ready};
13use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
14
15const COMPRESSED_BUF_SIZE: usize = 8192;
16const WRITE_BUF_SIZE: usize = 16384; const DEFAULT_COMPRESS_LEVEL: u32 = 1; fn counter_delta_to_usize(before: u64, after: u64) -> usize {
20 let delta = after.saturating_sub(before);
21 usize::try_from(delta).expect("flate2 byte counter delta is bounded by the caller buffer size")
22}
23
24#[derive(Debug, Clone, Copy)]
26enum DrainState {
27 Idle,
28 Draining { pos: usize, len: usize },
29}
30
31fn poll_drain_buf<S: AsyncWrite>(
34 mut inner: Pin<&mut S>,
35 cx: &mut Context<'_>,
36 buf: &[u8],
37 state: &mut DrainState,
38) -> Poll<io::Result<()>> {
39 let DrainState::Draining { pos, len } = state else {
40 return Poll::Ready(Ok(()));
41 };
42 while *pos < *len {
43 let n = ready!(inner.as_mut().poll_write(cx, &buf[*pos..*len]))?;
44 if n == 0 {
45 return Poll::Ready(Err(io::Error::new(
46 io::ErrorKind::WriteZero,
47 "inner stream write returned 0",
48 )));
49 }
50 *pos += n;
51 }
52 *state = DrainState::Idle;
53 Poll::Ready(Ok(()))
54}
55
56fn poll_compress_drain<S: AsyncWrite>(
58 compressor: &mut flate2::Compress,
59 mut inner: Pin<&mut S>,
60 cx: &mut Context<'_>,
61 buf: &mut [u8],
62 state: &mut DrainState,
63 flush: flate2::FlushCompress,
64) -> Poll<io::Result<()>> {
65 loop {
66 ready!(poll_drain_buf(inner.as_mut(), cx, buf, state))?;
67
68 let before_out = compressor.total_out();
69 compressor
70 .compress(&[], buf, flush)
71 .map_err(io::Error::other)?;
72 let produced = counter_delta_to_usize(before_out, compressor.total_out());
73
74 if produced > 0 {
75 *state = DrainState::Draining {
76 pos: 0,
77 len: produced,
78 };
79 }
80
81 if produced < buf.len() {
82 ready!(poll_drain_buf(inner.as_mut(), cx, buf, state))?;
83 return Poll::Ready(Ok(()));
84 }
85 }
86}
87
88fn try_decompress(
91 decompressor: &mut flate2::Decompress,
92 input: &[u8],
93 buf: &mut ReadBuf<'_>,
94 stats: &mut u64,
95) -> io::Result<(usize, bool)> {
96 let out_slice = buf.initialize_unfilled();
97 if out_slice.is_empty() {
98 return Ok((0, true));
99 }
100
101 let before_in = decompressor.total_in();
102 let before_out = decompressor.total_out();
103
104 let decompress_status = decompressor
105 .decompress(input, out_slice, flate2::FlushDecompress::None)
106 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
107
108 let consumed = counter_delta_to_usize(before_in, decompressor.total_in());
109 let produced = counter_delta_to_usize(before_out, decompressor.total_out());
110
111 if produced > 0 {
112 *stats += produced as u64;
113 buf.advance(produced);
114 }
115
116 Ok((
117 consumed,
118 produced > 0 || matches!(decompress_status, flate2::Status::StreamEnd),
119 ))
120}
121
122pin_project_lite::pin_project! {
123 pub struct DecompressStream<S> {
128 #[pin]
129 inner: S,
130 decompressor: flate2::Decompress,
132 compressed_buf: Box<[u8]>,
133 compressed_pos: usize,
134 compressed_len: usize,
135 compressor: flate2::Compress,
137 write_buf: Box<[u8]>,
138 flush_buf: Box<[u8]>,
139 write_drain: DrainState,
140 flush_drain: DrainState,
141 bytes_compressed_in: u64,
143 bytes_decompressed_out: u64,
144 }
145}
146
147impl<S> DecompressStream<S> {
148 pub fn new(inner: S) -> Self {
150 Self::with_level(inner, DEFAULT_COMPRESS_LEVEL)
151 }
152
153 pub fn with_level(inner: S, level: u32) -> Self {
155 let level = flate2::Compression::new(level.min(9));
156 Self {
157 inner,
158 decompressor: flate2::Decompress::new(false),
160 compressed_buf: vec![0u8; COMPRESSED_BUF_SIZE].into_boxed_slice(),
161 compressed_pos: 0,
162 compressed_len: 0,
163 compressor: flate2::Compress::new(level, false),
164 write_buf: vec![0u8; WRITE_BUF_SIZE].into_boxed_slice(),
165 flush_buf: vec![0u8; WRITE_BUF_SIZE].into_boxed_slice(),
166 write_drain: DrainState::Idle,
167 flush_drain: DrainState::Idle,
168 bytes_compressed_in: 0,
169 bytes_decompressed_out: 0,
170 }
171 }
172
173 pub fn into_inner(self) -> S {
175 self.inner
176 }
177
178 #[inline]
180 pub const fn get_ref(&self) -> &S {
181 &self.inner
182 }
183
184 #[inline]
186 pub const fn get_mut(&mut self) -> &mut S {
187 &mut self.inner
188 }
189
190 #[inline]
192 pub const fn bandwidth_stats(&self) -> (u64, u64) {
193 (self.bytes_compressed_in, self.bytes_decompressed_out)
194 }
195}
196
197impl<S: fmt::Debug> fmt::Debug for DecompressStream<S> {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 f.debug_struct("DecompressStream")
200 .field("inner", &self.inner)
201 .field("compressed_pos", &self.compressed_pos)
202 .field("compressed_len", &self.compressed_len)
203 .field("bytes_compressed_in", &self.bytes_compressed_in)
204 .field("bytes_decompressed_out", &self.bytes_decompressed_out)
205 .finish_non_exhaustive()
206 }
207}
208
209impl<S: AsyncRead> AsyncRead for DecompressStream<S> {
210 fn poll_read(
211 self: Pin<&mut Self>,
212 cx: &mut Context<'_>,
213 buf: &mut ReadBuf<'_>,
214 ) -> Poll<io::Result<()>> {
215 let mut this = self.project();
216 loop {
217 if *this.compressed_pos < *this.compressed_len {
218 let input = &this.compressed_buf[*this.compressed_pos..*this.compressed_len];
219 let (consumed, done) =
220 try_decompress(this.decompressor, input, buf, this.bytes_decompressed_out)?;
221 *this.compressed_pos += consumed;
222 if done {
223 return Poll::Ready(Ok(()));
224 }
225 continue;
226 }
227
228 let (_, done) =
229 try_decompress(this.decompressor, &[], buf, this.bytes_decompressed_out)?;
230 if done {
231 return Poll::Ready(Ok(()));
232 }
233
234 *this.compressed_pos = 0;
236 *this.compressed_len = 0;
237 let mut read_buf = ReadBuf::new(this.compressed_buf);
238 ready!(this.inner.as_mut().poll_read(cx, &mut read_buf))?;
239 let n = read_buf.filled().len();
240 if n == 0 {
241 return Poll::Ready(Ok(()));
242 }
243 *this.compressed_len = n;
244 *this.bytes_compressed_in += n as u64;
245 }
246 }
247}
248
249impl<S: AsyncWrite> AsyncWrite for DecompressStream<S> {
250 fn poll_write(
251 self: Pin<&mut Self>,
252 cx: &mut Context<'_>,
253 buf: &[u8],
254 ) -> Poll<io::Result<usize>> {
255 let mut this = self.project();
256
257 ready!(poll_drain_buf(
258 this.inner.as_mut(),
259 cx,
260 this.write_buf,
261 this.write_drain,
262 ))?;
263
264 let before_in = this.compressor.total_in();
265 let before_out = this.compressor.total_out();
266
267 this.compressor
268 .compress(buf, this.write_buf, flate2::FlushCompress::None)
269 .map_err(io::Error::other)?;
270
271 let consumed = counter_delta_to_usize(before_in, this.compressor.total_in());
272 let produced = counter_delta_to_usize(before_out, this.compressor.total_out());
273
274 if produced > 0 {
275 *this.write_drain = DrainState::Draining {
276 pos: 0,
277 len: produced,
278 };
279 match poll_drain_buf(this.inner.as_mut(), cx, this.write_buf, this.write_drain) {
280 Poll::Ready(Ok(())) | Poll::Pending => {}
281 Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
282 }
283 }
284
285 Poll::Ready(Ok(if consumed > 0 {
286 consumed
287 } else {
288 buf.len().min(1)
289 }))
290 }
291
292 fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
293 let mut this = self.project();
294 ready!(poll_drain_buf(
295 this.inner.as_mut(),
296 cx,
297 this.write_buf,
298 this.write_drain
299 ))?;
300 ready!(poll_compress_drain(
301 this.compressor,
302 this.inner.as_mut(),
303 cx,
304 this.flush_buf,
305 this.flush_drain,
306 flate2::FlushCompress::Sync,
307 ))?;
308 this.inner.poll_flush(cx)
309 }
310
311 fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
312 let mut this = self.project();
313 ready!(poll_drain_buf(
314 this.inner.as_mut(),
315 cx,
316 this.write_buf,
317 this.write_drain
318 ))?;
319 ready!(poll_compress_drain(
320 this.compressor,
321 this.inner.as_mut(),
322 cx,
323 this.flush_buf,
324 this.flush_drain,
325 flate2::FlushCompress::Finish,
326 ))?;
327 this.inner.poll_shutdown(cx)
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use tokio::io::{AsyncReadExt, AsyncWriteExt};
335
336 fn deflate_compress(data: &[u8]) -> Vec<u8> {
337 use flate2::write::DeflateEncoder;
338 use std::io::Write;
339 let mut enc = DeflateEncoder::new(Vec::new(), flate2::Compression::default());
340 enc.write_all(data).unwrap();
341 enc.finish().unwrap()
342 }
343
344 async fn feed_compressed(
345 data: &[u8],
346 ) -> (
347 tokio::io::DuplexStream,
348 DecompressStream<tokio::io::DuplexStream>,
349 ) {
350 let compressed = deflate_compress(data);
351 let (mut tx, rx) = tokio::io::duplex(4096);
352 tx.write_all(&compressed).await.unwrap();
353 tx.shutdown().await.unwrap();
354 (tx, DecompressStream::new(rx))
355 }
356
357 #[tokio::test]
358 async fn test_roundtrip_read() {
359 let original = b"Hello, NNTP world! COMPRESS DEFLATE test data.\r\n";
360 let (_tx, mut stream) = feed_compressed(original).await;
361
362 let mut output = Vec::new();
363 stream.read_to_end(&mut output).await.unwrap();
364
365 assert_eq!(output, original);
366 }
367
368 #[tokio::test]
369 async fn test_roundtrip_write_flush() {
370 use flate2::read::DeflateDecoder;
372 use std::io::Read;
373
374 let original = b"GROUP alt.test\r\n";
375
376 let (reader, writer_side) = tokio::io::duplex(4096);
377 let mut stream = DecompressStream::new(writer_side);
378
379 stream.write_all(original).await.unwrap();
380 stream.flush().await.unwrap();
381 stream.shutdown().await.unwrap();
382
383 drop(stream);
384
385 let mut compressed_output = Vec::new();
387 let mut reader = reader;
388 reader.read_to_end(&mut compressed_output).await.unwrap();
389
390 let mut decoder = DeflateDecoder::new(&compressed_output[..]);
392 let mut decompressed = Vec::new();
393 decoder.read_to_end(&mut decompressed).unwrap();
394
395 assert_eq!(decompressed, original);
396 }
397
398 #[tokio::test]
399 async fn test_large_data_roundtrip() {
400 let mut original = Vec::with_capacity(1024 * 1024);
402 for i in 0..1024 * 64 {
403 original.extend_from_slice(
404 format!("Line {i}: Some NNTP article content here\r\n").as_bytes(),
405 );
406 }
407
408 let compressed = deflate_compress(&original);
409
410 let (mut writer, reader) = tokio::io::duplex(16384);
411
412 tokio::spawn(async move {
413 writer.write_all(&compressed).await.unwrap();
414 writer.shutdown().await.unwrap();
415 });
416
417 let mut stream = DecompressStream::new(reader);
418 let mut output = Vec::new();
419 stream.read_to_end(&mut output).await.unwrap();
420
421 assert_eq!(output.len(), original.len());
422 assert_eq!(output, original);
423 }
424
425 #[tokio::test]
426 async fn test_bandwidth_stats() {
427 let original = b"Test data for bandwidth tracking\r\n";
428 let (_tx, mut stream) = feed_compressed(original).await;
429
430 let mut output = Vec::new();
431 stream.read_to_end(&mut output).await.unwrap();
432
433 let (compressed_in, decompressed_out) = stream.bandwidth_stats();
434 assert!(compressed_in > 0);
435 assert_eq!(decompressed_out, original.len() as u64);
436 }
437
438 #[test]
439 fn test_debug_impl() {
440 let stream = DecompressStream::new(std::io::Cursor::new(Vec::<u8>::new()));
441 let debug_str = format!("{stream:?}");
442 assert!(debug_str.contains("DecompressStream"));
443 assert!(debug_str.contains("compressed_pos"));
444 assert!(debug_str.contains("bytes_compressed_in"));
445 }
446}