#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u64)]
pub enum ErrorCode {
BufferedStreamRejected = 0x3994bd84,
SessionGone = 0x170d7b68,
FlowControlError = 0x045d4487,
AlpnError = 0x0817b3dd,
RequirementsNotMet = 0x212c0d48,
}
impl ErrorCode {
pub fn from_code(code: u64) -> Option<Self> {
match code {
0x3994bd84 => Some(Self::BufferedStreamRejected),
0x170d7b68 => Some(Self::SessionGone),
0x045d4487 => Some(Self::FlowControlError),
0x0817b3dd => Some(Self::AlpnError),
0x212c0d48 => Some(Self::RequirementsNotMet),
_ => None,
}
}
pub fn description(self) -> &'static str {
match self {
Self::BufferedStreamRejected => {
"WebTransport data stream rejected due to lack of associated session"
}
Self::SessionGone => {
"WebTransport data stream aborted because the associated session has been closed"
}
Self::FlowControlError => {
"WebTransport session aborted because a flow control error was encountered"
}
Self::AlpnError => "WebTransport ALPN negotiation failed",
Self::RequirementsNotMet => {
"WebTransport requirements not met: missing required SETTINGS or transport parameters"
}
}
}
}
pub struct ApplicationErrorCode;
impl ApplicationErrorCode {
pub const FIRST: u64 = 0x52e4a40fa8db;
pub const LAST: u64 = 0x52e5ac983162;
pub fn to_http3_code(app_code: u32) -> u64 {
let n = u64::from(app_code);
Self::FIRST + n + n / 0x1e
}
pub fn from_http3_code(http3_code: u64) -> Option<u32> {
if !(Self::FIRST..=Self::LAST).contains(&http3_code) {
return None;
}
if http3_code.wrapping_sub(0x21).is_multiple_of(0x1f) {
return None;
}
let shifted = http3_code - Self::FIRST;
let app_code = shifted - shifted / 0x1f;
u32::try_from(app_code).ok()
}
pub fn is_application_error(http3_code: u64) -> bool {
(Self::FIRST..=Self::LAST).contains(&http3_code)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Protocol(ErrorCode),
Application {
code: u32,
message: String,
},
Unknown(u64),
}
impl Error {
pub fn protocol(code: ErrorCode) -> Self {
Self::Protocol(code)
}
pub fn application(code: u32, message: impl Into<String>) -> Self {
let mut msg = message.into();
if msg.len() > 1024 {
let mut truncate_at = 1024;
while truncate_at > 0 && !msg.is_char_boundary(truncate_at) {
truncate_at -= 1;
}
msg.truncate(truncate_at);
}
Self::Application { code, message: msg }
}
pub fn from_http3_code(code: u64) -> Self {
if let Some(error_code) = ErrorCode::from_code(code) {
Self::Protocol(error_code)
} else if let Some(app_code) = ApplicationErrorCode::from_http3_code(code) {
Self::Application {
code: app_code,
message: String::new(),
}
} else {
Self::Unknown(code)
}
}
pub fn is_out_of_range_reset(&self) -> bool {
matches!(self, Self::Unknown(_))
}
pub fn to_http3_code(&self) -> u64 {
match self {
Self::Protocol(code) => *code as u64,
Self::Application { code, .. } => ApplicationErrorCode::to_http3_code(*code),
Self::Unknown(code) => *code,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_code_from_code() {
assert_eq!(
ErrorCode::from_code(0x3994bd84),
Some(ErrorCode::BufferedStreamRejected)
);
assert_eq!(
ErrorCode::from_code(0x170d7b68),
Some(ErrorCode::SessionGone)
);
assert_eq!(
ErrorCode::from_code(0x045d4487),
Some(ErrorCode::FlowControlError)
);
assert_eq!(ErrorCode::from_code(0x0817b3dd), Some(ErrorCode::AlpnError));
assert_eq!(
ErrorCode::from_code(0x212c0d48),
Some(ErrorCode::RequirementsNotMet)
);
assert_eq!(ErrorCode::from_code(0x12345678), None);
}
#[test]
fn test_application_error_code_reserved() {
for i in 0..10 {
let reserved = 0x1f * i + 0x21;
assert_eq!(ApplicationErrorCode::from_http3_code(reserved), None);
}
}
#[test]
fn test_application_error_code_range() {
assert!(!ApplicationErrorCode::is_application_error(0));
assert!(!ApplicationErrorCode::is_application_error(
ApplicationErrorCode::FIRST - 1
));
assert!(ApplicationErrorCode::is_application_error(
ApplicationErrorCode::FIRST
));
assert!(ApplicationErrorCode::is_application_error(
ApplicationErrorCode::LAST
));
assert!(!ApplicationErrorCode::is_application_error(
ApplicationErrorCode::LAST + 1
));
}
#[test]
fn test_error_from_http3_code() {
let err = Error::from_http3_code(0x3994bd84);
assert_eq!(err, Error::Protocol(ErrorCode::BufferedStreamRejected));
let err = Error::from_http3_code(ApplicationErrorCode::FIRST);
assert!(matches!(err, Error::Application { code: 0, .. }));
let err = Error::from_http3_code(0x12345678);
assert_eq!(err, Error::Unknown(0x12345678));
assert!(err.is_out_of_range_reset());
}
#[test]
fn test_is_out_of_range_reset() {
assert!(!Error::protocol(ErrorCode::SessionGone).is_out_of_range_reset());
assert!(!Error::application(0, "").is_out_of_range_reset());
assert!(Error::Unknown(0x99).is_out_of_range_reset());
}
}