#![deny(unsafe_code)]
#![deny(missing_debug_implementations)]
#![warn(missing_docs)]
const MAX_PENDING_1RTT: usize = 1024;
use rustc_hash::FxHashMap;
use std::net::{SocketAddr, UdpSocket};
use std::sync::Arc;
use zenith_api::{normalize, CanonicalRequest, CanonicalResponse, Transport};
use zenith_http3::connection::{
FrameAction, Http3Connection, Http3ConnectionConfig,
};
use zenith_http3::frame::{Frame, Http3Error};
use zenith_http3::StreamPriority;
use zenith_net::transport::{
QuicServerConnection as NetQuicConn, QuicServer as NetQuicServer,
QuicServerConfig as NetQuicConfig, QuicServerError as NetQuicError,
QuicServerState,
};
use zenith_net::{build_connection_close_app, build_connection_close_transport};
use zenith_tls::cert_manager::{CertGeneration, CertRotateError};
use zenith_tls::fingerprint::Ja3Fingerprint;
use zenith_tls::quic::QuicVersion;
#[derive(Debug)]
pub enum QuicTransportError {
Io(std::io::Error),
Cert(CertRotateError),
Quic(String),
}
impl std::fmt::Display for QuicTransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "quic io error: {e}"),
Self::Cert(e) => write!(f, "quic cert error: {e}"),
Self::Quic(m) => write!(f, "quic transport error: {m}"),
}
}
}
impl std::error::Error for QuicTransportError {}
impl From<std::io::Error> for QuicTransportError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl From<CertRotateError> for QuicTransportError {
fn from(e: CertRotateError) -> Self {
Self::Cert(e)
}
}
impl From<NetQuicError> for QuicTransportError {
fn from(e: NetQuicError) -> Self {
Self::Quic(e.to_string())
}
}
type QuicResponsePackets = Vec<(Vec<u8>, SocketAddr)>;
#[derive(Debug, Clone)]
pub struct QuicServerConfig {
pub bind_addr: SocketAddr,
pub version: QuicVersion,
pub max_connections: usize,
pub idle_timeout_ms: u64,
}
impl Default for QuicServerConfig {
fn default() -> Self {
Self {
bind_addr: SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 443),
version: QuicVersion::V1,
max_connections: 65536,
idle_timeout_ms: 30_000,
}
}
}
#[derive(Debug, Default)]
struct PendingH3Stream {
body: Vec<u8>,
headers_complete: bool,
headers: Vec<(Vec<u8>, Vec<u8>)>,
fin_received: bool,
is_early: bool,
priority: StreamPriority,
}
fn send_h3_response(
conn: &mut QuicConn,
stream_id: u64,
resp: &CanonicalResponse,
pri_key: (u8, bool),
scheduled: &mut Vec<((u8, bool), u64, Vec<u8>)>,
) {
match conn.h3_conn.send_response(stream_id, resp) {
Ok(h3_frames) => {
match conn.net_conn.build_stream_packet(stream_id, 0, true, &h3_frames) {
Ok(pkt) => {
conn.response_cache.insert(stream_id, pkt.clone());
scheduled.push((pri_key, stream_id, pkt));
}
Err(e) => {
tracing::debug!("[H3 RESP] stream_id={} build_stream_packet error: {}", stream_id, e);
}
}
}
Err(e) => {
tracing::debug!("[H3 RESP] stream_id={} send_response error: {}", stream_id, e);
}
}
}
fn error_response(status: u16, error: &str, reason: &str) -> CanonicalResponse {
let mut resp = CanonicalResponse::new(status);
let escaped_error = escape_json_string(error);
let escaped_reason = escape_json_string(reason);
resp.set_body(
format!(r#"{{"error":"{escaped_error}","reason":"{escaped_reason}"}}"#).into_bytes(),
);
let _ = resp.add_header(b"content-type", b"application/json");
resp
}
fn escape_json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out
}
fn bind_pending_fingerprint(conn: &mut QuicConn) -> Option<Ja3Fingerprint> {
if conn.fingerprint.is_none()
&& let Some(fp) = conn.net_conn.take_client_fingerprint()
{
tracing::debug!(
ja3 = %fp.ja3_hash, ja4 = %fp.ja4_hash,
"QUIC H3 连接绑定 JA3/JA4 指纹"
);
conn.set_fingerprint(fp.clone());
return Some(fp);
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum H3StreamKind {
Request,
Control,
QpackEncoder,
QpackDecoder,
Unknown,
}
fn classify_stream(stream_id: u64, data: &[u8]) -> H3StreamKind {
let low_bits = stream_id % 4;
match low_bits {
0 => H3StreamKind::Request,
2 => {
if data.is_empty() {
return H3StreamKind::Unknown;
}
match data[0] {
t if t == zenith_http3::stream::STREAM_TYPE_CONTROL as u8 => H3StreamKind::Control,
t if t == zenith_http3::stream::STREAM_TYPE_QPACK_ENCODER as u8 => {
H3StreamKind::QpackEncoder
}
t if t == zenith_http3::stream::STREAM_TYPE_QPACK_DECODER as u8 => {
H3StreamKind::QpackDecoder
}
_ => H3StreamKind::Unknown,
}
}
_ => H3StreamKind::Unknown,
}
}
fn quic_error_code(e: &NetQuicError) -> (u64, u64) {
let msg = e.to_string();
if msg.contains("STREAM_LIMIT_ERROR") {
return (0x04, 0x08); }
if msg.contains("STREAM_STATE_ERROR") {
return (0x05, 0x08); }
if msg.contains("FRAME_SIZE_ERROR") {
return (0x06, 0);
}
if msg.contains("TRANSPORT_PARAMETER_ERROR") {
return (0x08, 0); }
if msg.contains("PROTOCOL_VIOLATION") {
return (0x0A, 0); }
if msg.contains("FRAME_ENCODING_ERROR") {
return (0x07, 0); }
match e {
NetQuicError::FlowControl(_) => (0x03, 0x08), NetQuicError::PacketParse(_) => (0x07, 0), NetQuicError::HandshakeNotComplete => (0x07, 0), NetQuicError::Crypto(_) => (0x0100, 0x06), _ => (0x07, 0), }
}
fn h3_error_code(e: &Http3Error) -> u64 {
match e {
Http3Error::FrameFormatError(msg) => {
if msg.contains("encoder stream") {
return 0x0201; }
if msg.contains("decoder stream") {
return 0x0202; }
if msg.contains("QPACK") || msg.contains("qpack") || msg.contains("static index") {
return 0x0200; }
if msg.contains("SETTINGS reserved") || msg.contains("HTTP/2 settings") {
return 0x0109; }
if msg.contains("MissingSettings") || msg.contains("missing_settings") {
return 0x010A; }
if msg.contains("H3_FRAME_UNEXPECTED") || msg.contains("frame unexpected") {
return 0x0105; }
if msg.contains("pseudo") || msg.contains("pseudo-header") || msg.contains("mandatory") {
return 0x010E; }
if msg.contains("control stream") || msg.contains("closed") {
return 0x0104; }
return 0x0105; }
Http3Error::StreamError(_, code) => {
*code as u64
}
Http3Error::ProtocolError(msg) if msg.contains("connection closed") => {
if msg.contains("connection closed") {
return 0x0105; }
0x0105
}
Http3Error::ProtocolError(msg) => {
if msg.contains("H3_MISSING_SETTINGS") || msg.contains("missing_settings") {
return 0x010A; }
if msg.contains("H3_FRAME_UNEXPECTED") || msg.contains("frame unexpected") {
return 0x0105; }
if msg.contains("H3_SETTINGS_ERROR") || msg.contains("settings error") {
return 0x0109; }
0x0105 }
_ => 0x0102, }
}
fn parse_and_feed_h3_frames(
h3_conn: &mut Http3Connection,
stream_id: u64,
data: &[u8],
partial_buf: &mut Vec<u8>,
) -> Result<Vec<FrameAction>, Http3Error> {
partial_buf.extend_from_slice(data);
let mut actions = Vec::new();
let mut consumed_total = 0usize;
while consumed_total < partial_buf.len() {
match Frame::parse(&partial_buf[consumed_total..]) {
Ok((consumed, None)) => {
consumed_total += consumed;
continue;
}
Ok((consumed, Some(frame))) => {
consumed_total += consumed;
match h3_conn.on_frame(stream_id, frame) {
Ok(action) => actions.push(action),
Err(Http3Error::FrameTooShort) => break,
Err(e) => return Err(e),
}
}
Err(Http3Error::FrameTooShort) => break,
Err(e) => return Err(e),
}
}
if consumed_total > 0 {
partial_buf.drain(..consumed_total);
}
Ok(actions)
}
#[derive(Debug)]
struct MigrationState {
new_addr: SocketAddr,
challenge: [u8; 8],
bytes_received: u64,
bytes_sent: u64,
validated: bool,
}
struct QuicConn {
client_addr: SocketAddr,
net_conn: NetQuicConn,
h3_conn: Http3Connection,
active_streams: FxHashMap<u64, PendingH3Stream>,
pending_control_out: Vec<u8>,
fingerprint: Option<Ja3Fingerprint>,
partial_frame_buf: FxHashMap<u64, Vec<u8>>,
stream_kinds: FxHashMap<u64, H3StreamKind>,
migration: Option<MigrationState>,
priority_hints: FxHashMap<u64, StreamPriority>,
response_cache: FxHashMap<u64, Vec<u8>>,
uni_ids: ServerUniStreamIds,
}
#[derive(Debug)]
struct ServerUniStreamIds {
control: Option<u64>,
qpack_encoder: Option<u64>,
qpack_decoder: Option<u64>,
}
impl ServerUniStreamIds {
#[inline]
fn new() -> Self {
Self { control: None, qpack_encoder: None, qpack_decoder: None }
}
#[inline]
fn control(&mut self) -> Option<u64> {
Some(*self.control.get_or_insert(3))
}
#[inline]
fn qpack_encoder(&mut self) -> Option<u64> {
Some(*self.qpack_encoder.get_or_insert(7))
}
#[inline]
fn qpack_decoder(&mut self) -> Option<u64> {
Some(*self.qpack_decoder.get_or_insert(11))
}
}
impl std::fmt::Debug for QuicConn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QuicConn")
.field("client_addr", &self.client_addr)
.field("net_state", &self.net_conn.state)
.field("h3_state", &self.h3_conn.state())
.field("active_streams", &self.active_streams.len())
.field("handshake_done", &self.net_conn.is_handshake_done())
.finish()
}
}
impl QuicConn {
fn new(client_addr: SocketAddr, net_conn: NetQuicConn) -> Self {
let mut h3_conn = Http3Connection::new(Http3ConnectionConfig::default());
h3_conn.init();
Self {
client_addr,
net_conn,
h3_conn,
active_streams: FxHashMap::default(),
pending_control_out: Vec::new(),
fingerprint: None,
partial_frame_buf: FxHashMap::default(),
stream_kinds: FxHashMap::default(),
migration: None,
priority_hints: FxHashMap::default(),
response_cache: FxHashMap::default(),
uni_ids: ServerUniStreamIds::new(),
}
}
pub fn set_fingerprint(&mut self, fp: Ja3Fingerprint) {
self.fingerprint = Some(fp);
}
#[inline]
fn control_stream_id(&mut self) -> Option<u64> {
self.uni_ids.control()
}
#[inline]
fn qpack_encoder_stream_id(&mut self) -> Option<u64> {
self.uni_ids.qpack_encoder()
}
#[inline]
fn qpack_decoder_stream_id(&mut self) -> Option<u64> {
self.uni_ids.qpack_decoder()
}
fn try_complete_request<F>(
&mut self,
stream_id: u64,
app_handler: &F,
scheduled: &mut Vec<((u8, bool), u64, Vec<u8>)>,
) -> bool
where
F: Fn(CanonicalRequest, Option<&Ja3Fingerprint>) -> CanonicalResponse,
{
let Some(pending) = self.active_streams.get(&stream_id) else {
return false;
};
tracing::debug!(
"[H3 PROC] stream_id={} headers_complete={} body_len={} fin_received={}",
stream_id, pending.headers_complete, pending.body.len(), pending.fin_received
);
if !(pending.headers_complete && (!pending.body.is_empty() || pending.fin_received)) {
return false;
}
let is_early_req = pending.is_early;
let pri_key = pending.priority.schedule_key();
let mut canonical_req = match crate::normalize::normalize_http3_request(
&pending.headers,
Transport::Tls13,
) {
Ok(r) => r,
Err(e) => {
tracing::warn!(
"[H3 NORM] stream_id={} normalize rejected: {}",
stream_id, e
);
let resp = error_response(400, "bad request", "normalize_failed");
send_h3_response(self, stream_id, &resp, pri_key, scheduled);
self.active_streams.remove(&stream_id);
return true;
}
};
canonical_req.set_body(pending.body.clone());
let sni_mismatch = match self.net_conn.sni() {
Some(sni_host) => {
let auth = canonical_req.authority_str();
if auth.is_empty() {
false
} else {
let (auth_no_port_raw, _) = normalize::split_host_port(auth);
let auth_no_port = normalize::unbracket_ipv6(auth_no_port_raw);
if sni_host != auth && sni_host != auth_no_port {
tracing::warn!(
"[H3 SNI] stream_id={} sni={} authority={} mismatch → 421",
stream_id, sni_host, auth
);
true
} else {
false
}
}
}
None => false,
};
if sni_mismatch {
let resp = error_response(421, "misdirected request", "sni_host_mismatch");
send_h3_response(self, stream_id, &resp, pri_key, scheduled);
self.active_streams.remove(&stream_id);
return true;
}
if is_early_req
&& !matches!(
canonical_req.method.as_str(),
"GET" | "HEAD" | "OPTIONS" | "TRACE"
)
{
tracing::warn!(
"[H3 EARLY] stream_id={} non-safe method {:?} rejected (0-RTT anti-replay)",
stream_id, canonical_req.method
);
self.active_streams.remove(&stream_id);
return true;
}
tracing::debug!(
"[H3 RESP] stream_id={} method={} path={} → building response",
stream_id, canonical_req.method.as_str(), canonical_req.path_str()
);
let resp = app_handler(canonical_req, self.fingerprint.as_ref());
tracing::debug!(
"[H3 RESP] stream_id={} status={} body_len={}",
stream_id, resp.status_code, resp.body().len()
);
send_h3_response(self, stream_id, &resp, pri_key, scheduled);
self.active_streams.remove(&stream_id);
true
}
}
struct DcidKey {
key: ring::hmac::Key,
}
impl DcidKey {
fn try_new() -> Option<Self> {
let mut key_bytes = [0u8; 32];
if !zenith_foundation::random::try_fill_random(&mut key_bytes) {
return None;
}
Some(Self {
key: ring::hmac::Key::new(ring::hmac::HMAC_SHA256, &key_bytes),
})
}
#[inline]
fn tag(&self, dcid: &[u8]) -> [u8; 32] {
let t = ring::hmac::sign(&self.key, dcid);
let mut out = [0u8; 32];
out.copy_from_slice(t.as_ref());
out
}
}
pub struct QuicTransportServer {
config: QuicServerConfig,
rustls_config: Option<Arc<zenith_tls::ServerConfig>>,
net_server: Option<NetQuicServer>,
connections: FxHashMap<[u8; 32], QuicConn>,
server_dcid_index: FxHashMap<[u8; 32], [u8; 32]>,
dcid_key: Option<DcidKey>,
pub pending_1rtt: Vec<(Vec<u8>, SocketAddr)>,
last_fingerprint: Option<Ja3Fingerprint>,
}
impl std::fmt::Debug for QuicTransportServer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("QuicTransportServer")
.field("config", &self.config)
.field("connections", &self.connections.len())
.field("pending_1rtt", &self.pending_1rtt.len())
.finish()
}
}
impl QuicTransportServer {
pub fn new(config: QuicServerConfig) -> Self {
let dcid_key = match DcidKey::try_new() {
Some(k) => Some(k),
None => {
tracing::error!(
"DCID 路由密钥生成失败(OS 熵源不可用):连接路由 fail-closed,所有包将被丢弃"
);
None
}
};
Self {
config,
rustls_config: None,
net_server: None,
connections: FxHashMap::default(),
server_dcid_index: FxHashMap::default(),
dcid_key,
pending_1rtt: Vec::new(),
last_fingerprint: None,
}
}
pub fn bind_cert(&mut self, cert_gen: &CertGeneration) -> Result<(), QuicTransportError> {
let rustls_config = cert_gen
.to_server_config(vec![b"h3".to_vec()])
.map_err(QuicTransportError::Cert)?;
self.rustls_config = Some(rustls_config);
Ok(())
}
pub fn bind(&mut self) -> Result<(), QuicTransportError> {
let rustls_config = self
.rustls_config
.clone()
.ok_or_else(|| QuicTransportError::Quic("no rustls config — call bind_cert first".into()))?;
let net_config = NetQuicConfig::new(
self.config.bind_addr,
rustls_config,
self.config.version,
)?;
let net_server = NetQuicServer::bind(net_config)?;
self.net_server = Some(net_server);
Ok(())
}
pub fn local_addr(&self) -> Option<SocketAddr> {
self.net_server.as_ref().and_then(|s| {
s.socket().local_addr().ok()
})
}
#[inline]
pub fn is_socketless(&self) -> bool {
self.net_server.is_none()
}
pub fn active_connections(&self) -> usize {
self.connections.len()
}
pub fn take_last_fingerprint(&mut self) -> Option<Ja3Fingerprint> {
self.last_fingerprint.take()
}
pub fn socket(&self) -> Option<&UdpSocket> {
self.net_server.as_ref().map(|s| s.socket())
}
fn extract_long_dcid(packet: &[u8]) -> Option<&[u8]> {
if packet.len() < 6 {
return None;
}
let dcid_len = packet[5] as usize;
if dcid_len > 20 || 6 + dcid_len > packet.len() {
return None;
}
Some(&packet[6..6 + dcid_len])
}
fn extract_short_dcid(packet: &[u8], dcid_len: usize) -> Option<&[u8]> {
if packet.len() < 1 + dcid_len {
return None;
}
Some(&packet[1..1 + dcid_len])
}
#[inline]
fn dcid_tag(&self, dcid: &[u8]) -> Option<[u8; 32]> {
self.dcid_key.as_ref().map(|k| k.tag(dcid))
}
fn find_conn_by_addr(&self, from: SocketAddr) -> Option<[u8; 32]> {
self.connections
.iter()
.find(|(_, c)| c.client_addr == from)
.map(|(k, _)| *k)
}
fn process_streams<F>(
&mut self,
route_tag: &[u8; 32],
app_handler: &F,
) -> QuicResponsePackets
where
F: Fn(CanonicalRequest, Option<&Ja3Fingerprint>) -> CanonicalResponse,
{
let mut response_packets = Vec::new();
let mut scheduled: Vec<((u8, bool), u64, Vec<u8>)> = Vec::new();
let conn = match self.connections.get_mut(route_tag) {
Some(c) => c,
None => return response_packets,
};
let hs_done = conn.net_conn.handshake_done_sent;
tracing::debug!("[H3 CTRL] check: handshake_done_sent={} h3_state={:?}", hs_done, conn.h3_conn.state());
if hs_done {
match conn.h3_conn.take_server_control_stream_init() {
Ok(Some(ctrl_init)) => {
tracing::debug!("[H3 CTRL] ctrl_init len={} bytes={:02x?}", ctrl_init.len(), &ctrl_init[..ctrl_init.len().min(16)]);
let control_sid = conn.control_stream_id();
let build_result = match control_sid {
Some(sid) => conn.net_conn.build_stream_packet(sid, 0, false, &ctrl_init),
None => {
tracing::error!("[H3 CTRL] server uni stream id space exhausted (fail-closed)");
return response_packets;
}
};
match build_result {
Ok(pkt) => {
tracing::debug!(
"[H3 CTRL] sent control stream init (SETTINGS) on stream_id=3, {}B",
pkt.len()
);
response_packets.push((pkt, conn.client_addr));
}
Err(e) => {
tracing::debug!("[H3 CTRL] failed to send control stream init: {}", e);
}
}
}
Ok(None) => {
tracing::debug!("[H3 CTRL] Ok(None) - settings already sent");
}
Err(e) => {
tracing::debug!("[H3 CTRL] take_server_control_stream_init error: {}", e);
}
}
}
let mut stream_ids: Vec<u64> = conn.net_conn.stream_rx.keys().copied().collect();
let early_ids: Vec<u64> = conn.net_conn.early_stream_rx.keys().copied().collect();
stream_ids.extend(early_ids.iter().copied());
stream_ids.sort_by_key(|sid| if sid % 4 == 2 { 0 } else { 1 });
for stream_id in stream_ids {
let is_early = conn.net_conn.early_stream_rx.contains_key(&stream_id);
let (data_opt, fin_received) = if is_early {
(
conn.net_conn.take_early_stream_data(stream_id),
conn.net_conn.take_early_stream_fin(stream_id),
)
} else {
(
conn.net_conn.take_stream_data(stream_id),
conn.net_conn.take_stream_fin(stream_id),
)
};
let stream_data = match data_opt {
Some(d) if !d.is_empty() => d,
_ => {
if conn.net_conn.dup_stream_sids.contains(&stream_id)
&& let Some(cached) = conn.response_cache.get(&stream_id)
{
tracing::debug!(
"[H3 RETRANS] stream_id={} retransmit cached response {}B",
stream_id, cached.len()
);
response_packets.push((cached.clone(), conn.client_addr));
}
if fin_received {
if let Some(pending) = conn.active_streams.get_mut(&stream_id) {
pending.fin_received = true;
pending.is_early = is_early;
}
}
continue;
}
};
let is_first_slice = !conn.stream_kinds.contains_key(&stream_id);
let kind = if is_first_slice {
let k = classify_stream(stream_id, &stream_data);
if k != H3StreamKind::Unknown {
conn.stream_kinds.insert(stream_id, k);
}
k
} else {
conn.stream_kinds
.get(&stream_id)
.copied()
.unwrap_or(H3StreamKind::Unknown)
};
tracing::debug!(
"[H3 PROC] stream_id={} kind={:?} data_len={} fin={} first_bytes={:02x?}",
stream_id, kind, stream_data.len(), fin_received,
&stream_data[..8.min(stream_data.len())]
);
match kind {
H3StreamKind::Request => {
let partial = conn
.partial_frame_buf
.entry(stream_id)
.or_default();
let actions = match parse_and_feed_h3_frames(
&mut conn.h3_conn,
stream_id,
&stream_data,
partial,
) {
Ok(a) => a,
Err(e) => {
let err_code = if e.to_string().contains("connection closed")
&& conn.h3_conn.last_close_error_code != 0
{
conn.h3_conn.last_close_error_code
} else {
let code = h3_error_code(&e);
if conn.h3_conn.last_close_error_code == 0 {
conn.h3_conn.last_close_error_code = code;
}
code
};
tracing::warn!(
"[H3 PROC] stream_id={} parse error -> CONNECTION_CLOSE: {} (code=0x{:04x})",
stream_id, e, err_code
);
let cc_frame = build_connection_close_app(
err_code,
"h3 frame parse error",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(be) => {
tracing::warn!(
"[H3 PROC] build_1rtt_packet FAILED: {}, trying Initial/Handshake fallback",
be
);
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
};
tracing::debug!("[H3 PROC] stream_id={} actions_count={}", stream_id, actions.len());
for action in actions {
tracing::debug!("[H3 PROC] stream_id={} action={:?}", stream_id, action);
match action {
FrameAction::HeadersReceived { headers, .. } => {
let pending = conn.active_streams.entry(stream_id).or_default();
pending.headers_complete = true;
let header_pri = headers
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(b"priority"))
.map(|(_, v)| zenith_http3::parse_priority_field(v));
pending.priority = conn
.priority_hints
.remove(&stream_id)
.or(header_pri)
.unwrap_or_default();
pending.headers = headers;
pending.body = Vec::new();
pending.fin_received = fin_received;
pending.is_early = is_early;
}
FrameAction::DataReceived { stream_id: sid, data } => {
let pending = conn.active_streams.entry(sid).or_default();
pending.body.extend_from_slice(&data);
pending.fin_received = fin_received;
pending.is_early = is_early;
}
FrameAction::SettingsAck => {
tracing::debug!(
"[H3 CTRL] peer SETTINGS acknowledged (no echo; RFC 9114 §7.2.4)"
);
}
_ => {}
}
}
if conn.try_complete_request(stream_id, app_handler, &mut scheduled) {
continue;
}
}
H3StreamKind::Control => {
if fin_received {
let _ = conn.h3_conn.stream_manager_mut().accept_control_stream(stream_id);
let h3_data = if is_first_slice {
if stream_data.len() > 1 {
&stream_data[1..]
} else {
&[][..]
}
} else {
&stream_data[..]
};
if !h3_data.is_empty() {
let partial = conn.partial_frame_buf.entry(stream_id).or_default();
if let Err(e) = parse_and_feed_h3_frames(&mut conn.h3_conn, stream_id, h3_data, partial) {
tracing::warn!("[H3 CTRL] stream_id={} parse error -> CONNECTION_CLOSE: {}", stream_id, e);
let cc_frame = build_connection_close_app(h3_error_code(&e), "control stream parse error");
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
}
let settings_acked = conn.h3_conn.stream_manager()
.get(stream_id)
.map(|s| s.settings_acked)
.unwrap_or(false);
if !settings_acked {
tracing::warn!(
"[H3 CTRL] stream_id={} closed (FIN) before SETTINGS -> H3_MISSING_SETTINGS",
stream_id
);
let cc_frame = build_connection_close_app(
0x010A, "control stream closed before SETTINGS",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
tracing::warn!(
"[H3 CTRL] stream_id={} closed (FIN) -> H3_CLOSED_CRITICAL_STREAM",
stream_id
);
let cc_frame = build_connection_close_app(
0x0104, "control stream closed",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
let h3_data = if is_first_slice {
if stream_data.len() > 1 {
&stream_data[1..]
} else {
continue;
}
} else {
&stream_data[..]
};
let _ = conn.h3_conn.stream_manager_mut().accept_control_stream(stream_id);
let partial = conn
.partial_frame_buf
.entry(stream_id)
.or_default();
let actions = match parse_and_feed_h3_frames(
&mut conn.h3_conn,
stream_id,
h3_data,
partial,
) {
Ok(a) => a,
Err(e) => {
tracing::warn!(
"[H3 CTRL] stream_id={} parse error -> CONNECTION_CLOSE: {}",
stream_id, e
);
let cc_frame = build_connection_close_app(
h3_error_code(&e),
"control stream parse error",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(be) => {
tracing::warn!(
"[H3 CTRL] build_1rtt_packet FAILED: {}, trying Initial/Handshake fallback",
be
);
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
};
for action in actions {
match action {
FrameAction::SettingsAck => {
tracing::debug!(
"[H3 PROC] peer SETTINGS acknowledged (no echo; RFC 9114 §7.2.4)"
);
}
FrameAction::HeadersReceived { headers, .. } => {
let pending = conn.active_streams.entry(stream_id).or_default();
pending.headers_complete = true;
pending.headers = headers;
}
FrameAction::DataReceived { data, .. } => {
let pending = conn.active_streams.entry(stream_id).or_default();
pending.body.extend_from_slice(&data);
}
FrameAction::PriorityUpdate {
element_id,
priority,
is_push: false,
} => {
match conn.active_streams.get_mut(&element_id) {
Some(pending) => pending.priority = priority,
None => {
conn.priority_hints.insert(element_id, priority);
}
}
tracing::debug!(
"[H3 PRI] stream_id={} urgency={} incremental={}",
element_id, priority.urgency, priority.incremental
);
}
_ => {}
}
}
}
H3StreamKind::QpackEncoder => {
let h3_data = if is_first_slice {
if stream_data.len() > 1 {
&stream_data[1..]
} else {
continue;
}
} else {
&stream_data[..]
};
if is_first_slice
&& let Err(e) = conn.h3_conn.note_peer_qpack_stream(stream_id, true)
{
tracing::warn!("duplicate qpack encoder stream -> CONNECTION_CLOSE: {}", e);
let cc_frame = build_connection_close_app(
h3_error_code(&e),
"qpack encoder stream error",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
let actions = match conn.h3_conn.on_qpack_encoder_stream(h3_data) {
Ok(a) => a,
Err(e) => {
tracing::warn!("qpack encoder stream error -> CONNECTION_CLOSE: {}", e);
let cc_frame = build_connection_close_app(
h3_error_code(&e),
"qpack encoder stream error",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
};
for action in actions {
if let FrameAction::HeadersReceived {
stream_id: unblocked_sid,
headers,
} = action
{
let pending = conn.active_streams.entry(unblocked_sid).or_default();
pending.headers_complete = true;
pending.headers = headers;
if conn.try_complete_request(unblocked_sid, app_handler, &mut scheduled) {
tracing::debug!(
"[H3 QPACK] stream_id={} headers unblocked → request completed",
unblocked_sid
);
}
}
}
}
H3StreamKind::QpackDecoder => {
let h3_data = if is_first_slice {
if stream_data.len() > 1 {
&stream_data[1..]
} else {
continue;
}
} else {
&stream_data[..]
};
if is_first_slice
&& let Err(e) = conn.h3_conn.note_peer_qpack_stream(stream_id, false)
{
tracing::warn!("duplicate qpack decoder stream -> CONNECTION_CLOSE: {}", e);
let cc_frame = build_connection_close_app(
h3_error_code(&e),
"qpack decoder stream error",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
if let Err(e) = conn.h3_conn.on_qpack_decoder_stream(h3_data) {
tracing::warn!("qpack decoder stream error -> CONNECTION_CLOSE: {}", e);
let cc_frame = build_connection_close_app(
h3_error_code(&e),
"qpack decoder stream error",
);
match conn.net_conn.build_1rtt_packet(&cc_frame) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(_) => {
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
response_packets.push((pkt, conn.client_addr));
}
}
}
return response_packets;
}
}
H3StreamKind::Unknown => {
}
}
}
if !scheduled.is_empty() {
scheduled.sort_by_key(|(key, _, _)| *key);
let addr = conn.client_addr;
for (_, _, pkt) in scheduled.drain(..) {
response_packets.push((pkt, addr));
}
}
if !conn.pending_control_out.is_empty() {
let data = conn.pending_control_out.clone();
conn.pending_control_out.clear();
match conn.control_stream_id() {
Some(sid) => match conn.net_conn.build_stream_packet(sid, 0, false, &data) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(e) => tracing::warn!("control stream build error: {}", e),
},
None => tracing::error!("control stream id exhausted (fail-closed, frame dropped)"),
}
}
if hs_done {
let (enc_instructions, dec_instructions) = conn.h3_conn.take_qpack_stream_instructions();
if !enc_instructions.is_empty() {
match conn.qpack_encoder_stream_id() {
Some(sid) => match conn.net_conn.build_stream_packet(sid, 0, false, &enc_instructions) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(e) => tracing::warn!("qpack encoder instructions error: {}", e),
},
None => tracing::error!("qpack encoder stream id exhausted (fail-closed)"),
}
}
if !dec_instructions.is_empty() {
match conn.qpack_decoder_stream_id() {
Some(sid) => match conn.net_conn.build_stream_packet(sid, 0, false, &dec_instructions) {
Ok(pkt) => response_packets.push((pkt, conn.client_addr)),
Err(e) => tracing::warn!("qpack decoder instructions error: {}", e),
},
None => tracing::error!("qpack decoder stream id exhausted (fail-closed)"),
}
}
}
conn.h3_conn.stream_manager_mut().purge_closed();
conn.net_conn.dup_stream_sids.clear();
response_packets
}
fn create_connection(
&mut self,
client_addr: SocketAddr,
client_scid: Vec<u8>,
client_dcid: Vec<u8>,
server_scid: Vec<u8>,
rustls_config: &Arc<zenith_tls::ServerConfig>,
) -> Result<[u8; 32], QuicTransportError> {
if self.connections.len() >= self.config.max_connections {
tracing::warn!(
"QUIC 连接数达上限 {max},拒绝新连接 from {client_addr}",
max = self.config.max_connections,
);
return Err(QuicTransportError::Quic(
"max_connections reached, refusing new connection".into(),
));
}
let net_conn = NetQuicConn::new(
client_addr,
client_scid,
client_dcid.clone(),
server_scid,
rustls_config.clone(),
self.config.version,
)?;
let server_dcid = net_conn.server_dcid.clone();
let bridge_conn = QuicConn::new(client_addr, net_conn);
let client_tag = self.dcid_tag(&client_dcid).ok_or_else(|| {
QuicTransportError::Quic("DCID 路由密钥不可用(熵源失败),拒绝新连接".into())
})?;
let server_tag = self.dcid_tag(&server_dcid).ok_or_else(|| {
QuicTransportError::Quic("DCID 路由密钥不可用(熵源失败),拒绝新连接".into())
})?;
self.connections.insert(client_tag, bridge_conn);
self.server_dcid_index.insert(server_tag, client_tag);
Ok(client_tag)
}
pub fn handle_packet<F>(
&mut self,
data: &[u8],
from: SocketAddr,
app_handler: &F,
) -> Result<QuicResponsePackets, QuicTransportError>
where
F: Fn(CanonicalRequest, Option<&Ja3Fingerprint>) -> CanonicalResponse,
{
if data.is_empty() {
return Ok(Vec::new());
}
let is_long = (data[0] & 0x80) != 0;
tracing::debug!("recv {}B from {} first_byte=0x{:02x} long={}", data.len(), from, data[0], is_long);
if is_long {
if let Some(dcid) = Self::extract_long_dcid(data) {
tracing::debug!("long header dcid={:02x?} len={}", dcid, dcid.len());
let Some(dcid_tag) = self.dcid_tag(dcid) else {
tracing::error!("DCID 路由密钥不可用,丢弃 long header 数据包");
return Ok(Vec::new());
};
let route_key = if self.connections.contains_key(&dcid_tag) {
dcid_tag
} else if let Some(client_tag) = self.server_dcid_index.get(&dcid_tag) {
tracing::debug!(
"long header: dcid={:02x?} -> client_dcid tag matched (via server_dcid_index)",
dcid
);
*client_tag
} else {
dcid_tag
};
if self.connections.contains_key(&route_key) {
tracing::debug!("existing connection found");
let first_end = zenith_net::transport::quic_server::parse_long_header_full(data)
.map(|p| p.pn_offset + p.length as usize)
.unwrap_or(data.len());
let Some(conn) = self.connections.get_mut(&route_key) else {
return Ok(Vec::new());
};
let client_addr = conn.client_addr;
let output = match data[0] & 0x30 {
0x00 => {
match conn.net_conn.handle_initial_packet(data) {
Ok(pkts) => pkts,
Err(e) => {
tracing::warn!(
"handle_initial_packet error -> CONNECTION_CLOSE: {}",
e
);
let (error_code, frame_type) = quic_error_code(&e);
let cc_frame = build_connection_close_transport(
error_code,
frame_type,
"initial error",
);
let mut err_pkts: QuicResponsePackets = Vec::new();
let i_r = conn.net_conn.build_initial_cc_packet(&cc_frame);
if let Err(ref e) = i_r {
tracing::warn!("[CC INITIAL] build err: {}", e);
}
if let Ok(pkt) = i_r {
err_pkts.push((pkt, client_addr));
}
let h_r = conn.net_conn.build_handshake_cc_packet(&cc_frame);
if let Err(ref e) = h_r {
tracing::warn!("[CC HANDSHAKE] build err: {}", e);
}
if let Ok(pkt) = h_r {
err_pkts.push((pkt, client_addr));
}
let r_r = conn.net_conn.build_1rtt_packet(&cc_frame);
if let Err(ref e) = r_r {
tracing::warn!("[CC 1RTT] build err: {}", e);
}
if let Ok(pkt) = r_r {
err_pkts.push((pkt, client_addr));
}
tracing::info!("[CC TOTAL] sending {} packets to {}", err_pkts.len(), client_addr);
return Ok(err_pkts);
}
}
}
0x10 => {
match conn.net_conn.handle_0rtt_packet(data) {
Ok(pkts) => pkts,
Err(e) => {
tracing::debug!("handle_0rtt_packet error: {}", e);
Vec::new()
}
}
}
_ => {
match conn.net_conn.handle_handshake_packet(data) {
Ok(pkts) => pkts,
Err(e) => {
tracing::warn!(
"handle_handshake_packet error -> CONNECTION_CLOSE: {}",
e
);
let (mut error_code, frame_type) = quic_error_code(&e);
if matches!(e, NetQuicError::Crypto(_)) {
let alert_code = conn.net_conn.peer_alert_code();
let errmsg = e.to_string();
tracing::warn!("[TLS DIAG HS] rustls error: {} (alert=0x{alert_code:x})", errmsg);
let (adj, fix) = if errmsg.contains("unexpected message")
|| errmsg.contains("KeyUpdate")
|| errmsg.contains("EndOfEarlyData")
|| errmsg.contains("unexpected_message")
|| errmsg.contains("received unexpected")
{
(0x010A, true)
} else if errmsg.contains("MissingQuicTransportParameters")
|| errmsg.contains("missing_extension")
|| errmsg.contains("MissingExtension")
{
(0x016D, true)
} else {
(alert_code, false)
};
error_code = if fix { adj } else { alert_code };
}
let cc_frame = build_connection_close_transport(
error_code,
frame_type,
"handshake error",
);
let client_addr = conn.client_addr;
let mut err_pkts: QuicResponsePackets = Vec::new();
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
err_pkts.push((pkt, client_addr));
}
if let Ok(pkt) = conn.net_conn.build_1rtt_packet(&cc_frame) {
err_pkts.push((pkt, client_addr));
}
return Ok(err_pkts);
}
}
}
};
if let Some(fp) = bind_pending_fingerprint(conn) {
self.last_fingerprint = Some(fp);
}
tracing::debug!("existing conn produced {} response packets", output.len());
let mut all_output: QuicResponsePackets =
output.into_iter().map(|p| (p, client_addr)).collect();
let mut hs_was_done = self
.connections
.get(&route_key)
.map(|c| c.net_conn.handshake_done_sent)
.unwrap_or(false);
let mut deferred_1rtt: Vec<(Vec<u8>, SocketAddr)> = Vec::new();
let mut off = first_end;
while off < data.len() {
let remaining = &data[off..];
let is_long = (remaining[0] & 0x80) != 0;
if is_long {
let pkt_end = zenith_net::transport::quic_server::parse_long_header_full(remaining)
.map(|p| p.pn_offset + p.length as usize)
.unwrap_or(remaining.len());
tracing::debug!("coalesced long header: {}B, pkt_end={}", remaining.len(), pkt_end);
let Some(conn) = self.connections.get_mut(&route_key) else {
break;
};
let addr = conn.client_addr;
let mut conn_new_fp: Option<Ja3Fingerprint> = None;
let out = match remaining[0] & 0x30 {
0x00 => {
let out = conn.net_conn.handle_initial_packet(remaining)?;
conn_new_fp = bind_pending_fingerprint(conn);
out
}
0x10 => match conn.net_conn.handle_0rtt_packet(remaining) {
Ok(pkts) => pkts,
Err(e) => {
tracing::debug!("coalesced 0-RTT error: {}", e);
Vec::new()
}
},
_ => conn.net_conn.handle_handshake_packet(remaining)?,
};
let hs_now = conn.net_conn.handshake_done_sent;
if let Some(fp) = conn_new_fp {
self.last_fingerprint = Some(fp);
}
tracing::debug!("coalesced long produced {} packets", out.len());
all_output.extend(out.into_iter().map(|p| (p, addr)));
if hs_now && !hs_was_done {
if off + pkt_end < data.len() {
deferred_1rtt.push((data[off + pkt_end..].to_vec(), from));
}
break;
}
hs_was_done = hs_now;
off += pkt_end;
} else {
tracing::debug!("coalesced short header: {}B", remaining.len());
let Some(conn) = self.connections.get_mut(&route_key) else {
break;
};
let addr = conn.client_addr;
if !conn.net_conn.handshake_done_sent {
tracing::debug!(
"coalesced short 在握手完成前到达,推迟到 pending_1rtt"
);
if self.pending_1rtt.len() >= MAX_PENDING_1RTT {
self.pending_1rtt.remove(0);
}
self.pending_1rtt.push((remaining.to_vec(), from));
break;
}
match conn.net_conn.handle_short_packet(remaining) {
Ok(out) => {
tracing::debug!("coalesced short produced {} packets", out.len());
all_output.extend(out.into_iter().map(|p| (p, addr)));
}
Err(e) => tracing::debug!("coalesced short error: {}", e),
}
break; }
}
let handshake_just_completed = !deferred_1rtt.is_empty();
if !deferred_1rtt.is_empty() {
let n = deferred_1rtt.len();
self.pending_1rtt.extend(deferred_1rtt);
tracing::debug!(
"handshake just completed; deferred {n} coalesced 1-RTT sub-packet(s) to pending_1rtt for immediate HANDSHAKE_DONE flush"
);
}
let h3_output = if handshake_just_completed {
Vec::new()
} else {
self.process_streams(&route_key, app_handler)
};
tracing::debug!("h3 produced {} response packets", h3_output.len());
all_output.extend(h3_output);
return Ok(all_output);
} else {
tracing::debug!("new connection - creating");
if data.len() >= 6 {
let dcid_len = data[5] as usize;
let dcid_end = 6 + dcid_len;
if dcid_end < data.len() {
let scid_len = data[dcid_end] as usize;
let scid_start = dcid_end + 1;
let scid_end = scid_start + scid_len;
if scid_end <= data.len() {
let client_dcid = data[6..dcid_end].to_vec();
let client_scid = data[scid_start..scid_end].to_vec();
let server_scid = {
let mut scid = vec![0u8; 8];
if !zenith_foundation::random::try_fill_random(&mut scid) {
return Err(QuicTransportError::Quic(
"SCID random generation failed".into(),
));
}
if scid.iter().all(|b| *b == 0) {
scid[0] = 0x01;
}
scid
};
let rustls_config = self
.rustls_config
.clone()
.ok_or_else(|| {
tracing::error!("QUIC 新连接到达但 rustls config 未绑定(须先 bind_cert)");
QuicTransportError::Quic("no rustls config".into())
})?;
let route_tag = match self.create_connection(
from,
client_scid,
client_dcid,
server_scid,
&rustls_config,
) {
Ok(tag) => {
tracing::debug!("new connection created, route_tag={:02x?}", tag);
tag
}
Err(e) => {
tracing::debug!("create_connection error: {}", e);
return Ok(Vec::new());
}
};
let Some(conn) = self.connections.get_mut(&route_tag) else {
return Ok(Vec::new());
};
let output = match conn.net_conn.handle_initial_packet(data) {
Ok(o) => {
tracing::debug!("initial packet handled, {} response packets", o.len());
if let Some(fp) = bind_pending_fingerprint(conn) {
self.last_fingerprint = Some(fp);
}
o
}
Err(e) => {
let msg = e.to_string();
let is_tp_error = msg.contains("TRANSPORT_PARAMETER_ERROR");
if matches!(e, NetQuicError::PacketParse(_)) && !is_tp_error {
tracing::debug!(
"new conn handle_initial_packet parse error (silent): {}",
e
);
return Ok(Vec::new());
}
tracing::warn!(
"new conn handle_initial_packet error -> CONNECTION_CLOSE: {}",
e
);
let (mut error_code, frame_type) = quic_error_code(&e);
if matches!(e, NetQuicError::Crypto(_)) {
let alert_code = conn.net_conn.peer_alert_code();
let errmsg = e.to_string();
tracing::warn!("[TLS DIAG] rustls error: {} (alert=0x{alert_code:x})", errmsg);
let (adj_alert, fix) = if errmsg.contains("unexpected message")
|| errmsg.contains("KeyUpdate")
|| errmsg.contains("EndOfEarlyData")
|| errmsg.contains("unexpected_message")
{
(0x010A, true) } else if errmsg.contains("MissingQuicTransportParameters")
|| errmsg.contains("missing_extension")
|| errmsg.contains("MissingExtension")
{
(0x016D, true) } else {
(alert_code, false)
};
error_code = if fix { adj_alert } else { alert_code };
}
let cc_frame = build_connection_close_transport(
error_code,
frame_type,
"initial error",
);
let client_addr = conn.client_addr;
let mut err_pkts: QuicResponsePackets = Vec::new();
if let Ok(pkt) = conn.net_conn.build_initial_cc_packet(&cc_frame) {
err_pkts.push((pkt, client_addr));
}
if let Ok(pkt) = conn.net_conn.build_handshake_cc_packet(&cc_frame) {
err_pkts.push((pkt, client_addr));
}
if let Ok(pkt) = conn.net_conn.build_1rtt_packet(&cc_frame) {
err_pkts.push((pkt, client_addr));
}
return Ok(err_pkts);
}
};
let client_addr = conn.client_addr;
let mut all_output: QuicResponsePackets =
output.into_iter().map(|p| (p, client_addr)).collect();
let h3_output = self.process_streams(&route_tag, app_handler);
all_output.extend(h3_output);
tracing::debug!("handle_packet returning {} packets to send to {}", all_output.len(), client_addr);
return Ok(all_output);
}
}
}
}
}
} else {
let server_dcid_len = self
.connections
.values()
.next()
.map(|c| c.net_conn.server_dcid.len())
.unwrap_or(8);
if let Some(srv_dcid) = Self::extract_short_dcid(data, server_dcid_len) {
let Some(srv_tag) = self.dcid_tag(srv_dcid) else {
tracing::error!("DCID 路由密钥不可用,丢弃 short header 数据包");
return Ok(Vec::new());
};
if let Some(client_tag) = self.server_dcid_index.get(&srv_tag) {
let client_tag = *client_tag;
tracing::debug!("short header: srv_dcid={:02x?} → client tag matched", srv_dcid);
if let Some(conn) = self.connections.get_mut(&client_tag) {
let mut path_challenge_pkt: Option<Vec<u8>> = None;
if from != conn.client_addr {
let need_new = match &conn.migration {
Some(m) => m.new_addr != from,
None => true,
};
if need_new {
let mut challenge = [0u8; 8];
if !zenith_foundation::random::try_fill_random(&mut challenge) {
return Err(QuicTransportError::Quic(
"PATH_CHALLENGE 随机生成失败".into(),
));
}
let frame = zenith_net::transport::quic_server::build_path_challenge_frame(&challenge);
if let Ok(pkt) = conn.net_conn.build_1rtt_packet(&frame) {
path_challenge_pkt = Some(pkt);
}
conn.migration = Some(MigrationState {
new_addr: from,
challenge,
bytes_received: 0,
bytes_sent: 0,
validated: false,
});
tracing::debug!("[QUIC MIGRATION] new addr {} → PATH_CHALLENGE sent", from);
}
conn.client_addr = from;
}
if let Some(m) = &mut conn.migration
&& m.new_addr == from
&& !m.validated
{
m.bytes_received = m.bytes_received.saturating_add(data.len() as u64);
}
if !conn.net_conn.handshake_done_sent {
tracing::debug!(
"1-RTT short 包在握手完成前到达,推迟到 pending_1rtt"
);
if self.pending_1rtt.len() >= MAX_PENDING_1RTT {
self.pending_1rtt.remove(0);
}
self.pending_1rtt.push((data.to_vec(), from));
return Ok(Vec::new());
}
let output = match conn.net_conn.handle_short_packet(data) {
Ok(o) => o,
Err(e) => {
tracing::warn!(
"handle_short_packet error -> CONNECTION_CLOSE: {}",
e
);
let client_tag_copy = client_tag;
let cc_addr = conn.client_addr;
let ack_frame_opt = conn.net_conn.pending_err_ack.take();
let is_crypto = matches!(e, NetQuicError::Crypto(_));
let peer_alert = if is_crypto { conn.net_conn.peer_alert_code() } else { 0 };
let errmsg = e.to_string();
if is_crypto {
tracing::warn!("[TLS DIAG 1RTT] rustls error: {} (alert=0x{peer_alert:x})", errmsg);
}
let _ = conn;
let h3_output = self.process_streams(&client_tag_copy, app_handler);
let (mut error_code, frame_type) = quic_error_code(&e);
if is_crypto {
let (adj, fix) = if errmsg.contains("unexpected message")
|| errmsg.contains("KeyUpdate")
|| errmsg.contains("EndOfEarlyData")
|| errmsg.contains("unexpected_message")
|| errmsg.contains("received unexpected")
{
(0x010A, true)
} else if errmsg.contains("MissingQuicTransportParameters")
|| errmsg.contains("missing_extension")
|| errmsg.contains("MissingExtension")
{
(0x016D, true)
} else {
(peer_alert, false)
};
error_code = if fix { adj } else { peer_alert };
}
let cc_frame = build_connection_close_transport(
error_code,
frame_type,
"transport error",
);
let mut err_output: QuicResponsePackets = h3_output;
if let Some(conn) = self.connections.get_mut(&client_tag_copy) {
if let Ok(pkt) = conn.net_conn.build_1rtt_cc_packet(
ack_frame_opt.as_deref().unwrap_or(&[]),
&cc_frame,
) {
err_output.push((pkt, cc_addr));
} else if let Ok(pkt) = conn.net_conn.build_1rtt_packet(&cc_frame) {
err_output.push((pkt, cc_addr));
}
}
return Ok(err_output);
}
};
let validated_now = match &conn.migration {
Some(m) => !m.validated && conn.net_conn.pending_path_response.contains(&m.challenge),
None => false,
};
if validated_now {
if let Some(m) = &mut conn.migration {
m.validated = true;
let ch = m.challenge;
conn.net_conn.pending_path_response.retain(|d| *d != ch);
}
tracing::debug!("[QUIC MIGRATION] path validated → migrated to {}", conn.client_addr);
}
let mut all_output: QuicResponsePackets =
output.into_iter().map(|p| (p, conn.client_addr)).collect();
if let Some(pc) = path_challenge_pkt {
all_output.push((pc, conn.client_addr));
}
let amp_budget = match &conn.migration {
Some(m) if !m.validated => Some(
3u64.saturating_mul(m.bytes_received).saturating_sub(m.bytes_sent),
),
_ => None,
};
if let Some(budget) = amp_budget {
let mut allowed = budget;
let mut sent_now = 0u64;
let mut kept: QuicResponsePackets = Vec::new();
for (pkt, addr) in all_output {
let plen = pkt.len() as u64;
if plen <= allowed {
allowed -= plen;
sent_now += plen;
kept.push((pkt, addr));
}
}
all_output = kept;
if let Some(m) = &mut conn.migration {
m.bytes_sent = m.bytes_sent.saturating_add(sent_now);
}
}
let h3_output = self.process_streams(&client_tag, app_handler);
all_output.extend(h3_output);
let should_close = self.connections.get(&client_tag)
.is_some_and(|c| c.net_conn.state == QuicServerState::Closed);
if should_close {
self.server_dcid_index.retain(|_, v| *v != client_tag);
self.connections.remove(&client_tag);
tracing::debug!(
"[QUIC CLEANUP] removed closed connection, tag={:02x?}",
client_tag
);
}
return Ok(all_output);
}
} else {
tracing::debug!("short header: no server_dcid_index entry for {:02x?}", srv_dcid);
}
}
if let Some(dcid) = self.find_conn_by_addr(from) {
tracing::debug!("short header: fallback by addr → route_tag={:02x?}", dcid);
if let Some(conn) = self.connections.get_mut(&dcid) {
if !conn.net_conn.handshake_done_sent {
tracing::debug!(
"1-RTT short 包(fallback)在握手完成前到达,推迟到 pending_1rtt"
);
if self.pending_1rtt.len() >= MAX_PENDING_1RTT {
self.pending_1rtt.remove(0);
}
self.pending_1rtt.push((data.to_vec(), from));
return Ok(Vec::new());
}
let output = match conn.net_conn.handle_short_packet(data) {
Ok(o) => o,
Err(e) => {
tracing::debug!("handle_short_packet error: {}", e);
return Ok(Vec::new());
}
};
let mut all_output: QuicResponsePackets =
output.into_iter().map(|p| (p, conn.client_addr)).collect();
let h3_output = self.process_streams(&dcid, app_handler);
all_output.extend(h3_output);
let should_close = self.connections.get(&dcid)
.is_some_and(|c| c.net_conn.state == QuicServerState::Closed);
if should_close {
self.server_dcid_index.retain(|_, v| *v != dcid);
self.connections.remove(&dcid);
tracing::debug!(
"[QUIC CLEANUP] removed closed connection (fallback), tag={:02x?}",
dcid
);
}
return Ok(all_output);
}
}
if self.pending_1rtt.len() >= MAX_PENDING_1RTT {
self.pending_1rtt.remove(0);
tracing::warn!(
"pending_1rtt 达到上限 {MAX_PENDING_1RTT},丢弃最旧包(DoS 防护)"
);
}
self.pending_1rtt.push((data.to_vec(), from));
tracing::debug!("short header - no matching connection, cached as pending");
}
tracing::debug!("returning empty response (no matching path)");
Ok(Vec::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_stream_qpack_prefixes_rfc_9204() {
assert_eq!(classify_stream(2, &[0x00]), H3StreamKind::Control);
assert_eq!(classify_stream(2, &[0x02]), H3StreamKind::QpackEncoder);
assert_eq!(classify_stream(2, &[0x03]), H3StreamKind::QpackDecoder);
assert_eq!(zenith_http3::stream::STREAM_TYPE_CONTROL, 0x00);
assert_eq!(zenith_http3::stream::STREAM_TYPE_QPACK_ENCODER, 0x02);
assert_eq!(zenith_http3::stream::STREAM_TYPE_QPACK_DECODER, 0x03);
assert_eq!(classify_stream(2, &[0x01]), H3StreamKind::Unknown);
assert_eq!(classify_stream(2, &[0xff]), H3StreamKind::Unknown);
assert_eq!(classify_stream(2, &[]), H3StreamKind::Unknown);
assert_eq!(classify_stream(1, &[0x00]), H3StreamKind::Unknown);
assert_eq!(classify_stream(0, &[0x02]), H3StreamKind::Request);
assert_eq!(classify_stream(6, &[0x03]), H3StreamKind::QpackDecoder);
}
#[test]
fn server_uni_stream_ids_fixed_layout() {
let mut ids = ServerUniStreamIds::new();
assert_eq!(ids.control(), Some(3));
assert_eq!(ids.qpack_encoder(), Some(7));
assert_eq!(ids.qpack_decoder(), Some(11));
assert_eq!(ids.control(), Some(3));
assert_eq!(ids.qpack_encoder(), Some(7));
assert_eq!(ids.qpack_decoder(), Some(11));
let mut ids2 = ServerUniStreamIds::new();
assert_eq!(ids2.qpack_decoder(), Some(11));
assert_eq!(ids2.control(), Some(3));
assert_eq!(ids2.qpack_encoder(), Some(7));
}
}