use bytes::Bytes;
use qmux::proto::{ConnectionClose, Frame, ResetStream, StopSending, Stream};
use qmux::{StreamId, Version};
use web_transport_proto::VarInt;
fn assert_round_trip(version: Version, bytes: &[u8], expected: &Frame) {
let decoded = Frame::decode(Bytes::copy_from_slice(bytes), version)
.expect("decode succeeds")
.expect("decoder returns a frame (not an ignored frame type)");
assert_frames_eq(&decoded, expected, version);
let encoded = expected.encode(version).expect("encode succeeds");
assert_eq!(
encoded.as_ref(),
bytes,
"encoding {expected:?} for {version:?} must produce identical bytes"
);
}
fn assert_frames_eq(got: &Frame, want: &Frame, version: Version) {
match (got, want) {
(Frame::Stream(a), Frame::Stream(b)) => {
assert_eq!(a.id.0.into_inner(), b.id.0.into_inner(), "stream id");
assert_eq!(a.data.as_ref(), b.data.as_ref(), "stream data");
assert_eq!(a.fin, b.fin, "stream fin");
}
(Frame::ResetStream(a), Frame::ResetStream(b)) => {
assert_eq!(a.id.0.into_inner(), b.id.0.into_inner(), "reset id");
assert_eq!(a.code.into_inner(), b.code.into_inner(), "reset code");
if version != Version::WebTransport {
assert_eq!(a.final_size, b.final_size, "reset final_size");
}
}
(Frame::StopSending(a), Frame::StopSending(b)) => {
assert_eq!(a.id.0.into_inner(), b.id.0.into_inner(), "stop id");
assert_eq!(a.code.into_inner(), b.code.into_inner(), "stop code");
}
(Frame::ConnectionClose(a), Frame::ConnectionClose(b)) => {
assert_eq!(a.code.into_inner(), b.code.into_inner(), "close code");
assert_eq!(a.reason, b.reason, "close reason");
}
(Frame::MaxData(a), Frame::MaxData(b)) => assert_eq!(a, b, "max_data"),
(
Frame::MaxStreamData {
id: a_id,
max: a_max,
},
Frame::MaxStreamData {
id: b_id,
max: b_max,
},
) => {
assert_eq!(
a_id.0.into_inner(),
b_id.0.into_inner(),
"max_stream_data id"
);
assert_eq!(a_max, b_max, "max_stream_data max");
}
(Frame::MaxStreamsBidi(a), Frame::MaxStreamsBidi(b)) => {
assert_eq!(a, b, "max_streams_bidi")
}
(Frame::MaxStreamsUni(a), Frame::MaxStreamsUni(b)) => assert_eq!(a, b, "max_streams_uni"),
(Frame::DataBlocked(a), Frame::DataBlocked(b)) => assert_eq!(a, b, "data_blocked"),
(
Frame::StreamDataBlocked {
id: a_id,
limit: a_lim,
},
Frame::StreamDataBlocked {
id: b_id,
limit: b_lim,
},
) => {
assert_eq!(
a_id.0.into_inner(),
b_id.0.into_inner(),
"stream_data_blocked id"
);
assert_eq!(a_lim, b_lim, "stream_data_blocked limit");
}
(Frame::StreamsBlockedBidi(a), Frame::StreamsBlockedBidi(b)) => {
assert_eq!(a, b, "streams_blocked_bidi")
}
(Frame::StreamsBlockedUni(a), Frame::StreamsBlockedUni(b)) => {
assert_eq!(a, b, "streams_blocked_uni")
}
(a, b) => panic!("frame variants don't match: got {a:?}, want {b:?}"),
}
}
fn sid(v: u64) -> StreamId {
StreamId(VarInt::try_from(v).unwrap())
}
fn code(v: u64) -> VarInt {
VarInt::try_from(v).unwrap()
}
#[test]
fn webtransport_stream_no_fin() {
let bytes = [0x08, 0x04, b'h', b'i'];
let frame = Frame::Stream(Stream {
id: sid(4),
data: Bytes::from_static(b"hi"),
fin: false,
});
assert_round_trip(Version::WebTransport, &bytes, &frame);
}
#[test]
fn webtransport_stream_fin() {
let bytes = [0x09, 0x08, b'b', b'y', b'e'];
let frame = Frame::Stream(Stream {
id: sid(8),
data: Bytes::from_static(b"bye"),
fin: true,
});
assert_round_trip(Version::WebTransport, &bytes, &frame);
}
#[test]
fn webtransport_reset_stream() {
let bytes = [0x04, 0x04, 0x2a];
let frame = Frame::ResetStream(ResetStream {
id: sid(4),
code: code(42),
final_size: 0,
});
assert_round_trip(Version::WebTransport, &bytes, &frame);
}
#[test]
fn webtransport_stop_sending() {
let bytes = [0x05, 0x04, 0x2a];
let frame = Frame::StopSending(StopSending {
id: sid(4),
code: code(42),
});
assert_round_trip(Version::WebTransport, &bytes, &frame);
}
#[test]
fn webtransport_connection_close() {
let bytes = [0x1d, 0x2a, b'b', b'y', b'e'];
let frame = Frame::ConnectionClose(ConnectionClose {
code: code(42),
reason: "bye".to_string(),
});
assert_round_trip(Version::WebTransport, &bytes, &frame);
}
#[test]
fn qmux00_stream_with_len_no_fin() {
let bytes = [0x0a, 0x04, 0x02, b'h', b'i'];
let frame = Frame::Stream(Stream {
id: sid(4),
data: Bytes::from_static(b"hi"),
fin: false,
});
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_stream_with_len_and_fin() {
let bytes = [0x0b, 0x08, 0x03, b'b', b'y', b'e'];
let frame = Frame::Stream(Stream {
id: sid(8),
data: Bytes::from_static(b"bye"),
fin: true,
});
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_reset_stream() {
let bytes = [0x04, 0x04, 0x2a, 0x40, 0x80];
let frame = Frame::ResetStream(ResetStream {
id: sid(4),
code: code(42),
final_size: 128,
});
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_stop_sending() {
let bytes = [0x05, 0x04, 0x2a];
let frame = Frame::StopSending(StopSending {
id: sid(4),
code: code(42),
});
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_application_close() {
let bytes = [0x1d, 0x2a, 0x00, 0x03, b'b', b'y', b'e'];
let frame = Frame::ConnectionClose(ConnectionClose {
code: code(42),
reason: "bye".to_string(),
});
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_max_data() {
let bytes = [0x10, 0x44, 0x00];
let frame = Frame::MaxData(1024);
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_max_stream_data() {
let bytes = [0x11, 0x04, 0x44, 0x00];
let frame = Frame::MaxStreamData {
id: sid(4),
max: 1024,
};
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_max_streams_bidi() {
let bytes = [0x12, 0x40, 0x64];
let frame = Frame::MaxStreamsBidi(100);
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_max_streams_uni() {
let bytes = [0x13, 0x40, 0x64];
let frame = Frame::MaxStreamsUni(100);
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_data_blocked() {
let bytes = [0x14, 0x44, 0x00];
let frame = Frame::DataBlocked(1024);
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_stream_data_blocked() {
let bytes = [0x15, 0x04, 0x44, 0x00];
let frame = Frame::StreamDataBlocked {
id: sid(4),
limit: 1024,
};
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_streams_blocked_bidi() {
let bytes = [0x16, 0x40, 0x64];
let frame = Frame::StreamsBlockedBidi(100);
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_streams_blocked_uni() {
let bytes = [0x17, 0x40, 0x64];
let frame = Frame::StreamsBlockedUni(100);
assert_round_trip(Version::QMux00, &bytes, &frame);
}
#[test]
fn qmux00_transport_parameters_initial_max_data_only() {
let bytes = [
0xff, 0x51, 0x53, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x04, 0x04, 0x02, 0x44, 0x00,
];
let decoded = Frame::decode(Bytes::copy_from_slice(&bytes), Version::QMux00)
.unwrap()
.unwrap();
let mut params = match decoded {
Frame::TransportParameters(p) => {
assert_eq!(p.initial_max_data, 1024);
assert_eq!(p.initial_max_stream_data_bidi_local, 0);
assert_eq!(p.initial_max_streams_bidi, 0);
assert_eq!(p.max_record_size, qmux::proto::DEFAULT_MAX_RECORD_SIZE);
p
}
other => panic!("expected TransportParameters, got {other:?}"),
};
params.max_record_size = 0;
let encoded = Frame::TransportParameters(params)
.encode(Version::QMux00)
.unwrap();
assert_eq!(encoded.as_ref(), bytes);
}
#[test]
fn qmux00_encoding_has_no_record_size_prefix() {
let cases: Vec<Frame> = vec![
Frame::Stream(Stream {
id: sid(4),
data: Bytes::from_static(b"hi"),
fin: false,
}),
Frame::MaxData(1024),
Frame::ConnectionClose(ConnectionClose {
code: code(42),
reason: "bye".to_string(),
}),
];
for frame in &cases {
let bytes = frame.encode(Version::QMux00).unwrap();
match frame {
Frame::Stream(_) => assert_eq!(bytes[0], 0x0a, "stream without fin = type 0x0a"),
Frame::MaxData(_) => assert_eq!(bytes[0], 0x10, "max_data = type 0x10"),
Frame::ConnectionClose(_) => {
assert_eq!(bytes[0], 0x1d, "application_close = type 0x1d")
}
_ => unreachable!(),
}
}
}