pub(super) use quic_parser::CryptoFrame;
use quic_parser::{decrypt_initial, parse_crypto_frames, parse_initial, reassemble_crypto_stream};
#[cfg(not(feature = "tls"))]
use tls_parser::{TlsClientHelloContents, TlsExtension};
use tls_parser::{TlsMessage, TlsMessageHandshake};
use super::types::{QuicInitial, QuicVersion};
#[derive(Debug, Clone)]
pub(super) struct InitialMeta {
pub version: QuicVersion,
pub dcid: Vec<u8>,
pub scid: Vec<u8>,
pub token_present: bool,
}
pub const PARSER_KIND_STR: &str = "quic";
pub fn parser_kind() -> &'static str {
PARSER_KIND_STR
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError {
NotInitial,
AeadDecryptFailed,
CryptoFrameDecode,
#[doc(hidden)]
_NoClientHello,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotInitial => f.write_str("not a QUIC long-header Initial"),
Self::AeadDecryptFailed => f.write_str("Initial AEAD decrypt failed"),
Self::CryptoFrameDecode => f.write_str("CRYPTO frame decode failed"),
Self::_NoClientHello => f.write_str("no TLS ClientHello in CRYPTO stream"),
}
}
}
impl std::error::Error for ParseError {}
impl From<ParseError> for crate::Error {
fn from(e: ParseError) -> Self {
use crate::error::{ErrorCode, Module};
let code = match &e {
ParseError::NotInitial => ErrorCode::Parse,
ParseError::AeadDecryptFailed | ParseError::CryptoFrameDecode => ErrorCode::Parse,
ParseError::_NoClientHello => ErrorCode::Parse,
};
crate::Error::with_code(Module::Quic, code, e.to_string())
}
}
pub fn parse(datagram: &[u8]) -> Result<QuicInitial, ParseError> {
let (meta, frames) = decode_frames(datagram)?;
let crypto_stream = reassemble_crypto_stream(&frames);
Ok(build_from_stream(meta, &crypto_stream))
}
pub(super) fn decode_frames(
datagram: &[u8],
) -> Result<(InitialMeta, Vec<CryptoFrame>), ParseError> {
let header = parse_initial(datagram).map_err(|_| ParseError::NotInitial)?;
let decrypted = decrypt_initial(&header).map_err(|_| ParseError::AeadDecryptFailed)?;
let frames = parse_crypto_frames(&decrypted).map_err(|_| ParseError::CryptoFrameDecode)?;
let meta = InitialMeta {
version: QuicVersion::from_raw(header.version),
dcid: header.dcid.to_vec(),
scid: header.scid.to_vec(),
token_present: !header.token.is_empty(),
};
Ok((meta, frames))
}
pub(super) fn build_from_stream(meta: InitialMeta, crypto_stream: &[u8]) -> QuicInitial {
#[cfg(feature = "tls")]
let client_hello = extract_client_hello(crypto_stream);
#[cfg(feature = "tls")]
let (sni, alpn) = client_hello
.as_ref()
.map(|ch| (ch.sni.clone(), ch.alpn.clone()))
.unwrap_or((None, Vec::new()));
#[cfg(not(feature = "tls"))]
let (sni, alpn) = extract_tls_metadata(crypto_stream).unwrap_or((None, Vec::new()));
QuicInitial {
version: meta.version,
dcid: meta.dcid,
scid: meta.scid,
token_present: meta.token_present,
sni,
alpn,
#[cfg(feature = "tls")]
client_hello,
}
}
#[cfg(feature = "tls")]
fn extract_client_hello(crypto_stream: &[u8]) -> Option<crate::tls::TlsClientHello> {
let (_, msg) = tls_parser::parse_tls_message_handshake(crypto_stream).ok()?;
let ch = match msg {
TlsMessage::Handshake(TlsMessageHandshake::ClientHello(ch)) => ch,
_ => return None,
};
Some(crate::tls::build_client_hello(
crate::tls::TlsVersion::Tls1_3,
&ch,
))
}
#[cfg(not(feature = "tls"))]
fn extract_tls_metadata(crypto_stream: &[u8]) -> Option<(Option<String>, Vec<String>)> {
let (_, msgs) = tls_parser::parse_tls_message_handshake(crypto_stream).ok()?;
let ch = match msgs {
TlsMessage::Handshake(TlsMessageHandshake::ClientHello(ch)) => ch,
_ => return None,
};
let (sni, alpn) = extract_from_client_hello(&ch);
Some((sni, alpn))
}
#[cfg(not(feature = "tls"))]
fn extract_from_client_hello(ch: &TlsClientHelloContents<'_>) -> (Option<String>, Vec<String>) {
let mut sni = None;
let mut alpn = Vec::new();
if let Some(ext_bytes) = ch.ext {
let mut rest = ext_bytes;
while let Ok((next, ext)) = tls_parser::parse_tls_extension(rest) {
match ext {
TlsExtension::SNI(entries) => {
if let Some((_kind, name)) = entries.first()
&& let Ok(s) = std::str::from_utf8(name)
{
sni = Some(s.to_string());
}
}
TlsExtension::ALPN(protos) => {
for proto in protos {
if let Ok(s) = std::str::from_utf8(proto) {
alpn.push(s.to_string());
}
}
}
_ => {}
}
if next.len() >= rest.len() {
break;
}
rest = next;
}
}
(sni, alpn)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_returns_not_initial() {
assert_eq!(parse(&[]).unwrap_err(), ParseError::NotInitial);
}
#[test]
fn non_long_header_returns_not_initial() {
let bytes = [0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(parse(&bytes).unwrap_err(), ParseError::NotInitial);
}
#[test]
fn parse_error_implements_error() {
fn _is_error<E: std::error::Error>() {}
_is_error::<ParseError>();
}
#[test]
fn parser_kind_is_quic() {
assert_eq!(parser_kind(), "quic");
}
}