#![allow(dead_code)]
use crate::quiche::{self, ConnectionError, Shutdown};
pub(crate) type QResult<T> = std::result::Result<T, quiche::Error>;
pub(crate) trait QuicConn {
fn stream_recv(&mut self, id: u64, out: &mut [u8]) -> QResult<(usize, bool)>;
fn stream_send(&mut self, id: u64, buf: &[u8], fin: bool) -> QResult<usize>;
fn stream_shutdown(&mut self, id: u64, direction: Shutdown, err: u64) -> QResult<()>;
fn stream_readable_next(&mut self) -> Option<u64>;
fn stream_writable_next(&mut self) -> Option<u64>;
fn stream_writable(&mut self, id: u64, len: usize) -> QResult<bool>;
fn stream_readable(&self, id: u64) -> bool;
fn stream_finished(&self, id: u64) -> bool;
fn stream_capacity(&mut self, id: u64) -> QResult<usize>;
fn stream_priority(&mut self, id: u64, urgency: u8, incremental: bool) -> QResult<()>;
fn peer_streams_left_bidi(&self) -> u64;
fn peer_streams_left_uni(&self) -> u64;
fn close(&mut self, app: bool, err: u64, reason: &[u8]) -> QResult<()>;
fn peer_error(&self) -> Option<&ConnectionError>;
fn local_error(&self) -> Option<&ConnectionError>;
fn is_timed_out(&self) -> bool;
}
impl QuicConn for tokio_quiche::quic::QuicheConnection {
#[inline]
fn stream_recv(&mut self, id: u64, out: &mut [u8]) -> QResult<(usize, bool)> {
self.stream_recv(id, out)
}
#[inline]
fn stream_send(&mut self, id: u64, buf: &[u8], fin: bool) -> QResult<usize> {
self.stream_send(id, buf, fin)
}
#[inline]
fn stream_shutdown(&mut self, id: u64, direction: Shutdown, err: u64) -> QResult<()> {
self.stream_shutdown(id, direction, err)
}
#[inline]
fn stream_readable_next(&mut self) -> Option<u64> {
self.stream_readable_next()
}
#[inline]
fn stream_writable_next(&mut self) -> Option<u64> {
self.stream_writable_next()
}
#[inline]
fn stream_writable(&mut self, id: u64, len: usize) -> QResult<bool> {
self.stream_writable(id, len)
}
#[inline]
fn stream_readable(&self, id: u64) -> bool {
self.stream_readable(id)
}
#[inline]
fn stream_finished(&self, id: u64) -> bool {
self.stream_finished(id)
}
#[inline]
fn stream_capacity(&mut self, id: u64) -> QResult<usize> {
self.stream_capacity(id)
}
#[inline]
fn stream_priority(&mut self, id: u64, urgency: u8, incremental: bool) -> QResult<()> {
self.stream_priority(id, urgency, incremental)
}
#[inline]
fn peer_streams_left_bidi(&self) -> u64 {
self.peer_streams_left_bidi()
}
#[inline]
fn peer_streams_left_uni(&self) -> u64 {
self.peer_streams_left_uni()
}
#[inline]
fn close(&mut self, app: bool, err: u64, reason: &[u8]) -> QResult<()> {
self.close(app, err, reason)
}
#[inline]
fn peer_error(&self) -> Option<&ConnectionError> {
self.peer_error()
}
#[inline]
fn local_error(&self) -> Option<&ConnectionError> {
self.local_error()
}
#[inline]
fn is_timed_out(&self) -> bool {
self.is_timed_out()
}
}
#[cfg(test)]
pub(crate) mod mock {
use std::collections::{HashMap, HashSet, VecDeque};
use super::*;
#[derive(Clone, Debug)]
pub(crate) enum RecvStep {
Data { bytes: Vec<u8>, fin: bool },
Err(quiche::Error),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct ShutdownCall {
pub id: u64,
pub is_write: bool,
pub code: u64,
}
#[derive(Default)]
pub(crate) struct MockConn {
pub readable_next: VecDeque<u64>,
pub writable_next: VecDeque<u64>,
pub readable_ids: HashSet<u64>,
pub finished_ids: HashSet<u64>,
pub recv_script: HashMap<u64, VecDeque<RecvStep>>,
pub recv_calls: Vec<u64>,
pub send_capacity: HashMap<u64, usize>,
pub send_errors: HashMap<u64, VecDeque<quiche::Error>>,
pub sent: Vec<(u64, Vec<u8>, bool)>,
pub capacity: HashMap<u64, QResult<usize>>,
pub writable_results: HashMap<u64, QResult<bool>>,
pub rearms: Vec<(u64, usize)>,
pub priority_errors: HashMap<u64, VecDeque<quiche::Error>>,
pub priorities: Vec<(u64, u8, bool)>,
pub shutdown_errors: HashMap<u64, VecDeque<quiche::Error>>,
pub shutdowns: Vec<ShutdownCall>,
pub streams_left_bidi: u64,
pub streams_left_uni: u64,
pub closed: Option<(bool, u64, Vec<u8>)>,
pub close_result: Option<quiche::Error>,
pub peer_error: Option<ConnectionError>,
pub local_error: Option<ConnectionError>,
pub timed_out: bool,
}
impl MockConn {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn script_recv(&mut self, id: u64, steps: impl IntoIterator<Item = RecvStep>) {
self.recv_script.entry(id).or_default().extend(steps);
self.readable_ids.insert(id);
}
pub(crate) fn queue_readable(&mut self, ids: impl IntoIterator<Item = u64>) {
self.readable_next.extend(ids);
}
}
impl QuicConn for MockConn {
fn stream_recv(&mut self, id: u64, out: &mut [u8]) -> QResult<(usize, bool)> {
self.recv_calls.push(id);
let q = self
.recv_script
.get_mut(&id)
.and_then(|q| q.pop_front())
.unwrap_or(RecvStep::Err(quiche::Error::Done));
match q {
RecvStep::Data { bytes, fin } => {
let n = bytes.len().min(out.len());
out[..n].copy_from_slice(&bytes[..n]);
if n < bytes.len() {
self.recv_script
.entry(id)
.or_default()
.push_front(RecvStep::Data {
bytes: bytes[n..].to_vec(),
fin,
});
return Ok((n, false));
}
if self
.recv_script
.get(&id)
.map(|q| q.is_empty())
.unwrap_or(true)
{
self.readable_ids.remove(&id);
}
Ok((n, fin))
}
RecvStep::Err(e) => {
self.readable_ids.remove(&id);
Err(e)
}
}
}
fn stream_send(&mut self, id: u64, buf: &[u8], fin: bool) -> QResult<usize> {
if let Some(e) = self.send_errors.get_mut(&id).and_then(|q| q.pop_front()) {
return Err(e);
}
let accept = self
.send_capacity
.get(&id)
.copied()
.unwrap_or(buf.len())
.min(buf.len());
self.sent
.push((id, buf[..accept].to_vec(), fin && accept == buf.len()));
Ok(accept)
}
fn stream_shutdown(&mut self, id: u64, direction: Shutdown, err: u64) -> QResult<()> {
if let Some(e) = self
.shutdown_errors
.get_mut(&id)
.and_then(|q| q.pop_front())
{
return Err(e);
}
self.shutdowns.push(ShutdownCall {
id,
is_write: direction == Shutdown::Write,
code: err,
});
Ok(())
}
fn stream_readable_next(&mut self) -> Option<u64> {
self.readable_next.pop_front()
}
fn stream_writable_next(&mut self) -> Option<u64> {
self.writable_next.pop_front()
}
fn stream_writable(&mut self, id: u64, len: usize) -> QResult<bool> {
self.rearms.push((id, len));
match self.writable_results.get(&id) {
Some(Ok(v)) => Ok(*v),
Some(Err(e)) => Err(*e),
None => Ok(false),
}
}
fn stream_readable(&self, id: u64) -> bool {
self.readable_ids.contains(&id)
}
fn stream_finished(&self, id: u64) -> bool {
self.finished_ids.contains(&id)
}
fn stream_capacity(&mut self, id: u64) -> QResult<usize> {
match self.capacity.get(&id) {
Some(Ok(v)) => Ok(*v),
Some(Err(e)) => Err(*e),
None => Ok(usize::MAX),
}
}
fn stream_priority(&mut self, id: u64, urgency: u8, incremental: bool) -> QResult<()> {
if let Some(e) = self
.priority_errors
.get_mut(&id)
.and_then(|q| q.pop_front())
{
return Err(e);
}
self.priorities.push((id, urgency, incremental));
Ok(())
}
fn peer_streams_left_bidi(&self) -> u64 {
self.streams_left_bidi
}
fn peer_streams_left_uni(&self) -> u64 {
self.streams_left_uni
}
fn close(&mut self, app: bool, err: u64, reason: &[u8]) -> QResult<()> {
if let Some(e) = self.close_result {
return Err(e);
}
self.closed = Some((app, err, reason.to_vec()));
Ok(())
}
fn peer_error(&self) -> Option<&ConnectionError> {
self.peer_error.as_ref()
}
fn local_error(&self) -> Option<&ConnectionError> {
self.local_error.as_ref()
}
fn is_timed_out(&self) -> bool {
self.timed_out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mock_recv_delivers_then_done() {
let mut c = MockConn::new();
c.script_recv(
4,
[RecvStep::Data {
bytes: b"abc".to_vec(),
fin: true,
}],
);
assert!(c.stream_readable(4));
let mut out = [0u8; 16];
assert_eq!(c.stream_recv(4, &mut out).unwrap(), (3, true));
assert_eq!(&out[..3], b"abc");
assert!(!c.stream_readable(4));
assert!(matches!(
c.stream_recv(4, &mut out),
Err(quiche::Error::Done)
));
}
#[test]
fn mock_recv_truncates_to_out_len() {
let mut c = MockConn::new();
c.script_recv(
0,
[RecvStep::Data {
bytes: b"abcdef".to_vec(),
fin: true,
}],
);
let mut out = [0u8; 4];
assert_eq!(c.stream_recv(0, &mut out).unwrap(), (4, false));
assert_eq!(&out, b"abcd");
let mut out2 = [0u8; 4];
assert_eq!(c.stream_recv(0, &mut out2).unwrap(), (2, true));
assert_eq!(&out2[..2], b"ef");
}
#[test]
fn mock_send_partial_and_record() {
let mut c = MockConn::new();
c.send_capacity.insert(8, 2);
assert_eq!(c.stream_send(8, b"hello", false).unwrap(), 2);
assert_eq!(c.sent, vec![(8, b"he".to_vec(), false)]);
}
#[test]
fn mock_readable_next_is_destructive() {
let mut c = MockConn::new();
c.queue_readable([4, 8]);
assert_eq!(c.stream_readable_next(), Some(4));
assert_eq!(c.stream_readable_next(), Some(8));
assert_eq!(c.stream_readable_next(), None);
}
}
}