use super::{Connection, Role};
use crate::error::{ConnectionError, H3Code};
use crate::event::{Action, Event, Recv};
use crate::frame::{
CANCEL_PUSH, DATA, FrameHeader, FrameHeaderParser, GOAWAY, H2_RESERVED, HEADERS, MAX_PUSH_ID,
PUSH_PROMISE, SETTINGS, parse_single_varint_payload,
};
use crate::qpack::instructions::{DecoderStreamParser, EncoderStreamParser};
use crate::settings::decode_peer;
use crate::stream::{StreamId, UniKind};
use crate::varint;
pub(crate) enum PeerUni {
Type {
buf: [u8; 8],
n: usize,
},
Control(ControlRecv),
Encoder(EncoderStreamParser),
Decoder(DecoderStreamParser),
Raw,
Discard,
}
#[derive(Default)]
pub(crate) struct ControlRecv {
hdr: FrameHeaderParser,
cur: Option<(u64, u64)>,
payload: Vec<u8>,
}
impl PeerUni {
pub(super) fn buffered_bytes(&self) -> usize {
match self {
PeerUni::Control(c) => c.payload.capacity(),
PeerUni::Decoder(p) => p.buffered_bytes(),
_ => 0,
}
}
}
fn interpreted(ty: u64) -> bool {
matches!(ty, SETTINGS | GOAWAY | MAX_PUSH_ID | CANCEL_PUSH)
}
impl Connection {
pub(super) fn recv_uni(
&mut self,
s: StreamId,
bytes: &[u8],
fin: bool,
) -> Result<Recv, ConnectionError> {
let mut st = self
.peer_uni
.remove(&s)
.unwrap_or(PeerUni::Type { buf: [0; 8], n: 0 });
let mut rest = bytes;
while let PeerUni::Type { buf, n } = &mut st {
let Some((&b, tail)) = rest.split_first() else {
break;
};
rest = tail;
buf[*n] = b;
*n += 1;
if let Some((ty, _)) = varint::decode(&buf[..*n]) {
st = self.uni_type(s, ty, fin)?;
}
}
let start = bytes.len() - rest.len();
let mut out = Recv::Consumed(bytes.len());
match &mut st {
PeerUni::Type { .. } | PeerUni::Discard => {}
PeerUni::Control(c) => self.control_bytes(c, rest)?,
PeerUni::Encoder(p) => {
p.feed(rest)
.map_err(|code| self.close_with(code, "QPACK encoder stream error"))?;
}
PeerUni::Decoder(p) => {
p.feed(rest)
.map_err(|code| self.close_with(code, "QPACK decoder stream error"))?;
}
PeerUni::Raw if !rest.is_empty() => {
out = Recv::Raw {
consumed: bytes.len(),
range: start..bytes.len(),
};
}
PeerUni::Raw => {}
}
if !fin {
self.peer_uni.insert(s, st);
} else if matches!(
st,
PeerUni::Control(_) | PeerUni::Encoder(_) | PeerUni::Decoder(_)
) {
return Err(self.close_with(H3Code::CLOSED_CRITICAL_STREAM, "critical stream closed"));
}
Ok(out)
}
fn uni_type(&mut self, s: StreamId, ty: u64, fin: bool) -> Result<PeerUni, ConnectionError> {
let (kind, st) = match ty {
0x00 => (UniKind::Control, PeerUni::Control(ControlRecv::default())),
0x02 => (UniKind::QpackEncoder, PeerUni::Encoder(EncoderStreamParser)),
0x03 => (UniKind::QpackDecoder, PeerUni::Decoder(Default::default())),
0x01 => {
let code = match self.role {
Role::Client => H3Code::ID_ERROR,
Role::Server => H3Code::STREAM_CREATION_ERROR,
};
return Err(self.close_with(code, "push stream"));
}
_ if self.config.uni_types.contains(&ty) => {
self.events.push_back(Event::UniStream { stream: s, ty });
return Ok(PeerUni::Raw);
}
_ => {
if !fin {
self.actions.push_back(Action::StopSending {
stream: s,
code: H3Code::STREAM_CREATION_ERROR,
});
}
return Ok(PeerUni::Discard);
}
};
if std::mem::replace(&mut self.peer_critical[kind as usize], true) {
return Err(self.close_with(H3Code::STREAM_CREATION_ERROR, "duplicate critical stream"));
}
Ok(st)
}
fn control_bytes(
&mut self,
c: &mut ControlRecv,
mut bytes: &[u8],
) -> Result<(), ConnectionError> {
loop {
let Some((ty, left)) = c.cur else {
if bytes.is_empty() {
return Ok(());
}
let (n, h) = c.hdr.feed(bytes);
bytes = &bytes[n..];
if let Some(h) = h {
self.control_frame_start(h)?;
c.cur = Some((h.ty, h.len));
}
continue;
};
let take = left.min(bytes.len() as u64) as usize;
if interpreted(ty) {
c.payload.extend_from_slice(&bytes[..take]);
}
bytes = &bytes[take..];
if left > take as u64 {
c.cur = Some((ty, left - take as u64));
return Ok(());
}
c.cur = None;
self.control_frame_end(ty, std::mem::take(&mut c.payload))?;
}
}
fn control_frame_start(&mut self, h: FrameHeader) -> Result<(), ConnectionError> {
const UNEXPECTED: Option<(H3Code, &str)> = Some((
H3Code::FRAME_UNEXPECTED,
"frame not allowed on control stream",
));
let first = self.peer_settings.is_none();
let fail = match h.ty {
_ if first && h.ty != SETTINGS => Some((
H3Code::MISSING_SETTINGS,
"control stream must start with SETTINGS",
)),
SETTINGS if !first => UNEXPECTED,
SETTINGS if h.len > self.config.max_control_frame_size as u64 => Some((
H3Code::EXCESSIVE_LOAD,
"SETTINGS exceeds max_control_frame_size",
)),
DATA | HEADERS | PUSH_PROMISE => UNEXPECTED,
t if H2_RESERVED.contains(&t) => UNEXPECTED,
MAX_PUSH_ID if self.role == Role::Client => {
Some((H3Code::FRAME_UNEXPECTED, "MAX_PUSH_ID received by client"))
}
GOAWAY | MAX_PUSH_ID | CANCEL_PUSH if h.len > 8 => {
Some((H3Code::FRAME_ERROR, "fixed-layout control frame too long"))
}
_ => None,
};
match fail {
Some((code, reason)) => Err(self.close_with(code, reason)),
None => Ok(()),
}
}
fn control_frame_end(&mut self, ty: u64, payload: Vec<u8>) -> Result<(), ConnectionError> {
if ty == SETTINGS {
let s =
decode_peer(&payload).map_err(|code| self.close_with(code, "invalid SETTINGS"))?;
self.peer_settings = Some(s);
self.events.push_back(Event::PeerSettings);
return Ok(());
}
if !interpreted(ty) {
return Ok(());
}
let Some(id) = parse_single_varint_payload(&payload) else {
return Err(self.close_with(H3Code::FRAME_ERROR, "malformed control frame payload"));
};
let r = match ty {
GOAWAY => self.on_goaway(id).map_err(|code| (code, "invalid GOAWAY")),
MAX_PUSH_ID if self.max_push_id.is_some_and(|m| id < m) => {
Err((H3Code::ID_ERROR, "MAX_PUSH_ID decreased"))
}
MAX_PUSH_ID => {
self.max_push_id = Some(id);
Ok(())
}
_ => Err((H3Code::ID_ERROR, "CANCEL_PUSH without push")),
};
r.map_err(|(code, reason)| self.close_with(code, reason))
}
}