#![allow(clippy::items_after_statements)]
#![allow(clippy::too_many_lines)]
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::num::NonZeroU64;
use std::time::Instant;
use super::*;
use super::stream_id::Dir;
use super::streams::StreamRef;
use crate::constants::{
DATA_HEADER_LEN, FRAME_CLOSE, FRAME_MAX_DATA, FRAME_MAX_STREAM_DATA, FRAME_MAX_STREAMS_BIDI,
FRAME_MAX_STREAMS_UNI, FRAME_PADDING, FRAME_PING, FRAME_RESET_STREAM, FRAME_STREAM_BASE,
MAX_PLAINTEXT, PKT_DATA, PROLOGUE, REKEY_EPOCH_MSGS, STREAM_FIN, STREAM_LEN, STREAM_OFF,
VERSION,
};
use crate::core::{EstablishedSession, Install, Role, Transmit};
use crate::error::ConnectionLost;
use crate::identity::Identity;
use crate::packet::{Handshake, ReferenceSuite};
use crate::testutil::CountingIdentity;
use crate::varint::{self, VarInt};
type Suite = ReferenceSuite;
type Id = CountingIdentity<Suite>;
pub(crate) fn abandon_recv(conn: &mut Connection<Suite>, now: Instant, r: StreamRef) {
conn.abandon_recv(now, r);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Wire {
Padding,
Ping,
Close {
code: u64,
reason: Vec<u8>,
},
Reset {
id: u64,
code: u64,
final_size: u64,
},
Stream {
id: u64,
offset: u64,
data: Vec<u8>,
fin: bool,
had_len: bool,
},
MaxData(u64),
MaxStreamData {
id: u64,
max: u64,
},
MaxStreamsBidi(u64),
MaxStreamsUni(u64),
Datagram {
data: Vec<u8>,
had_len: bool,
},
Ack {
largest: u64,
ack_delay: u64,
ranges: Vec<(u64, u64)>,
first_range: u64,
},
PathChallenge([u8; 8]),
PathResponse([u8; 8]),
}
pub(crate) const WIRE_PATH_CHALLENGE: u64 = 0x1a;
pub(crate) const WIRE_PATH_RESPONSE: u64 = 0x1b;
pub(crate) fn take_varint(buf: &[u8], at: &mut usize) -> u64 {
let (v, n) = varint::decode(&buf[*at..]).expect("a complete varint");
*at += n;
u64::from(v)
}
pub(crate) fn parse_frames(pt: &[u8]) -> Vec<Wire> {
let mut out = Vec::new();
let mut at = 0usize;
while at < pt.len() {
let ty = take_varint(pt, &mut at);
match ty {
t if t == FRAME_PADDING => out.push(Wire::Padding),
t if t == FRAME_PING => out.push(Wire::Ping),
t if t == FRAME_CLOSE => {
let code = take_varint(pt, &mut at);
let len = take_varint(pt, &mut at) as usize;
let reason = pt[at..at + len].to_vec();
at += len;
out.push(Wire::Close { code, reason });
}
t if t == FRAME_RESET_STREAM => {
let id = take_varint(pt, &mut at);
let code = take_varint(pt, &mut at);
let final_size = take_varint(pt, &mut at);
out.push(Wire::Reset {
id,
code,
final_size,
});
}
t if (FRAME_STREAM_BASE..=crate::constants::FRAME_STREAM_MAX).contains(&t) => {
let id = take_varint(pt, &mut at);
let offset = if t & STREAM_OFF != 0 {
take_varint(pt, &mut at)
} else {
0
};
let had_len = t & STREAM_LEN != 0;
let len = if had_len {
take_varint(pt, &mut at) as usize
} else {
pt.len() - at
};
let data = pt[at..at + len].to_vec();
at += len;
out.push(Wire::Stream {
id,
offset,
data,
fin: t & STREAM_FIN != 0,
had_len,
});
}
t if t == FRAME_MAX_DATA => out.push(Wire::MaxData(take_varint(pt, &mut at))),
t if t == FRAME_MAX_STREAM_DATA => {
let id = take_varint(pt, &mut at);
let max = take_varint(pt, &mut at);
out.push(Wire::MaxStreamData { id, max });
}
t if t == FRAME_MAX_STREAMS_BIDI => {
out.push(Wire::MaxStreamsBidi(take_varint(pt, &mut at)));
}
t if t == FRAME_MAX_STREAMS_UNI => {
out.push(Wire::MaxStreamsUni(take_varint(pt, &mut at)));
}
t if t == crate::constants::FRAME_DATAGRAM
|| t == crate::constants::FRAME_DATAGRAM_LEN =>
{
let had_len = t == crate::constants::FRAME_DATAGRAM_LEN;
let len = if had_len {
take_varint(pt, &mut at) as usize
} else {
pt.len() - at
};
let data = pt[at..at + len].to_vec();
at += len;
out.push(Wire::Datagram { data, had_len });
}
t if t == crate::constants::FRAME_ACK => {
let largest = take_varint(pt, &mut at);
let ack_delay = take_varint(pt, &mut at);
let range_count = take_varint(pt, &mut at);
let first_range = take_varint(pt, &mut at);
let mut ranges = Vec::with_capacity(range_count as usize);
for _ in 0..range_count {
let gap = take_varint(pt, &mut at);
let len = take_varint(pt, &mut at);
ranges.push((gap, len));
}
out.push(Wire::Ack {
largest,
ack_delay,
ranges,
first_range,
});
}
t if t == WIRE_PATH_CHALLENGE || t == WIRE_PATH_RESPONSE => {
assert!(
pt.len() - at >= 8,
"§8.4: a path frame carries exactly 8 opaque bytes; the \
core emitted {} — that is §8.2's structural class and \
the peer would CLOSE on it",
pt.len() - at,
);
let mut v = [0u8; 8];
v.copy_from_slice(&pt[at..at + 8]);
at += 8;
if t == WIRE_PATH_CHALLENGE {
out.push(Wire::PathChallenge(v));
} else {
out.push(Wire::PathResponse(v));
}
}
other => panic!(
"the core emitted frame type {other:#x}, which §8.3 does not \
place in any slice built so far — if this is a frame a new \
slice legitimately emits, this decoder has aged out and the \
arm belongs here (working rule 15), not in the caller"
),
}
}
out
}
pub(crate) fn put(out: &mut Vec<u8>, v: u64) {
varint::encode(VarInt::new(v).expect("fits the 62-bit space"), out);
}
pub(crate) fn stream_frame(id: u64, offset: u64, data: &[u8], fin: bool) -> Vec<u8> {
let mut f = Vec::new();
let mut ty = FRAME_STREAM_BASE | STREAM_OFF | STREAM_LEN;
if fin {
ty |= STREAM_FIN;
}
put(&mut f, ty);
put(&mut f, id);
put(&mut f, offset);
put(&mut f, data.len() as u64);
f.extend_from_slice(data);
f
}
pub(crate) fn reset_frame(id: u64, code: u64, final_size: u64) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, FRAME_RESET_STREAM);
put(&mut f, id);
put(&mut f, code);
put(&mut f, final_size);
f
}
pub(crate) fn max_stream_data_frame(id: u64, max: u64) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, FRAME_MAX_STREAM_DATA);
put(&mut f, id);
put(&mut f, max);
f
}
pub(crate) fn max_data_frame(max: u64) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, FRAME_MAX_DATA);
put(&mut f, max);
f
}
pub(crate) fn path_challenge_frame(v: [u8; 8]) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, WIRE_PATH_CHALLENGE);
f.extend_from_slice(&v);
f
}
pub(crate) fn path_response_frame(v: [u8; 8]) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, WIRE_PATH_RESPONSE);
f.extend_from_slice(&v);
f
}
pub(crate) fn path_frame_with_body(ty: u64, body: &[u8]) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, ty);
f.extend_from_slice(body);
f
}
pub(crate) fn max_streams_uni_frame(max: u64) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, FRAME_MAX_STREAMS_UNI);
put(&mut f, max);
f
}
pub(crate) fn max_streams_bidi_frame(max: u64) -> Vec<u8> {
let mut f = Vec::new();
put(&mut f, FRAME_MAX_STREAMS_BIDI);
put(&mut f, max);
f
}
pub(crate) fn raw_id(index: u64, dir: Dir, opened_by_initiator: bool) -> u64 {
let dir_bit = match dir {
Dir::Bi => 0,
Dir::Uni => 0x02,
};
let opener_bit = u64::from(!opened_by_initiator);
(index << 2) | dir_bit | opener_bit
}
pub(crate) const A_INDEX: u32 = 0x1111_1111;
pub(crate) const B_INDEX: u32 = 0x2222_2222;
pub(crate) fn v4(a: u8, port: u16) -> SocketAddr {
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, a)), port)
}
pub(crate) fn a_addr() -> SocketAddr {
v4(1, 1)
}
pub(crate) fn b_addr() -> SocketAddr {
v4(2, 2)
}
pub(crate) fn t0() -> Instant {
Instant::now()
}
pub(crate) fn handshake_pair() -> (EstablishedSession<Suite>, EstablishedSession<Suite>) {
let epoch = NonZeroU64::new(REKEY_EPOCH_MSGS).expect("REKEY_EPOCH_MSGS is nonzero");
let a: Id = CountingIdentity::seeded([7u8; 32]);
let b: Id = CountingIdentity::seeded([9u8; 32]);
let b_pub = *b.public_static();
let (ap, ask) = a.open().expect("identity opens");
let (bp, bsk) = b.open().expect("identity opens");
let init = <Suite as Handshake>::initiator(ap, PROLOGUE, b_pub);
let (msg1, sent) =
<Suite as Handshake>::write_msg1(init, ask, &[0u8; crate::constants::MSG1_PAYLOAD_LEN])
.expect("msg1");
let resp = <Suite as Handshake>::responder(bp, PROLOGUE, bsk).expect("responder");
let (_claimed, mid) = <Suite as Handshake>::read_msg1_intro(resp, &msg1).expect("msg1 intro");
let (_payload, read) = <Suite as Handshake>::complete(mid).expect("complete");
let (msg2, b_transport) = <Suite as Handshake>::write_msg2(read).expect("msg2");
let a_transport = <Suite as Handshake>::read_msg2(sent, &msg2).expect("read msg2");
let (a_seal, a_open) = <Suite as Handshake>::into_datagram(a_transport, epoch);
let (b_seal, b_open) = <Suite as Handshake>::into_datagram(b_transport, epoch);
(
EstablishedSession {
seal: a_seal,
open: a_open,
our_index: A_INDEX,
peer_index: B_INDEX,
anchor: b_addr(),
},
EstablishedSession {
seal: b_seal,
open: b_open,
our_index: B_INDEX,
peer_index: A_INDEX,
anchor: a_addr(),
},
)
}
#[derive(Debug, Default, Clone)]
pub(crate) struct Drained {
pub(crate) outs: Vec<ConnOutput>,
pub(crate) deadline: Option<Instant>,
}
impl Drained {
pub(crate) fn transmits(&self) -> Vec<Transmit> {
self.outs
.iter()
.filter_map(|o| match o {
ConnOutput::Transmit(t) => Some(t.clone()),
_ => None,
})
.collect()
}
pub(crate) fn count_events(&self, f: impl Fn(&ConnEvent) -> bool) -> usize {
self.outs
.iter()
.filter(|o| matches!(o, ConnOutput::Event(e) if f(e)))
.count()
}
pub(crate) fn closed(&self) -> Option<ConnectionLost> {
self.outs.iter().find_map(|o| match o {
ConnOutput::Event(ConnEvent::Closed(l)) => Some(l.clone()),
_ => None,
})
}
pub(crate) fn position(&self, f: impl Fn(&ConnOutput) -> bool) -> Option<usize> {
self.outs.iter().position(f)
}
}
pub(crate) fn drain(conn: &mut Connection<Suite>) -> Drained {
let mut d = Drained::default();
for _ in 0..200_000 {
match conn.poll_output() {
ConnOutput::Timeout(t) => {
d.deadline = t;
return d;
}
other => d.outs.push(other),
}
}
panic!("poll_output() did not reach the terminal Timeout (§16.4)");
}
pub(crate) struct Pair {
pub(crate) a: Connection<Suite>,
pub(crate) b: Connection<Suite>,
pub(crate) a_to_b: Vec<Vec<u8>>,
pub(crate) b_to_a: Vec<Vec<u8>>,
}
impl Pair {
pub(crate) fn installed_at(now: Instant) -> Self {
let (sa, sb) = handshake_pair();
let mut a = Connection::connecting([0x5au8; 32]);
let mut b = Connection::connecting([0xa5u8; 32]);
a.handle_endpoint_event(
now,
Install {
session: sa,
role: Role::Initiator,
anchor_from_msg1: false,
},
);
b.handle_endpoint_event(
now,
Install {
session: sb,
role: Role::Responder,
anchor_from_msg1: false,
},
);
let mut p = Self {
a,
b,
a_to_b: Vec::new(),
b_to_a: Vec::new(),
};
let _ = p.drain_a();
let _ = p.drain_b();
p
}
pub(crate) fn unestablished() -> Self {
Self {
a: Connection::connecting([0x5au8; 32]),
b: Connection::connecting([0xa5u8; 32]),
a_to_b: Vec::new(),
b_to_a: Vec::new(),
}
}
pub(crate) fn install(&mut self, now: Instant) {
let (sa, sb) = handshake_pair();
self.a.handle_endpoint_event(
now,
Install {
session: sa,
role: Role::Initiator,
anchor_from_msg1: false,
},
);
self.b.handle_endpoint_event(
now,
Install {
session: sb,
role: Role::Responder,
anchor_from_msg1: false,
},
);
}
pub(crate) fn drain_a(&mut self) -> Drained {
let d = drain(&mut self.a);
for t in d.transmits() {
self.a_to_b.push(t.data);
}
d
}
pub(crate) fn drain_b(&mut self) -> Drained {
let d = drain(&mut self.b);
for t in d.transmits() {
self.b_to_a.push(t.data);
}
d
}
pub(crate) fn flush_a_to_b(&mut self, now: Instant) -> Drained {
self.flush_a_to_b_from(now, a_addr())
}
pub(crate) fn flush_a_to_b_from(&mut self, now: Instant, src: SocketAddr) -> Drained {
let queued = std::mem::take(&mut self.a_to_b);
let mut all = Drained::default();
for dgram in queued {
self.b.handle_datagram(now, src, &dgram);
let d = self.drain_b();
all.outs.extend(d.outs);
all.deadline = d.deadline;
}
all
}
pub(crate) fn flush_b_to_a(&mut self, now: Instant) -> Drained {
self.flush_b_to_a_from(now, b_addr())
}
pub(crate) fn flush_b_to_a_from(&mut self, now: Instant, src: SocketAddr) -> Drained {
let queued = std::mem::take(&mut self.b_to_a);
let mut all = Drained::default();
for dgram in queued {
self.a.handle_datagram(now, src, &dgram);
let d = self.drain_a();
all.outs.extend(d.outs);
all.deadline = d.deadline;
}
all
}
pub(crate) fn pump(&mut self, now: Instant) -> (Drained, Drained) {
self.pump_from(now, a_addr(), b_addr())
}
pub(crate) fn pump_from(
&mut self,
now: Instant,
a_src: SocketAddr,
b_src: SocketAddr,
) -> (Drained, Drained) {
let mut da = Drained::default();
let mut db = Drained::default();
for _ in 0..4096 {
self.a.handle_timeout(now);
self.b.handle_timeout(now);
let d = self.drain_a();
da.outs.extend(d.outs);
let d = self.drain_b();
db.outs.extend(d.outs);
if self.a_to_b.is_empty() && self.b_to_a.is_empty() {
return (da, db);
}
let d = self.flush_a_to_b_from(now, a_src);
db.outs.extend(d.outs);
let d = self.flush_b_to_a_from(now, b_src);
da.outs.extend(d.outs);
}
panic!("the two cores never went quiet");
}
pub(crate) fn step_from(
&mut self,
now: Instant,
a_src: SocketAddr,
b_src: SocketAddr,
) -> (Drained, Drained) {
self.a.handle_timeout(now);
self.b.handle_timeout(now);
let mut da = self.drain_a();
let mut db = self.drain_b();
let d = self.flush_a_to_b_from(now, a_src);
db.outs.extend(d.outs);
let d = self.flush_b_to_a_from(now, b_src);
da.outs.extend(d.outs);
(da, db)
}
}
pub(crate) fn write_all(
conn: &mut Connection<Suite>,
now: Instant,
r: StreamRef,
data: &[u8],
) -> usize {
let mut sent = 0usize;
let mut blocked = 0usize;
while sent < data.len() {
match conn
.write(now, r, &data[sent..])
.expect("write must not error")
{
0 => {
blocked += 1;
assert!(
blocked <= 1000,
"write stayed blocked after {sent} of {} bytes",
data.len()
);
}
n => sent += n,
}
}
blocked
}
pub(crate) fn write_until_blocked(conn: &mut Connection<Suite>, now: Instant, r: StreamRef) -> u64 {
let chunk = vec![0u8; 4096];
let mut sent = 0u64;
for _ in 0..100_000 {
match conn.write(now, r, &chunk).expect("write must not error") {
0 => return sent,
n => sent += n as u64,
}
}
panic!("write never blocked after {sent} bytes");
}
pub(crate) fn read_available(
conn: &mut Connection<Suite>,
now: Instant,
r: StreamRef,
) -> (Vec<u8>, bool) {
let mut got = Vec::new();
let mut buf = vec![0u8; 64 * 1024];
for _ in 0..100_000 {
match conn.read(now, r, &mut buf).expect("read must not error") {
None => return (got, true),
Some(0) => return (got, false),
Some(n) => got.extend_from_slice(&buf[..n]),
}
}
panic!("read never settled");
}
pub(crate) fn tick(conn: &mut Connection<Suite>, now: Instant) {
conn.handle_timeout(now);
}
pub(crate) fn accept_all(conn: &mut Connection<Suite>, dir: Dir) -> Vec<(u64, StreamRef)> {
let mut out = Vec::new();
while let Some(r) = conn.accept(dir) {
let id = conn
.stream_id(r)
.expect("a peer-opened stream exists only after establishment")
.as_u64();
out.push((id, r));
assert!(
out.len() <= 4096,
"accept() never stopped returning streams"
);
}
out.sort_by_key(|(id, _)| *id);
out
}
pub(crate) fn read_exactly(
conn: &mut Connection<Suite>,
now: Instant,
r: StreamRef,
want: usize,
) -> Vec<u8> {
let mut got = Vec::new();
let mut buf = vec![0u8; want];
while got.len() < want {
let room = want - got.len();
match conn
.read(now, r, &mut buf[..room])
.expect("read must not error")
{
None => panic!("end of stream after {} of {want} bytes", got.len()),
Some(0) => panic!("no data after {} of {want} bytes", got.len()),
Some(n) => got.extend_from_slice(&buf[..n]),
}
}
got
}
impl Solo {
pub(crate) fn deliver_stream_bytes(
&mut self,
now: Instant,
id: u64,
offset: u64,
len: usize,
fin: bool,
) -> Drained {
pub(crate) const CHUNK: usize = 1024;
let payload = ramp(offset as usize, len);
let mut all = Drained::default();
let mut at = 0usize;
while at < len {
let n = CHUNK.min(len - at);
let last = at + n == len;
let f = stream_frame(id, offset + at as u64, &payload[at..at + n], fin && last);
let d = self.deliver(now, &f);
all.outs.extend(d.outs);
all.deadline = d.deadline;
at += n;
}
if len == 0 && fin {
let d = self.deliver(now, &stream_frame(id, offset, &[], true));
all.outs.extend(d.outs);
}
all
}
pub(crate) fn deliver_packed(&mut self, now: Instant, frames: &[Vec<u8>]) -> Drained {
let mut all = Drained::default();
let mut pt: Vec<u8> = Vec::new();
for f in frames {
if !pt.is_empty() && pt.len() + f.len() > MAX_PLAINTEXT {
let d = self.deliver(now, &pt);
all.outs.extend(d.outs);
all.deadline = d.deadline;
pt.clear();
}
pt.extend_from_slice(f);
}
if !pt.is_empty() {
let d = self.deliver(now, &pt);
all.outs.extend(d.outs);
all.deadline = d.deadline;
}
all
}
}
pub(crate) struct RawPeer {
pub(crate) seal: <Suite as Handshake>::Seal,
pub(crate) open: <Suite as Handshake>::Open,
pub(crate) our_index: u32,
pub(crate) peer_index: u32,
}
impl RawPeer {
pub(crate) fn from_session(s: EstablishedSession<Suite>) -> Self {
Self {
seal: s.seal,
open: s.open,
our_index: s.our_index,
peer_index: s.peer_index,
}
}
pub(crate) fn seal(&mut self, plaintext: &[u8]) -> Vec<u8> {
let counter = self.seal.next_counter();
let mut dgram = data_header(self.peer_index, counter);
let mut body = vec![0u8; plaintext.len() + crate::constants::AEAD_TAG_LEN];
let (_got, n) = self
.seal
.encrypt_next(&dgram, plaintext, &mut body)
.expect("peer seal");
body.truncate(n);
dgram.extend_from_slice(&body);
dgram
}
pub(crate) fn open_dgram(&mut self, dgram: &[u8]) -> Vec<u8> {
let (header, body) = dgram.split_at(DATA_HEADER_LEN);
assert_eq!(header[0], PKT_DATA, "§3.4 packet type");
assert_eq!(header[1], VERSION, "§3.4 version");
assert_eq!(
u32::from_le_bytes(header[2..6].try_into().unwrap()),
self.our_index,
"§3.4 receiver_index routes to the peer"
);
let counter = u64::from_le_bytes(header[6..14].try_into().unwrap());
let mut out = vec![0u8; body.len()];
let n = self
.open
.decrypt_at(counter, header, body, &mut out)
.expect("the packet must open");
out.truncate(n);
out
}
}
pub(crate) fn data_header(receiver_index: u32, counter: u64) -> Vec<u8> {
let mut h = Vec::with_capacity(DATA_HEADER_LEN);
h.push(PKT_DATA);
h.push(VERSION);
h.extend_from_slice(&receiver_index.to_le_bytes());
h.extend_from_slice(&counter.to_le_bytes());
h
}
pub(crate) struct Solo {
pub(crate) conn: Connection<Suite>,
pub(crate) peer: RawPeer,
}
impl Solo {
pub(crate) fn connecting() -> (
Connection<Suite>,
EstablishedSession<Suite>,
EstablishedSession<Suite>,
) {
let (sa, sb) = handshake_pair();
(Connection::connecting([0xa5u8; 32]), sa, sb)
}
pub(crate) fn around(conn: Connection<Suite>, peer: EstablishedSession<Suite>) -> Self {
Self {
conn,
peer: RawPeer::from_session(peer),
}
}
pub(crate) fn installed_at(now: Instant) -> Self {
Self::installed_with(now, false)
}
pub(crate) fn installed_from_msg1_at(now: Instant) -> Self {
Self::installed_with(now, true)
}
fn installed_with(now: Instant, anchor_from_msg1: bool) -> Self {
let (sa, sb) = handshake_pair();
let mut conn = Connection::connecting([0xa5u8; 32]);
conn.handle_endpoint_event(
now,
Install {
session: sb,
role: Role::Responder,
anchor_from_msg1,
},
);
let _ = drain(&mut conn);
Self {
conn,
peer: RawPeer::from_session(sa),
}
}
pub(crate) fn deliver(&mut self, now: Instant, frames: &[u8]) -> Drained {
self.deliver_from(now, a_addr(), frames)
}
pub(crate) fn deliver_from(&mut self, now: Instant, src: SocketAddr, frames: &[u8]) -> Drained {
let dgram = self.peer.seal(frames);
self.conn.handle_datagram(now, src, &dgram);
drain(&mut self.conn)
}
pub(crate) fn drain_frames(&mut self, d: &Drained) -> Vec<Wire> {
self.packets(d).into_iter().flatten().collect()
}
pub(crate) fn packets(&mut self, d: &Drained) -> Vec<Vec<Wire>> {
d.transmits()
.iter()
.map(|t| {
let pt = self.peer.open_dgram(&t.data);
parse_frames(&pt)
})
.collect()
}
pub(crate) fn peer_uni(index: u64) -> u64 {
raw_id(index, Dir::Uni, true)
}
pub(crate) fn peer_bidi(index: u64) -> u64 {
raw_id(index, Dir::Bi, true)
}
pub(crate) fn our_uni(index: u64) -> u64 {
raw_id(index, Dir::Uni, false)
}
}
pub(crate) fn ramp(offset: usize, len: usize) -> Vec<u8> {
(offset..offset + len).map(|i| (i % 251) as u8).collect()
}
pub(crate) fn assert_violation(d: &Drained, frames: &[Wire], code: u64) {
assert_eq!(
d.closed(),
Some(ConnectionLost::ProtocolViolation { code }),
"§8.2: the violation surfaces as ProtocolViolation with its code"
);
let closes: Vec<u64> = frames
.iter()
.filter_map(|f| match f {
Wire::Close { code, .. } => Some(*code),
_ => None,
})
.collect();
assert_eq!(
closes,
vec![code],
"§8.2: exactly one CLOSE, carrying the same code the peer reads"
);
}
pub(crate) fn assert_alive(d: &Drained) {
assert_eq!(
d.closed(),
None,
"the connection must survive this: {:?}",
d.outs
);
}