#![allow(clippy::items_after_statements)]
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
use std::time::Duration;
use futures_core::Stream;
use futures_sink::Sink;
use tokio_util::bytes::{Bytes, BytesMut};
use tokio_util::codec::LengthDelimitedCodec;
use slither::constants::{INITIAL_MAX_STREAM_DATA, MAX_PLAINTEXT};
use slither::testutil::{Pair, local, settle};
const PATIENCE: Duration = Duration::from_secs(20);
const NOT_BEFORE: Duration = Duration::from_millis(200);
async fn within<F: Future>(fut: F, what: &str) -> F::Output {
match tokio::time::timeout(PATIENCE, fut).await {
Ok(v) => v,
Err(_) => panic!("{what}: still pending after {PATIENCE:?} of virtual time"),
}
}
async fn next<S>(stream: &mut S) -> Option<S::Item>
where
S: Stream + Unpin,
{
std::future::poll_fn(|cx| Pin::new(&mut *stream).poll_next(cx)).await
}
fn poll_next_once<S>(stream: &mut S) -> Poll<Option<S::Item>>
where
S: Stream + Unpin,
{
let mut cx = Context::from_waker(Waker::noop());
Pin::new(stream).poll_next(&mut cx)
}
async fn sink_send<S>(sink: &mut S, item: Bytes) -> Result<(), S::Error>
where
S: Sink<Bytes> + Unpin,
{
std::future::poll_fn(|cx| Pin::new(&mut *sink).poll_ready(cx)).await?;
Pin::new(&mut *sink).start_send(item)?;
std::future::poll_fn(|cx| Pin::new(&mut *sink).poll_flush(cx)).await
}
async fn sink_close<S>(sink: &mut S) -> Result<(), S::Error>
where
S: Sink<Bytes> + Unpin,
{
std::future::poll_fn(|cx| Pin::new(&mut *sink).poll_close(cx)).await
}
fn frame(i: usize, len: usize) -> Bytes {
Bytes::from(
(0..len)
.map(|j| ((i * 31 + j) % 251) as u8)
.collect::<Vec<u8>>(),
)
}
fn frame_sizes() -> Vec<usize> {
vec![
0,
1,
MAX_PLAINTEXT - 1,
MAX_PLAINTEXT,
MAX_PLAINTEXT + 1,
4096,
]
}
#[tokio::test(start_paused = true)]
async fn s32_framed_length_delimited_round_trips_objects_in_order() {
local(async {
let pair = Pair::seeded(0x5320_0001);
let (ca, cb) = pair.establish().await;
let sizes = frame_sizes();
let want: Vec<Bytes> = sizes
.iter()
.copied()
.enumerate()
.map(|(i, n)| frame(i, n))
.collect();
let send_side = {
let want = want.clone();
async move {
let mut framed = within(ca.framed_bi(LengthDelimitedCodec::new()), "framed_bi")
.await
.expect("framed_bi");
for (i, item) in want.iter().enumerate() {
within(sink_send(&mut framed, item.clone()), "sink send")
.await
.unwrap_or_else(|e| panic!("frame {i} failed to send: {e:?}"));
}
within(sink_close(&mut framed), "sink close")
.await
.expect("close");
}
};
let recv_side = async move {
let mut framed = within(
cb.accept_framed_bi(LengthDelimitedCodec::new()),
"accept_framed_bi",
)
.await
.expect("accept_framed_bi");
let mut got: Vec<BytesMut> = Vec::new();
for i in 0..sizes.len() {
let item = within(next(&mut framed), "next frame")
.await
.unwrap_or_else(|| {
panic!(
"the stream ended after {i} frames — `Framed` ends at \
EOF, so this is an `AsyncRead` that reported EOF \
before the sender finished"
)
})
.unwrap_or_else(|e| panic!("frame {i} decoded as an error: {e:?}"));
got.push(item);
}
assert!(
within(next(&mut framed), "end of framed stream")
.await
.is_none(),
"`Framed` ends at the peer's clean EOF"
);
got
};
let ((), got) = tokio::join!(send_side, recv_side);
assert_eq!(
got.len(),
want.len(),
"S32: {} frames in, {} out",
want.len(),
got.len()
);
for (i, (g, w)) in got.iter().zip(want.iter()).enumerate() {
assert_eq!(
g.len(),
w.len(),
"frame {i}: length differs — a coalescing build merges two \
frames, a splitting one halves them"
);
assert_eq!(
&g[..],
&w[..],
"frame {i}: content differs. The index is mixed into every \
byte, so this is a reorder, not merely a corruption"
);
}
})
.await;
}
#[tokio::test(start_paused = true)]
async fn s32_a_consumer_that_polls_once_claims_exactly_one_item() {
local(async {
let pair = Pair::seeded(0x5320_0002);
let (ca, cb) = pair.establish().await;
let sent: Vec<Vec<u8>> = (0..3usize).map(|i| frame(i, 64 + i).to_vec()).collect();
for (i, m) in sent.iter().enumerate() {
within(cb.send_message(m), "send_message")
.await
.unwrap_or_else(|e| panic!("message {i}: {e:?}"));
}
settle().await;
let mut claimed: Vec<Vec<u8>> = Vec::new();
{
let mut msgs = ca.messages();
match poll_next_once(&mut msgs) {
Poll::Ready(Some(Ok(m))) => claimed.push(m),
other => panic!(
"one poll of `messages()` with three messages waiting must \
yield exactly one item, got {other:?}"
),
}
}
for i in 0..2 {
let m = within(
ca.recv_message(),
"recv_message after the adapter was dropped",
)
.await
.unwrap_or_else(|e| {
panic!(
"message {} is gone: {e:?}. Ruling 58 — the adapter \
claimed ahead of its consumer and took it into an \
intermediate queue that died with the adapter",
i + 1
)
});
claimed.push(m);
}
assert_eq!(
claimed.len(),
3,
"three messages were sent and three must be recoverable"
);
let mut got = claimed.clone();
let mut want = sent.clone();
got.sort();
want.sort();
assert_eq!(
got, want,
"every message is accounted for exactly once — no duplicate \
delivery (an adapter that claimed and also left the item) and no \
loss (an adapter that claimed and dropped it)"
);
})
.await;
}
#[tokio::test(start_paused = true)]
async fn s32_sink_backpressure_maps_onto_flow_control_credit() {
local(async {
let pair = Pair::seeded(0x5320_0003);
let (ca, cb) = pair.establish().await;
const FRAME: usize = 8 * 1024;
const OFFERED: usize = 96;
let mut writer = within(ca.framed_bi(LengthDelimitedCodec::new()), "framed_bi")
.await
.expect("framed_bi");
within(sink_send(&mut writer, frame(0, FRAME)), "first send")
.await
.expect("first send");
settle().await;
let mut reader = within(
cb.accept_framed_bi(LengthDelimitedCodec::new()),
"accept_framed_bi",
)
.await
.expect("accept_framed_bi");
let mut accepted = 1usize;
let mut stalled = false;
for i in 1..OFFERED {
match tokio::time::timeout(NOT_BEFORE, sink_send(&mut writer, frame(i, FRAME))).await {
Ok(Ok(())) => accepted += 1,
Ok(Err(e)) => panic!("send {i} failed with {e:?} rather than parking"),
Err(_) => {
stalled = true;
break;
}
}
}
assert!(
stalled,
"S32: the sink accepted all {OFFERED} frames ({} bytes) without \
ever parking. Flow control cannot admit that much unread data, \
so this build is buffering inside `compat/` — the intermediate \
buffer §10.6 forbids and `CONTRACT-8.md` §9 item 1 names",
OFFERED * FRAME
);
let accepted_bytes = (accepted * FRAME) as u64;
let bound = INITIAL_MAX_STREAM_DATA + 128 * 1024;
assert!(
accepted_bytes <= bound,
"S32: {accepted_bytes} bytes were accepted against an \
`INITIAL_MAX_STREAM_DATA` of {INITIAL_MAX_STREAM_DATA}. It parked \
eventually, but not on *this* credit — something else is holding \
the excess"
);
let to_drain = (accepted / 2).clamp(1, 16);
for i in 0..to_drain {
within(next(&mut reader), "drain")
.await
.unwrap_or_else(|| panic!("the framed stream ended while draining frame {i}"))
.unwrap_or_else(|e| panic!("drain {i} failed: {e:?}"));
}
within(
sink_send(&mut writer, frame(OFFERED, FRAME)),
"send after the reader drained",
)
.await
.expect(
"the writer resumes once credit is re-granted — a build that parks \
the writer without waking it on a re-grant deadlocks here",
);
})
.await;
}
#[tokio::test(start_paused = true)]
async fn s32_framed_bi_opens_exactly_one_bi_stream() {
local(async {
let pair = Pair::seeded(0x5320_0004);
let (ca, cb) = pair.establish().await;
let mut writer = within(ca.framed_bi(LengthDelimitedCodec::new()), "framed_bi")
.await
.expect("framed_bi");
within(sink_send(&mut writer, frame(0, 128)), "send")
.await
.expect("send");
settle().await;
let mut accepted = within(cb.accept_bi(), "accept_bi")
.await
.expect("accept_bi");
assert!(
tokio::time::timeout(NOT_BEFORE, cb.accept_bi())
.await
.is_err(),
"`framed_bi` opens **one** bi stream (`CONTRACT-8.md` §5: \
\"identical to `Framed::new(conn.open_bi().await?, codec)`\"). A \
second one arriving means the constructor opened a stream per \
direction"
);
assert!(
tokio::time::timeout(NOT_BEFORE, cb.accept_uni())
.await
.is_err(),
"`framed_bi` opens no uni stream. One appearing here would also \
collide with the message verb, which is S30's hazard"
);
let mut got = Vec::new();
let mut buf = [0u8; 512];
while got.len() < 4 + 128 {
let n = within(
tokio::io::AsyncReadExt::read(&mut accepted, &mut buf),
"read the framed bytes",
)
.await
.expect("read");
assert!(n > 0, "the stream ended before the first frame was whole");
got.extend_from_slice(&buf[..n]);
}
assert_eq!(
u32::from_be_bytes([got[0], got[1], got[2], got[3]]),
128,
"`LengthDelimitedCodec`'s default is a 4-byte big-endian length \
prefix; a different value here means the constructor is not the \
plain `Framed::new` §5 says it is"
);
assert_eq!(
&got[4..4 + 128],
&frame(0, 128)[..],
"the payload behind the prefix is the frame that was sent"
);
})
.await;
}