use super::header::{Header, Headers, MAX_HEADERS, MAX_HEADER_VALUE_LEN, MAX_START_LINE_LEN};
use super::method::Method;
use super::status::StatusCode;
use crate::core::SipError;
use crate::sip::uri::SipUri;
use std::str::FromStr;
pub const MAX_FOLD_LINES_PER_HEADER: usize = 32;
pub fn split_message(data: &[u8]) -> Result<(&str, &str, &[u8]), SipError> {
let (sep_start, sep_len) = find_separator(data)
.ok_or_else(|| SipError::Parse("no header/body separator found".to_string()))?;
let head = &data[..sep_start];
let body = &data[sep_start + sep_len..];
let head_str = std::str::from_utf8(head)
.map_err(|e| SipError::Parse(format!("header section not valid UTF-8: {e}")))?;
let (start_line, header_block) = split_first_line(head_str);
if start_line.len() > MAX_START_LINE_LEN {
return Err(SipError::Parse(format!(
"start line exceeds {MAX_START_LINE_LEN} bytes",
)));
}
Ok((start_line, header_block, body))
}
fn find_separator(data: &[u8]) -> Option<(usize, usize)> {
if let Some(pos) = find_subslice(data, b"\r\n\r\n") {
return Some((pos, 4));
}
if let Some(pos) = find_subslice(data, b"\n\n") {
return Some((pos, 2));
}
None
}
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() || haystack.len() < needle.len() {
return None;
}
haystack.windows(needle.len()).position(|w| w == needle)
}
fn split_first_line(head: &str) -> (&str, &str) {
if let Some(pos) = head.find("\r\n") {
(&head[..pos], &head[pos + 2..])
} else if let Some(pos) = head.find('\n') {
(&head[..pos], &head[pos + 1..])
} else {
(head, "")
}
}
pub fn parse_header_block(block: &str) -> Result<Headers, SipError> {
let mut headers = Headers::new();
let mut current: Option<String> = None;
let mut fold_lines_for_current: usize = 0;
for raw_line in split_lines(block) {
if raw_line.is_empty() {
continue;
}
let first_byte = raw_line.as_bytes()[0];
if first_byte == b' ' || first_byte == b'\t' {
let folded = current.as_mut().ok_or_else(|| {
SipError::InvalidHeader(format!(
"fold continuation with no preceding header line: {raw_line:?}",
))
})?;
fold_lines_for_current += 1;
if fold_lines_for_current > MAX_FOLD_LINES_PER_HEADER {
return Err(SipError::InvalidHeader(format!(
"header has more than {MAX_FOLD_LINES_PER_HEADER} fold lines",
)));
}
folded.push(' ');
folded.push_str(raw_line.trim());
if folded.len() > MAX_HEADER_VALUE_LEN.saturating_add(MAX_START_LINE_LEN) {
return Err(SipError::InvalidHeader(
"folded header exceeds size limit".to_string(),
));
}
continue;
}
if let Some(line) = current.take() {
let header = Header::parse_line(&line)?;
headers.push(header)?;
}
current = Some(raw_line.to_string());
fold_lines_for_current = 0;
}
if let Some(line) = current.take() {
let header = Header::parse_line(&line)?;
headers.push(header)?;
}
Ok(headers)
}
fn split_lines(s: &str) -> impl Iterator<Item = &str> {
s.lines()
}
pub fn parse_request_line(line: &str) -> Result<(Method, String, String), SipError> {
if line.len() > MAX_START_LINE_LEN {
return Err(SipError::Parse(format!(
"request line exceeds {MAX_START_LINE_LEN} bytes",
)));
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 3 {
return Err(SipError::Parse(format!(
"invalid request line (expected 3 whitespace-separated parts): {line:?}",
)));
}
let method = Method::from_str(parts[0])?;
let uri_str = parts[1];
let version = parts[2].to_string();
if version != "SIP/2.0" {
return Err(SipError::Parse(format!(
"invalid SIP version in request line: {version}",
)));
}
SipUri::parse(uri_str).map_err(|e| SipError::Parse(format!("invalid Request-URI: {e}")))?;
Ok((method, uri_str.to_string(), version))
}
pub fn parse_status_line(line: &str) -> Result<(String, StatusCode, String), SipError> {
if line.len() > MAX_START_LINE_LEN {
return Err(SipError::Parse(format!(
"status line exceeds {MAX_START_LINE_LEN} bytes",
)));
}
let mut parts = line.splitn(3, ' ');
let version = parts
.next()
.ok_or_else(|| SipError::Parse(format!("empty status line: {line:?}")))?;
let code_str = parts
.next()
.ok_or_else(|| SipError::Parse(format!("status line missing code: {line:?}")))?;
let reason = parts
.next()
.ok_or_else(|| SipError::Parse("status line missing SP after status code".to_string()))?;
if version != "SIP/2.0" {
return Err(SipError::Parse(format!(
"invalid SIP version in status line: {version}",
)));
}
if code_str.len() != 3 {
return Err(SipError::Parse(format!(
"status code must be exactly 3 digits, got {} chars: {code_str:?}",
code_str.len(),
)));
}
let code: u16 = code_str
.parse()
.map_err(|_| SipError::Parse(format!("invalid status code: {code_str}")))?;
if !(100..=699).contains(&code) {
return Err(SipError::Parse(format!("status code out of range: {code}")));
}
if let Some(b) = reason
.as_bytes()
.iter()
.copied()
.find(|&b| b == 0x7F || (b < 0x20 && b != 0x09))
{
return Err(SipError::Parse(format!(
"reason phrase contains forbidden control byte 0x{b:02X}",
)));
}
Ok((
version.to_string(),
StatusCode::new(code),
reason.to_string(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_message_crlf_separator() {
let msg = b"INVITE sip:bob@x SIP/2.0\r\nVia: x\r\n\r\nBODY";
let (start, headers, body) = split_message(msg).unwrap();
assert_eq!(start, "INVITE sip:bob@x SIP/2.0");
assert_eq!(headers, "Via: x");
assert_eq!(body, b"BODY");
}
#[test]
fn test_split_message_lf_only_fallback() {
let msg = b"INVITE sip:bob@x SIP/2.0\nVia: x\n\nBODY";
let (start, headers, body) = split_message(msg).unwrap();
assert_eq!(start, "INVITE sip:bob@x SIP/2.0");
assert_eq!(headers, "Via: x");
assert_eq!(body, b"BODY");
}
#[test]
fn test_split_message_no_separator_rejects() {
let msg = b"INVITE sip:bob@x SIP/2.0\r\nVia: x\r\n";
let err = split_message(msg).unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_split_message_empty_body() {
let msg = b"INVITE sip:bob@x SIP/2.0\r\nVia: x\r\n\r\n";
let (_, _, body) = split_message(msg).unwrap();
assert_eq!(body, b"");
}
#[test]
fn test_split_message_oversized_start_line() {
let mut msg = Vec::new();
msg.extend_from_slice(b"INVITE ");
msg.extend(std::iter::repeat_n(b'x', MAX_START_LINE_LEN));
msg.extend_from_slice(b" SIP/2.0\r\n\r\n");
let err = split_message(&msg).unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_split_message_non_utf8_header_rejects() {
let mut msg = Vec::from(&b"INVITE sip:bob@x SIP/2.0\r\nX-Bad: "[..]);
msg.push(0xFF);
msg.extend_from_slice(b"\r\n\r\n");
let err = split_message(&msg).unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_split_message_non_utf8_body_ok() {
let mut msg = Vec::from(&b"INVITE sip:bob@x SIP/2.0\r\n\r\n"[..]);
msg.push(0xFF);
msg.push(0xFE);
let (_, _, body) = split_message(&msg).unwrap();
assert_eq!(body, &[0xFF, 0xFE]);
}
#[test]
fn test_parse_header_block_simple() {
let block = "Via: SIP/2.0/UDP h\r\nFrom: <sip:a@b>\r\n";
let hs = parse_header_block(block).unwrap();
assert_eq!(hs.len(), 2);
assert_eq!(hs.get_first_value("Via"), Some("SIP/2.0/UDP h"));
assert_eq!(hs.get_first_value("From"), Some("<sip:a@b>"));
}
#[test]
fn test_parse_header_block_folding() {
let block = "Foo: a\r\n bar\r\n";
let hs = parse_header_block(block).unwrap();
assert_eq!(hs.len(), 1);
assert_eq!(hs.get_first_value("Foo"), Some("a bar"));
}
#[test]
fn test_parse_header_block_folding_tab() {
let block = "Foo: a\r\n\tbar\r\n";
let hs = parse_header_block(block).unwrap();
assert_eq!(hs.get_first_value("Foo"), Some("a bar"));
}
#[test]
fn test_parse_header_block_folding_multi_line() {
let block = "Subject: line1\r\n line2\r\n line3\r\n";
let hs = parse_header_block(block).unwrap();
assert_eq!(hs.len(), 1);
assert_eq!(hs.get_first_value("Subject"), Some("line1 line2 line3"));
}
#[test]
fn test_parse_header_block_fold_without_preceding_rejects() {
let block = " orphan\r\nFrom: <sip:a@b>\r\n";
let err = parse_header_block(block).unwrap_err();
assert!(matches!(err, SipError::InvalidHeader(_)));
}
#[test]
fn test_parse_header_block_max_headers_enforced() {
let mut block = String::new();
for _ in 0..(MAX_HEADERS + 1) {
block.push_str("Via: x\r\n");
}
let err = parse_header_block(&block).unwrap_err();
assert!(matches!(err, SipError::InvalidHeader(_)));
}
#[test]
fn test_parse_header_block_lf_only() {
let block = "Via: SIP/2.0/UDP h\nFrom: <sip:a@b>\n";
let hs = parse_header_block(block).unwrap();
assert_eq!(hs.len(), 2);
}
#[test]
fn test_parse_request_line_invite() {
let (m, uri, ver) = parse_request_line("INVITE sip:bob@example.com SIP/2.0").unwrap();
assert_eq!(m, Method::Invite);
assert_eq!(uri, "sip:bob@example.com");
assert_eq!(ver, "SIP/2.0");
}
#[test]
fn test_parse_request_line_two_parts_rejects() {
let err = parse_request_line("INVITE sip:bob@example.com").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_parse_request_line_unknown_method_rejects() {
let err = parse_request_line("BOGUS sip:bob@example.com SIP/2.0").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_parse_request_line_bad_version_rejects() {
let err = parse_request_line("INVITE sip:bob@example.com HTTP/1.1").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_parse_request_line_oversized_rejects() {
let line = "INVITE ".to_string() + &"x".repeat(MAX_START_LINE_LEN) + " SIP/2.0";
let err = parse_request_line(&line).unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_request_line_rejects_non_sip_uri() {
let line = "INVITE http://example.com SIP/2.0";
let result = parse_request_line(line);
assert!(result.is_err(), "non-sip URI should be rejected");
}
#[test]
fn test_request_line_accepts_tel_uri() {
let line = "INVITE tel:+12025551234 SIP/2.0";
let result = parse_request_line(line);
assert!(result.is_ok(), "tel: URI should be accepted");
}
#[test]
fn test_request_line_accepts_sips_uri() {
let line = "INVITE sips:bob@example.com SIP/2.0";
let result = parse_request_line(line);
assert!(result.is_ok());
}
#[test]
fn test_parse_status_line_simple() {
let (ver, code, reason) = parse_status_line("SIP/2.0 200 OK").unwrap();
assert_eq!(ver, "SIP/2.0");
assert_eq!(code, StatusCode::OK);
assert_eq!(reason, "OK");
}
#[test]
fn test_parse_status_line_multi_word_reason() {
let (ver, code, reason) = parse_status_line("SIP/2.0 486 Busy Here").unwrap();
assert_eq!(ver, "SIP/2.0");
assert_eq!(code, StatusCode::BUSY_HERE);
assert_eq!(reason, "Busy Here");
}
#[test]
fn test_status_line_missing_sp_after_code_rejects() {
let line = "SIP/2.0 200"; assert!(parse_status_line(line).is_err());
}
#[test]
fn test_status_line_empty_reason_phrase_accepts() {
let line = "SIP/2.0 200 "; let result = parse_status_line(line);
assert!(result.is_ok(), "empty reason phrase is RFC-legal");
let (_, code, reason) = result.unwrap();
assert_eq!(code, StatusCode::OK);
assert_eq!(reason, "");
}
#[test]
fn test_parse_status_line_reason_with_bare_cr_rejects() {
let err = parse_status_line("SIP/2.0 200 a\rb").unwrap_err();
match err {
SipError::Parse(m) => assert!(m.contains("0x0D"), "got: {m}"),
other => panic!("expected Parse, got {other:?}"),
}
}
#[test]
fn test_parse_status_line_reason_with_bare_lf_rejects() {
let err = parse_status_line("SIP/2.0 200 a\nb").unwrap_err();
match err {
SipError::Parse(m) => assert!(m.contains("0x0A"), "got: {m}"),
other => panic!("expected Parse, got {other:?}"),
}
}
#[test]
fn test_parse_status_line_reason_with_nul_rejects() {
let err = parse_status_line("SIP/2.0 200 a\0b").unwrap_err();
match err {
SipError::Parse(m) => assert!(m.contains("0x00"), "got: {m}"),
other => panic!("expected Parse, got {other:?}"),
}
}
#[test]
fn test_parse_status_line_reason_with_del_rejects() {
let err = parse_status_line("SIP/2.0 200 a\x7fb").unwrap_err();
match err {
SipError::Parse(m) => assert!(m.contains("0x7F"), "got: {m}"),
other => panic!("expected Parse, got {other:?}"),
}
}
#[test]
fn test_parse_status_line_reason_with_htab_accepts() {
let (_, _, reason) = parse_status_line("SIP/2.0 200 hi\tthere").unwrap();
assert_eq!(reason, "hi\tthere");
}
#[test]
fn test_parse_status_line_reason_with_high_bit_accepts() {
let (_, _, reason) = parse_status_line("SIP/2.0 200 café").unwrap();
assert_eq!(reason, "café");
}
#[test]
fn test_parse_status_line_bad_version_rejects() {
let err = parse_status_line("HTTP/1.1 200 OK").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_parse_status_line_bad_code_rejects() {
let err = parse_status_line("SIP/2.0 NOTNUM OK").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_parse_status_line_oversized_rejects() {
let line = "SIP/2.0 200 ".to_string() + &"x".repeat(MAX_START_LINE_LEN);
let err = parse_status_line(&line).unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_status_code_zero_rejected() {
let line = "SIP/2.0 0 OK";
assert!(parse_status_line(line).is_err());
}
#[test]
fn test_status_code_too_high_rejected() {
let line = "SIP/2.0 700 ?";
assert!(parse_status_line(line).is_err());
}
#[test]
fn test_status_code_max_rejected() {
let line = "SIP/2.0 65535 ?";
assert!(parse_status_line(line).is_err());
}
#[test]
fn test_status_code_99_rejected() {
let line = "SIP/2.0 99 ?";
assert!(parse_status_line(line).is_err());
}
#[test]
fn test_status_code_4_digits_rejected() {
let line = "SIP/2.0 0233 25%";
let err = parse_status_line(line).expect_err("4-digit code must reject");
let SipError::Parse(msg) = err else {
panic!("expected SipError::Parse, got {err:?}")
};
assert!(
msg.contains("3 digits"),
"error should mention the 3-digit requirement; got: {msg}",
);
}
#[test]
fn test_version_sip0_rejected() {
let line = "SIP/0 200 OK";
assert!(parse_status_line(line).is_err());
}
#[test]
fn test_version_sip3_rejected() {
let line = "SIP/3.0 200 OK";
assert!(parse_status_line(line).is_err());
}
#[test]
fn test_request_version_sip0_rejected() {
let line = "INVITE sip:bob@x SIP/0";
assert!(parse_request_line(line).is_err());
}
#[test]
fn test_request_version_garbage_rejected() {
let line = "INVITE sip:bob@x SIP/garbage";
assert!(parse_request_line(line).is_err());
}
#[test]
fn test_fold_count_cap_rejects() {
let mut block = String::from("Subject: x");
for _ in 0..(MAX_FOLD_LINES_PER_HEADER + 1) {
block.push_str("\r\n y");
}
block.push_str("\r\n");
let err = parse_header_block(&block).unwrap_err();
match err {
SipError::InvalidHeader(msg) => {
assert!(msg.contains("fold lines"), "got: {msg}");
}
other => panic!("expected InvalidHeader, got {other:?}"),
}
}
#[test]
fn test_fold_count_at_limit_accepts() {
let mut block = String::from("Subject: x");
for _ in 0..MAX_FOLD_LINES_PER_HEADER {
block.push_str("\r\n y");
}
block.push_str("\r\n");
let hs = parse_header_block(&block).unwrap();
assert_eq!(hs.len(), 1);
let v = hs.get_first_value("Subject").unwrap();
let expected = "x".to_string() + &" y".repeat(MAX_FOLD_LINES_PER_HEADER);
assert_eq!(v, expected);
}
#[test]
fn test_empty_input_rejects() {
let err = split_message(b"").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_single_byte_input_rejects() {
let err = split_message(b"X").unwrap_err();
assert!(matches!(err, SipError::Parse(_)));
}
#[test]
fn test_only_crlf_rejects() {
let res = split_message(b"\r\n\r\n");
assert!(res.is_ok(), "split_message accepts \\r\\n\\r\\n");
let (start, _hdr, _body) = res.unwrap();
assert!(start.is_empty(), "start line is empty");
assert!(parse_request_line(start).is_err());
assert!(parse_status_line(start).is_err());
}
#[test]
fn test_header_with_embedded_nul_pinned_accepted() {
let msg = b"INVITE sip:a@b SIP/2.0\r\nFoo: ba\0r\r\n\r\n";
let (start, hdr_block, body) = split_message(msg).unwrap();
assert_eq!(start, "INVITE sip:a@b SIP/2.0");
assert_eq!(body, b"");
let hs = parse_header_block(hdr_block).unwrap();
let v = hs.get_first_value("Foo").unwrap();
assert_eq!(v.as_bytes(), b"ba\0r");
}
}