use core::fmt;
use crate::quic::varint::QuicVarIntLen;
use super::super::codepoints::{Http3ErrorCode, Http3StreamType};
use super::super::enums::stream::{Http3EndpointRole, Http3UniStreamKind};
use super::super::ids::{Http3PushId, Http3StreamId};
use super::uni::Http3UniStreamHeader;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http3CriticalUniStreamKind {
Control,
QpackEncoder,
QpackDecoder,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http3PeerUniStreamEvent {
Control {
stream_id: Http3StreamId,
},
Push {
stream_id: Http3StreamId,
push_id: Http3PushId,
},
QpackEncoder {
stream_id: Http3StreamId,
},
QpackDecoder {
stream_id: Http3StreamId,
},
Unknown {
stream_id: Http3StreamId,
stream_type: Http3StreamType,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http3PeerUniStreamError {
InvalidPeerUnidirectionalStream {
stream_id: Http3StreamId,
},
DuplicateCriticalStream {
kind: Http3CriticalUniStreamKind,
existing: Http3StreamId,
received: Http3StreamId,
},
ClientInitiatedPushStream {
stream_id: Http3StreamId,
push_id: Http3PushId,
},
ClosedCriticalStream {
kind: Http3CriticalUniStreamKind,
stream_id: Http3StreamId,
},
}
impl Http3PeerUniStreamError {
pub const fn error_code(self) -> Option<Http3ErrorCode> {
match self {
Self::InvalidPeerUnidirectionalStream { .. } => None,
Self::DuplicateCriticalStream { .. } | Self::ClientInitiatedPushStream { .. } => {
Some(Http3ErrorCode::STREAM_CREATION_ERROR)
}
Self::ClosedCriticalStream { .. } => Some(Http3ErrorCode::CLOSED_CRITICAL_STREAM),
}
}
}
impl fmt::Display for Http3PeerUniStreamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPeerUnidirectionalStream { stream_id } => write!(
f,
"stream ID {} is not a peer-initiated unidirectional QUIC stream",
stream_id.value()
),
Self::DuplicateCriticalStream {
kind,
existing,
received,
} => write!(
f,
"HTTP/3 {kind:?} critical stream {} duplicates stream {}",
received.value(),
existing.value()
),
Self::ClientInitiatedPushStream { stream_id, push_id } => write!(
f,
"client-initiated HTTP/3 push stream {} carries Push ID {}",
stream_id.value(),
push_id.value()
),
Self::ClosedCriticalStream { kind, stream_id } => write!(
f,
"HTTP/3 {kind:?} critical stream {} was closed",
stream_id.value()
),
}
}
}
impl core::error::Error for Http3PeerUniStreamError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Http3PeerUniStreamState {
local_role: Http3EndpointRole,
control: Option<Http3StreamId>,
qpack_encoder: Option<Http3StreamId>,
qpack_decoder: Option<Http3StreamId>,
}
impl Http3PeerUniStreamState {
pub const fn new(local_role: Http3EndpointRole) -> Self {
Self {
local_role,
control: None,
qpack_encoder: None,
qpack_decoder: None,
}
}
pub const fn role(self) -> Http3EndpointRole {
self.local_role
}
pub const fn control_stream_id(self) -> Option<Http3StreamId> {
self.control
}
pub const fn qpack_encoder_stream_id(self) -> Option<Http3StreamId> {
self.qpack_encoder
}
pub const fn qpack_decoder_stream_id(self) -> Option<Http3StreamId> {
self.qpack_decoder
}
pub fn receive_header(
&mut self,
stream_id: Http3StreamId,
header: Http3UniStreamHeader<'_>,
) -> Result<Http3PeerUniStreamEvent, Http3PeerUniStreamError> {
self.validate_peer_unidirectional_stream_id(stream_id)?;
match header.kind() {
Http3UniStreamKind::Control => {
self.receive_critical(stream_id, Http3CriticalUniStreamKind::Control)
}
Http3UniStreamKind::QpackEncoder => {
self.receive_critical(stream_id, Http3CriticalUniStreamKind::QpackEncoder)
}
Http3UniStreamKind::QpackDecoder => {
self.receive_critical(stream_id, Http3CriticalUniStreamKind::QpackDecoder)
}
Http3UniStreamKind::Push(push_id) => {
if self.local_role == Http3EndpointRole::Server {
return Err(Http3PeerUniStreamError::ClientInitiatedPushStream {
stream_id,
push_id,
});
}
Ok(Http3PeerUniStreamEvent::Push { stream_id, push_id })
}
Http3UniStreamKind::Unknown(stream_type) => Ok(Http3PeerUniStreamEvent::Unknown {
stream_id,
stream_type,
}),
}
}
pub fn receive_closed(self, stream_id: Http3StreamId) -> Result<(), Http3PeerUniStreamError> {
if self.control == Some(stream_id) {
return Err(Http3PeerUniStreamError::ClosedCriticalStream {
kind: Http3CriticalUniStreamKind::Control,
stream_id,
});
}
if self.qpack_encoder == Some(stream_id) {
return Err(Http3PeerUniStreamError::ClosedCriticalStream {
kind: Http3CriticalUniStreamKind::QpackEncoder,
stream_id,
});
}
if self.qpack_decoder == Some(stream_id) {
return Err(Http3PeerUniStreamError::ClosedCriticalStream {
kind: Http3CriticalUniStreamKind::QpackDecoder,
stream_id,
});
}
Ok(())
}
fn validate_peer_unidirectional_stream_id(
self,
stream_id: Http3StreamId,
) -> Result<(), Http3PeerUniStreamError> {
let value = stream_id.value();
let expected_low_bits = match self.local_role {
Http3EndpointRole::Client => 0x03,
Http3EndpointRole::Server => 0x02,
};
if value > QuicVarIntLen::Eight.max_value() || value & 0x03 != expected_low_bits {
return Err(Http3PeerUniStreamError::InvalidPeerUnidirectionalStream { stream_id });
}
Ok(())
}
fn receive_critical(
&mut self,
stream_id: Http3StreamId,
kind: Http3CriticalUniStreamKind,
) -> Result<Http3PeerUniStreamEvent, Http3PeerUniStreamError> {
let existing = match kind {
Http3CriticalUniStreamKind::Control => self.control,
Http3CriticalUniStreamKind::QpackEncoder => self.qpack_encoder,
Http3CriticalUniStreamKind::QpackDecoder => self.qpack_decoder,
};
if let Some(existing) = existing {
return Err(Http3PeerUniStreamError::DuplicateCriticalStream {
kind,
existing,
received: stream_id,
});
}
match kind {
Http3CriticalUniStreamKind::Control => {
self.control = Some(stream_id);
Ok(Http3PeerUniStreamEvent::Control { stream_id })
}
Http3CriticalUniStreamKind::QpackEncoder => {
self.qpack_encoder = Some(stream_id);
Ok(Http3PeerUniStreamEvent::QpackEncoder { stream_id })
}
Http3CriticalUniStreamKind::QpackDecoder => {
self.qpack_decoder = Some(stream_id);
Ok(Http3PeerUniStreamEvent::QpackDecoder { stream_id })
}
}
}
}