use core::fmt;
use super::super::codepoints::{Http3ErrorCode, Http3FrameType};
use super::super::enums::stream::Http3EndpointRole;
use super::super::frame::Http3Frame;
use super::super::frame::Http3FramePayloadParseError;
use super::super::frame::{Http3CancelPush, Http3Goaway, Http3MaxPushId};
use super::super::ids::Http3PushId;
use super::super::settings::{Http3PeerSettings, Http3Settings, Http3SettingsSemanticError};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http3ControlFrame<'a> {
Settings {
frame: Http3Settings<'a>,
peer_settings: Http3PeerSettings,
},
CancelPush(Http3CancelPush<'a>),
Goaway(Http3Goaway<'a>),
MaxPushId(Http3MaxPushId<'a>),
Unknown(Http3Frame<'a>),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Http3ControlStreamError {
MissingSettings {
actual: Http3FrameType,
},
UnexpectedFrame {
frame_type: Http3FrameType,
},
FramePayload {
frame_type: Http3FrameType,
error: Http3FramePayloadParseError,
},
Settings(Http3SettingsSemanticError),
InvalidGoawayStreamId {
identifier: u64,
},
IncreasedGoawayIdentifier {
previous: u64,
current: u64,
},
ReducedMaximumPushId {
previous: Http3PushId,
current: Http3PushId,
},
}
impl Http3ControlStreamError {
pub const fn error_code(self) -> Http3ErrorCode {
match self {
Self::MissingSettings { .. } => Http3ErrorCode::MISSING_SETTINGS,
Self::UnexpectedFrame { .. } => Http3ErrorCode::FRAME_UNEXPECTED,
Self::FramePayload { .. } => Http3ErrorCode::FRAME_ERROR,
Self::Settings(_) => Http3ErrorCode::SETTINGS_ERROR,
Self::InvalidGoawayStreamId { .. }
| Self::IncreasedGoawayIdentifier { .. }
| Self::ReducedMaximumPushId { .. } => Http3ErrorCode::ID_ERROR,
}
}
}
impl fmt::Display for Http3ControlStreamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingSettings { actual } => write!(
f,
"HTTP/3 control stream starts with frame type {} instead of SETTINGS",
actual.value()
),
Self::UnexpectedFrame { frame_type } => write!(
f,
"HTTP/3 frame type {} is unexpected on the control stream",
frame_type.value()
),
Self::FramePayload { frame_type, error } => write!(
f,
"HTTP/3 control-stream frame type {} payload: {error}",
frame_type.value()
),
Self::Settings(error) => write!(f, "HTTP/3 control-stream SETTINGS: {error}"),
Self::InvalidGoawayStreamId { identifier } => write!(
f,
"HTTP/3 GOAWAY identifier {identifier} is not a client-initiated bidirectional stream ID"
),
Self::IncreasedGoawayIdentifier { previous, current } => write!(
f,
"HTTP/3 GOAWAY identifier increased from {previous} to {current}"
),
Self::ReducedMaximumPushId { previous, current } => write!(
f,
"HTTP/3 MAX_PUSH_ID decreased from {} to {}",
previous.value(),
current.value()
),
}
}
}
impl core::error::Error for Http3ControlStreamError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::FramePayload { error, .. } => Some(error),
Self::Settings(error) => Some(error),
Self::MissingSettings { .. }
| Self::UnexpectedFrame { .. }
| Self::InvalidGoawayStreamId { .. }
| Self::IncreasedGoawayIdentifier { .. }
| Self::ReducedMaximumPushId { .. } => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Http3ControlStreamState {
local_role: Http3EndpointRole,
settings: Option<Http3PeerSettings>,
last_goaway_identifier: Option<u64>,
maximum_push_id: Option<Http3PushId>,
}
impl Http3ControlStreamState {
pub const fn new(local_role: Http3EndpointRole) -> Self {
Self {
local_role,
settings: None,
last_goaway_identifier: None,
maximum_push_id: None,
}
}
pub const fn role(self) -> Http3EndpointRole {
self.local_role
}
pub const fn settings(self) -> Option<Http3PeerSettings> {
self.settings
}
pub const fn last_goaway_identifier(self) -> Option<u64> {
self.last_goaway_identifier
}
pub const fn maximum_push_id(self) -> Option<Http3PushId> {
self.maximum_push_id
}
pub fn receive<'a>(
&mut self,
frame: Http3Frame<'a>,
) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
let frame_type = frame.frame_type();
if self.settings.is_none() {
if frame_type != Http3FrameType::SETTINGS {
return Err(Http3ControlStreamError::MissingSettings { actual: frame_type });
}
return self.receive_settings(frame);
}
match frame_type {
Http3FrameType::SETTINGS
| Http3FrameType::DATA
| Http3FrameType::HEADERS
| Http3FrameType::PUSH_PROMISE => {
Err(Http3ControlStreamError::UnexpectedFrame { frame_type })
}
frame_type if is_http2_reserved_frame_type(frame_type) => {
Err(Http3ControlStreamError::UnexpectedFrame { frame_type })
}
Http3FrameType::CANCEL_PUSH => self.receive_cancel_push(frame),
Http3FrameType::GOAWAY => self.receive_goaway(frame),
Http3FrameType::MAX_PUSH_ID => self.receive_max_push_id(frame),
_ => Ok(Http3ControlFrame::Unknown(frame)),
}
}
fn receive_settings<'a>(
&mut self,
frame: Http3Frame<'a>,
) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
let settings = Http3Settings::from_frame(frame).map_err(|error| {
Http3ControlStreamError::FramePayload {
frame_type: Http3FrameType::SETTINGS,
error,
}
})?;
let peer_settings = settings
.validate_semantics()
.map_err(Http3ControlStreamError::Settings)?;
self.settings = Some(peer_settings);
Ok(Http3ControlFrame::Settings {
frame: settings,
peer_settings,
})
}
fn receive_cancel_push<'a>(
&mut self,
frame: Http3Frame<'a>,
) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
if self.local_role == Http3EndpointRole::Client {
return Err(Http3ControlStreamError::UnexpectedFrame {
frame_type: Http3FrameType::CANCEL_PUSH,
});
}
Http3CancelPush::from_frame(frame)
.map(Http3ControlFrame::CancelPush)
.map_err(|error| frame_payload_error(Http3FrameType::CANCEL_PUSH, error))
}
fn receive_goaway<'a>(
&mut self,
frame: Http3Frame<'a>,
) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
let goaway = Http3Goaway::from_frame(frame)
.map_err(|error| frame_payload_error(Http3FrameType::GOAWAY, error))?;
let identifier = goaway.identifier();
if self.local_role == Http3EndpointRole::Client && identifier & 0x03 != 0 {
return Err(Http3ControlStreamError::InvalidGoawayStreamId { identifier });
}
if let Some(previous) = self.last_goaway_identifier
&& identifier > previous
{
return Err(Http3ControlStreamError::IncreasedGoawayIdentifier {
previous,
current: identifier,
});
}
self.last_goaway_identifier = Some(identifier);
Ok(Http3ControlFrame::Goaway(goaway))
}
fn receive_max_push_id<'a>(
&mut self,
frame: Http3Frame<'a>,
) -> Result<Http3ControlFrame<'a>, Http3ControlStreamError> {
if self.local_role == Http3EndpointRole::Client {
return Err(Http3ControlStreamError::UnexpectedFrame {
frame_type: Http3FrameType::MAX_PUSH_ID,
});
}
let maximum_push_id = Http3MaxPushId::from_frame(frame)
.map_err(|error| frame_payload_error(Http3FrameType::MAX_PUSH_ID, error))?;
let current = maximum_push_id.push_id();
if let Some(previous) = self.maximum_push_id
&& current.value() < previous.value()
{
return Err(Http3ControlStreamError::ReducedMaximumPushId { previous, current });
}
self.maximum_push_id = Some(current);
Ok(Http3ControlFrame::MaxPushId(maximum_push_id))
}
}
fn frame_payload_error(
frame_type: Http3FrameType,
error: Http3FramePayloadParseError,
) -> Http3ControlStreamError {
Http3ControlStreamError::FramePayload { frame_type, error }
}
const fn is_http2_reserved_frame_type(frame_type: Http3FrameType) -> bool {
matches!(frame_type.value(), 0x02 | 0x06 | 0x08 | 0x09)
}