use super::hello::{ECHClientHello, ECHClientHelloOuter, HpkeSymmetricCipherSuite};
use super::{ClientHello, ClientHelloExtension};
use crate::{
ApplicationProtocol, CipherSuite, ExtensionId, ProtocolVersion, enums::CompressionAlgorithm,
};
use nom::{
IResult, Parser,
bytes::streaming::take,
combinator::{complete, cond, map, map_opt, map_parser, verify},
error::{ErrorKind, make_error},
multi::{length_data, many0},
number::streaming::{be_u8, be_u16},
};
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorExt as _};
use rama_core::telemetry::tracing;
use rama_net::address::{Domain, Host};
use std::str;
pub fn parse_client_hello(i: &[u8]) -> Result<ClientHello, BoxError> {
match parse_client_hello_inner(i) {
Err(err) => Err(
BoxError::from_static_str("parse client hello handshake message")
.context_debug_field("err", err.to_owned()),
),
Ok((i, hello)) => {
if !i.is_empty() {
tracing::debug!(
ech = hello.has_encrypted_client_hello(),
"parse_client_hello: parse client hello handshake message: unexpected trailer content",
);
}
Ok(hello)
}
}
}
pub fn parse_client_hello_handshake(i: &[u8]) -> Result<ClientHello, BoxError> {
match parse_client_hello_handshake_inner(i) {
Err(err) => Err(
BoxError::from_static_str("parse client hello handshake message")
.context_debug_field("err", err.to_owned()),
),
Ok((i, hello)) => {
if !i.is_empty() {
tracing::debug!(
ech = hello.has_encrypted_client_hello(),
"parse_client_hello_handshake: parse client hello handshake message: unexpected trailer content",
);
}
Ok(hello)
}
}
}
fn parse_client_hello_handshake_inner(i: &[u8]) -> IResult<&[u8], ClientHello> {
let (i, _) = verify(take(3usize), |s: &[u8]| {
matches!(s, [0x16, 0x03, 0x00..=0x04])
})
.parse(i)?;
let (i, _) = be_u16(i)?;
let (i, _) = verify(take(4usize), |s: &[u8]| matches!(s, [0x01, ..])).parse(i)?;
parse_client_hello_inner(i)
}
pub fn extract_sni_from_client_hello_handshake(i: &[u8]) -> Result<Option<Domain>, BoxError> {
parse_client_hello_handshake_sni_inner(i)
.map(|(_, domain)| domain)
.map_err(|err| {
BoxError::from_static_str("parse client hello handshake message to find SNI")
.context_debug_field("err", err.to_owned())
})
}
pub fn extract_sni_from_client_hello_record(i: &[u8]) -> Result<Option<Domain>, BoxError> {
parse_client_hello_record_sni_inner(i)
.map(|(_, domain)| domain)
.map_err(|err| {
BoxError::from_static_str("parse client hello record message to find SNI")
.context_debug_field("err", err.to_owned())
})
}
fn parse_client_hello_handshake_sni_inner(i: &[u8]) -> IResult<&[u8], Option<Domain>> {
let (i, _) = verify(take(3usize), |s: &[u8]| {
matches!(s, [0x16, 0x03, 0x00..=0x04])
})
.parse(i)?;
let (i, _) = be_u16(i)?;
let (i, _) = verify(take(4usize), |s: &[u8]| matches!(s, [0x01, ..])).parse(i)?;
parse_client_hello_record_sni_inner(i)
}
fn parse_client_hello_record_sni_inner(i: &[u8]) -> IResult<&[u8], Option<Domain>> {
let (i, _) = take(34usize)(i)?;
let (i, sidlen) = verify(be_u8, |&n| n <= 32).parse(i)?;
let (i, _) = cond(sidlen > 0, take(sidlen as usize)).parse(i)?;
let (i, ciphers_len) = be_u16(i)?;
let (i, _) = take(ciphers_len)(i)?;
let (i, comp_len) = be_u8(i)?;
let (i, _) = take(comp_len)(i)?;
if i.is_empty() {
return Ok((&[], None));
}
let (after_len, ext_len) = be_u16(i)?;
let (_, mut ext_payload) = take(ext_len as usize).parse(after_len)?;
while !ext_payload.is_empty() {
let (next, domain) = parse_tls_client_hello_extension_sni_host_or_skip(ext_payload)?;
if let Some(domain) = domain {
return Ok((next, Some(domain)));
}
ext_payload = next;
}
Ok((&[], None))
}
fn parse_client_hello_inner(i: &[u8]) -> IResult<&[u8], ClientHello> {
let (i, version) = be_u16(i)?;
let (i, _random) = take(32usize)(i)?;
let (i, sidlen) = verify(be_u8, |&n| n <= 32).parse(i)?;
let (i, _sid) = cond(sidlen > 0, take(sidlen as usize)).parse(i)?;
let (i, ciphers_len) = be_u16(i)?;
let (i, cipher_suites) = parse_cipher_suites(i, ciphers_len as usize)?;
let (i, comp_len) = be_u8(i)?;
let (i, compression_algorithms) = parse_compressions_algs(i, comp_len as usize)?;
let (i, extensions) = if i.is_empty() {
(i, Vec::new())
} else {
let (after_len, ext_len) = be_u16(i)?;
let (after_ext, mut ext_payload) = take(ext_len as usize).parse(after_len)?;
let mut extensions = vec![];
while !ext_payload.is_empty() {
let (next, ch_ext) = parse_tls_client_hello_extension(ext_payload)?;
extensions.push(ch_ext);
ext_payload = next;
}
(after_ext, extensions)
};
Ok((
i,
ClientHello {
protocol_version: version.into(),
cipher_suites,
compression_algorithms,
extensions,
},
))
}
fn parse_cipher_suites(i: &[u8], len: usize) -> IResult<&[u8], Vec<CipherSuite>> {
if len == 0 {
return Ok((i, Vec::new()));
}
if len % 2 == 1 || len > i.len() {
return Err(nom::Err::Error(make_error(i, ErrorKind::LengthValue)));
}
let v = (i[..len])
.chunks(2)
.map(|chunk| CipherSuite::from(((chunk[0] as u16) << 8) | chunk[1] as u16))
.collect();
Ok((&i[len..], v))
}
fn parse_compressions_algs(i: &[u8], len: usize) -> IResult<&[u8], Vec<CompressionAlgorithm>> {
if len == 0 {
return Ok((i, Vec::new()));
}
if len > i.len() {
return Err(nom::Err::Error(make_error(i, ErrorKind::LengthValue)));
}
let v = (i[..len])
.iter()
.map(|&it| CompressionAlgorithm::from(it))
.collect();
Ok((&i[len..], v))
}
fn parse_tls_client_hello_extension_sni_host_or_skip(i: &[u8]) -> IResult<&[u8], Option<Domain>> {
let (i, ext_type) = be_u16(i)?;
let id = ExtensionId::from(ext_type);
let (i, ext_data) = length_data(be_u16).parse(i)?;
if id == ExtensionId::SERVER_NAME {
parse_tls_extension_sni(ext_data)
} else {
Ok((i, None))
}
}
fn parse_tls_client_hello_extension(i: &[u8]) -> IResult<&[u8], ClientHelloExtension> {
let (i, ext_type) = be_u16(i)?;
let id = ExtensionId::from(ext_type);
let (i, ext_data) = length_data(be_u16).parse(i)?;
let ext_len = ext_data.len() as u16;
let (_, ext) = match id {
ExtensionId::SERVER_NAME => parse_tls_extension_sni_content(ext_data),
ExtensionId::SUPPORTED_GROUPS => parse_tls_extension_elliptic_curves_content(ext_data),
ExtensionId::EC_POINT_FORMATS => parse_tls_extension_ec_point_formats_content(ext_data),
ExtensionId::SIGNATURE_ALGORITHMS => {
parse_tls_extension_signature_algorithms_content(ext_data)
}
ExtensionId::APPLICATION_LAYER_PROTOCOL_NEGOTIATION => {
parse_tls_extension_alpn_content(ext_data)
}
ExtensionId::SUPPORTED_VERSIONS => {
parse_tls_extension_supported_versions_content(ext_data, ext_len)
}
ExtensionId::COMPRESS_CERTIFICATE => {
parse_tls_extension_certificate_compression_content(ext_data)
}
ExtensionId::DELEGATED_CREDENTIAL => parse_tls_extension_delegated_credentials(ext_data),
ExtensionId::RECORD_SIZE_LIMIT => {
let (i, v) = be_u16(ext_data)?;
Ok((i, ClientHelloExtension::RecordSizeLimit(v)))
}
ExtensionId::ENCRYPTED_CLIENT_HELLO => {
let (i, ech) = parse_ech_client_hello(ext_data)?;
Ok((i, ClientHelloExtension::EncryptedClientHello(ech)))
}
ExtensionId::APPLICATION_SETTINGS => {
parse_tls_extension_application_settings_content(ext_data, true)
}
ExtensionId::OLD_APPLICATION_SETTINGS => {
parse_tls_extension_application_settings_content(ext_data, false)
}
_ => Ok((
i,
ClientHelloExtension::Opaque {
id,
data: ext_data.to_vec(),
},
)),
}?;
Ok((i, ext))
}
fn parse_tls_extension_sni_content(i: &[u8]) -> IResult<&[u8], ClientHelloExtension> {
let (i, domain) = parse_tls_extension_sni(i)?;
Ok((i, ClientHelloExtension::ServerName(domain)))
}
fn parse_tls_extension_sni(i: &[u8]) -> IResult<&[u8], Option<Domain>> {
if i.is_empty() {
return Ok((i, None));
}
let (i, list_len) = be_u16(i)?;
let (i, mut v) = map_parser(
take(list_len),
many0(complete(parse_tls_extension_sni_hostname)),
)
.parse(i)?;
if v.len() > 1 {
return Err(nom::Err::Error(nom::error::Error::new(
i,
ErrorKind::TooLarge,
)));
}
Ok((i, v.pop()))
}
fn parse_tls_extension_sni_hostname(i: &[u8]) -> IResult<&[u8], Domain> {
let (i, nt) = be_u8(i)?;
if nt != 0 {
return Err(nom::Err::Error(nom::error::Error::new(i, ErrorKind::IsNot)));
}
let (i, v) = length_data(be_u16).parse(i)?;
let host: Host = str::from_utf8(v)
.map_err(|_e| nom::Err::Error(nom::error::Error::new(i, ErrorKind::Not)))?
.parse()
.map_err(|_e| nom::Err::Error(nom::error::Error::new(i, ErrorKind::Not)))?;
match host.try_into_domain() {
Ok(domain) => Ok((i, domain)),
Err(_) => Err(nom::Err::Error(nom::error::Error::new(i, ErrorKind::Not))),
}
}
fn parse_tls_extension_elliptic_curves_content(i: &[u8]) -> IResult<&[u8], ClientHelloExtension> {
map_parser(
length_data(be_u16),
map(parse_u16_type, ClientHelloExtension::SupportedGroups),
)
.parse(i)
}
fn parse_tls_extension_ec_point_formats_content(i: &[u8]) -> IResult<&[u8], ClientHelloExtension> {
map_parser(
length_data(be_u8),
map(parse_u8_type, ClientHelloExtension::ECPointFormats),
)
.parse(i)
}
fn parse_tls_extension_supported_versions_content(
i: &[u8],
ext_len: u16,
) -> IResult<&[u8], ClientHelloExtension> {
if ext_len == 2 {
map(be_u16, |x| {
ClientHelloExtension::SupportedVersions(vec![ProtocolVersion::from(x)])
})
.parse(i)
} else {
let (i, _) = be_u8(i)?;
if ext_len == 0 {
return Err(nom::Err::Error(make_error(i, ErrorKind::Verify)));
}
let (i, l) = map_parser(take(ext_len - 1), parse_u16_type).parse(i)?;
Ok((i, ClientHelloExtension::SupportedVersions(l)))
}
}
fn parse_tls_extension_signature_algorithms_content(
i: &[u8],
) -> IResult<&[u8], ClientHelloExtension> {
map_parser(
length_data(be_u16),
map(parse_u16_type, ClientHelloExtension::SignatureAlgorithms),
)
.parse(i)
}
fn parse_tls_extension_delegated_credentials(i: &[u8]) -> IResult<&[u8], ClientHelloExtension> {
map_parser(
length_data(be_u16),
map(parse_u16_type, ClientHelloExtension::DelegatedCredentials),
)
.parse(i)
}
fn parse_tls_extension_alpn_content(i: &[u8]) -> IResult<&[u8], ClientHelloExtension> {
map_parser(
length_data(be_u16),
map(
parse_protocol_name_list,
ClientHelloExtension::ApplicationLayerProtocolNegotiation,
),
)
.parse(i)
}
fn parse_tls_extension_certificate_compression_content(
i: &[u8],
) -> IResult<&[u8], ClientHelloExtension> {
map_parser(
length_data(be_u8),
map(parse_u16_type, ClientHelloExtension::CertificateCompression),
)
.parse(i)
}
fn parse_protocol_name_list(mut i: &[u8]) -> IResult<&[u8], Vec<ApplicationProtocol>> {
let mut v = vec![];
while !i.is_empty() {
let (n, alpn) = map_parser(length_data(be_u8), parse_protocol_name).parse(i)?;
v.push(alpn);
i = n;
}
Ok((i, v))
}
fn parse_protocol_name(i: &[u8]) -> IResult<&[u8], ApplicationProtocol> {
let alpn = ApplicationProtocol::from(i);
Ok((&[], alpn))
}
fn parse_tls_extension_application_settings_content(
i: &[u8],
new_codepoint: bool,
) -> IResult<&[u8], ClientHelloExtension> {
let (n, alps) = map_parser(length_data(be_u16), parse_protocol_name_list).parse(i)?;
let application_settings = ClientHelloExtension::ApplicationSettings {
protocols: alps,
new_codepoint,
};
Ok((n, application_settings))
}
fn parse_u8_type<T: From<u8>>(i: &[u8]) -> IResult<&[u8], Vec<T>> {
let v = i.iter().map(|i| T::from(*i)).collect();
Ok((&[], v))
}
fn parse_u16_type<T: From<u16>>(i: &[u8]) -> IResult<&[u8], Vec<T>> {
let len = i.len();
if len == 0 {
return Ok((i, Vec::new()));
}
if len % 2 == 1 || len > i.len() {
return Err(nom::Err::Error(make_error(i, ErrorKind::LengthValue)));
}
let v = (i[..len])
.chunks(2)
.map(|chunk| T::from(((chunk[0] as u16) << 8) | chunk[1] as u16))
.collect();
Ok((&i[len..], v))
}
fn parse_ech_client_hello(input: &[u8]) -> IResult<&[u8], ECHClientHello> {
let (input, is_outer) = map_opt(be_u8, |v| match v {
0 => Some(true),
1 => Some(false),
_ => None,
})
.parse(input)?;
match is_outer {
true => {
let (input, (kdf_id, aead_id, config_id)) = (be_u16, be_u16, be_u8).parse(input)?;
let (input, enc) = length_data(be_u16).parse(input)?;
let (input, payload) = length_data(be_u16).parse(input)?;
Ok((
input,
ECHClientHello::Outer(ECHClientHelloOuter {
cipher_suite: HpkeSymmetricCipherSuite {
aead_id: aead_id.into(),
kdf_id: kdf_id.into(),
},
config_id,
enc: enc.to_vec(),
payload: payload.to_vec(),
}),
))
}
false => Ok((input, ECHClientHello::Inner)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
CertificateCompressionAlgorithm, ECPointFormat, ExtensionId, SignatureScheme,
SupportedGroup,
};
use rama_net::address::Domain;
#[test]
fn test_parse_tls_extension_sni_hostname() {
let test_cases = [
("", None),
("\x00", None),
("\x00\x00\x05x.com", Some(Domain::from_static("x.com"))),
(
"\x00\x00\x10fp.ramaproxy.org",
Some(Domain::from_static("fp.ramaproxy.org")),
),
("\x00\x00\x11fp.ramaproxy.org", None),
("\x01\x00\x10fp.ramaproxy.org", None),
("\x00\x00\x09127.0.0.1", None),
("\x00\x00\x276670:2e72:616d:6170:726f:7879:2e6f:7267", None),
];
for (input, expected_output) in test_cases {
let result = parse_tls_extension_sni_hostname(input.as_bytes());
match expected_output {
Some(domain) => assert_eq!(domain, result.unwrap().1),
None => {
result.unwrap_err();
}
}
}
}
#[test]
fn test_parse_client_hello_zero_bytes_failure() {
parse_client_hello(&[]).unwrap_err();
}
fn ch_record_body_prefix() -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&[0x03, 0x03]); v.extend_from_slice(&[0u8; 32]); v.push(0x00); v.extend_from_slice(&[0x00, 0x02]); v.extend_from_slice(&[0x13, 0x01]); v.extend_from_slice(&[0x01, 0x00]); v
}
#[test]
fn parse_client_hello_without_extensions_section() {
let body = ch_record_body_prefix();
let ch = parse_client_hello(&body).expect("legacy extensionless CH must parse");
assert!(ch.extensions().is_empty());
assert_eq!(ch.cipher_suites(), &[CipherSuite::TLS13_AES_128_GCM_SHA256]);
}
#[test]
fn parse_client_hello_with_complete_extensions() {
let mut body = ch_record_body_prefix();
body.extend_from_slice(&[0x00, 0x04]); body.extend_from_slice(&[0x00, 0x17, 0x00, 0x00]); let ch = parse_client_hello(&body).expect("complete CH must parse");
assert_eq!(ch.extensions().len(), 1);
}
#[test]
fn parse_client_hello_truncated_extensions_section_is_error() {
let mut body = ch_record_body_prefix();
body.extend_from_slice(&[0x00, 0x0a]); body.extend_from_slice(&[0x00, 0x17, 0x00, 0x00]); parse_client_hello(&body)
.expect_err("truncated extensions must not parse as extension-less");
}
#[test]
fn parse_client_hello_handshake_truncated_extensions_is_error() {
let mut body = ch_record_body_prefix();
body.extend_from_slice(&[0x00, 0x04]);
body.extend_from_slice(&[0x00, 0x17, 0x00, 0x00]);
let wrap_handshake = |body: &[u8]| -> Vec<u8> {
let mut hs = Vec::new();
hs.push(0x01);
let blen = body.len();
hs.extend_from_slice(&[
((blen >> 16) & 0xff) as u8,
((blen >> 8) & 0xff) as u8,
(blen & 0xff) as u8,
]);
hs.extend_from_slice(body);
let mut rec = Vec::new();
rec.extend_from_slice(&[0x16, 0x03, 0x01]);
let hlen = hs.len();
rec.extend_from_slice(&[((hlen >> 8) & 0xff) as u8, (hlen & 0xff) as u8]);
rec.extend_from_slice(&hs);
rec
};
let complete_record = wrap_handshake(&body);
let ch =
parse_client_hello_handshake(&complete_record).expect("complete record must parse");
assert_eq!(ch.extensions().len(), 1);
let truncated_record = &complete_record[..complete_record.len() - 2];
parse_client_hello_handshake(truncated_record)
.expect_err("truncated handshake-level CH must not parse as extension-less");
}
#[test]
fn test_parse_client_hello_pcap_dump_apple_itunes_bytes_success() {
let client_hello = parse_client_hello(&[
0x03, 0x03, 0x74, 0xbd, 0x2a, 0x45, 0x51, 0x29, 0x95, 0x42, 0x61, 0x17, 0xab, 0x20,
0x8f, 0xf2, 0x30, 0xea, 0x72, 0x0f, 0x2e, 0xcd, 0x73, 0xff, 0xcb, 0xbc, 0x89, 0x10,
0x46, 0xc8, 0xb7, 0x3c, 0x31, 0xf0, 0x20, 0x25, 0xea, 0x68, 0xb2, 0x13, 0x62, 0xf7,
0x4b, 0x0f, 0x82, 0x57, 0xf6, 0xe9, 0x41, 0xc5, 0x28, 0x74, 0xa9, 0xf4, 0x80, 0x73,
0x90, 0x4f, 0x85, 0xe7, 0xa7, 0xaa, 0x84, 0x37, 0xe8, 0xdf, 0x97, 0x00, 0x2a, 0x7a,
0x7a, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2c, 0xc0, 0x2b, 0xcc, 0xa9, 0xc0,
0x30, 0xc0, 0x2f, 0xcc, 0xa8, 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x13, 0x00,
0x9d, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0x01,
0x00, 0x01, 0x89, 0x8a, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x18, 0x00,
0x00, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x69, 0x74, 0x75, 0x6e, 0x65, 0x73, 0x2e,
0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00, 0x00, 0xff,
0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x3a, 0x3a, 0x00, 0x1d,
0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x10,
0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31,
0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00,
0x18, 0x00, 0x16, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x02, 0x03, 0x08,
0x05, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x12, 0x00,
0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0x3a, 0x3a, 0x00, 0x01, 0x00, 0x00, 0x1d,
0x00, 0x20, 0x49, 0xee, 0x60, 0xa1, 0x29, 0xc0, 0x44, 0x44, 0xc3, 0x02, 0x8a, 0x25,
0x8c, 0x86, 0x64, 0xc3, 0x3a, 0xc0, 0xec, 0xbb, 0x6c, 0xe7, 0x93, 0xda, 0x51, 0xca,
0xef, 0x59, 0xc9, 0xee, 0x41, 0x31, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b,
0x00, 0x0b, 0x0a, 0xda, 0xda, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00,
0x1b, 0x00, 0x03, 0x02, 0x00, 0x01, 0xda, 0xda, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00,
0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
])
.expect("to parse");
assert_eq!(
client_hello.cipher_suites(),
&[
CipherSuite::from(0x7a7a),
CipherSuite::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS13_AES_256_GCM_SHA384,
CipherSuite::TLS13_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA,
]
);
assert_eq!(client_hello.extensions().len(), 16);
assert_eq_opaque_extension(
&client_hello.extensions()[0],
ExtensionId::from(0x8a8a),
&[],
);
assert_eq_server_name_extension(
&client_hello.extensions()[1],
Some(&Domain::from_static("init.itunes.apple.com")),
);
assert_eq_opaque_extension(
&client_hello.extensions()[2],
ExtensionId::EXTENDED_MASTER_SECRET,
&[],
);
assert_eq_opaque_extension(
&client_hello.extensions()[3],
ExtensionId::RENEGOTIATION_INFO,
&[0x00],
);
assert_eq_supported_groups_extension(
&client_hello.extensions()[4],
&[
SupportedGroup::from(0x3a3a),
SupportedGroup::X25519,
SupportedGroup::SECP256R1,
SupportedGroup::SECP384R1,
SupportedGroup::SECP521R1,
],
);
assert_eq_ec_point_formats_extension(
&client_hello.extensions()[5],
&[ECPointFormat::Uncompressed],
);
assert_eq_alpn_extension(
&client_hello.extensions()[6],
&[ApplicationProtocol::HTTP_2, ApplicationProtocol::HTTP_11],
);
assert_eq_opaque_extension(
&client_hello.extensions()[7],
ExtensionId::STATUS_REQUEST,
&[0x01, 0x00, 0x00, 0x00, 0x00],
);
assert_eq_signature_algorithms_extension(
&client_hello.extensions()[8],
&[
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::RSA_PKCS1_SHA1,
],
);
assert_eq_opaque_extension(
&client_hello.extensions()[9],
ExtensionId::SIGNED_CERTIFICATE_TIMESTAMP,
&[],
);
assert_eq_opaque_extension(
&client_hello.extensions()[10],
ExtensionId::KEY_SHARE,
&[
0x00, 0x29, 0x3a, 0x3a, 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, 0x49, 0xee, 0x60,
0xa1, 0x29, 0xc0, 0x44, 0x44, 0xc3, 0x02, 0x8a, 0x25, 0x8c, 0x86, 0x64, 0xc3, 0x3a,
0xc0, 0xec, 0xbb, 0x6c, 0xe7, 0x93, 0xda, 0x51, 0xca, 0xef, 0x59, 0xc9, 0xee, 0x41,
0x31,
],
);
assert_eq_opaque_extension(
&client_hello.extensions()[11],
ExtensionId::PSK_KEY_EXCHANGE_MODES,
&[0x01, 0x01],
);
assert_eq_supported_versions_extension(
&client_hello.extensions()[12],
&[
ProtocolVersion::from(0xdada),
ProtocolVersion::TLSv1_3,
ProtocolVersion::TLSv1_2,
ProtocolVersion::TLSv1_1,
ProtocolVersion::TLSv1_0,
],
);
assert_eq_supported_certificate_compression_extension(
&client_hello.extensions()[13],
&[CertificateCompressionAlgorithm::Zlib],
);
assert_eq_opaque_extension(
&client_hello.extensions()[14],
ExtensionId::from(0xdada), &[0x00],
);
assert_eq_opaque_extension(
&client_hello.extensions()[15],
ExtensionId::from(0x0015), &[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
],
);
}
fn assert_eq_opaque_extension(
ext: &ClientHelloExtension,
expected_id: ExtensionId,
expected_data: &[u8],
) {
match ext {
ClientHelloExtension::Opaque { id, data } => {
assert_eq!(*id, expected_id);
assert_eq!(data, expected_data);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_server_name_extension(
ext: &ClientHelloExtension,
expected_domain: Option<&Domain>,
) {
match ext {
ClientHelloExtension::ServerName(domain) => {
assert_eq!(domain.as_ref(), expected_domain);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_supported_groups_extension(
ext: &ClientHelloExtension,
expected_groups: &[SupportedGroup],
) {
match ext {
ClientHelloExtension::SupportedGroups(groups) => {
assert_eq!(groups, expected_groups);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_ec_point_formats_extension(
ext: &ClientHelloExtension,
expected_ec_point_formats: &[ECPointFormat],
) {
match ext {
ClientHelloExtension::ECPointFormats(points) => {
assert_eq!(points, expected_ec_point_formats);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_alpn_extension(
ext: &ClientHelloExtension,
expected_alpn_list: &[ApplicationProtocol],
) {
match ext {
ClientHelloExtension::ApplicationLayerProtocolNegotiation(alpn_list) => {
assert_eq!(alpn_list, expected_alpn_list);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_signature_algorithms_extension(
ext: &ClientHelloExtension,
expected_signature_algorithms: &[SignatureScheme],
) {
match ext {
ClientHelloExtension::SignatureAlgorithms(algorithms) => {
assert_eq!(algorithms, expected_signature_algorithms);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_supported_versions_extension(
ext: &ClientHelloExtension,
expected_version_list: &[ProtocolVersion],
) {
match ext {
ClientHelloExtension::SupportedVersions(version_list) => {
assert_eq!(version_list, expected_version_list);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
fn assert_eq_supported_certificate_compression_extension(
ext: &ClientHelloExtension,
expected_certificate_compression: &[CertificateCompressionAlgorithm],
) {
match ext {
ClientHelloExtension::CertificateCompression(algorithms) => {
assert_eq!(algorithms, expected_certificate_compression);
}
other => {
panic!("unexpected extension: {other:?}");
}
}
}
#[test]
fn test_extract_sni_from_client_hello_handshake() {
#[expect(clippy::single_element_loop)]
for (content, expected_sni) in [(
&[
0x16, 0x03, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0xfc, 0x03, 0x03, 0x02, 0x15, 0xfd,
0xe2, 0x92, 0xc0, 0x46, 0x9f, 0x92, 0xbe, 0xd7, 0xe9, 0x1a, 0x3c, 0x50, 0x5e, 0x55,
0x49, 0x17, 0xa6, 0xf8, 0xa5, 0xca, 0xa4, 0x6d, 0x60, 0xcc, 0xea, 0xf7, 0x25, 0xf0,
0x6e, 0x20, 0x41, 0x20, 0x18, 0x66, 0x5c, 0xae, 0x08, 0xb0, 0x10, 0x96, 0x3c, 0xad,
0xb4, 0x13, 0xe1, 0x92, 0xce, 0x96, 0xad, 0x9d, 0x45, 0x05, 0xb7, 0xa6, 0x4c, 0x01,
0x71, 0x08, 0x74, 0x0d, 0x1f, 0x35, 0x00, 0x2a, 0x3a, 0x3a, 0x13, 0x01, 0x13, 0x02,
0x13, 0x03, 0xc0, 0x2c, 0xc0, 0x2b, 0xcc, 0xa9, 0xc0, 0x30, 0xc0, 0x2f, 0xcc, 0xa8,
0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x13, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x35,
0x00, 0x2f, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0x01, 0x00, 0x01, 0x89, 0xda, 0xda,
0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, 0x00, 0x0f, 0x6f, 0x6e, 0x65,
0x2e, 0x6f, 0x6e, 0x65, 0x2e, 0x6f, 0x6e, 0x65, 0x2e, 0x6f, 0x6e, 0x65, 0x00, 0x17,
0x00, 0x00, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0xfa,
0xfa, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01,
0x00, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x16,
0x00, 0x14, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x05, 0x08, 0x05,
0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x33,
0x00, 0x2b, 0x00, 0x29, 0xfa, 0xfa, 0x00, 0x01, 0x00, 0x00, 0x1d, 0x00, 0x20, 0x7c,
0xe1, 0xc6, 0xc2, 0x01, 0x69, 0x42, 0xba, 0x2b, 0xec, 0x07, 0x2f, 0x04, 0xbd, 0xb6,
0x2a, 0x7e, 0x04, 0x6b, 0x96, 0x98, 0x51, 0x4e, 0x80, 0xb3, 0x2a, 0x4c, 0x4f, 0x1f,
0x39, 0x82, 0x2b, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b, 0x00, 0x0b, 0x0a,
0x6a, 0x6a, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00, 0x1b, 0x00, 0x03,
0x02, 0x00, 0x01, 0x3a, 0x3a, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00, 0xd3, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
Domain::from_static("one.one.one.one"),
)] {
let domain = extract_sni_from_client_hello_handshake(content).unwrap();
assert_eq!(Some(expected_sni), domain);
}
}
#[test]
fn test_extract_sni_from_client_hello_record() {
#[expect(clippy::single_element_loop)]
for (content, expected_sni) in [(
&[
0x03, 0x03, 0x74, 0xbd, 0x2a, 0x45, 0x51, 0x29, 0x95, 0x42, 0x61, 0x17, 0xab, 0x20,
0x8f, 0xf2, 0x30, 0xea, 0x72, 0x0f, 0x2e, 0xcd, 0x73, 0xff, 0xcb, 0xbc, 0x89, 0x10,
0x46, 0xc8, 0xb7, 0x3c, 0x31, 0xf0, 0x20, 0x25, 0xea, 0x68, 0xb2, 0x13, 0x62, 0xf7,
0x4b, 0x0f, 0x82, 0x57, 0xf6, 0xe9, 0x41, 0xc5, 0x28, 0x74, 0xa9, 0xf4, 0x80, 0x73,
0x90, 0x4f, 0x85, 0xe7, 0xa7, 0xaa, 0x84, 0x37, 0xe8, 0xdf, 0x97, 0x00, 0x2a, 0x7a,
0x7a, 0x13, 0x01, 0x13, 0x02, 0x13, 0x03, 0xc0, 0x2c, 0xc0, 0x2b, 0xcc, 0xa9, 0xc0,
0x30, 0xc0, 0x2f, 0xcc, 0xa8, 0xc0, 0x0a, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x13, 0x00,
0x9d, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0xc0, 0x08, 0xc0, 0x12, 0x00, 0x0a, 0x01,
0x00, 0x01, 0x89, 0x8a, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x18, 0x00,
0x00, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x2e, 0x69, 0x74, 0x75, 0x6e, 0x65, 0x73, 0x2e,
0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x17, 0x00, 0x00, 0xff,
0x01, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0a, 0x3a, 0x3a, 0x00, 0x1d,
0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x10,
0x00, 0x0e, 0x00, 0x0c, 0x02, 0x68, 0x32, 0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31,
0x2e, 0x31, 0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00,
0x18, 0x00, 0x16, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x02, 0x03, 0x08,
0x05, 0x08, 0x05, 0x05, 0x01, 0x08, 0x06, 0x06, 0x01, 0x02, 0x01, 0x00, 0x12, 0x00,
0x00, 0x00, 0x33, 0x00, 0x2b, 0x00, 0x29, 0x3a, 0x3a, 0x00, 0x01, 0x00, 0x00, 0x1d,
0x00, 0x20, 0x49, 0xee, 0x60, 0xa1, 0x29, 0xc0, 0x44, 0x44, 0xc3, 0x02, 0x8a, 0x25,
0x8c, 0x86, 0x64, 0xc3, 0x3a, 0xc0, 0xec, 0xbb, 0x6c, 0xe7, 0x93, 0xda, 0x51, 0xca,
0xef, 0x59, 0xc9, 0xee, 0x41, 0x31, 0x00, 0x2d, 0x00, 0x02, 0x01, 0x01, 0x00, 0x2b,
0x00, 0x0b, 0x0a, 0xda, 0xda, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x03, 0x01, 0x00,
0x1b, 0x00, 0x03, 0x02, 0x00, 0x01, 0xda, 0xda, 0x00, 0x01, 0x00, 0x00, 0x15, 0x00,
0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
],
Domain::from_static("init.itunes.apple.com"),
)] {
let domain = extract_sni_from_client_hello_record(content).unwrap();
assert_eq!(Some(expected_sni), domain);
}
}
#[test]
fn parse_alps_from_client_hello() {
#[expect(clippy::single_element_loop)]
for (content, expected_alps) in [(
&[
0x3, 0x3, 0x16, 0xbc, 0xa5, 0xcd, 0xa6, 0xa, 0x97, 0x8f, 0xde, 0x13, 0x7f, 0x5e,
0x84, 0x1b, 0x3f, 0x27, 0x4c, 0x1b, 0xc7, 0x5d, 0x31, 0xf1, 0xb7, 0x67, 0x6a, 0xb3,
0xc9, 0x11, 0xa2, 0x96, 0x6, 0x58, 0x20, 0x68, 0x1e, 0x6c, 0x75, 0x5, 0xa1, 0x3b,
0x62, 0x29, 0xc8, 0x29, 0x57, 0xe8, 0x45, 0x97, 0x95, 0x24, 0x6f, 0xc3, 0xba, 0x6c,
0xd5, 0x46, 0xca, 0xff, 0x77, 0x4b, 0x25, 0xfd, 0x2b, 0x69, 0xa9, 0x0, 0x20, 0x8a,
0x8a, 0x13, 0x1, 0x13, 0x2, 0x13, 0x3, 0xc0, 0x2b, 0xc0, 0x2f, 0xc0, 0x2c, 0xc0,
0x30, 0xcc, 0xa9, 0xcc, 0xa8, 0xc0, 0x13, 0xc0, 0x14, 0x0, 0x9c, 0x0, 0x9d, 0x0,
0x2f, 0x0, 0x35, 0x1, 0x0, 0x6, 0x69, 0x4a, 0x4a, 0x0, 0x0, 0x0, 0x2d, 0x0, 0x2,
0x1, 0x1, 0x0, 0xd, 0x0, 0x12, 0x0, 0x10, 0x4, 0x3, 0x8, 0x4, 0x4, 0x1, 0x5, 0x3,
0x8, 0x5, 0x5, 0x1, 0x8, 0x6, 0x6, 0x1, 0x0, 0x17, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x3,
0x2, 0x0, 0x2, 0x0, 0xb, 0x0, 0x2, 0x1, 0x0, 0x0, 0x5, 0x0, 0x5, 0x1, 0x0, 0x0,
0x0, 0x0, 0x0, 0xa, 0x0, 0xc, 0x0, 0xa, 0x2a, 0x2a, 0x11, 0xec, 0x0, 0x1d, 0x0,
0x17, 0x0, 0x18, 0xfe, 0xd, 0x0, 0xda, 0x0, 0x0, 0x1, 0x0, 0x1, 0x43, 0x0, 0x20,
0xee, 0xaa, 0x32, 0x96, 0x85, 0x85, 0x5b, 0xfd, 0xd4, 0xc8, 0x60, 0xb0, 0xd1, 0xfd,
0x20, 0x6b, 0x59, 0x60, 0x48, 0xa2, 0xff, 0x5c, 0x97, 0xae, 0x61, 0x4e, 0x34, 0xb2,
0xe8, 0x28, 0x98, 0x7f, 0x0, 0xb0, 0x58, 0x18, 0x34, 0x1e, 0x5c, 0x9f, 0xe6, 0x1c,
0x45, 0x5e, 0x7d, 0x22, 0xe4, 0x89, 0x36, 0xef, 0xdd, 0x77, 0xbe, 0xdd, 0xff, 0x2f,
0x5e, 0x86, 0xed, 0x23, 0xaa, 0xf9, 0xec, 0x3b, 0x1, 0xee, 0xe8, 0xbf, 0x89, 0xe,
0x39, 0xf2, 0x2f, 0x45, 0xb6, 0x70, 0x2c, 0x6a, 0x9f, 0xe, 0x10, 0xa, 0x9a, 0xe7,
0x1a, 0xa8, 0xa4, 0xeb, 0x46, 0x3, 0x7b, 0x82, 0xa2, 0xf4, 0x1a, 0x83, 0x6a, 0x67,
0x29, 0x30, 0x8e, 0x9c, 0x28, 0x46, 0xcd, 0xa1, 0x66, 0x7f, 0x3e, 0x6a, 0xe2, 0x53,
0x2d, 0x17, 0xda, 0xab, 0x73, 0xe, 0xe, 0xec, 0x6, 0xbc, 0x19, 0x17, 0x91, 0x45,
0x82, 0x95, 0xfa, 0x9d, 0x5c, 0x4c, 0xb, 0xdb, 0x7, 0xa2, 0x8d, 0xd4, 0x6, 0x21,
0x8b, 0x1d, 0x64, 0x63, 0x3, 0xc0, 0x7d, 0x42, 0x29, 0x84, 0x0, 0xbb, 0xa2, 0x6f,
0xf6, 0x3a, 0x54, 0x5c, 0xb8, 0xda, 0x44, 0x63, 0xa9, 0x54, 0x1a, 0x63, 0xde, 0xd9,
0x93, 0x77, 0x88, 0x91, 0xc3, 0x21, 0xdc, 0x36, 0xc2, 0xbc, 0x4e, 0xbe, 0x3a, 0x2,
0x37, 0x65, 0xa3, 0x67, 0x10, 0x3d, 0xbf, 0x91, 0xbe, 0x61, 0x51, 0x16, 0xac, 0xdd,
0xb9, 0x50, 0xfa, 0x21, 0x65, 0x7a, 0x4f, 0x76, 0x2d, 0x35, 0xa, 0xf2, 0x7c, 0x19,
0x0, 0x10, 0x0, 0xe, 0x0, 0xc, 0x2, 0x68, 0x32, 0x8, 0x68, 0x74, 0x74, 0x70, 0x2f,
0x31, 0x2e, 0x31, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x12, 0x0, 0x10, 0x0, 0x0,
0xd, 0x77, 0x69, 0x6b, 0x69, 0x70, 0x65, 0x64, 0x69, 0x61, 0x2e, 0x6f, 0x72, 0x67,
0xff, 0x1, 0x0, 0x1, 0x0, 0x44, 0xcd, 0x0, 0x5, 0x0, 0x3, 0x2, 0x68, 0x32, 0x0,
0x33, 0x4, 0xef, 0x4, 0xed, 0x2a, 0x2a, 0x0, 0x1, 0x0, 0x11, 0xec, 0x4, 0xc0, 0x5e,
0xa8, 0x36, 0x55, 0x26, 0xa9, 0x33, 0x2c, 0x4b, 0x51, 0x20, 0xa9, 0x9b, 0x27, 0x8a,
0x5a, 0x55, 0x28, 0xc, 0xea, 0x1, 0x8d, 0x96, 0x2f, 0x8, 0x1c, 0x95, 0xd, 0x51,
0x1, 0x2, 0x96, 0x9, 0xde, 0x78, 0x26, 0xc8, 0x92, 0x4a, 0x46, 0xa4, 0x5e, 0xf5,
0xcb, 0x49, 0xb9, 0xf0, 0x2c, 0x50, 0x57, 0x3e, 0xe6, 0x97, 0x87, 0x19, 0xf3, 0xac,
0xfb, 0x7b, 0x87, 0xdb, 0x20, 0x47, 0x58, 0x2c, 0x62, 0x45, 0x98, 0xc1, 0xf8, 0xd4,
0x1, 0x3d, 0xcb, 0x93, 0x90, 0x22, 0x24, 0xf9, 0x9, 0x8a, 0x49, 0x66, 0x69, 0x3a,
0x32, 0x19, 0xb7, 0x13, 0x1a, 0xd9, 0xc2, 0xad, 0xd7, 0x18, 0xcf, 0xf6, 0xc7, 0x99,
0x23, 0xc8, 0x85, 0x41, 0xc6, 0x8e, 0xcb, 0xcc, 0xc2, 0x17, 0x2c, 0xb8, 0xb2, 0xb1,
0xc9, 0xb6, 0x26, 0x60, 0xa9, 0xf0, 0x2d, 0x3b, 0x6a, 0x2d, 0xc5, 0x46, 0x61, 0xcb,
0x18, 0xbb, 0x85, 0xe0, 0x47, 0x3e, 0xe7, 0x53, 0xca, 0xba, 0x14, 0x10, 0x6, 0xa2,
0xc3, 0xd5, 0x40, 0x87, 0x44, 0x48, 0xf8, 0x1, 0x2b, 0xf2, 0x10, 0x3e, 0x85, 0x78,
0x17, 0xbf, 0xa5, 0x88, 0xe9, 0xf2, 0x86, 0x4a, 0x80, 0x2, 0x40, 0x28, 0x9b, 0x3,
0x49, 0x15, 0x80, 0x61, 0x9a, 0x8d, 0x76, 0xb7, 0x1c, 0x77, 0x4, 0x6d, 0x45, 0xb3,
0x96, 0xe1, 0x7, 0x50, 0xb8, 0x34, 0xe, 0xda, 0xf, 0x6b, 0xb5, 0xaf, 0xdb, 0x6,
0x2f, 0xa0, 0xc5, 0xb1, 0xcb, 0xcc, 0x54, 0x61, 0xc, 0x95, 0x23, 0x4a, 0x17, 0x1b,
0x59, 0x26, 0x18, 0xb1, 0x78, 0x4, 0xc5, 0x61, 0xd8, 0x47, 0x68, 0xae, 0xf1, 0x24,
0x98, 0x84, 0x53, 0x80, 0x62, 0x8c, 0xb6, 0x72, 0xa9, 0xd, 0xe4, 0x7f, 0xb9, 0xc3,
0x31, 0x79, 0x9c, 0xa0, 0xfa, 0xa1, 0x17, 0xe5, 0x52, 0xa5, 0xa6, 0x35, 0x6f, 0x4d,
0xb3, 0x79, 0xd4, 0x6c, 0x69, 0xb3, 0xb5, 0xa1, 0xa8, 0xcc, 0xb6, 0x70, 0xe9, 0x48,
0x11, 0xe0, 0x67, 0xde, 0xfc, 0x61, 0x6, 0x92, 0x9c, 0xa6, 0x2b, 0x75, 0xc7, 0x14,
0x73, 0xcf, 0x68, 0x31, 0x51, 0x2, 0xc, 0xda, 0x64, 0xb7, 0x74, 0xc3, 0x90, 0x4,
0x14, 0xc2, 0xf1, 0x72, 0x79, 0xf5, 0x47, 0x93, 0x8f, 0xf1, 0x2e, 0xc0, 0xbc, 0x5,
0x5d, 0xa7, 0x7d, 0x4, 0x5b, 0x8c, 0xf, 0xe9, 0x1, 0x21, 0x12, 0x17, 0x80, 0xdb,
0x84, 0x73, 0xe5, 0xcb, 0x74, 0x4a, 0x83, 0xb5, 0xeb, 0x77, 0x5b, 0x8a, 0xb5, 0x10,
0x3b, 0xd, 0x7f, 0xc1, 0x42, 0xbf, 0x33, 0xe, 0xb2, 0x67, 0xc2, 0xd9, 0x3b, 0x1e,
0x83, 0x8b, 0xc6, 0x19, 0xc4, 0xbb, 0x1c, 0x53, 0x3c, 0x23, 0x26, 0x5, 0x14, 0xdb,
0x83, 0xf3, 0x20, 0x89, 0x91, 0x1c, 0x94, 0x23, 0x8b, 0x7a, 0xf8, 0x9a, 0x4e, 0x5f,
0xda, 0xc, 0xd6, 0x46, 0x56, 0x11, 0x70, 0x6b, 0x40, 0xd8, 0xaa, 0xc8, 0x7c, 0xa0,
0x22, 0x85, 0xcf, 0xc, 0x4c, 0x22, 0x6f, 0x68, 0x68, 0x35, 0x57, 0x99, 0x32, 0x82,
0xa7, 0xe4, 0x8c, 0x1e, 0xc1, 0x57, 0xac, 0xc7, 0xaa, 0xc7, 0x9b, 0x77, 0x7e, 0x7d,
0x96, 0xb3, 0x6c, 0xd5, 0x9c, 0x32, 0x67, 0xc8, 0xcd, 0x38, 0x21, 0x34, 0xd4, 0xcb,
0xd4, 0xc8, 0xb9, 0x30, 0xf7, 0xe, 0x3f, 0xf0, 0xa1, 0xe5, 0xd8, 0x5, 0x57, 0x49,
0xa6, 0xc0, 0xca, 0x63, 0x37, 0x85, 0x60, 0x8d, 0x74, 0xc9, 0x89, 0x93, 0x61, 0xc3,
0x27, 0x48, 0x3f, 0x63, 0x20, 0x37, 0x54, 0x4f, 0x5c, 0x24, 0x74, 0x18, 0xb9, 0x43,
0xef, 0xf2, 0x68, 0xc, 0xb1, 0xa5, 0xad, 0x5a, 0x84, 0x27, 0xa2, 0x8f, 0x31, 0xf8,
0xa5, 0x45, 0xb6, 0x40, 0x21, 0x25, 0x7b, 0x16, 0xe7, 0x6d, 0x2e, 0x63, 0x9a, 0xf4,
0x18, 0x2f, 0x70, 0x66, 0x18, 0xc6, 0x22, 0x41, 0x18, 0xb3, 0x1a, 0x22, 0x83, 0xa2,
0x32, 0xaa, 0x1e, 0x81, 0xf4, 0x8c, 0x4e, 0xd4, 0x95, 0x2f, 0x23, 0xb6, 0x8d, 0x75,
0x89, 0x3d, 0x9a, 0xc0, 0xe, 0x2b, 0x61, 0x6, 0xb0, 0x91, 0x4b, 0x46, 0x9d, 0x12,
0x3c, 0x20, 0xd7, 0xb9, 0x1, 0x1c, 0x4b, 0x60, 0xda, 0xe2, 0xa, 0xdc, 0xe6, 0x62,
0x42, 0x15, 0x82, 0xd2, 0x14, 0x16, 0xe5, 0x12, 0x9a, 0x20, 0x90, 0x23, 0xa4, 0x47,
0xb6, 0x5f, 0xd4, 0x4, 0x23, 0x80, 0x48, 0xa2, 0xc7, 0xc9, 0x3f, 0x67, 0xc6, 0xe2,
0x77, 0x2a, 0x71, 0xbc, 0x7b, 0xbd, 0xb8, 0x7e, 0xc9, 0x73, 0xd, 0x69, 0x88, 0x12,
0x92, 0x44, 0x46, 0x4c, 0x3, 0x33, 0xa1, 0xd3, 0x81, 0xfe, 0xa6, 0x7b, 0xd2, 0x69,
0x45, 0xa1, 0x67, 0x5a, 0x30, 0x35, 0xa1, 0x50, 0x7a, 0x40, 0x4, 0x10, 0x59, 0x83,
0xc8, 0xbb, 0x7e, 0x55, 0x78, 0x1, 0xfb, 0x57, 0xe9, 0xd3, 0x63, 0x7, 0x4b, 0xcb,
0xa2, 0xf7, 0x5c, 0xed, 0xd7, 0x29, 0x53, 0x16, 0xc6, 0x67, 0xf7, 0x4c, 0x4b, 0xf7,
0x86, 0x24, 0x29, 0xa3, 0x8d, 0x3, 0x43, 0x55, 0xe9, 0x1, 0x6f, 0x17, 0x1b, 0x2f,
0xec, 0x2f, 0x5a, 0xd1, 0x96, 0x86, 0xd2, 0xb8, 0x93, 0xe1, 0x25, 0x12, 0xf8, 0xb6,
0xa7, 0xf1, 0xbb, 0xf4, 0x17, 0x46, 0xa, 0x10, 0xa1, 0xba, 0x80, 0x70, 0xd5, 0xa0,
0x70, 0xfa, 0xd2, 0xbe, 0x56, 0x1a, 0x40, 0x17, 0x38, 0x2c, 0x3e, 0x70, 0x9f, 0xcc,
0x62, 0xc6, 0x49, 0xb9, 0x25, 0x2c, 0x2b, 0x9a, 0x4f, 0x57, 0xb4, 0x1e, 0xd5, 0x3d,
0xf9, 0x91, 0xa, 0x7b, 0xe8, 0x84, 0xd4, 0x6, 0x8d, 0x16, 0x44, 0xb0, 0x89, 0x82,
0x14, 0xed, 0x13, 0x80, 0xcf, 0xf7, 0x40, 0x44, 0x6a, 0x68, 0xca, 0xfb, 0x80, 0x96,
0x44, 0xa7, 0xd1, 0x26, 0xa4, 0x67, 0xc, 0x19, 0x31, 0x8, 0xb5, 0x9f, 0x32, 0x53,
0x91, 0xd9, 0x39, 0xcf, 0xa3, 0x1c, 0x1a, 0xb0, 0x12, 0xe9, 0x9c, 0xa0, 0x25, 0x98,
0x19, 0x36, 0x9c, 0x6e, 0x39, 0x3, 0xa7, 0x3e, 0x38, 0xc2, 0xa9, 0xba, 0x7, 0x70,
0xb3, 0xcf, 0xab, 0x88, 0x59, 0x2b, 0x83, 0xbe, 0x97, 0xc7, 0xb8, 0x36, 0x5, 0xc9,
0xdb, 0x88, 0x99, 0x25, 0x61, 0x72, 0x77, 0xd2, 0x3b, 0xe3, 0x95, 0x1f, 0x38, 0xa2,
0x7f, 0xc5, 0x4a, 0xa0, 0x1a, 0x29, 0x68, 0x53, 0x5b, 0xc, 0xf2, 0x8a, 0xb4, 0xf5,
0x12, 0xb6, 0x30, 0xb, 0x6, 0xcd, 0xe3, 0x41, 0x88, 0x57, 0x8b, 0x53, 0xc5, 0x8e,
0xe8, 0xa5, 0x19, 0xb5, 0x26, 0x4a, 0xab, 0x97, 0x2, 0x7f, 0x73, 0x6c, 0x8c, 0x10,
0x7e, 0x11, 0x41, 0x62, 0x85, 0xe4, 0xc4, 0xf, 0x70, 0x14, 0xcf, 0x1c, 0x4f, 0xec,
0xe1, 0xc1, 0x5d, 0xd8, 0x94, 0x1b, 0x90, 0x32, 0xde, 0x6c, 0x70, 0xa5, 0x75, 0x33,
0x2b, 0xb7, 0x72, 0xc5, 0x49, 0x52, 0xe3, 0xe9, 0x63, 0xc7, 0xe1, 0xb1, 0x9b, 0xb8,
0x1d, 0xe6, 0x41, 0x75, 0xf3, 0x11, 0xa, 0x9e, 0xb3, 0x15, 0x73, 0x73, 0x61, 0xb3,
0x11, 0x88, 0xf8, 0xa9, 0x74, 0x25, 0x0, 0xa4, 0xf5, 0xb5, 0x16, 0xbf, 0xbc, 0x4c,
0xfd, 0x36, 0xb7, 0x1c, 0x60, 0x93, 0xbb, 0x75, 0x79, 0xde, 0x25, 0x18, 0x15, 0x4b,
0x5d, 0x2, 0xb2, 0x6c, 0x31, 0x87, 0x17, 0xd4, 0xb0, 0x70, 0xcf, 0xb2, 0xb8, 0x24,
0xb0, 0x2f, 0x4, 0x8b, 0xd, 0xa5, 0xf9, 0x59, 0x3e, 0xf8, 0x8f, 0x3b, 0x56, 0x95,
0x26, 0x84, 0x2, 0xcb, 0x13, 0xab, 0x8f, 0xb, 0x97, 0xef, 0x49, 0xc, 0xb, 0x9,
0x48, 0x32, 0xea, 0xaa, 0xff, 0x93, 0x5e, 0x7e, 0x32, 0x21, 0x62, 0x2, 0xc5, 0xc8,
0x6b, 0xa3, 0xfc, 0x9c, 0x3a, 0x8, 0x4c, 0x83, 0xdb, 0x14, 0xa1, 0xb1, 0x6, 0x82,
0xbb, 0xa9, 0x65, 0xe0, 0x60, 0x34, 0x11, 0x25, 0xce, 0xdd, 0xc6, 0x49, 0x7d, 0x6a,
0x83, 0x4d, 0x4, 0x5c, 0xbf, 0xd9, 0xcb, 0x21, 0xd2, 0x7f, 0xb2, 0x6c, 0x92, 0xda,
0x4c, 0x42, 0xaa, 0xd2, 0x75, 0x65, 0xf9, 0x19, 0xb5, 0xc1, 0xab, 0xd2, 0xb4, 0x61,
0x6f, 0x5c, 0x36, 0x6, 0xb8, 0x38, 0xc8, 0xcb, 0x40, 0x1c, 0x53, 0x1, 0x83, 0x26,
0x42, 0x1, 0x59, 0x5d, 0x59, 0x46, 0x9b, 0x90, 0x54, 0xcd, 0x2, 0x19, 0x8d, 0xab,
0x30, 0x5f, 0x11, 0xd1, 0xa1, 0x4d, 0x59, 0x67, 0xeb, 0x5c, 0x99, 0x79, 0x34, 0x5c,
0x20, 0x94, 0x4e, 0xba, 0x87, 0x75, 0xe4, 0x89, 0xce, 0xbd, 0xdc, 0x2b, 0x1a, 0xbc,
0xa7, 0x36, 0x9b, 0xb8, 0x3, 0xd5, 0xb2, 0xbd, 0x58, 0x6, 0xc6, 0x5b, 0xa9, 0xdc,
0x55, 0x81, 0xf0, 0x58, 0x7f, 0x96, 0xa5, 0x9e, 0xcd, 0x82, 0x4a, 0x6d, 0x5a, 0x69,
0x8c, 0x2b, 0x49, 0x16, 0x27, 0x51, 0xa9, 0x7e, 0xf8, 0x8a, 0xf1, 0x94, 0xd8, 0x4f,
0x66, 0x4b, 0xfd, 0x54, 0x47, 0xb8, 0x14, 0x5, 0xeb, 0x9b, 0xe5, 0x9e, 0x48, 0xc6,
0xef, 0x5f, 0x38, 0xe1, 0x98, 0x2d, 0xa8, 0xb3, 0x97, 0xe2, 0x24, 0x7, 0xdc, 0x2f,
0xf8, 0x5c, 0x7f, 0x8, 0x9a, 0x41, 0x4b, 0x67, 0xa8, 0x76, 0xb8, 0x14, 0xba, 0x3a,
0x62, 0xf9, 0x35, 0x8, 0x43, 0xdd, 0x81, 0x35, 0xa2, 0x6, 0x70, 0x0, 0x1d, 0x0,
0x20, 0xd3, 0x86, 0xf6, 0xe7, 0x26, 0x27, 0x50, 0xc3, 0x4c, 0xba, 0xb5, 0x9c, 0xff,
0x86, 0xd1, 0x35, 0xd0, 0xb3, 0x97, 0xb5, 0x99, 0xf4, 0xbb, 0x86, 0xb2, 0x8, 0x21,
0x27, 0xf2, 0x56, 0x88, 0x2, 0x0, 0x23, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x7, 0x6, 0xda,
0xda, 0x3, 0x4, 0x3, 0x3, 0xaa, 0xaa, 0x0, 0x1, 0x0,
],
ClientHelloExtension::ApplicationSettings {
protocols: vec![ApplicationProtocol::HTTP_2],
new_codepoint: true,
},
)] {
let client_hello = parse_client_hello(content).unwrap();
let found_alps = client_hello
.extensions()
.iter()
.find(|ext| {
ext.id() == ExtensionId::APPLICATION_SETTINGS
|| ext.id() == ExtensionId::OLD_APPLICATION_SETTINGS
})
.unwrap();
match (found_alps, expected_alps) {
(
ClientHelloExtension::ApplicationSettings {
protocols: found_alps,
new_codepoint: found_new_codepoint,
},
ClientHelloExtension::ApplicationSettings {
protocols: expected_alps,
new_codepoint: expected_new_codepoint,
},
) => {
assert_eq!(expected_alps, *found_alps);
assert_eq!(expected_new_codepoint, *found_new_codepoint);
}
(found, expected) => panic!("unexpected ext {found:?}; expected: {expected:?}"),
}
}
}
}