use super::*;
const EINVAL: i32 = 22;
struct RefusingStream {
ready: Vec<u8>,
refusing: bool,
refuse_once_drained: bool,
}
impl RefusingStream {
const fn after_the_answer(ready: Vec<u8>) -> Self {
Self {
ready,
refusing: false,
refuse_once_drained: true,
}
}
const fn from_the_start(ready: Vec<u8>) -> Self {
Self {
ready,
refusing: true,
refuse_once_drained: false,
}
}
fn window(&self) -> io::Result<()> {
if self.refusing {
return Err(io::Error::from_raw_os_error(EINVAL));
}
Ok(())
}
}
impl FrameStream for RefusingStream {
fn read_bytes(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let take = self.ready.len().min(buf.len());
let Some(target) = buf.get_mut(..take) else {
return Ok(0);
};
target.copy_from_slice(self.ready.drain(..take).as_slice());
if self.ready.is_empty() && self.refuse_once_drained {
self.refusing = true;
}
Ok(take)
}
fn write_all_bytes(&mut self, _bytes: &[u8]) -> io::Result<()> {
Ok(())
}
fn flush_bytes(&mut self) -> io::Result<()> {
Ok(())
}
fn set_read_deadline(&mut self, _timeout: Duration) -> io::Result<()> {
self.window()
}
fn clear_read_deadline(&mut self) -> io::Result<()> {
self.window()
}
}
fn over(stream: RefusingStream) -> Connection<RefusingStream> {
Connection {
#[cfg(unix)]
read_wake: None,
stream,
buffer: Vec::new(),
open_conversations: BTreeSet::new(),
pump_fences_pending: 0,
pump_diagnostics: ParticipantPumpDiagnostics::default(),
negotiated_version: None,
}
}
fn bytes_of(frame: &Frame) -> Result<Vec<u8>, SdkError> {
let len = encoded_len(frame).map_err(|error| protocol_error(&error))?;
let mut bytes = vec![0_u8; len];
let written = encode(frame, &mut bytes).map_err(|error| protocol_error(&error))?;
bytes.truncate(written);
Ok(bytes)
}
const fn answer() -> Frame {
Frame::ConnectAck {
flags: 0,
selected_version: CLIENT_MAX_VERSION,
capabilities: 0,
}
}
#[test]
fn a_reply_owed_read_keeps_its_answer_when_the_window_is_refused_after_it() -> Result<(), SdkError>
{
let mut connection = over(RefusingStream::after_the_answer(bytes_of(&answer())?));
let replied = connection.round_trip(&Frame::Ping { flags: 0 })?;
assert_eq!(replied, answer(), "the answer read before the reset");
Ok(())
}
#[test]
fn a_bounded_read_keeps_its_answer_when_the_window_is_refused_after_it() -> Result<(), SdkError> {
let mut connection = over(RefusingStream::after_the_answer(bytes_of(&answer())?));
let replied = connection.receive_optional_within(Duration::from_secs(30))?;
assert_eq!(replied, Some(answer()), "the answer read before the reset");
Ok(())
}
#[test]
fn a_conversation_drain_keeps_its_answer_when_the_window_is_refused_after_it()
-> Result<(), SdkError> {
let mut connection = over(RefusingStream::after_the_answer(bytes_of(&answer())?));
let replied = connection.receive_with_timeout(CONVERSATION_DRAIN_TIMEOUT)?;
assert_eq!(replied, Some(answer()), "the answer read before the reset");
Ok(())
}
#[test]
fn a_pump_fence_keeps_its_quiet_answer_when_the_window_is_refused_after_it() -> Result<(), SdkError>
{
let mut connection = over(RefusingStream::after_the_answer(bytes_of(&Frame::Pong {
flags: 0,
})?));
let drained = connection.try_receive_participant()?;
assert!(
drained.is_none(),
"the fence's Pong was read before the reset, so the drain is quiet: {drained:?}"
);
Ok(())
}
#[test]
fn a_reply_owed_read_keeps_a_buffered_answer_when_the_window_is_refused_before_it()
-> Result<(), SdkError> {
let mut connection = over(RefusingStream::from_the_start(bytes_of(&answer())?));
let replied = connection.round_trip(&Frame::Ping { flags: 0 })?;
assert_eq!(replied, answer(), "the answer buffered before the reset");
Ok(())
}
#[test]
fn a_read_that_finds_the_end_names_it_and_the_refused_window() -> Result<(), SdkError> {
let mut connection = over(RefusingStream::from_the_start(Vec::new()));
let outcome = connection.round_trip(&Frame::Ping { flags: 0 });
let Err(SdkError::Connection { description }) = outcome else {
return Err(SdkError::Protocol {
description: format!(
"a peer that is gone must fail the read as a lost connection, got {outcome:?}"
),
});
};
assert!(
description.contains("closed the connection"),
"the read's own end of file is named: {description}"
);
assert!(
description.contains("os error 22"),
"the refused window is named beside it: {description}"
);
Ok(())
}