use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use rand::RngExt;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use crate::async_stream::{AsyncPing, AsyncStream};
use crate::util::allocate_vec;
pub const NUM_FIRST_PADDINGS: usize = 8;
pub const MAX_PADDING_SIZE: u8 = 255;
pub const PADDING_HEADER_SIZE: usize = 3;
pub const MAX_PAYLOAD_SIZE: usize = 65535;
const MAX_FRAME_SIZE: usize = PADDING_HEADER_SIZE + MAX_PAYLOAD_SIZE + MAX_PADDING_SIZE as usize;
pub const NONINDEX_CHARS: &[u8] = b"!\"#$&'()*+,;<>?@X";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaddingType {
None = 0,
Variant1 = 1,
}
impl PaddingType {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(PaddingType::None),
1 => Some(PaddingType::Variant1),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaddingDirection {
Client,
Server,
}
pub fn generate_padding_header(len: usize) -> String {
let mut rng = rand::rng();
let mut buf = Vec::with_capacity(len);
for _ in 0..len.min(16) {
let idx = rng.random_range(0..16);
buf.push(NONINDEX_CHARS[idx]);
}
for _ in 16..len {
buf.push(NONINDEX_CHARS[16]); }
String::from_utf8(buf).unwrap()
}
pub fn parse_padding_type_request(value: &str) -> Vec<PaddingType> {
value
.split(',')
.filter_map(|s| s.trim().parse::<u8>().ok())
.filter_map(PaddingType::from_u8)
.collect()
}
const SMALL_PAYLOAD_THRESHOLD: usize = 100;
const MEDIUM_PAYLOAD_LOW: usize = 400;
const MEDIUM_PAYLOAD_HIGH: usize = 1024;
const FRAGMENT_SIZE_MIN: usize = 200;
const FRAGMENT_SIZE_MAX: usize = 300;
pub struct NaivePaddingStream<S> {
inner: S,
padding_type: PaddingType,
direction: PaddingDirection,
num_read_frames: usize,
num_written_frames: usize,
read_buffer: Box<[u8]>,
read_buffer_start: usize,
read_buffer_end: usize,
current_frame: Option<(usize, usize, usize)>,
write_buffer: Box<[u8]>,
write_start: usize,
write_end: usize,
write_payload_len: usize,
}
impl<S> NaivePaddingStream<S> {
pub fn new(inner: S, direction: PaddingDirection, padding_type: PaddingType) -> Self {
let read_buffer = allocate_vec(MAX_FRAME_SIZE).into_boxed_slice();
let write_buffer = allocate_vec(MAX_FRAME_SIZE).into_boxed_slice();
Self {
inner,
padding_type,
direction,
num_read_frames: 0,
num_written_frames: 0,
read_buffer,
read_buffer_start: 0,
read_buffer_end: 0,
current_frame: None,
write_buffer,
write_start: 0,
write_end: 0,
write_payload_len: 0,
}
}
#[inline]
fn drain_read_buffer(&mut self, buf: &mut ReadBuf<'_>) -> usize {
let available = self.read_buffer_end - self.read_buffer_start;
let to_copy = available.min(buf.remaining());
buf.put_slice(&self.read_buffer[self.read_buffer_start..self.read_buffer_start + to_copy]);
self.read_buffer_start += to_copy;
if self.read_buffer_start >= self.read_buffer_end {
self.read_buffer_start = 0;
self.read_buffer_end = 0;
}
to_copy
}
#[inline]
fn maybe_reset_read_buffer(&mut self) {
if self.read_buffer_start == self.read_buffer_end {
self.read_buffer_start = 0;
self.read_buffer_end = 0;
}
}
#[inline]
fn should_pad_reads(&self) -> bool {
self.padding_type != PaddingType::None && self.num_read_frames < NUM_FIRST_PADDINGS
}
#[inline]
fn should_pad_writes(&self) -> bool {
self.padding_type != PaddingType::None && self.num_written_frames < NUM_FIRST_PADDINGS
}
fn generate_padding_size(&self, payload_len: usize) -> u8 {
let mut rng = rand::rng();
if self.direction == PaddingDirection::Server && payload_len < SMALL_PAYLOAD_THRESHOLD {
let min_padding = (MAX_PADDING_SIZE as usize).saturating_sub(payload_len);
rng.random_range(min_padding as u8..=MAX_PADDING_SIZE)
} else {
rng.random_range(0..=MAX_PADDING_SIZE)
}
}
fn compute_fragment_limit(&self, payload_len: usize, remaining: usize) -> usize {
if self.direction == PaddingDirection::Server
&& payload_len > MEDIUM_PAYLOAD_LOW
&& payload_len < MEDIUM_PAYLOAD_HIGH
{
let limit = rand::rng().random_range(FRAGMENT_SIZE_MIN..=FRAGMENT_SIZE_MAX);
remaining.min(limit)
} else {
remaining
}
}
fn encode_frame(&mut self, payload: &[u8]) -> io::Result<()> {
if payload.len() > MAX_PAYLOAD_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"Payload too large: {} > {}",
payload.len(),
MAX_PAYLOAD_SIZE
),
));
}
let padding_size = self.generate_padding_size(payload.len());
let payload_len = payload.len() as u16;
let frame_size = PADDING_HEADER_SIZE + payload.len() + padding_size as usize;
self.write_buffer[0] = (payload_len >> 8) as u8;
self.write_buffer[1] = (payload_len & 0xff) as u8;
self.write_buffer[2] = padding_size;
self.write_buffer[PADDING_HEADER_SIZE..PADDING_HEADER_SIZE + payload.len()]
.copy_from_slice(payload);
let padding_start = PADDING_HEADER_SIZE + payload.len();
self.write_buffer[padding_start..padding_start + padding_size as usize].fill(0);
self.write_start = 0;
self.write_end = frame_size;
self.write_payload_len = payload.len();
Ok(())
}
#[inline]
fn reset_read_buffer_offset(&mut self) {
if self.read_buffer_start > 0 && self.read_buffer_end > self.read_buffer_start {
self.read_buffer
.copy_within(self.read_buffer_start..self.read_buffer_end, 0);
self.read_buffer_end -= self.read_buffer_start;
self.read_buffer_start = 0;
} else if self.read_buffer_start == self.read_buffer_end {
self.read_buffer_start = 0;
self.read_buffer_end = 0;
}
}
#[inline]
fn try_decode_to_buf(&mut self, buf: &mut ReadBuf<'_>) -> Option<usize> {
let available = self.read_buffer_end - self.read_buffer_start;
let (payload_len, padding_len, payload_delivered) = match self.current_frame {
Some((pl, pd, delivered)) => (pl, pd, delivered),
None => {
if available < PADDING_HEADER_SIZE {
return None;
}
let payload_len = u16::from_be_bytes([
self.read_buffer[self.read_buffer_start],
self.read_buffer[self.read_buffer_start + 1],
]) as usize;
let padding_len = self.read_buffer[self.read_buffer_start + 2] as usize;
self.read_buffer_start += PADDING_HEADER_SIZE;
self.current_frame = Some((payload_len, padding_len, 0));
(payload_len, padding_len, 0)
}
};
let payload_remaining = payload_len - payload_delivered;
let available = self.read_buffer_end - self.read_buffer_start;
if payload_remaining > 0 {
if available == 0 {
return None; }
let can_deliver = payload_remaining.min(available).min(buf.remaining());
buf.put_slice(
&self.read_buffer[self.read_buffer_start..self.read_buffer_start + can_deliver],
);
self.read_buffer_start += can_deliver;
let new_delivered = payload_delivered + can_deliver;
if new_delivered < payload_len {
self.current_frame = Some((payload_len, padding_len, new_delivered));
return Some(can_deliver);
}
let available = self.read_buffer_end - self.read_buffer_start;
if available < padding_len {
self.current_frame = Some((payload_len, padding_len, new_delivered));
return Some(can_deliver);
}
self.read_buffer_start += padding_len;
self.current_frame = None;
self.num_read_frames += 1;
self.maybe_reset_read_buffer();
return Some(can_deliver);
}
if available < padding_len {
return None;
}
self.read_buffer_start += padding_len;
self.current_frame = None;
self.num_read_frames += 1;
self.maybe_reset_read_buffer();
Some(0)
}
}
impl<S: AsyncWrite + Unpin> NaivePaddingStream<S> {
fn poll_write_buffered(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<usize>>> {
let remaining = self.write_end - self.write_start;
if remaining == 0 {
return Poll::Ready(Ok(Some(self.write_payload_len)));
}
let fragment_limit = self.compute_fragment_limit(self.write_payload_len, remaining);
let to_write = &self.write_buffer[self.write_start..self.write_start + fragment_limit];
match Pin::new(&mut self.inner).poll_write(cx, to_write) {
Poll::Ready(Ok(n)) => {
self.write_start += n;
if self.write_start >= self.write_end {
self.num_written_frames += 1;
Poll::Ready(Ok(Some(self.write_payload_len)))
} else {
Poll::Ready(Ok(None))
}
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
}
}
impl<S: AsyncRead + Unpin> AsyncRead for NaivePaddingStream<S> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let this = &mut *self;
if buf.remaining() == 0 {
return Poll::Ready(Ok(()));
}
if !this.should_pad_reads() {
if this.read_buffer_end > this.read_buffer_start {
this.drain_read_buffer(buf);
return Poll::Ready(Ok(()));
}
return Pin::new(&mut this.inner).poll_read(cx, buf);
}
loop {
if let Some(n) = this.try_decode_to_buf(buf) {
if n > 0 {
return Poll::Ready(Ok(()));
}
if !this.should_pad_reads() {
if this.read_buffer_end > this.read_buffer_start {
this.drain_read_buffer(buf);
return Poll::Ready(Ok(()));
}
return Pin::new(&mut this.inner).poll_read(cx, buf);
}
continue;
}
if this.read_buffer_end >= this.read_buffer.len() {
this.reset_read_buffer_offset();
}
let read_slice = &mut this.read_buffer[this.read_buffer_end..];
let mut temp_read_buf = ReadBuf::new(read_slice);
match Pin::new(&mut this.inner).poll_read(cx, &mut temp_read_buf) {
Poll::Ready(Ok(())) => {
let filled_len = temp_read_buf.filled().len();
if filled_len == 0 {
return Poll::Ready(Ok(()));
}
this.read_buffer_end += filled_len;
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
}
}
const MAX_PAYLOAD_PER_FRAME: usize = 64 * 1024 - PADDING_HEADER_SIZE - MAX_PADDING_SIZE as usize;
impl<S: AsyncWrite + Unpin> AsyncWrite for NaivePaddingStream<S> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let this = &mut *self;
if !this.should_pad_writes() {
return Pin::new(&mut this.inner).poll_write(cx, buf);
}
if this.write_start < this.write_end {
match this.poll_write_buffered(cx) {
Poll::Ready(Ok(Some(payload_len))) => return Poll::Ready(Ok(payload_len)),
Poll::Ready(Ok(None)) => {
cx.waker().wake_by_ref();
return Poll::Pending;
}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
let payload = if buf.len() > MAX_PAYLOAD_PER_FRAME {
&buf[..MAX_PAYLOAD_PER_FRAME]
} else {
buf
};
this.encode_frame(payload)?;
match this.poll_write_buffered(cx) {
Poll::Ready(Ok(Some(payload_len))) => Poll::Ready(Ok(payload_len)),
Poll::Ready(Ok(None)) => {
cx.waker().wake_by_ref();
Poll::Pending
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
Poll::Pending => Poll::Pending,
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let this = &mut *self;
while this.write_start < this.write_end {
match this.poll_write_buffered(cx) {
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
Pin::new(&mut this.inner).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let this = &mut *self;
while this.write_start < this.write_end {
match this.poll_write_buffered(cx) {
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
Poll::Pending => return Poll::Pending,
}
}
Pin::new(&mut this.inner).poll_shutdown(cx)
}
}
impl<S: AsyncPing> AsyncPing for NaivePaddingStream<S> {
fn supports_ping(&self) -> bool {
self.inner.supports_ping()
}
fn poll_write_ping(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<bool>> {
Poll::Ready(Ok(false))
}
}
impl<S: AsyncStream> AsyncStream for NaivePaddingStream<S> {}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::VecDeque;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[test]
fn test_generate_padding_header() {
let header = generate_padding_header(20);
assert_eq!(header.len(), 20);
assert!(header.chars().all(|c| NONINDEX_CHARS.contains(&(c as u8))));
assert!(header.chars().skip(16).all(|c| c == 'X'));
}
#[test]
fn test_padding_type_parse() {
let types = parse_padding_type_request("1, 0");
assert_eq!(types, vec![PaddingType::Variant1, PaddingType::None]);
}
#[test]
fn test_padding_type_from_u8() {
assert_eq!(PaddingType::from_u8(0), Some(PaddingType::None));
assert_eq!(PaddingType::from_u8(1), Some(PaddingType::Variant1));
assert_eq!(PaddingType::from_u8(2), None);
}
fn encode_test_frame(payload: &[u8], padding_len: u8) -> Vec<u8> {
let payload_len = payload.len() as u16;
let mut frame = Vec::with_capacity(3 + payload.len() + padding_len as usize);
frame.extend_from_slice(&payload_len.to_be_bytes());
frame.push(padding_len);
frame.extend_from_slice(payload);
frame.extend(std::iter::repeat_n(0u8, padding_len as usize));
frame
}
#[test]
fn test_frame_encoding_format() {
let frame = encode_test_frame(b"hello", 10);
assert_eq!(frame.len(), 3 + 5 + 10); assert_eq!(&frame[0..2], &[0x00, 0x05]); assert_eq!(frame[2], 10); assert_eq!(&frame[3..8], b"hello"); assert!(frame[8..].iter().all(|&b| b == 0)); }
#[test]
fn test_frame_encoding_empty_payload() {
let frame = encode_test_frame(b"", 50);
assert_eq!(frame.len(), 3 + 50);
assert_eq!(&frame[0..2], &[0x00, 0x00]); assert_eq!(frame[2], 50); }
#[test]
fn test_frame_encoding_max_payload() {
let payload = vec![0xAB; MAX_PAYLOAD_SIZE];
let frame = encode_test_frame(&payload, 0);
assert_eq!(frame.len(), 3 + MAX_PAYLOAD_SIZE);
assert_eq!(&frame[0..2], &[0xFF, 0xFF]); }
struct MockStream {
read_chunks: VecDeque<Vec<u8>>,
written: Vec<u8>,
}
impl MockStream {
fn new(chunks: Vec<Vec<u8>>) -> Self {
Self {
read_chunks: chunks.into(),
written: Vec::new(),
}
}
fn from_data(data: Vec<u8>) -> Self {
Self::new(vec![data])
}
}
impl AsyncRead for MockStream {
fn poll_read(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
if let Some(chunk) = self.read_chunks.pop_front() {
let to_copy = chunk.len().min(buf.remaining());
buf.put_slice(&chunk[..to_copy]);
if to_copy < chunk.len() {
self.read_chunks.push_front(chunk[to_copy..].to_vec());
}
Poll::Ready(Ok(()))
} else {
Poll::Ready(Ok(()))
}
}
}
impl AsyncWrite for MockStream {
fn poll_write(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.written.extend_from_slice(buf);
Poll::Ready(Ok(buf.len()))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
}
impl Unpin for MockStream {}
impl AsyncPing for MockStream {
fn supports_ping(&self) -> bool {
false
}
fn poll_write_ping(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<bool>> {
Poll::Ready(Ok(false))
}
}
impl AsyncStream for MockStream {}
#[tokio::test]
async fn test_read_single_padded_frame() {
let payload = b"hello world";
let frame = encode_test_frame(payload, 20);
let mock = MockStream::from_data(frame);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(n, payload.len());
assert_eq!(&buf[..n], payload);
}
#[tokio::test]
async fn test_read_multiple_padded_frames() {
let payloads = [b"frame1".as_slice(), b"frame2", b"frame3"];
let mut data = Vec::new();
for payload in &payloads {
data.extend(encode_test_frame(payload, 10));
}
let mock = MockStream::from_data(data);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
for expected in &payloads {
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], *expected);
}
}
#[tokio::test]
async fn test_read_pure_padding_frame_skipped() {
let mut data = Vec::new();
data.extend(encode_test_frame(b"", 50)); data.extend(encode_test_frame(b"real data", 10));
let mock = MockStream::from_data(data);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], b"real data");
}
#[tokio::test]
async fn test_read_partial_frame_header() {
let payload = b"test payload";
let frame = encode_test_frame(payload, 5);
let chunk1 = frame[..2].to_vec();
let chunk2 = frame[2..].to_vec();
let mock = MockStream::new(vec![chunk1, chunk2]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], payload);
}
#[tokio::test]
async fn test_read_partial_frame_payload() {
let payload = b"split payload data";
let frame = encode_test_frame(payload, 5);
let split_point = 3 + payload.len() / 2;
let chunk1 = frame[..split_point].to_vec();
let chunk2 = frame[split_point..].to_vec();
let mock = MockStream::new(vec![chunk1, chunk2]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
let mut result = Vec::new();
loop {
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
if n == 0 {
break;
}
result.extend_from_slice(&buf[..n]);
if result.len() >= payload.len() {
break;
}
}
assert_eq!(result, payload);
}
#[tokio::test]
async fn test_read_transition_to_raw_mode() {
let mut data = Vec::new();
for i in 0..NUM_FIRST_PADDINGS {
let payload = format!("frame{}", i);
data.extend(encode_test_frame(payload.as_bytes(), 5));
}
let raw_data = b"raw data after padding";
data.extend_from_slice(raw_data);
let mock = MockStream::from_data(data);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
for i in 0..NUM_FIRST_PADDINGS {
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
let expected = format!("frame{}", i);
assert_eq!(&buf[..n], expected.as_bytes(), "frame {} mismatch", i);
}
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], raw_data, "raw data after transition was lost!");
}
#[tokio::test]
async fn test_read_transition_with_coalesced_data() {
let mut data = Vec::new();
for i in 0..7 {
let payload = format!("f{}", i);
data.extend(encode_test_frame(payload.as_bytes(), 3));
}
let first_7_frames = data.clone();
data.clear();
data.extend(encode_test_frame(b"f7", 3));
data.extend_from_slice(b"RAWDATA");
let mock = MockStream::new(vec![first_7_frames, data]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
for i in 0..8 {
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
let expected = format!("f{}", i);
assert_eq!(&buf[..n], expected.as_bytes());
}
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], b"RAWDATA", "coalesced raw data was lost!");
}
#[tokio::test]
async fn test_read_transition_after_pure_padding_frame() {
let mut data = Vec::new();
for i in 0..7 {
let payload = format!("f{}", i);
data.extend(encode_test_frame(payload.as_bytes(), 3));
}
data.extend(encode_test_frame(b"", 50));
data.extend_from_slice(b"RAWDATA");
let mock = MockStream::from_data(data);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
for i in 0..7 {
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
let expected = format!("f{}", i);
assert_eq!(&buf[..n], expected.as_bytes(), "frame {} mismatch", i);
}
let mut result = Vec::new();
loop {
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
if n == 0 {
break;
}
result.extend_from_slice(&buf[..n]);
if result == b"RAWDATA" {
break;
}
}
assert_eq!(
result, b"RAWDATA",
"raw data after pure padding frame 8 was corrupted!"
);
}
#[tokio::test]
async fn test_write_adds_padding_header() {
let mock = MockStream::new(vec![]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
let payload = b"test write";
stream.write_all(payload).await.unwrap();
stream.flush().await.unwrap();
let written = &stream.inner.written;
assert!(written.len() >= 3 + payload.len());
let payload_len = u16::from_be_bytes([written[0], written[1]]) as usize;
let padding_len = written[2] as usize;
assert_eq!(payload_len, payload.len());
assert_eq!(&written[3..3 + payload_len], payload);
assert_eq!(written.len(), 3 + payload_len + padding_len);
}
#[tokio::test]
async fn test_write_transition_to_raw_mode() {
let mock = MockStream::new(vec![]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
for i in 0..NUM_FIRST_PADDINGS {
let payload = format!("w{}", i);
stream.write_all(payload.as_bytes()).await.unwrap();
}
let padded_end = stream.inner.written.len();
let raw_payload = b"raw write data";
stream.write_all(raw_payload).await.unwrap();
let raw_written = &stream.inner.written[padded_end..];
assert_eq!(raw_written, raw_payload);
}
#[tokio::test]
async fn test_write_frame_count_tracking() {
let mock = MockStream::new(vec![]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
assert_eq!(stream.num_written_frames, 0);
for i in 0..5 {
stream.write_all(b"x").await.unwrap();
assert_eq!(stream.num_written_frames, i + 1);
}
}
#[tokio::test]
async fn test_server_padding_bias_for_small_payloads() {
let mock = MockStream::new(vec![]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Server, PaddingType::Variant1);
let small_payload = b"tiny";
stream.write_all(small_payload).await.unwrap();
let written = &stream.inner.written;
let padding_len = written[2] as usize;
assert!(
padding_len >= (MAX_PADDING_SIZE as usize).saturating_sub(small_payload.len()),
"server padding for small payload should be biased high, got {}",
padding_len
);
}
#[tokio::test]
async fn test_padding_type_none_passthrough() {
let test_data = b"passthrough data";
let mock = MockStream::from_data(test_data.to_vec());
let mut stream = NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::None);
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(&buf[..n], test_data);
}
#[tokio::test]
async fn test_read_eof_handling() {
let mock = MockStream::new(vec![]);
let mut stream =
NaivePaddingStream::new(mock, PaddingDirection::Client, PaddingType::Variant1);
let mut buf = vec![0u8; 100];
let n = stream.read(&mut buf).await.unwrap();
assert_eq!(n, 0);
}
}