use core::fmt;
use super::{
QuicTransportParameter, QuicTransportParameterId, QuicTransportParameterIter,
QuicTransportParameterParseError, QuicTransportParameterValueError,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuicTransportParameterSender {
Client,
Server,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuicTransportParameterHandshakeContext<'a> {
Client {
initial_source_connection_id: &'a [u8],
},
Server {
initial_source_connection_id: &'a [u8],
original_destination_connection_id: &'a [u8],
retry_source_connection_id: Option<&'a [u8]>,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuicTransportParameterHandshakeError {
SenderContextMismatch {
sender: QuicTransportParameterSender,
context_sender: QuicTransportParameterSender,
},
Structural {
error: QuicTransportParameterParseError,
},
Missing {
id: QuicTransportParameterId,
},
Unexpected {
id: QuicTransportParameterId,
offset: usize,
},
ConnectionIdMismatch {
id: QuicTransportParameterId,
offset: usize,
},
}
impl fmt::Display for QuicTransportParameterHandshakeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SenderContextMismatch {
sender,
context_sender,
} => write!(
f,
"QUIC transport-parameter sender {sender:?} does not match handshake context for {context_sender:?}"
),
Self::Structural { error } => {
write!(
f,
"QUIC handshake transport parameters are not structural: {error}"
)
}
Self::Missing { id } => write!(
f,
"QUIC handshake transport parameter ID {} is required but missing",
id.raw()
),
Self::Unexpected { id, offset } => write!(
f,
"QUIC handshake transport parameter ID {} at tuple offset {offset} is unexpected",
id.raw()
),
Self::ConnectionIdMismatch { id, offset } => write!(
f,
"QUIC handshake transport parameter ID {} at tuple offset {offset} does not match its packet connection ID",
id.raw()
),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QuicTransportParameterSemanticError {
Structural {
error: QuicTransportParameterParseError,
},
Value {
id: QuicTransportParameterId,
offset: usize,
error: QuicTransportParameterValueError,
},
ForbiddenSender {
id: QuicTransportParameterId,
offset: usize,
},
BelowMinimum {
id: QuicTransportParameterId,
offset: usize,
value: u64,
minimum: u64,
},
AboveMaximum {
id: QuicTransportParameterId,
offset: usize,
value: u64,
maximum: u64,
},
}
impl fmt::Display for QuicTransportParameterSemanticError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Structural { error } => {
write!(f, "QUIC transport parameters are not structural: {error}")
}
Self::Value { id, offset, error } => write!(
f,
"QUIC transport parameter ID {} at tuple offset {offset} has an invalid value: {error}",
id.raw()
),
Self::ForbiddenSender { id, offset } => write!(
f,
"QUIC client transport parameter ID {} at tuple offset {offset} is server-only",
id.raw()
),
Self::BelowMinimum {
id,
offset,
value,
minimum,
} => write!(
f,
"QUIC transport parameter ID {} at tuple offset {offset} has value {value}, below minimum {minimum}",
id.raw()
),
Self::AboveMaximum {
id,
offset,
value,
maximum,
} => write!(
f,
"QUIC transport parameter ID {} at tuple offset {offset} has value {value}, above maximum {maximum}",
id.raw()
),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct QuicTransportParametersV1<'a> {
bytes: &'a [u8],
sender: QuicTransportParameterSender,
}
impl<'a> QuicTransportParametersV1<'a> {
pub const fn as_bytes(self) -> &'a [u8] {
self.bytes
}
pub const fn sender(self) -> QuicTransportParameterSender {
self.sender
}
pub const fn iter(self) -> QuicTransportParameterIter<'a> {
QuicTransportParameterIter::new(self.bytes)
}
pub fn validate_handshake(
self,
context: QuicTransportParameterHandshakeContext<'_>,
) -> Result<(), QuicTransportParameterHandshakeError> {
let context_sender = match context {
QuicTransportParameterHandshakeContext::Client { .. } => {
QuicTransportParameterSender::Client
}
QuicTransportParameterHandshakeContext::Server { .. } => {
QuicTransportParameterSender::Server
}
};
if self.sender != context_sender {
return Err(
QuicTransportParameterHandshakeError::SenderContextMismatch {
sender: self.sender,
context_sender,
},
);
}
match context {
QuicTransportParameterHandshakeContext::Client {
initial_source_connection_id,
} => validate_client_handshake(self, initial_source_connection_id),
QuicTransportParameterHandshakeContext::Server {
initial_source_connection_id,
original_destination_connection_id,
retry_source_connection_id,
} => validate_server_handshake(
self,
initial_source_connection_id,
original_destination_connection_id,
retry_source_connection_id,
),
}
}
}
fn validate_client_handshake(
parameters: QuicTransportParametersV1<'_>,
initial_source_connection_id: &[u8],
) -> Result<(), QuicTransportParameterHandshakeError> {
let mut initial_present = false;
let mut offset = 0;
for parameter in parameters.iter() {
let parameter = parameter
.map_err(|error| QuicTransportParameterHandshakeError::Structural { error })?;
let id = parameter.parameter_id();
if id == QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID {
initial_present = true;
if parameter.value() != initial_source_connection_id {
return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
id,
offset,
});
}
}
offset += parameter.as_bytes().len();
}
if !initial_present {
return Err(QuicTransportParameterHandshakeError::Missing {
id: QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID,
});
}
Ok(())
}
fn validate_server_handshake(
parameters: QuicTransportParametersV1<'_>,
initial_source_connection_id: &[u8],
original_destination_connection_id: &[u8],
retry_source_connection_id: Option<&[u8]>,
) -> Result<(), QuicTransportParameterHandshakeError> {
let mut initial_present = false;
let mut original_present = false;
let mut retry_present = false;
let mut offset = 0;
for parameter in parameters.iter() {
let parameter = parameter
.map_err(|error| QuicTransportParameterHandshakeError::Structural { error })?;
let id = parameter.parameter_id();
match id {
QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID => {
initial_present = true;
if parameter.value() != initial_source_connection_id {
return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
id,
offset,
});
}
}
QuicTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID => {
original_present = true;
if parameter.value() != original_destination_connection_id {
return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
id,
offset,
});
}
}
QuicTransportParameterId::RETRY_SOURCE_CONNECTION_ID => {
let Some(expected) = retry_source_connection_id else {
return Err(QuicTransportParameterHandshakeError::Unexpected { id, offset });
};
retry_present = true;
if parameter.value() != expected {
return Err(QuicTransportParameterHandshakeError::ConnectionIdMismatch {
id,
offset,
});
}
}
_ => {}
}
offset += parameter.as_bytes().len();
}
if !initial_present {
return Err(QuicTransportParameterHandshakeError::Missing {
id: QuicTransportParameterId::INITIAL_SOURCE_CONNECTION_ID,
});
}
if !original_present {
return Err(QuicTransportParameterHandshakeError::Missing {
id: QuicTransportParameterId::ORIGINAL_DESTINATION_CONNECTION_ID,
});
}
if retry_source_connection_id.is_some() && !retry_present {
return Err(QuicTransportParameterHandshakeError::Missing {
id: QuicTransportParameterId::RETRY_SOURCE_CONNECTION_ID,
});
}
Ok(())
}
pub(super) fn validate<'a>(
bytes: &'a [u8],
sender: QuicTransportParameterSender,
) -> Result<QuicTransportParametersV1<'a>, QuicTransportParameterSemanticError> {
let mut offset = 0;
let parameters = QuicTransportParameterIter::new(bytes);
for parameter in parameters {
let parameter =
parameter.map_err(|error| QuicTransportParameterSemanticError::Structural { error })?;
validate_parameter(parameter, sender, offset)?;
offset += parameter.as_bytes().len();
}
Ok(QuicTransportParametersV1 { bytes, sender })
}
fn validate_parameter(
parameter: QuicTransportParameter<'_>,
sender: QuicTransportParameterSender,
offset: usize,
) -> Result<(), QuicTransportParameterSemanticError> {
let id = parameter.parameter_id();
if sender == QuicTransportParameterSender::Client && is_server_only(id) {
return Err(QuicTransportParameterSemanticError::ForbiddenSender { id, offset });
}
match id.raw() {
0x00 | 0x0f | 0x10 => parameter
.connection_id()
.map(|_| ())
.map_err(|error| value_error(id, offset, error)),
0x01 | 0x04 | 0x05 | 0x06 | 0x07 => parameter
.integer()
.map(|_| ())
.map_err(|error| value_error(id, offset, error)),
0x02 => parameter
.stateless_reset_token()
.map(|_| ())
.map_err(|error| value_error(id, offset, error)),
0x03 => validate_minimum(parameter, offset, 1200),
0x08 | 0x09 => validate_maximum(parameter, offset, 1u64 << 60),
0x0a => validate_maximum(parameter, offset, 20),
0x0b => validate_maximum(parameter, offset, (1u64 << 14) - 1),
0x0c => parameter
.disable_active_migration()
.map_err(|error| value_error(id, offset, error)),
0x0d => parameter
.preferred_address()
.map(|_| ())
.map_err(|error| value_error(id, offset, error)),
0x0e => validate_minimum(parameter, offset, 2),
_ => Ok(()),
}
}
fn validate_minimum(
parameter: QuicTransportParameter<'_>,
offset: usize,
minimum: u64,
) -> Result<(), QuicTransportParameterSemanticError> {
let id = parameter.parameter_id();
let value = parameter
.integer()
.map_err(|error| value_error(id, offset, error))?
.value();
if value < minimum {
return Err(QuicTransportParameterSemanticError::BelowMinimum {
id,
offset,
value,
minimum,
});
}
Ok(())
}
fn validate_maximum(
parameter: QuicTransportParameter<'_>,
offset: usize,
maximum: u64,
) -> Result<(), QuicTransportParameterSemanticError> {
let id = parameter.parameter_id();
let value = parameter
.integer()
.map_err(|error| value_error(id, offset, error))?
.value();
if value > maximum {
return Err(QuicTransportParameterSemanticError::AboveMaximum {
id,
offset,
value,
maximum,
});
}
Ok(())
}
fn value_error(
id: QuicTransportParameterId,
offset: usize,
error: QuicTransportParameterValueError,
) -> QuicTransportParameterSemanticError {
QuicTransportParameterSemanticError::Value { id, offset, error }
}
const fn is_server_only(id: QuicTransportParameterId) -> bool {
matches!(id.raw(), 0x00 | 0x02 | 0x0d | 0x10)
}