use crate::error::NetError;
use crate::packet::IpVersion;
use crate::source_admission::IpAddr;
use crate::transport::{TimerAction, TimerType, TimerWheel};
pub const QUIC_VERSION_1: u32 = 1;
pub const MAX_CID_LEN: usize = 20;
pub const MAX_TOKEN_LEN: usize = 255;
pub const MIN_QUIC_HEADER_LEN: usize = 1;
pub const RETRY_INTEGRITY_TAG_LEN: usize = 16;
pub const MAX_STREAM_ID: u64 = 0x0000_FFFF_FFFF;
pub const MAX_QUIC_CONNECTIONS: usize = 1024;
pub const MAX_STREAMS_PER_CONN: usize = 128;
pub const QUIC_STREAM_TABLE_SIZE: usize = MAX_STREAMS_PER_CONN * 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuicHeaderType {
Long,
Short,
VersionNegotiation,
Retry,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LongFrameType {
Initial,
Handshake,
ZeroRtt,
}
#[derive(Debug, Clone, Copy)]
pub struct QuicHeader {
pub header_type: QuicHeaderType,
pub version: u32,
pub dcid: [u8; MAX_CID_LEN],
pub dcid_len: u8,
pub scid: [u8; MAX_CID_LEN],
pub scid_len: u8,
pub packet_number: u64,
pub token: [u8; MAX_TOKEN_LEN],
pub token_len: u8,
pub long_frame: LongFrameType,
pub first_byte: u8,
pub length: u64,
}
impl QuicHeader {
pub const fn empty() -> Self {
Self {
header_type: QuicHeaderType::Short,
version: 0,
dcid: [0u8; MAX_CID_LEN],
dcid_len: 0,
scid: [0u8; MAX_CID_LEN],
scid_len: 0,
packet_number: 0,
token: [0u8; MAX_TOKEN_LEN],
token_len: 0,
long_frame: LongFrameType::Initial,
first_byte: 0,
length: 0,
}
}
#[inline]
pub fn is_long_header(&self) -> bool {
self.header_type == QuicHeaderType::Long
|| self.header_type == QuicHeaderType::VersionNegotiation
|| self.header_type == QuicHeaderType::Retry
}
#[inline]
pub fn is_initial(&self) -> bool {
self.header_type == QuicHeaderType::Long
&& self.long_frame == LongFrameType::Initial
}
#[inline]
pub fn has_token(&self) -> bool {
self.token_len > 0
}
#[inline]
pub fn compare_packet_number(a: u64, b: u64) -> i32 {
use std::cmp::Ordering;
match a.cmp(&b) {
Ordering::Equal => 0,
Ordering::Greater => {
if a - b < (1u64 << 61) {
1
} else {
-1
}
}
Ordering::Less => {
if b - a < (1u64 << 61) {
-1
} else {
1
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QuicConnState {
Initial,
Handshake,
Established,
Closing,
Closed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum QuicStreamState {
Idle,
Open,
HalfClosedLocal,
HalfClosedRemote,
Closed,
}
#[derive(Debug, Clone, Copy)]
pub struct QuicStream {
pub stream_id: u64,
pub state: QuicStreamState,
pub max_local_offset: u64,
pub max_remote_offset: u64,
pub local_offset: u64,
pub remote_offset: u64,
pub is_uni: bool,
}
impl QuicStream {
const fn empty() -> Self {
Self {
stream_id: 0,
state: QuicStreamState::Idle,
max_local_offset: 0,
max_remote_offset: 0,
local_offset: 0,
remote_offset: 0,
is_uni: false,
}
}
pub fn new(stream_id: u64, is_uni: bool) -> Self {
Self {
stream_id,
state: QuicStreamState::Open,
max_local_offset: 0,
max_remote_offset: 0,
local_offset: 0,
remote_offset: 0,
is_uni,
}
}
#[inline]
pub fn remote_idx(stream_id: u64) -> usize {
stream_id as usize
}
}
pub struct QuicConnection {
pub state: QuicConnState,
pub scid: [u8; MAX_CID_LEN],
pub scid_len: u8,
pub dcid: [u8; MAX_CID_LEN],
pub dcid_len: u8,
pub retry_token: [u8; MAX_TOKEN_LEN],
pub retry_token_len: u8,
pub token_attempts: u8,
pub remote_addr: IpAddr,
pub remote_port: u16,
pub local_addr: IpAddr,
pub local_port: u16,
pub ip_version: IpVersion,
pub max_pn_seen: u64,
pub max_remote_stream_id: u64,
pub next_local_bidi_id: u64,
pub next_local_uni_id: u64,
pub streams: Vec<QuicStream>,
pub stream_count: usize,
pub pto_ms: u64,
pub address_verified: bool,
pub bytes_received: u64,
pub bytes_sent: u64,
pub path_validation: PathValidationState,
pub path_challenge_data: u64,
pub migration_remote_addr: IpAddr,
pub migration_remote_port: u16,
pub migration_bytes_sent: u64,
pub migration_bytes_received: u64,
}
impl core::fmt::Debug for QuicConnection {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("QuicConnection")
.field("state", &self.state)
.field("stream_count", &self.stream_count)
.field("bytes_received", &self.bytes_received)
.field("address_verified", &self.address_verified)
.finish()
}
}
impl QuicConnection {
pub fn new(
scid: &[u8],
dcid: &[u8],
remote_addr: IpAddr,
remote_port: u16,
local_addr: IpAddr,
local_port: u16,
ip_version: IpVersion,
) -> Self {
let mut scid_buf = [0u8; MAX_CID_LEN];
let scid_len = scid.len().min(MAX_CID_LEN) as u8;
scid_buf[..scid_len as usize].copy_from_slice(&scid[..scid_len as usize]);
let mut dcid_buf = [0u8; MAX_CID_LEN];
let dcid_len = dcid.len().min(MAX_CID_LEN) as u8;
dcid_buf[..dcid_len as usize].copy_from_slice(&dcid[..dcid_len as usize]);
Self {
state: QuicConnState::Initial,
scid: scid_buf,
scid_len,
dcid: dcid_buf,
dcid_len,
retry_token: [0u8; MAX_TOKEN_LEN],
retry_token_len: 0,
token_attempts: 0,
remote_addr,
remote_port,
local_addr,
local_port,
ip_version,
max_pn_seen: 0,
max_remote_stream_id: 0,
next_local_bidi_id: 0,
next_local_uni_id: 0,
streams: vec![QuicStream::empty(); QUIC_STREAM_TABLE_SIZE],
stream_count: 0,
pto_ms: 1000,
address_verified: false,
bytes_received: 0,
bytes_sent: 0,
path_validation: PathValidationState::Idle,
path_challenge_data: 0,
migration_remote_addr: remote_addr,
migration_remote_port: remote_port,
migration_bytes_sent: 0,
migration_bytes_received: 0,
}
}
pub fn get_or_create_remote_stream(&mut self, stream_id: u64) -> Option<&mut QuicStream> {
let idx = QuicStream::remote_idx(stream_id);
if idx >= self.streams.len() {
return None;
}
let entry = &mut self.streams[idx];
if entry.state == QuicStreamState::Idle {
entry.stream_id = stream_id;
entry.state = QuicStreamState::Open;
entry.is_uni = (stream_id & 0x02) != 0;
self.stream_count += 1;
}
Some(entry)
}
pub fn get_local_stream(&mut self, stream_id: u64) -> Option<&mut QuicStream> {
let idx = QuicStream::remote_idx(stream_id);
if idx >= self.streams.len() {
return None;
}
let entry = &mut self.streams[idx];
if entry.state == QuicStreamState::Idle {
return None;
}
Some(entry)
}
pub fn alloc_local_stream_id(&mut self, is_uni: bool) -> Option<u64> {
if self.stream_count >= MAX_STREAMS_PER_CONN {
return None;
}
let (id, idx) = if is_uni {
let idx = self.next_local_uni_id;
(idx.checked_mul(4)?.checked_add(2)?, idx)
} else {
let idx = self.next_local_bidi_id;
(idx.checked_mul(4)?, idx)
};
let slot = QuicStream::remote_idx(id);
if slot >= self.streams.len() {
return None;
}
if is_uni {
self.next_local_uni_id = idx.checked_add(1)?;
} else {
self.next_local_bidi_id = idx.checked_add(1)?;
}
self.streams[slot] = QuicStream::new(id, is_uni);
self.stream_count += 1;
Some(id)
}
pub fn verify_token(&mut self, token: &[u8]) -> Result<bool, NetError> {
if token.is_empty() || token.len() > MAX_TOKEN_LEN {
return Ok(false);
}
if self.retry_token_len == 0 {
return Ok(false);
}
if self.token_attempts >= 3 {
return Err(NetError::ResourceLimit(
"quic: token verification attempts exceeded".to_string(),
));
}
self.token_attempts += 1;
let len = self.retry_token_len as usize;
let ok = token.len() == len && constant_time_eq(&token[..len], &self.retry_token[..len]);
if ok {
self.address_verified = true;
}
Ok(ok)
}
pub fn observe_packet_number(&mut self, pn: u64) {
if QuicHeader::compare_packet_number(pn, self.max_pn_seen) > 0 {
self.max_pn_seen = pn;
}
}
pub fn record_rx_bytes(&mut self, n: u64) {
self.bytes_received = self.bytes_received.saturating_add(n);
}
pub fn record_tx_bytes(&mut self, n: u64) {
self.bytes_sent = self.bytes_sent.saturating_add(n);
}
#[inline]
pub fn is_amplification_risk(&self) -> bool {
!self.address_verified && self.bytes_sent > self.bytes_received.saturating_mul(3)
}
pub fn initiate_path_migration(
&mut self,
new_addr: IpAddr,
new_port: u16,
challenge_data: u64,
) -> PathChallengeFrame {
self.migration_remote_addr = new_addr;
self.migration_remote_port = new_port;
self.path_challenge_data = challenge_data;
self.migration_bytes_sent = 0;
self.migration_bytes_received = 0;
self.path_validation = PathValidationState::ChallengeSent;
PathChallengeFrame::new(challenge_data)
}
pub fn handle_path_challenge(&self, data: u64) -> PathResponseFrame {
PathResponseFrame::new(data)
}
pub fn handle_path_response(&mut self, data: u64) -> bool {
if self.path_validation != PathValidationState::ChallengeSent {
return false;
}
if zenith_foundation::ct_compare::constant_time_eq_u64(self.path_challenge_data, data) {
self.remote_addr = self.migration_remote_addr;
self.remote_port = self.migration_remote_port;
self.path_validation = PathValidationState::Validated;
true
} else {
self.path_validation = PathValidationState::Failed;
false
}
}
#[inline]
pub fn is_migration_in_progress(&self) -> bool {
matches!(self.path_validation, PathValidationState::ChallengeSent)
}
#[inline]
pub fn migration_amplification_limit_reached(&self) -> bool {
self.path_validation == PathValidationState::ChallengeSent
&& self.migration_bytes_sent
>= self.migration_bytes_received.saturating_mul(3)
}
#[inline]
pub fn record_migration_tx_bytes(&mut self, n: u64) {
self.migration_bytes_sent = self.migration_bytes_sent.saturating_add(n);
}
#[inline]
pub fn record_migration_rx_bytes(&mut self, n: u64) {
self.migration_bytes_received = self.migration_bytes_received.saturating_add(n);
}
#[inline]
pub fn reset_path_validation(&mut self) {
self.path_validation = PathValidationState::Idle;
self.path_challenge_data = 0;
self.migration_bytes_sent = 0;
self.migration_bytes_received = 0;
}
pub fn start_close(&mut self) {
self.state = QuicConnState::Closing;
}
pub fn finish_close(&mut self) {
self.state = QuicConnState::Closed;
}
#[inline]
pub fn is_closed(&self) -> bool {
self.state == QuicConnState::Closed
}
#[inline]
pub fn can_send_data(&self) -> bool {
matches!(self.state, QuicConnState::Established)
}
}
#[derive(Debug, Clone, Copy)]
pub struct QuicConnParams<'a> {
pub scid: &'a [u8],
pub dcid: &'a [u8],
pub remote_addr: IpAddr,
pub remote_port: u16,
pub local_addr: IpAddr,
pub local_port: u16,
pub ip_version: IpVersion,
}
pub struct QuicConnectionTable {
conns: Vec<Option<QuicConnection>>,
free_stack: Vec<usize>,
active: usize,
}
impl core::fmt::Debug for QuicConnectionTable {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("QuicConnectionTable")
.field("active", &self.active)
.field("capacity", &self.conns.len())
.finish()
}
}
impl QuicConnectionTable {
pub fn new(capacity: usize) -> Self {
let cap = capacity.clamp(16, MAX_QUIC_CONNECTIONS);
Self {
conns: (0..cap).map(|_| None).collect(),
free_stack: (0..cap).rev().collect(),
active: 0,
}
}
#[inline]
pub fn active_count(&self) -> usize {
self.active
}
#[inline]
pub fn capacity(&self) -> usize {
self.conns.len()
}
pub fn find_by_dcid(&self, dcid: &[u8]) -> Option<usize> {
if dcid.is_empty() || dcid.len() > MAX_CID_LEN {
return None;
}
for (idx, entry) in self.conns.iter().enumerate() {
if let Some(c) = entry {
let len = c.dcid_len as usize;
if len == dcid.len()
&& constant_time_eq(&c.dcid[..len], &dcid[..len])
{
return Some(idx);
}
}
}
None
}
pub fn allocate(&mut self, params: QuicConnParams<'_>) -> Result<usize, NetError> {
let idx = self
.free_stack
.pop()
.ok_or_else(|| NetError::ResourceLimit("quic: connection table full".to_string()))?;
let conn = QuicConnection::new(
params.scid,
params.dcid,
params.remote_addr,
params.remote_port,
params.local_addr,
params.local_port,
params.ip_version,
);
self.conns[idx] = Some(conn);
self.active += 1;
Ok(idx)
}
pub fn release(&mut self, idx: usize) {
if idx < self.conns.len() && self.conns[idx].is_some() {
self.conns[idx] = None;
self.free_stack.push(idx);
self.active = self.active.saturating_sub(1);
}
}
pub fn get(&mut self, idx: usize) -> Option<&mut QuicConnection> {
self.conns.get_mut(idx).and_then(|x| x.as_mut())
}
pub fn for_each_conn_mut<F>(&mut self, mut f: F)
where
F: FnMut(usize, &mut QuicConnection),
{
for (idx, entry) in self.conns.iter_mut().enumerate() {
if let Some(c) = entry {
f(idx, c);
}
}
}
pub fn tick(
&mut self,
wheel: &mut TimerWheel,
elapsed_ms: u64,
) -> Vec<(usize, TimerAction)> {
let expired = wheel.advance(elapsed_ms);
let mut results = Vec::with_capacity(expired.len());
for action in expired {
if matches!(
action.timer_type,
TimerType::QuicPto | TimerType::QuicCloseTimeout
) {
results.push((action.target_idx, action));
}
}
results
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuicAction {
NewConnection {
conn_idx: usize,
},
DataReceived {
conn_idx: usize,
stream_id: u64,
},
ConnectionClosed {
conn_idx: usize,
},
AmplificationBlocked {
conn_idx: usize,
},
InvalidPacket,
}
pub fn parse_quic_header(input: &[u8]) -> Option<QuicHeader> {
parse_quic_header_with_dcid_len(input, 8)
}
pub fn parse_quic_header_with_dcid_len(input: &[u8], server_scid_len: usize) -> Option<QuicHeader> {
if input.len() < MIN_QUIC_HEADER_LEN {
return None;
}
let first = input[0];
let mut off = 1;
let mut hdr = QuicHeader::empty();
hdr.first_byte = first;
let is_long = (first & 0x80) != 0;
if is_long {
if input.len() < 6 {
return None;
}
let version = u32::from_be_bytes([input[1], input[2], input[3], input[4]]);
hdr.version = version;
hdr.header_type = if version == 0 {
QuicHeaderType::VersionNegotiation
} else {
QuicHeaderType::Long
};
if version != 0 && (first & 0x40) == 0 {
return None;
}
off = 5;
if input.len() <= off {
return None;
}
let dcid_len = input[off] as usize;
off += 1;
if dcid_len > MAX_CID_LEN || input.len() < off + dcid_len {
return None;
}
hdr.dcid_len = dcid_len as u8;
hdr.dcid[..dcid_len].copy_from_slice(&input[off..off + dcid_len]);
off += dcid_len;
if input.len() <= off {
return None;
}
let scid_len = input[off] as usize;
off += 1;
if scid_len > MAX_CID_LEN || input.len() < off + scid_len {
return None;
}
hdr.scid_len = scid_len as u8;
hdr.scid[..scid_len].copy_from_slice(&input[off..off + scid_len]);
off += scid_len;
let tt = (first & 0x30) >> 4;
if version != 0 {
if tt == 3 {
hdr.header_type = QuicHeaderType::Retry;
let remaining = input.len().saturating_sub(off);
if remaining >= RETRY_INTEGRITY_TAG_LEN {
let token_len = (remaining - RETRY_INTEGRITY_TAG_LEN).min(MAX_TOKEN_LEN);
hdr.token[..token_len].copy_from_slice(&input[off..off + token_len]);
hdr.token_len = token_len as u8;
}
return Some(hdr);
}
hdr.long_frame = match tt {
0 => LongFrameType::Initial,
1 => LongFrameType::ZeroRtt,
_ => LongFrameType::Handshake,
};
if tt == 0 {
if input.len() <= off {
return None;
}
let token_len = input[off] as usize;
off += 1;
if token_len > MAX_TOKEN_LEN || input.len() < off + token_len {
return None;
}
hdr.token_len = token_len as u8;
hdr.token[..token_len].copy_from_slice(&input[off..off + token_len]);
off += token_len;
}
if input.len() <= off {
return Some(hdr);
}
let (length, _) = parse_varint(&input[off..])?;
hdr.length = length;
}
} else {
hdr.header_type = QuicHeaderType::Short;
let dcid_len = server_scid_len.min(MAX_CID_LEN);
if input.len() < off + dcid_len {
return None;
}
hdr.dcid_len = dcid_len as u8;
hdr.dcid[..dcid_len].copy_from_slice(&input[off..off + dcid_len]);
off += dcid_len;
let pn_len = (first & 0x03) as usize + 1;
if input.len() < off + pn_len {
return None;
}
let mut pn: u64 = 0;
for i in 0..pn_len {
pn = (pn << 8) | (input[off + i] as u64);
}
hdr.packet_number = pn;
}
Some(hdr)
}
pub fn parse_varint(input: &[u8]) -> Option<(u64, usize)> {
if input.is_empty() {
return None;
}
let first = input[0];
let len_tag = first >> 6;
let len = 1usize << len_tag;
if input.len() < len {
return None;
}
let mut v: u64 = (first & 0x3F) as u64;
for &b in input.iter().take(len).skip(1) {
v = (v << 8) | (b as u64);
}
Some((v, len))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum QuicFrameType {
Padding = 0x00,
Ping = 0x01,
Ack = 0x02,
AckEcn = 0x03,
RstStream = 0x04,
StopSending = 0x05,
MaxData = 0x10,
MaxStreamData = 0x11,
NewConnectionId = 0x18,
RetireConnectionId = 0x19,
PathChallenge = 0x1a,
PathResponse = 0x1b,
Stream = 0x08,
ConnectionClose = 0x1c,
}
impl QuicFrameType {
pub fn from_byte(byte: u8) -> Option<Self> {
match byte {
0x00 => Some(QuicFrameType::Padding),
0x01 => Some(QuicFrameType::Ping),
0x02 | 0x03 => Some(QuicFrameType::Ack),
0x04 => Some(QuicFrameType::RstStream),
0x05 => Some(QuicFrameType::StopSending),
0x08..=0x0F => Some(QuicFrameType::Stream),
0x10 => Some(QuicFrameType::MaxData),
0x11 => Some(QuicFrameType::MaxStreamData),
0x18 => Some(QuicFrameType::NewConnectionId),
0x19 => Some(QuicFrameType::RetireConnectionId),
0x1a => Some(QuicFrameType::PathChallenge),
0x1b => Some(QuicFrameType::PathResponse),
0x1C | 0x1D => Some(QuicFrameType::ConnectionClose),
_ => None,
}
}
}
pub const MAX_ACK_RANGES: usize = 8;
pub const MAX_STREAM_DATA_LEN: usize = 1024;
pub const MAX_REASON_PHRASE_LEN: usize = 255;
#[derive(Debug, Clone, Copy)]
pub struct AckFrame {
pub largest_acknowledged: u64,
pub ack_delay: u64,
pub ack_range_count: u8,
pub first_ack_range: u64,
pub ranges: [u64; MAX_ACK_RANGES],
}
impl AckFrame {
pub const fn empty() -> Self {
Self {
largest_acknowledged: 0,
ack_delay: 0,
ack_range_count: 0,
first_ack_range: 0,
ranges: [0u64; MAX_ACK_RANGES],
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct StreamFrame {
pub stream_id: u64,
pub offset: u64,
pub length: u64,
pub fin: bool,
pub data: [u8; MAX_STREAM_DATA_LEN],
}
impl StreamFrame {
pub const fn empty() -> Self {
Self {
stream_id: 0,
offset: 0,
length: 0,
fin: false,
data: [0u8; MAX_STREAM_DATA_LEN],
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ConnectionCloseFrame {
pub error_code: u64,
pub reason_phrase: [u8; MAX_REASON_PHRASE_LEN],
pub reason_len: u8,
pub is_app_close: bool,
}
impl ConnectionCloseFrame {
pub const fn empty() -> Self {
Self {
error_code: 0,
reason_phrase: [0u8; MAX_REASON_PHRASE_LEN],
reason_len: 0,
is_app_close: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RstStreamFrame {
pub stream_id: u64,
pub error_code: u64,
}
impl RstStreamFrame {
pub const fn empty() -> Self {
Self {
stream_id: 0,
error_code: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StopSendingFrame {
pub stream_id: u64,
pub error_code: u64,
}
impl StopSendingFrame {
pub const fn empty() -> Self {
Self {
stream_id: 0,
error_code: 0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MaxDataFrame {
pub max_data: u64,
}
impl MaxDataFrame {
pub const fn empty() -> Self {
Self { max_data: 0 }
}
}
#[derive(Debug, Clone, Copy)]
pub struct MaxStreamDataFrame {
pub stream_id: u64,
pub max_stream_data: u64,
}
impl MaxStreamDataFrame {
pub const fn empty() -> Self {
Self {
stream_id: 0,
max_stream_data: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathChallengeFrame {
pub data: u64,
}
impl PathChallengeFrame {
#[inline]
pub const fn new(data: u64) -> Self {
Self { data }
}
pub fn encode(&self, out: &mut [u8]) -> Option<usize> {
if out.len() < 9 {
return None;
}
out[0] = 0x1a;
out[1..9].copy_from_slice(&self.data.to_be_bytes());
Some(9)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PathResponseFrame {
pub data: u64,
}
impl PathResponseFrame {
#[inline]
pub const fn new(data: u64) -> Self {
Self { data }
}
pub fn encode(&self, out: &mut [u8]) -> Option<usize> {
if out.len() < 9 {
return None;
}
out[0] = 0x1b;
out[1..9].copy_from_slice(&self.data.to_be_bytes());
Some(9)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathValidationState {
Idle,
ChallengeSent,
Validated,
Failed,
}
pub fn parse_ack_frame(input: &[u8]) -> Option<AckFrame> {
if input.len() < 5 {
return None;
}
let mut off = 0;
off += 1;
let (largest_acknowledged, n) = parse_varint(&input[off..])?;
off += n;
let (ack_delay, n) = parse_varint(&input[off..])?;
off += n;
let (ack_range_count, n) = parse_varint(&input[off..])?;
off += n;
let (first_ack_range, n) = parse_varint(&input[off..])?;
off += n;
if ack_range_count > MAX_ACK_RANGES as u64 {
return None;
}
let ack_range_count = ack_range_count as u8;
if ack_range_count as usize > MAX_ACK_RANGES {
return None;
}
let mut ranges = [0u64; MAX_ACK_RANGES];
let mut smallest = largest_acknowledged.checked_sub(first_ack_range)?;
for slot in ranges.iter_mut().take(ack_range_count as usize) {
let (gap, n) = parse_varint(&input[off..])?;
off += n;
let (length, n) = parse_varint(&input[off..])?;
off += n;
let this_largest = smallest.checked_sub(gap.checked_add(2)?)?;
let this_smallest = this_largest.checked_sub(length)?;
*slot = this_smallest;
smallest = this_smallest;
}
Some(AckFrame {
largest_acknowledged,
ack_delay,
ack_range_count,
first_ack_range,
ranges,
})
}
pub fn parse_stream_frame(input: &[u8]) -> Option<StreamFrame> {
if input.len() < 2 {
return None;
}
let type_byte = input[0];
let off_bit = (type_byte & 0x04) != 0;
let len_bit = (type_byte & 0x02) != 0;
let fin_bit = (type_byte & 0x01) != 0;
let mut off = 1;
let (stream_id, n) = parse_varint(&input[off..])?;
off += n;
let offset = if off_bit {
let (v, n) = parse_varint(&input[off..])?;
off += n;
v
} else {
0
};
let length = if len_bit {
let (v, n) = parse_varint(&input[off..])?;
off += n;
v
} else {
(input.len() - off) as u64
};
if length as usize > MAX_STREAM_DATA_LEN {
return None;
}
if input.len() < off + length as usize {
return None;
}
let mut data = [0u8; MAX_STREAM_DATA_LEN];
data[..length as usize].copy_from_slice(&input[off..off + length as usize]);
Some(StreamFrame {
stream_id,
offset,
length,
fin: fin_bit,
data,
})
}
pub fn parse_connection_close_frame(input: &[u8]) -> Option<ConnectionCloseFrame> {
if input.len() < 3 {
return None;
}
let type_byte = input[0];
let is_app_close = (type_byte & 0x01) != 0;
let mut off = 1;
let (error_code, n) = parse_varint(&input[off..])?;
off += n;
let (reason_len, n) = parse_varint(&input[off..])?;
off += n;
if reason_len > MAX_REASON_PHRASE_LEN as u64 {
return None;
}
if input.len() < off + reason_len as usize {
return None;
}
let mut reason_phrase = [0u8; MAX_REASON_PHRASE_LEN];
reason_phrase[..reason_len as usize].copy_from_slice(&input[off..off + reason_len as usize]);
Some(ConnectionCloseFrame {
error_code,
reason_phrase,
reason_len: reason_len as u8,
is_app_close,
})
}
pub fn parse_rst_stream_frame(input: &[u8]) -> Option<RstStreamFrame> {
if input.len() < 3 {
return None;
}
let mut off = 1;
let (stream_id, n) = parse_varint(&input[off..])?;
off += n;
let (error_code, _) = parse_varint(&input[off..])?;
Some(RstStreamFrame {
stream_id,
error_code,
})
}
pub fn parse_stop_sending_frame(input: &[u8]) -> Option<StopSendingFrame> {
if input.len() < 3 {
return None;
}
let mut off = 1;
let (stream_id, n) = parse_varint(&input[off..])?;
off += n;
let (error_code, _) = parse_varint(&input[off..])?;
Some(StopSendingFrame {
stream_id,
error_code,
})
}
pub fn parse_max_data_frame(input: &[u8]) -> Option<MaxDataFrame> {
if input.len() < 2 {
return None;
}
let (max_data, _) = parse_varint(&input[1..])?;
Some(MaxDataFrame { max_data })
}
pub fn parse_max_stream_data_frame(input: &[u8]) -> Option<MaxStreamDataFrame> {
if input.len() < 3 {
return None;
}
let mut off = 1;
let (stream_id, n) = parse_varint(&input[off..])?;
off += n;
let (max_stream_data, _) = parse_varint(&input[off..])?;
Some(MaxStreamDataFrame {
stream_id,
max_stream_data,
})
}
pub fn parse_path_challenge_frame(input: &[u8]) -> Option<PathChallengeFrame> {
if input.len() < 9 || input[0] != 0x1a {
return None;
}
let data = u64::from_be_bytes([
input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8],
]);
Some(PathChallengeFrame { data })
}
pub fn parse_path_response_frame(input: &[u8]) -> Option<PathResponseFrame> {
if input.len() < 9 || input[0] != 0x1b {
return None;
}
let data = u64::from_be_bytes([
input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8],
]);
Some(PathResponseFrame { data })
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CongestionState {
SlowStart,
CongestionAvoidance,
Recovery,
}
#[derive(Debug, Clone, Copy)]
pub struct CongestionController {
pub cwnd: u64,
pub in_flight: u64,
pub srtt: u64,
pub rtt_var: u64,
pub pto_count: u32,
state: CongestionState,
ssthresh: u64,
min_rtt: u64,
}
impl CongestionController {
const INITIAL_CWND: u64 = 14_600;
const MAX_CWND: u64 = 16 * 1024 * 1024;
const MIN_CWND: u64 = 2 * 1460;
pub fn new() -> Self {
Self {
cwnd: Self::INITIAL_CWND,
in_flight: 0,
srtt: 0,
rtt_var: 0,
pto_count: 0,
state: CongestionState::SlowStart,
ssthresh: u64::MAX,
min_rtt: u64::MAX,
}
}
#[inline]
pub fn state(&self) -> CongestionState {
self.state
}
#[inline]
pub fn available_bytes(&self) -> u64 {
self.cwnd.saturating_sub(self.in_flight)
}
#[inline]
pub fn on_send(&mut self, bytes: u64) {
self.in_flight = self.in_flight.saturating_add(bytes);
}
pub fn on_ack(&mut self, acked_bytes: u64, rtt_us: u64) -> u64 {
self.in_flight = self.in_flight.saturating_sub(acked_bytes);
self.update_rtt(rtt_us);
let old_cwnd = self.cwnd;
match self.state {
CongestionState::SlowStart => {
self.cwnd = self.cwnd.saturating_add(acked_bytes.min(1460));
if self.cwnd >= self.ssthresh {
self.state = CongestionState::CongestionAvoidance;
}
}
CongestionState::CongestionAvoidance => {
let increment = acked_bytes * 1460 / self.cwnd.max(1);
self.cwnd = self.cwnd.saturating_add(increment.max(1));
}
CongestionState::Recovery => {
if self.in_flight == 0 {
self.state = CongestionState::CongestionAvoidance;
}
}
}
self.cwnd = self.cwnd.min(Self::MAX_CWND);
self.cwnd - old_cwnd
}
pub fn on_loss(&mut self, lost_bytes: u64) {
self.in_flight = self.in_flight.saturating_sub(lost_bytes);
match self.state {
CongestionState::Recovery => {}
_ => {
self.state = CongestionState::Recovery;
self.ssthresh = self.cwnd * 7 / 10;
self.cwnd = self.ssthresh.max(Self::MIN_CWND);
}
}
}
pub fn on_timeout(&mut self) {
self.pto_count += 1;
self.ssthresh = self.cwnd * 3 / 4;
self.cwnd = Self::INITIAL_CWND;
self.in_flight = 0;
self.state = CongestionState::SlowStart;
}
fn update_rtt(&mut self, rtt_us: u64) {
if self.min_rtt == u64::MAX || rtt_us < self.min_rtt {
self.min_rtt = rtt_us;
}
if self.srtt == 0 {
self.srtt = rtt_us;
self.rtt_var = rtt_us / 2;
} else {
let delta = self.srtt.abs_diff(rtt_us);
self.srtt = (self.srtt * 7 + rtt_us) / 8;
self.rtt_var = (self.rtt_var * 3 + delta) / 4;
}
}
pub fn pto_us(&self) -> u64 {
if self.srtt == 0 {
100_000
} else {
let base = self.srtt + (self.rtt_var * 4).max(1000);
let multiplier = 1u64.checked_shl(self.pto_count.min(10)).unwrap_or(1 << 10);
base * multiplier
}
}
pub fn reset_pto(&mut self) {
self.pto_count = 0;
}
}
impl Default for CongestionController {
fn default() -> Self {
Self::new()
}
}
use zenith_foundation::ct_compare::constant_time_eq;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quic_header_empty() {
let hdr = QuicHeader::empty();
assert_eq!(hdr.header_type, QuicHeaderType::Short);
assert!(!hdr.is_long_header());
assert!(!hdr.has_token());
}
#[test]
fn test_parse_short_header() {
let input: [u8; 13] = [
0x43,
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x11, 0x22, 0x33, 0x44, ];
let hdr = parse_quic_header(&input).unwrap();
assert_eq!(hdr.header_type, QuicHeaderType::Short);
assert_eq!(hdr.dcid_len, 8);
assert_eq!(&hdr.dcid[..8], &[0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11]);
assert_eq!(hdr.packet_number, 0x11223344);
assert!(!hdr.is_long_header());
}
#[test]
fn test_parse_long_initial_with_token() {
let mut input = Vec::new();
input.push(0xC1);
input.extend_from_slice(&1u32.to_be_bytes());
input.push(5);
input.extend_from_slice(&[1u8, 2, 3, 4, 5]);
input.push(4);
input.extend_from_slice(&[6u8, 7, 8, 9]);
input.push(2);
input.extend_from_slice(&[0xAA, 0xBB]);
input.push(0x04);
let hdr = parse_quic_header(&input).unwrap();
assert_eq!(hdr.header_type, QuicHeaderType::Long);
assert_eq!(hdr.long_frame, LongFrameType::Initial);
assert_eq!(hdr.version, 1);
assert_eq!(hdr.dcid_len, 5);
assert_eq!(&hdr.dcid[..5], &[1, 2, 3, 4, 5]);
assert_eq!(hdr.scid_len, 4);
assert_eq!(&hdr.scid[..4], &[6, 7, 8, 9]);
assert_eq!(hdr.token_len, 2);
assert_eq!(&hdr.token[..2], &[0xAA, 0xBB]);
assert!(hdr.is_initial());
assert!(hdr.has_token());
}
#[test]
fn test_parse_version_negotiation() {
let mut input = Vec::new();
input.push(0x82);
input.extend_from_slice(&0u32.to_be_bytes()); input.push(3);
input.extend_from_slice(&[0x11, 0x22, 0x33]);
input.push(3);
input.extend_from_slice(&[0x44, 0x55, 0x66]);
input.push(0);
input.push(0x08);
let hdr = parse_quic_header(&input).unwrap();
assert_eq!(hdr.header_type, QuicHeaderType::VersionNegotiation);
assert_eq!(hdr.version, 0);
}
fn build_long_packet(first: u8) -> Vec<u8> {
let mut v = vec![first];
v.extend_from_slice(&1u32.to_be_bytes());
v.push(0); v.push(0); v
}
#[test]
fn test_long_header_tt_bits_rfc9000() {
let mut init = build_long_packet(0xC0);
init.push(0); init.push(0); let h = parse_quic_header(&init).unwrap();
assert_eq!(h.header_type, QuicHeaderType::Long);
assert_eq!(h.long_frame, LongFrameType::Initial);
assert!(h.is_initial());
let h = parse_quic_header(&build_long_packet(0xD0)).unwrap();
assert_eq!(h.header_type, QuicHeaderType::Long);
assert_eq!(h.long_frame, LongFrameType::ZeroRtt);
let h = parse_quic_header(&build_long_packet(0xE0)).unwrap();
assert_eq!(h.header_type, QuicHeaderType::Long);
assert_eq!(h.long_frame, LongFrameType::Handshake);
let h = parse_quic_header(&build_long_packet(0xF0)).unwrap();
assert_eq!(h.header_type, QuicHeaderType::Retry);
}
#[test]
fn test_retry_detection_tt3_only() {
let h = parse_quic_header(&build_long_packet(0xE3)).unwrap();
assert_eq!(h.header_type, QuicHeaderType::Long);
assert_eq!(h.long_frame, LongFrameType::Handshake);
let mut pkt = build_long_packet(0xF0);
pkt.extend_from_slice(&[0x42; 8]); pkt.extend_from_slice(&[0u8; RETRY_INTEGRITY_TAG_LEN]); let h = parse_quic_header(&pkt).unwrap();
assert_eq!(h.header_type, QuicHeaderType::Retry);
assert_eq!(h.token_len, 8);
assert_eq!(&h.token[..8], &[0x42; 8]);
}
#[test]
fn test_token_gating_only_initial() {
let mut init = build_long_packet(0xC1);
init.push(2); init.extend_from_slice(&[0xAA, 0xBB]);
init.push(4); let h = parse_quic_header(&init).unwrap();
assert!(h.is_initial());
assert_eq!(h.token_len, 2);
assert_eq!(&h.token[..2], &[0xAA, 0xBB]);
assert_eq!(h.length, 4);
let mut zrtt = build_long_packet(0xD1);
zrtt.push(2); zrtt.extend_from_slice(&[0xAA, 0xBB]);
let h = parse_quic_header(&zrtt).unwrap();
assert_eq!(h.long_frame, LongFrameType::ZeroRtt);
assert_eq!(h.token_len, 0, "0-RTT 不得解析 Token 字段");
assert_eq!(h.length, 2, "0-RTT 的首个 varint 应解析为 Length");
}
#[test]
fn test_amplification_risk_direction_rfc9000() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.record_rx_bytes(500);
c.record_tx_bytes(100);
assert!(!c.is_amplification_risk(), "tx <= 3*rx 不应触发");
c.record_tx_bytes(1400);
assert!(!c.is_amplification_risk(), "tx == 3*rx 仍在预算内");
c.record_tx_bytes(1);
assert!(c.is_amplification_risk(), "tx > 3*rx 必须触发");
c.address_verified = true;
assert!(!c.is_amplification_risk());
conns.release(idx);
}
#[test]
fn test_quic_connection_lifecycle() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8, 2, 3, 4],
dcid: &[5u8, 6, 7, 8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 12345,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
assert_eq!(conns.active_count(), 1);
let c = conns.get(idx).unwrap();
assert_eq!(c.state, QuicConnState::Initial);
let sid = c.alloc_local_stream_id(false).unwrap();
assert_eq!(sid, 0);
let sid2 = c.alloc_local_stream_id(true).unwrap();
assert_eq!(sid2, 2);
conns.release(idx);
assert_eq!(conns.active_count(), 0);
}
#[test]
fn test_quic_token_amplification() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8, 2, 3],
dcid: &[4u8, 5, 6],
remote_addr: IpAddr::V4([192, 168, 1, 1]),
remote_port: 443,
local_addr: IpAddr::V4([10, 0, 0, 1]),
local_port: 0,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.retry_token[..4].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
c.retry_token_len = 4;
assert!(c.verify_token(&[0x11, 0x22, 0x33, 0x44]).unwrap());
assert!(c.address_verified);
conns.release(idx);
}
#[test]
fn test_quic_token_exceed_limit() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.retry_token[0] = 0xAA;
c.retry_token_len = 1;
for _ in 0..3 {
let _ = c.verify_token(&[0xFF]);
}
let res = c.verify_token(&[0xAA]);
assert!(res.is_err());
}
#[test]
fn test_stream_id_remote_idx() {
assert_eq!(QuicStream::remote_idx(0), 0);
assert_eq!(QuicStream::remote_idx(4), 4);
assert_eq!(QuicStream::remote_idx(8), 8);
}
#[test]
fn test_quic_stream_state_transitions() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
let s = c.get_or_create_remote_stream(8).unwrap();
assert_eq!(s.state, QuicStreamState::Open);
assert_eq!(s.stream_id, 8);
assert!(!s.is_uni);
s.state = QuicStreamState::Closed;
assert_eq!(s.state, QuicStreamState::Closed);
conns.release(idx);
}
#[test]
fn test_packet_number_compare() {
assert!(QuicHeader::compare_packet_number(10, 5) > 0);
assert!(QuicHeader::compare_packet_number(5, 10) < 0);
assert_eq!(QuicHeader::compare_packet_number(10, 10), 0);
assert!(QuicHeader::compare_packet_number(1, u64::MAX) > 0);
assert!(QuicHeader::compare_packet_number(u64::MAX, 1) < 0);
}
#[test]
fn test_quic_conn_amplification_detection() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.record_rx_bytes(100);
c.record_tx_bytes(400);
assert!(c.is_amplification_risk());
c.address_verified = true;
assert!(!c.is_amplification_risk());
conns.release(idx);
}
#[test]
fn test_quic_find_by_dcid_basic() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8, 2, 3],
dcid: &[4u8, 5, 6, 7, 8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let found = conns.find_by_dcid(&[4u8, 5, 6, 7, 8]);
assert_eq!(found, Some(idx));
assert_eq!(conns.find_by_dcid(&[4u8, 5, 6, 7]), None);
assert_eq!(conns.find_by_dcid(&[4u8, 5, 6, 7, 9]), None);
assert_eq!(conns.find_by_dcid(&[]), None);
}
#[test]
fn test_quic_find_by_dcid_no_panic_on_short_slice() {
let mut conns = QuicConnectionTable::new(4);
let _ = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[4u8, 5, 6], remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let res = conns.find_by_dcid(&[4u8, 5, 6]);
assert!(res.is_some());
}
#[test]
fn test_constant_time_eq_basic() {
assert!(constant_time_eq(b"", b""));
assert!(constant_time_eq(b"abc", b"abc"));
assert!(!constant_time_eq(b"abc", b"abd"));
assert!(!constant_time_eq(b"abc", b"ab"));
assert!(!constant_time_eq(b"abc", b"abcd"));
assert!(!constant_time_eq(b"abc", b""));
}
#[test]
fn test_quic_verify_token_constant_time() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.retry_token[..6].copy_from_slice(b"SECRET");
c.retry_token_len = 6;
assert!(c.verify_token(b"SECRET").unwrap());
assert!(c.address_verified);
c.address_verified = false;
c.token_attempts = 0;
assert!(!c.verify_token(b"WRONG1").unwrap());
assert!(!c.address_verified);
c.token_attempts = 0;
assert!(!c.verify_token(b"SEC").unwrap());
assert!(!c.verify_token(b"SECRETS").unwrap());
conns.release(idx);
}
#[test]
fn test_quic_close_lifecycle() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.state = QuicConnState::Established;
assert_eq!(c.state, QuicConnState::Established);
c.start_close();
assert_eq!(c.state, QuicConnState::Closing);
c.finish_close();
assert!(c.is_closed());
conns.release(idx);
}
#[test]
fn test_quic_conn_table_full() {
let mut conns = QuicConnectionTable::new(4);
let cap = conns.capacity();
for i in 0..cap as u8 {
let _ = conns.allocate(QuicConnParams {
scid: &[i],
dcid: &[i + 1],
remote_addr: IpAddr::V4([10, 0, 0, i]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
});
}
let res = conns.allocate(QuicConnParams {
scid: &[99u8],
dcid: &[100u8],
remote_addr: IpAddr::V4([10, 0, 0, 99]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
});
assert!(res.is_err());
}
#[test]
fn test_quic_for_each_conn() {
let mut conns = QuicConnectionTable::new(4);
for i in 0..3u8 {
let _ = conns.allocate(QuicConnParams {
scid: &[i],
dcid: &[i + 1],
remote_addr: IpAddr::V4([10, 0, 0, i]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
});
}
let mut count = 0;
conns.for_each_conn_mut(|_, c| {
c.pto_ms = 500;
count += 1;
});
assert_eq!(count, 3);
}
#[test]
fn test_frame_type_from_byte() {
assert_eq!(QuicFrameType::from_byte(0x00), Some(QuicFrameType::Padding));
assert_eq!(QuicFrameType::from_byte(0x01), Some(QuicFrameType::Ping));
assert_eq!(QuicFrameType::from_byte(0x02), Some(QuicFrameType::Ack));
assert_eq!(QuicFrameType::from_byte(0x03), Some(QuicFrameType::Ack));
assert_eq!(QuicFrameType::from_byte(0x04), Some(QuicFrameType::RstStream));
assert_eq!(QuicFrameType::from_byte(0x05), Some(QuicFrameType::StopSending));
assert_eq!(QuicFrameType::from_byte(0x08), Some(QuicFrameType::Stream));
assert_eq!(QuicFrameType::from_byte(0x0F), Some(QuicFrameType::Stream));
assert_eq!(QuicFrameType::from_byte(0x10), Some(QuicFrameType::MaxData));
assert_eq!(QuicFrameType::from_byte(0x11), Some(QuicFrameType::MaxStreamData));
assert_eq!(QuicFrameType::from_byte(0x18), Some(QuicFrameType::NewConnectionId));
assert_eq!(QuicFrameType::from_byte(0x19), Some(QuicFrameType::RetireConnectionId));
assert_eq!(QuicFrameType::from_byte(0x1C), Some(QuicFrameType::ConnectionClose));
assert_eq!(QuicFrameType::from_byte(0x1D), Some(QuicFrameType::ConnectionClose));
assert_eq!(QuicFrameType::from_byte(0xFF), None);
}
#[test]
fn test_parse_ack_frame_empty_ranges() {
let input = [0x02, 42, 0, 0, 0];
let frame = parse_ack_frame(&input).unwrap();
assert_eq!(frame.largest_acknowledged, 42);
assert_eq!(frame.ack_delay, 0);
assert_eq!(frame.ack_range_count, 0);
assert_eq!(frame.first_ack_range, 0);
}
#[test]
fn test_parse_ack_frame_with_ranges() {
let input = [
0x02, 50, 0x43, 0xE8, 1, 10, 5, 3, ];
let frame = parse_ack_frame(&input).unwrap();
assert_eq!(frame.largest_acknowledged, 50);
assert_eq!(frame.ack_delay, 1000);
assert_eq!(frame.ack_range_count, 1);
assert_eq!(frame.first_ack_range, 10);
assert_eq!(frame.ranges[0], 30);
}
#[test]
fn test_parse_ack_frame_too_short() {
let input = [0x02, 1, 2];
assert!(parse_ack_frame(&input).is_none());
}
#[test]
fn test_parse_ack_frame_exceeds_max_ranges() {
let input = [0x02, 100, 0, 9, 50];
assert!(parse_ack_frame(&input).is_none());
}
#[test]
fn test_parse_stream_frame_basic() {
let input = [0x08, 0, b'h', b'e', b'l', b'l', b'o'];
let frame = parse_stream_frame(&input).unwrap();
assert_eq!(frame.stream_id, 0);
assert_eq!(frame.offset, 0);
assert_eq!(frame.length, 5);
assert!(!frame.fin);
assert_eq!(&frame.data[..5], b"hello");
}
#[test]
fn test_parse_stream_frame_with_offset_and_fin() {
let input = [
0x0F, 4, 0x43, 0xE8, 3, b'a', b'b', b'c', ];
let frame = parse_stream_frame(&input).unwrap();
assert_eq!(frame.stream_id, 4);
assert_eq!(frame.offset, 1000);
assert_eq!(frame.length, 3);
assert!(frame.fin);
assert_eq!(&frame.data[..3], b"abc");
}
#[test]
fn test_parse_stream_frame_too_short() {
let input = [0x08];
assert!(parse_stream_frame(&input).is_none());
}
#[test]
fn test_parse_stream_frame_exceeds_max_data() {
let input = [
0x0A, 0, 0x44, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, ];
assert!(parse_stream_frame(&input).is_none());
}
#[test]
fn test_parse_connection_close_frame() {
let input = [
0x1C, 0x41, 0x00, 4, b't', b'e', b's', b't', ];
let frame = parse_connection_close_frame(&input).unwrap();
assert_eq!(frame.error_code, 256);
assert_eq!(frame.reason_len, 4);
assert_eq!(&frame.reason_phrase[..4], b"test");
assert!(!frame.is_app_close);
}
#[test]
fn test_parse_connection_close_frame_app_close() {
let input = [0x1D, 50, 0];
let frame = parse_connection_close_frame(&input).unwrap();
assert_eq!(frame.error_code, 50);
assert_eq!(frame.reason_len, 0);
assert!(frame.is_app_close);
}
#[test]
fn test_parse_connection_close_frame_too_short() {
let input = [0x1C, 1];
assert!(parse_connection_close_frame(&input).is_none());
}
#[test]
fn test_parse_connection_close_frame_reason_too_long() {
let mut input = vec![0u8; 5];
input[0] = 0x1C;
input[1] = 0; input[2] = 0xC0; input[3] = 0x80;
input[4] = 0x01;
assert!(parse_connection_close_frame(&input).is_none());
}
#[test]
fn test_parse_rst_stream_frame() {
let input = [
0x04, 10, 0x41, 0x01, ];
let frame = parse_rst_stream_frame(&input).unwrap();
assert_eq!(frame.stream_id, 10);
assert_eq!(frame.error_code, 257);
}
#[test]
fn test_parse_rst_stream_frame_too_short() {
let input = [0x04, 5];
assert!(parse_rst_stream_frame(&input).is_none());
}
#[test]
fn test_parse_max_data_frame() {
let input = [0x10, 0x43, 0xE8];
let frame = parse_max_data_frame(&input).unwrap();
assert_eq!(frame.max_data, 1000);
}
#[test]
fn test_parse_max_data_frame_simple() {
let input = [0x10, 50];
let frame = parse_max_data_frame(&input).unwrap();
assert_eq!(frame.max_data, 50);
}
#[test]
fn test_parse_max_data_frame_too_short() {
let input = [0x10];
assert!(parse_max_data_frame(&input).is_none());
}
#[test]
fn test_parse_max_stream_data_frame() {
let input = [
0x11, 5, 0x47, 0xD0, ];
let frame = parse_max_stream_data_frame(&input).unwrap();
assert_eq!(frame.stream_id, 5);
assert_eq!(frame.max_stream_data, 2000);
}
#[test]
fn test_parse_max_stream_data_frame_too_short() {
let input = [0x11, 1];
assert!(parse_max_stream_data_frame(&input).is_none());
}
#[test]
fn test_congestion_controller_initial() {
let cc = CongestionController::new();
assert_eq!(cc.cwnd, CongestionController::INITIAL_CWND);
assert_eq!(cc.in_flight, 0);
assert_eq!(cc.state(), CongestionState::SlowStart);
assert_eq!(cc.available_bytes(), CongestionController::INITIAL_CWND);
}
#[test]
fn test_congestion_controller_default() {
let cc = CongestionController::default();
assert_eq!(cc.cwnd, CongestionController::INITIAL_CWND);
assert_eq!(cc.state(), CongestionState::SlowStart);
}
#[test]
fn test_congestion_send_and_available() {
let mut cc = CongestionController::new();
cc.on_send(5000);
assert_eq!(cc.in_flight, 5000);
assert_eq!(cc.available_bytes(), CongestionController::INITIAL_CWND - 5000);
}
#[test]
fn test_congestion_slow_start() {
let mut cc = CongestionController::new();
let initial_cwnd = cc.cwnd;
cc.on_ack(1460, 100_000); assert!(cc.cwnd > initial_cwnd);
assert_eq!(cc.state(), CongestionState::SlowStart);
}
#[test]
fn test_congestion_congestion_avoidance() {
let mut cc = CongestionController::new();
cc.on_send(20_000);
cc.on_loss(15_000);
cc.on_ack(5000, 100_000); assert_eq!(cc.state(), CongestionState::CongestionAvoidance);
}
#[test]
fn test_congestion_loss() {
let mut cc = CongestionController::new();
cc.on_send(14_600); cc.on_loss(7_300);
assert_eq!(cc.state(), CongestionState::Recovery);
assert!(cc.cwnd < CongestionController::INITIAL_CWND);
assert_eq!(cc.in_flight, 7_300);
}
#[test]
fn test_congestion_timeout() {
let mut cc = CongestionController::new();
cc.on_send(10_000);
cc.on_timeout();
assert_eq!(cc.state(), CongestionState::SlowStart);
assert_eq!(cc.cwnd, CongestionController::INITIAL_CWND);
assert_eq!(cc.in_flight, 0);
assert_eq!(cc.pto_count, 1);
cc.on_timeout();
assert_eq!(cc.pto_count, 2);
}
#[test]
fn test_congestion_pto_calculation() {
let mut cc = CongestionController::new();
assert_eq!(cc.pto_us(), 100_000);
cc.on_ack(1460, 50_000); assert!(cc.srtt > 0);
let pto = cc.pto_us();
assert!(pto > 50_000);
cc.on_timeout();
let pto2 = cc.pto_us();
assert!(pto2 > pto); }
#[test]
fn test_congestion_pto_reset() {
let mut cc = CongestionController::new();
cc.on_timeout();
cc.on_timeout();
assert_eq!(cc.pto_count, 2);
cc.reset_pto();
assert_eq!(cc.pto_count, 0);
}
#[test]
fn test_congestion_recovery_to_avoidance() {
let mut cc = CongestionController::new();
cc.on_send(5000);
cc.on_loss(2000);
assert_eq!(cc.state(), CongestionState::Recovery);
cc.on_ack(3000, 100_000);
assert_eq!(cc.in_flight, 0);
assert_eq!(cc.state(), CongestionState::CongestionAvoidance);
}
#[test]
fn test_congestion_max_cwnd_limit() {
let mut cc = CongestionController::new();
for _ in 0..10000 {
cc.on_ack(1460, 50_000);
}
assert!(cc.cwnd <= CongestionController::MAX_CWND);
}
#[test]
fn test_constant_time_eq_empty_slices() {
assert!(constant_time_eq(b"", b""));
}
#[test]
fn test_constant_time_eq_single_byte() {
assert!(constant_time_eq(b"A", b"A"));
assert!(!constant_time_eq(b"A", b"B"));
}
#[test]
fn test_constant_time_eq_first_byte_diff() {
assert!(!constant_time_eq(b"abc", b"xbc"));
}
#[test]
fn test_constant_time_eq_middle_byte_diff() {
assert!(!constant_time_eq(b"abcdef", b"abXdef"));
}
#[test]
fn test_constant_time_eq_last_byte_diff() {
assert!(!constant_time_eq(b"abcdef", b"abcdeX"));
}
#[test]
fn test_constant_time_eq_all_bytes_max() {
let a = [0xFFu8; 32];
let b = [0xFFu8; 32];
assert!(constant_time_eq(&a, &b));
}
#[test]
fn test_constant_time_eq_all_bytes_zero() {
let a = [0x00u8; 32];
let b = [0x00u8; 32];
assert!(constant_time_eq(&a, &b));
}
#[test]
fn test_constant_time_eq_zeros_vs_ones() {
let a = [0x00u8; 32];
let b = [0x01u8; 32];
assert!(!constant_time_eq(&a, &b));
}
#[test]
fn test_constant_time_eq_left_shorter() {
assert!(!constant_time_eq(b"ab", b"abc"));
}
#[test]
fn test_constant_time_eq_right_shorter() {
assert!(!constant_time_eq(b"abc", b"ab"));
}
#[test]
fn test_dcid_len_zero() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[],
dcid: &[],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
assert_eq!(c.dcid_len, 0);
assert_eq!(c.scid_len, 0);
let found = conns.find_by_dcid(&[]);
assert_eq!(found, None);
conns.release(idx);
}
#[test]
fn test_dcid_len_boundary_20() {
let dcid = [0xAAu8; 20];
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[],
dcid: &dcid,
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
assert_eq!(c.dcid_len, 20);
let found = conns.find_by_dcid(&dcid);
assert_eq!(found, Some(idx));
conns.release(idx);
}
#[test]
fn test_dcid_len_one_byte() {
let dcid = [0x42u8; 1];
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[],
dcid: &dcid,
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let found = conns.find_by_dcid(&[0x42]);
assert_eq!(found, Some(idx));
assert_eq!(conns.find_by_dcid(&[0x43]), None);
conns.release(idx);
}
#[test]
fn test_quic_header_copy() {
let hdr = QuicHeader::empty();
let hdr2 = hdr;
assert_eq!(hdr2.header_type, QuicHeaderType::Short);
}
#[test]
fn test_quic_long_frame_types() {
assert_eq!(LongFrameType::Initial as u8, 0);
assert_eq!(LongFrameType::Handshake as u8, 1);
assert_eq!(LongFrameType::ZeroRtt as u8, 2);
}
#[test]
fn test_quic_conn_state_transitions() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
assert_eq!(c.state, QuicConnState::Initial);
c.state = QuicConnState::Handshake;
assert_eq!(c.state, QuicConnState::Handshake);
c.state = QuicConnState::Established;
assert_eq!(c.state, QuicConnState::Established);
assert!(c.can_send_data());
c.start_close();
assert_eq!(c.state, QuicConnState::Closing);
c.finish_close();
assert!(c.is_closed());
conns.release(idx);
}
#[test]
fn test_quic_stream_id_bidirectional() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
let sid1 = c.alloc_local_stream_id(false).unwrap();
assert_eq!(sid1, 0);
let sid2 = c.alloc_local_stream_id(false).unwrap();
assert_eq!(sid2, 4);
let sid3 = c.alloc_local_stream_id(false).unwrap();
assert_eq!(sid3, 8);
conns.release(idx);
}
#[test]
fn test_quic_stream_id_unidirectional() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
let sid1 = c.alloc_local_stream_id(true).unwrap();
assert_eq!(sid1, 2);
let sid2 = c.alloc_local_stream_id(true).unwrap();
assert_eq!(sid2, 6);
conns.release(idx);
}
#[test]
fn test_congestion_state_enum() {
let states = vec![
CongestionState::SlowStart,
CongestionState::CongestionAvoidance,
CongestionState::Recovery,
];
for state in states {
let _ = format!("{:?}", state);
}
}
#[test]
fn test_quic_conn_table_capacity() {
let conns = QuicConnectionTable::new(4);
assert!(conns.capacity() >= 4);
}
#[test]
fn test_quic_conn_active_count() {
let mut conns = QuicConnectionTable::new(16);
assert_eq!(conns.active_count(), 0);
let idx1 = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
assert_eq!(conns.active_count(), 1);
let idx2 = conns
.allocate(QuicConnParams {
scid: &[3u8],
dcid: &[4u8],
remote_addr: IpAddr::V4([10, 0, 0, 2]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
assert_eq!(conns.active_count(), 2);
conns.release(idx1);
assert_eq!(conns.active_count(), 1);
conns.release(idx2);
assert_eq!(conns.active_count(), 0);
}
#[test]
fn test_verify_token_zero_attempts() {
let mut conns = QuicConnectionTable::new(4);
let idx = conns
.allocate(QuicConnParams {
scid: &[1u8],
dcid: &[2u8],
remote_addr: IpAddr::V4([10, 0, 0, 1]),
remote_port: 0,
local_addr: IpAddr::V4([127, 0, 0, 1]),
local_port: 8443,
ip_version: IpVersion::V4,
})
.unwrap();
let c = conns.get(idx).unwrap();
c.retry_token[..4].copy_from_slice(b"test");
c.retry_token_len = 4;
assert_eq!(c.token_attempts, 0);
assert!(!c.address_verified);
conns.release(idx);
}
#[test]
fn test_path_challenge_frame_type() {
assert_eq!(QuicFrameType::from_byte(0x1a), Some(QuicFrameType::PathChallenge));
assert_eq!(QuicFrameType::from_byte(0x1b), Some(QuicFrameType::PathResponse));
}
#[test]
fn test_path_challenge_encode_decode() {
let frame = PathChallengeFrame::new(0xDEADBEEFCAFEBABE);
let mut buf = [0u8; 16];
let n = frame.encode(&mut buf).unwrap();
assert_eq!(n, 9);
assert_eq!(buf[0], 0x1a);
let decoded = parse_path_challenge_frame(&buf[..n]).unwrap();
assert_eq!(decoded.data, 0xDEADBEEFCAFEBABE);
assert_eq!(decoded, frame);
}
#[test]
fn test_path_response_encode_decode() {
let frame = PathResponseFrame::new(0x0123456789ABCDEF);
let mut buf = [0u8; 16];
let n = frame.encode(&mut buf).unwrap();
assert_eq!(n, 9);
assert_eq!(buf[0], 0x1b);
let decoded = parse_path_response_frame(&buf[..n]).unwrap();
assert_eq!(decoded.data, 0x0123456789ABCDEF);
assert_eq!(decoded, frame);
}
#[test]
fn test_parse_path_challenge_too_short() {
assert!(parse_path_challenge_frame(&[0x1a, 0x01, 0x02]).is_none());
assert!(parse_path_challenge_frame(&[0x00; 9]).is_none()); }
#[test]
fn test_path_migration_initiate_and_validate() {
let mut conn = QuicConnection::new(
&[1, 2, 3, 4],
&[5, 6, 7, 8],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
assert!(!conn.is_migration_in_progress());
assert_eq!(conn.path_validation, PathValidationState::Idle);
let challenge = conn.initiate_path_migration(
IpAddr::V4([192, 168, 1, 100]),
5443,
0xAAAABBBBCCCCDDDD,
);
assert!(conn.is_migration_in_progress());
assert_eq!(conn.path_validation, PathValidationState::ChallengeSent);
assert_eq!(challenge.data, 0xAAAABBBBCCCCDDDD);
let ok = conn.handle_path_response(0xAAAABBBBCCCCDDDD);
assert!(ok);
assert_eq!(conn.path_validation, PathValidationState::Validated);
assert_eq!(conn.remote_addr, IpAddr::V4([192, 168, 1, 100]));
assert_eq!(conn.remote_port, 5443);
}
#[test]
fn test_path_migration_wrong_response_rejected() {
let mut conn = QuicConnection::new(
&[1, 2, 3, 4],
&[5, 6, 7, 8],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
conn.initiate_path_migration(
IpAddr::V4([192, 168, 1, 100]),
5443,
0xAAAABBBBCCCCDDDD,
);
let ok = conn.handle_path_response(0x1111222233334444);
assert!(!ok);
assert_eq!(conn.path_validation, PathValidationState::Failed);
assert_eq!(conn.remote_addr, IpAddr::V4([10, 0, 0, 1]));
assert_eq!(conn.remote_port, 4433);
}
#[test]
fn test_path_response_without_challenge_ignored() {
let mut conn = QuicConnection::new(
&[1],
&[2],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
let ok = conn.handle_path_response(0xDEADBEEF);
assert!(!ok);
assert_eq!(conn.path_validation, PathValidationState::Idle);
}
#[test]
fn test_handle_path_challenge_returns_response() {
let conn = QuicConnection::new(
&[1],
&[2],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
let response = conn.handle_path_challenge(0xCAFEBABE12345678);
assert_eq!(response.data, 0xCAFEBABE12345678);
}
#[test]
fn test_migration_anti_amplification() {
let mut conn = QuicConnection::new(
&[1],
&[2],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
conn.initiate_path_migration(
IpAddr::V4([192, 168, 1, 100]),
5443,
0xAAAABBBBCCCCDDDD,
);
conn.record_migration_rx_bytes(100);
conn.record_migration_tx_bytes(299);
assert!(!conn.migration_amplification_limit_reached());
conn.record_migration_tx_bytes(1);
assert!(conn.migration_amplification_limit_reached());
}
#[test]
fn test_reset_path_validation() {
let mut conn = QuicConnection::new(
&[1],
&[2],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
conn.initiate_path_migration(
IpAddr::V4([192, 168, 1, 100]),
5443,
0xAAAABBBBCCCCDDDD,
);
conn.record_migration_tx_bytes(500);
conn.record_migration_rx_bytes(200);
conn.reset_path_validation();
assert_eq!(conn.path_validation, PathValidationState::Idle);
assert_eq!(conn.path_challenge_data, 0);
assert_eq!(conn.migration_bytes_sent, 0);
assert_eq!(conn.migration_bytes_received, 0);
}
#[test]
fn test_remote_idx_no_collision_across_types() {
assert_ne!(QuicStream::remote_idx(0), QuicStream::remote_idx(1));
assert_ne!(QuicStream::remote_idx(0), QuicStream::remote_idx(2));
assert_ne!(QuicStream::remote_idx(0), QuicStream::remote_idx(3));
assert_ne!(QuicStream::remote_idx(1), QuicStream::remote_idx(2));
assert_ne!(QuicStream::remote_idx(0), QuicStream::remote_idx(2));
assert_eq!(QuicStream::remote_idx(4) - QuicStream::remote_idx(0), 4);
}
#[test]
fn test_alloc_local_bidi_then_uni_no_conflict() {
let mut conn = QuicConnection::new(
&[1],
&[2],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
let bidi = conn.alloc_local_stream_id(false).unwrap();
let uni = conn.alloc_local_stream_id(true).unwrap();
assert_eq!(bidi, 0);
assert_eq!(uni, 2);
{
let b = conn.get_local_stream(bidi).unwrap();
assert_eq!(b.stream_id, 0);
assert!(!b.is_uni);
}
{
let u = conn.get_local_stream(uni).unwrap();
assert_eq!(u.stream_id, 2);
assert!(u.is_uni);
}
}
#[test]
fn test_remote_stream_slot_bounds_check_none() {
let mut conn = QuicConnection::new(
&[1],
&[2],
IpAddr::V4([10, 0, 0, 1]),
4433,
IpAddr::V4([10, 0, 0, 2]),
8443,
IpVersion::V4,
);
let overflow = conn.streams.len() as u64;
assert!(conn.get_or_create_remote_stream(overflow).is_none());
assert!(conn.get_local_stream(overflow).is_none());
assert!(conn.get_or_create_remote_stream(4).is_some());
}
#[test]
fn test_frame_type_stop_sending() {
assert_eq!(QuicFrameType::from_byte(0x04), Some(QuicFrameType::RstStream));
assert_eq!(QuicFrameType::from_byte(0x05), Some(QuicFrameType::StopSending));
assert_ne!(
QuicFrameType::from_byte(0x05),
Some(QuicFrameType::RstStream)
);
}
#[test]
fn test_parse_stop_sending_frame() {
let input = [
0x05, 10, 0x41, 0x01, ];
let frame = parse_stop_sending_frame(&input);
assert!(frame.is_some(), "STOP_SENDING 帧应解析成功");
let frame = frame.unwrap();
assert_eq!(frame.stream_id, 10);
assert_eq!(frame.error_code, 257);
}
#[test]
fn test_parse_stop_sending_frame_too_short() {
assert!(parse_stop_sending_frame(&[0x05, 5]).is_none());
}
}