#[inline]
fn read_u16(buf: &[u8], off: usize) -> Option<u16> {
let hi = u16::from(*buf.get(off)?);
let lo = u16::from(*buf.get(off + 1)?);
Some((hi << 8) | lo)
}
#[must_use]
pub fn parse_sni(buf: &[u8]) -> Option<String> {
if *buf.first()? != 22 {
return None;
}
let record_len = read_u16(buf, 3)? as usize;
let record_end = 5usize.checked_add(record_len)?;
let end = record_end.min(buf.len());
let hs = buf.get(5..end)?;
if *hs.first()? != 1 {
return None;
}
let mut p = 4usize;
p = p.checked_add(2)?;
p = p.checked_add(32)?;
let session_id_len = *hs.get(p)? as usize;
p = p.checked_add(1)?.checked_add(session_id_len)?;
let cipher_len = read_u16(hs, p)? as usize;
p = p.checked_add(2)?.checked_add(cipher_len)?;
let comp_len = *hs.get(p)? as usize;
p = p.checked_add(1)?.checked_add(comp_len)?;
let ext_total = read_u16(hs, p)? as usize;
p = p.checked_add(2)?;
let ext_end = p.checked_add(ext_total)?.min(hs.len());
while p + 4 <= ext_end {
let ext_type = read_u16(hs, p)?;
let ext_len = read_u16(hs, p + 2)? as usize;
let body_start = p + 4;
let body_end = body_start.checked_add(ext_len)?;
if body_end > ext_end {
return None;
}
if ext_type == 0 {
let snl = hs.get(body_start..body_end)?;
return parse_server_name_list(snl);
}
p = body_end;
}
None
}
fn parse_server_name_list(snl: &[u8]) -> Option<String> {
let list_len = read_u16(snl, 0)? as usize;
let mut q = 2usize;
let list_end = q.checked_add(list_len)?.min(snl.len());
while q + 3 <= list_end {
let name_type = *snl.get(q)?;
let name_len = read_u16(snl, q + 1)? as usize;
let name_start = q + 3;
let name_end = name_start.checked_add(name_len)?;
if name_end > list_end {
return None;
}
if name_type == 0 {
let raw = snl.get(name_start..name_end)?;
return std::str::from_utf8(raw).ok().map(str::to_string);
}
q = name_end;
}
None
}
#[cfg(test)]
#[allow(clippy::cast_possible_truncation)] mod tests {
use super::*;
fn build_client_hello(sni: Option<&str>) -> Vec<u8> {
let mut extensions = Vec::new();
if let Some(host) = sni {
let host = host.as_bytes();
let mut entry = Vec::new();
entry.push(0u8); entry.extend_from_slice(&(host.len() as u16).to_be_bytes());
entry.extend_from_slice(host);
let mut snl = Vec::new();
snl.extend_from_slice(&(entry.len() as u16).to_be_bytes());
snl.extend_from_slice(&entry);
extensions.extend_from_slice(&0u16.to_be_bytes());
extensions.extend_from_slice(&(snl.len() as u16).to_be_bytes());
extensions.extend_from_slice(&snl);
}
let dummy_body = [0x02u8, 0x03, 0x04];
extensions.extend_from_slice(&43u16.to_be_bytes());
extensions.extend_from_slice(&(dummy_body.len() as u16).to_be_bytes());
extensions.extend_from_slice(&dummy_body);
let mut body = Vec::new();
body.extend_from_slice(&[0x03, 0x03]); body.extend_from_slice(&[0u8; 32]); body.push(0u8); body.extend_from_slice(&2u16.to_be_bytes());
body.extend_from_slice(&[0x13, 0x01]);
body.push(1u8);
body.push(0u8);
body.extend_from_slice(&(extensions.len() as u16).to_be_bytes());
body.extend_from_slice(&extensions);
let mut hs = Vec::new();
hs.push(1u8); let blen = body.len();
hs.push(((blen >> 16) & 0xff) as u8);
hs.push(((blen >> 8) & 0xff) as u8);
hs.push((blen & 0xff) as u8);
hs.extend_from_slice(&body);
let mut rec = Vec::new();
rec.push(22u8); rec.extend_from_slice(&[0x03, 0x01]); rec.extend_from_slice(&(hs.len() as u16).to_be_bytes());
rec.extend_from_slice(&hs);
rec
}
#[test]
fn parses_sni_example_com() {
let buf = build_client_hello(Some("example.com"));
assert_eq!(parse_sni(&buf).as_deref(), Some("example.com"));
}
#[test]
fn parses_sni_subdomain() {
let buf = build_client_hello(Some("api.service.internal"));
assert_eq!(parse_sni(&buf).as_deref(), Some("api.service.internal"));
}
#[test]
fn no_sni_extension_returns_none() {
let buf = build_client_hello(None);
assert_eq!(parse_sni(&buf), None);
}
#[test]
fn truncated_returns_none() {
let buf = build_client_hello(Some("example.com"));
for cut in [0usize, 1, 5, 6, 10, 20, buf.len() / 2] {
let cut = cut.min(buf.len());
assert_eq!(parse_sni(&buf[..cut]), None, "cut={cut}");
}
}
#[test]
fn non_handshake_record_returns_none() {
let mut buf = build_client_hello(Some("example.com"));
buf[0] = 23; assert_eq!(parse_sni(&buf), None);
}
#[test]
fn not_a_client_hello_returns_none() {
let mut buf = build_client_hello(Some("example.com"));
buf[5] = 2; assert_eq!(parse_sni(&buf), None);
}
#[test]
fn empty_input_returns_none() {
assert_eq!(parse_sni(&[]), None);
assert_eq!(parse_sni(&[22]), None);
assert_eq!(parse_sni(&[22, 3, 1]), None);
}
#[test]
fn garbage_does_not_panic() {
for seed in 0u32..2000 {
let len = (seed % 64) as usize;
let v: Vec<u8> = (0..len)
.map(|i| (seed.wrapping_mul(31) ^ i as u32) as u8)
.collect();
let _ = parse_sni(&v);
}
let buf = [22u8, 3, 1, 0xff, 0xff, 1, 0, 0, 0];
let _ = parse_sni(&buf);
}
}