use crate::header::{HeaderName, Headers};
use std::fmt;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SipMethod {
Register,
Invite,
Ack,
Bye,
Cancel,
Options,
Info,
Refer,
Notify,
Update,
Prack,
Other(String),
}
impl SipMethod {
pub fn from_str(s: &str) -> Self {
match s.to_uppercase().as_str() {
"REGISTER" => SipMethod::Register,
"INVITE" => SipMethod::Invite,
"ACK" => SipMethod::Ack,
"BYE" => SipMethod::Bye,
"CANCEL" => SipMethod::Cancel,
"OPTIONS" => SipMethod::Options,
"INFO" => SipMethod::Info,
"REFER" => SipMethod::Refer,
"NOTIFY" => SipMethod::Notify,
"UPDATE" => SipMethod::Update,
"PRACK" => SipMethod::Prack,
other => SipMethod::Other(other.to_string()),
}
}
pub fn as_str(&self) -> &str {
match self {
SipMethod::Register => "REGISTER",
SipMethod::Invite => "INVITE",
SipMethod::Ack => "ACK",
SipMethod::Bye => "BYE",
SipMethod::Cancel => "CANCEL",
SipMethod::Options => "OPTIONS",
SipMethod::Info => "INFO",
SipMethod::Refer => "REFER",
SipMethod::Notify => "NOTIFY",
SipMethod::Update => "UPDATE",
SipMethod::Prack => "PRACK",
SipMethod::Other(s) => s.as_str(),
}
}
}
impl fmt::Display for SipMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StatusCode(pub u16);
impl StatusCode {
pub const TRYING: Self = Self(100);
pub const RINGING: Self = Self(180);
pub const SESSION_PROGRESS: Self = Self(183);
pub const OK: Self = Self(200);
pub const ACCEPTED: Self = Self(202);
pub const BAD_REQUEST: Self = Self(400);
pub const UNAUTHORIZED: Self = Self(401);
pub const FORBIDDEN: Self = Self(403);
pub const NOT_FOUND: Self = Self(404);
pub const METHOD_NOT_ALLOWED: Self = Self(405);
pub const PROXY_AUTH_REQUIRED: Self = Self(407);
pub const REQUEST_TIMEOUT: Self = Self(408);
pub const REQUEST_PENDING: Self = Self(491);
pub const BUSY_HERE: Self = Self(486);
pub const SERVER_ERROR: Self = Self(500);
pub const NOT_IMPLEMENTED: Self = Self(501);
pub fn reason_phrase(&self) -> &'static str {
match self.0 {
100 => "Trying",
180 => "Ringing",
183 => "Session Progress",
200 => "OK",
202 => "Accepted",
400 => "Bad Request",
401 => "Unauthorized",
403 => "Forbidden",
404 => "Not Found",
405 => "Method Not Allowed",
407 => "Proxy Authentication Required",
408 => "Request Timeout",
486 => "Busy Here",
491 => "Request Pending",
500 => "Server Internal Error",
501 => "Not Implemented",
_ => "Unknown",
}
}
pub fn is_provisional(&self) -> bool {
self.0 >= 100 && self.0 < 200
}
pub fn is_success(&self) -> bool {
self.0 >= 200 && self.0 < 300
}
pub fn is_redirect(&self) -> bool {
self.0 >= 300 && self.0 < 400
}
pub fn is_error(&self) -> bool {
self.0 >= 400
}
}
impl fmt::Display for StatusCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct SipRequest {
pub method: SipMethod,
pub uri: String,
pub version: String,
pub headers: Headers,
pub body: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SipResponse {
pub version: String,
pub status: StatusCode,
pub reason: String,
pub headers: Headers,
pub body: Option<String>,
}
#[derive(Debug, Clone)]
pub enum SipMessage {
Request(SipRequest),
Response(SipResponse),
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error("invalid start line: {0}")]
InvalidStartLine(String),
#[error("invalid header: {0}")]
InvalidHeader(String),
#[error("invalid status code: {0}")]
InvalidStatusCode(String),
#[error("incomplete message")]
Incomplete,
}
impl SipMessage {
pub fn parse(input: &str) -> Result<Self, ParseError> {
let mut lines = input.lines();
let start_line = lines.next().ok_or(ParseError::Incomplete)?;
let start_line = start_line.trim();
if start_line.starts_with("SIP/") {
Self::parse_response(start_line, &mut lines)
} else {
Self::parse_request(start_line, &mut lines)
}
}
fn parse_request<'a>(
start_line: &str,
lines: &mut impl Iterator<Item = &'a str>,
) -> Result<Self, ParseError> {
let parts: Vec<&str> = start_line.splitn(3, ' ').collect();
if parts.len() != 3 {
return Err(ParseError::InvalidStartLine(start_line.to_string()));
}
let method = SipMethod::from_str(parts[0]);
let uri = parts[1].to_string();
let version = parts[2].to_string();
let (headers, body) = Self::parse_headers_and_body(lines)?;
Ok(SipMessage::Request(SipRequest {
method,
uri,
version,
headers,
body,
}))
}
fn parse_response<'a>(
start_line: &str,
lines: &mut impl Iterator<Item = &'a str>,
) -> Result<Self, ParseError> {
let parts: Vec<&str> = start_line.splitn(3, ' ').collect();
if parts.len() < 2 {
return Err(ParseError::InvalidStartLine(start_line.to_string()));
}
let version = parts[0].to_string();
let status_code: u16 = parts[1]
.parse()
.map_err(|_| ParseError::InvalidStatusCode(parts[1].to_string()))?;
let reason = if parts.len() > 2 {
parts[2].to_string()
} else {
StatusCode(status_code).reason_phrase().to_string()
};
let (headers, body) = Self::parse_headers_and_body(lines)?;
Ok(SipMessage::Response(SipResponse {
version,
status: StatusCode(status_code),
reason,
headers,
body,
}))
}
fn parse_headers_and_body<'a>(
lines: &mut impl Iterator<Item = &'a str>,
) -> Result<(Headers, Option<String>), ParseError> {
let mut headers = Headers::new();
let mut body_lines = Vec::new();
let mut in_body = false;
for line in lines {
if in_body {
body_lines.push(line);
continue;
}
if line.trim().is_empty() {
in_body = true;
continue;
}
if line.starts_with(' ') || line.starts_with('\t') {
continue;
}
if let Some((name, value)) = line.split_once(':') {
let name = HeaderName::from_str(name.trim());
let value = value.trim().to_string();
headers.add(name, value);
} else {
return Err(ParseError::InvalidHeader(line.to_string()));
}
}
let body = if body_lines.is_empty() {
None
} else {
let b = body_lines.join("\r\n");
if b.trim().is_empty() {
None
} else {
Some(b)
}
};
Ok((headers, body))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.to_string().into_bytes()
}
pub fn headers(&self) -> &Headers {
match self {
SipMessage::Request(req) => &req.headers,
SipMessage::Response(res) => &res.headers,
}
}
pub fn headers_mut(&mut self) -> &mut Headers {
match self {
SipMessage::Request(req) => &mut req.headers,
SipMessage::Response(res) => &mut res.headers,
}
}
pub fn body(&self) -> Option<&str> {
match self {
SipMessage::Request(req) => req.body.as_deref(),
SipMessage::Response(res) => res.body.as_deref(),
}
}
pub fn call_id(&self) -> Option<String> {
self.headers()
.get(&HeaderName::CallId)
.map(|v| v.0.clone())
}
pub fn cseq(&self) -> Option<(u32, SipMethod)> {
let cseq_val = self.headers().get(&HeaderName::CSeq)?;
let parts: Vec<&str> = cseq_val.as_str().splitn(2, ' ').collect();
if parts.len() != 2 {
return None;
}
let seq: u32 = parts[0].parse().ok()?;
let method = SipMethod::from_str(parts[1]);
Some((seq, method))
}
pub fn is_request(&self) -> bool {
matches!(self, SipMessage::Request(_))
}
pub fn is_response(&self) -> bool {
matches!(self, SipMessage::Response(_))
}
pub fn method(&self) -> Option<&SipMethod> {
match self {
SipMessage::Request(req) => Some(&req.method),
SipMessage::Response(_) => None,
}
}
pub fn status(&self) -> Option<&StatusCode> {
match self {
SipMessage::Response(res) => Some(&res.status),
SipMessage::Request(_) => None,
}
}
}
impl fmt::Display for SipMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SipMessage::Request(req) => {
writeln!(f, "{} {} {}\r", req.method, req.uri, req.version)?;
for header in req.headers.iter() {
writeln!(f, "{}\r", header)?;
}
writeln!(f, "\r")?;
if let Some(body) = &req.body {
write!(f, "{}", body)?;
}
}
SipMessage::Response(res) => {
writeln!(f, "{} {} {}\r", res.version, res.status, res.reason)?;
for header in res.headers.iter() {
writeln!(f, "{}\r", header)?;
}
writeln!(f, "\r")?;
if let Some(body) = &res.body {
write!(f, "{}", body)?;
}
}
}
Ok(())
}
}
pub struct RequestBuilder {
method: SipMethod,
uri: String,
headers: Headers,
body: Option<String>,
}
impl RequestBuilder {
pub fn new(method: SipMethod, uri: impl Into<String>) -> Self {
Self {
method,
uri: uri.into(),
headers: Headers::new(),
body: None,
}
}
pub fn header(mut self, name: HeaderName, value: impl Into<String>) -> Self {
self.headers.add(name, value);
self
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(body.into());
self
}
pub fn build(mut self) -> SipMessage {
let content_length = self.body.as_ref().map_or(0, |b| b.len());
self.headers
.set(HeaderName::ContentLength, content_length.to_string());
SipMessage::Request(SipRequest {
method: self.method,
uri: self.uri,
version: "SIP/2.0".to_string(),
headers: self.headers,
body: self.body,
})
}
}
pub struct ResponseBuilder {
status: StatusCode,
headers: Headers,
body: Option<String>,
}
impl ResponseBuilder {
pub fn new(status: StatusCode) -> Self {
Self {
status,
headers: Headers::new(),
body: None,
}
}
pub fn header(mut self, name: HeaderName, value: impl Into<String>) -> Self {
self.headers.set(name, value);
self
}
pub fn body(mut self, body: impl Into<String>) -> Self {
self.body = Some(body.into());
self
}
pub fn from_request(request: &SipRequest, status: StatusCode) -> Self {
let mut headers = Headers::new();
for via in request.headers.get_all(&HeaderName::Via) {
headers.add(HeaderName::Via, via.as_str());
}
if let Some(from) = request.headers.get(&HeaderName::From) {
headers.add(HeaderName::From, from.as_str());
}
if let Some(to) = request.headers.get(&HeaderName::To) {
headers.add(HeaderName::To, to.as_str());
}
if let Some(call_id) = request.headers.get(&HeaderName::CallId) {
headers.add(HeaderName::CallId, call_id.as_str());
}
if let Some(cseq) = request.headers.get(&HeaderName::CSeq) {
headers.add(HeaderName::CSeq, cseq.as_str());
}
Self {
status,
headers,
body: None,
}
}
pub fn build(mut self) -> SipMessage {
let content_length = self.body.as_ref().map_or(0, |b| b.len());
self.headers
.set(HeaderName::ContentLength, content_length.to_string());
SipMessage::Response(SipResponse {
version: "SIP/2.0".to_string(),
status: self.status,
reason: self.status.reason_phrase().to_string(),
headers: self.headers,
body: self.body,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const INVITE_REQUEST: &str = "INVITE sip:bob@biloxi.com SIP/2.0\r\n\
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\r\n\
Max-Forwards: 70\r\n\
To: Bob <sip:bob@biloxi.com>\r\n\
From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n\
Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n\
CSeq: 314159 INVITE\r\n\
Contact: <sip:alice@pc33.atlanta.com>\r\n\
Content-Type: application/sdp\r\n\
Content-Length: 4\r\n\
\r\n\
test";
const OK_RESPONSE: &str = "SIP/2.0 200 OK\r\n\
Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds\r\n\
To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n\
From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n\
Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n\
CSeq: 314159 INVITE\r\n\
Contact: <sip:bob@192.0.2.4>\r\n\
Content-Length: 0\r\n\
\r\n";
#[test]
fn test_parse_invite_request() {
let msg = SipMessage::parse(INVITE_REQUEST).unwrap();
assert!(msg.is_request());
if let SipMessage::Request(req) = &msg {
assert_eq!(req.method, SipMethod::Invite);
assert_eq!(req.uri, "sip:bob@biloxi.com");
assert_eq!(req.version, "SIP/2.0");
assert_eq!(
req.headers.get(&HeaderName::CallId).unwrap().as_str(),
"a84b4c76e66710@pc33.atlanta.com"
);
assert_eq!(req.body.as_deref(), Some("test"));
}
}
#[test]
fn test_parse_ok_response() {
let msg = SipMessage::parse(OK_RESPONSE).unwrap();
assert!(msg.is_response());
if let SipMessage::Response(res) = &msg {
assert_eq!(res.status, StatusCode::OK);
assert_eq!(res.reason, "OK");
assert_eq!(res.version, "SIP/2.0");
}
}
#[test]
fn test_parse_register_request() {
let input = "REGISTER sip:registrar.biloxi.com SIP/2.0\r\n\
Via: SIP/2.0/UDP bobspc.biloxi.com:5060;branch=z9hG4bKnashds7\r\n\
Max-Forwards: 70\r\n\
To: Bob <sip:bob@biloxi.com>\r\n\
From: Bob <sip:bob@biloxi.com>;tag=456248\r\n\
Call-ID: 843817637684230@998sdasdh09\r\n\
CSeq: 1826 REGISTER\r\n\
Contact: <sip:bob@192.0.2.4>\r\n\
Expires: 7200\r\n\
Content-Length: 0\r\n\
\r\n";
let msg = SipMessage::parse(input).unwrap();
if let SipMessage::Request(req) = &msg {
assert_eq!(req.method, SipMethod::Register);
assert_eq!(req.uri, "sip:registrar.biloxi.com");
} else {
panic!("Expected request");
}
}
#[test]
fn test_cseq_parsing() {
let msg = SipMessage::parse(INVITE_REQUEST).unwrap();
let (seq, method) = msg.cseq().unwrap();
assert_eq!(seq, 314159);
assert_eq!(method, SipMethod::Invite);
}
#[test]
fn test_call_id() {
let msg = SipMessage::parse(INVITE_REQUEST).unwrap();
assert_eq!(
msg.call_id().unwrap(),
"a84b4c76e66710@pc33.atlanta.com"
);
}
#[test]
fn test_method_enum() {
assert_eq!(SipMethod::from_str("INVITE"), SipMethod::Invite);
assert_eq!(SipMethod::from_str("invite"), SipMethod::Invite);
assert_eq!(SipMethod::from_str("BYE"), SipMethod::Bye);
assert_eq!(SipMethod::from_str("REGISTER"), SipMethod::Register);
assert_eq!(SipMethod::from_str("INFO"), SipMethod::Info);
assert_eq!(
SipMethod::from_str("SUBSCRIBE"),
SipMethod::Other("SUBSCRIBE".to_string())
);
}
#[test]
fn test_status_code() {
assert!(StatusCode::TRYING.is_provisional());
assert!(StatusCode::RINGING.is_provisional());
assert!(StatusCode::SESSION_PROGRESS.is_provisional());
assert!(!StatusCode::SESSION_PROGRESS.is_success());
assert!(!StatusCode::SESSION_PROGRESS.is_error());
assert_eq!(StatusCode::SESSION_PROGRESS.0, 183);
assert_eq!(StatusCode::SESSION_PROGRESS.reason_phrase(), "Session Progress");
assert!(StatusCode::OK.is_success());
assert!(StatusCode::UNAUTHORIZED.is_error());
assert!(StatusCode::NOT_FOUND.is_error());
}
#[test]
fn test_parse_183_session_progress() {
let raw = "SIP/2.0 183 Session Progress\r\n\
Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776\r\n\
From: <sip:alice@example.com>;tag=abc123\r\n\
To: <sip:bob@example.com>;tag=xyz789\r\n\
Call-ID: early-media-test@10.0.0.1\r\n\
CSeq: 1 INVITE\r\n\
Content-Type: application/sdp\r\n\
Content-Length: 0\r\n\
\r\n";
let msg = SipMessage::parse(raw).expect("should parse 183");
assert!(msg.is_response());
let status = msg.status().expect("should have status");
assert_eq!(status.0, 183);
assert!(status.is_provisional());
assert!(!status.is_success());
assert_eq!(status.reason_phrase(), "Session Progress");
}
#[test]
fn test_request_builder() {
let msg = RequestBuilder::new(SipMethod::Register, "sip:registrar.example.com")
.header(
HeaderName::Via,
"SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776",
)
.header(HeaderName::From, "<sip:alice@example.com>;tag=abc123")
.header(HeaderName::To, "<sip:alice@example.com>")
.header(HeaderName::CallId, "unique-call-id@10.0.0.1")
.header(HeaderName::CSeq, "1 REGISTER")
.build();
assert!(msg.is_request());
if let SipMessage::Request(req) = &msg {
assert_eq!(req.method, SipMethod::Register);
assert_eq!(
req.headers.get(&HeaderName::ContentLength).unwrap().as_str(),
"0"
);
}
}
#[test]
fn test_response_builder_from_request() {
let invite = SipMessage::parse(INVITE_REQUEST).unwrap();
if let SipMessage::Request(req) = &invite {
let response = ResponseBuilder::from_request(req, StatusCode::OK).build();
if let SipMessage::Response(res) = &response {
assert_eq!(res.status, StatusCode::OK);
assert_eq!(
res.headers.get(&HeaderName::CallId).unwrap().as_str(),
"a84b4c76e66710@pc33.atlanta.com"
);
assert_eq!(
res.headers.get(&HeaderName::CSeq).unwrap().as_str(),
"314159 INVITE"
);
} else {
panic!("Expected response");
}
}
}
#[test]
fn test_message_serialization_roundtrip() {
let msg = RequestBuilder::new(SipMethod::Invite, "sip:bob@biloxi.com")
.header(
HeaderName::Via,
"SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776",
)
.header(HeaderName::From, "<sip:alice@atlanta.com>;tag=123")
.header(HeaderName::To, "<sip:bob@biloxi.com>")
.header(HeaderName::CallId, "test-call-id@pc33")
.header(HeaderName::CSeq, "1 INVITE")
.build();
let serialized = msg.to_string();
let parsed = SipMessage::parse(&serialized).unwrap();
assert!(parsed.is_request());
assert_eq!(parsed.call_id().unwrap(), "test-call-id@pc33");
}
#[test]
fn test_parse_error_invalid_start_line() {
let result = SipMessage::parse("NOT A VALID SIP MESSAGE");
assert!(result.is_ok()); }
#[test]
fn test_parse_error_empty() {
let result = SipMessage::parse("");
assert!(result.is_err());
}
#[test]
fn test_parse_401_response() {
let input = "SIP/2.0 401 Unauthorized\r\n\
Via: SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776\r\n\
From: <sip:alice@example.com>;tag=123\r\n\
To: <sip:alice@example.com>;tag=456\r\n\
Call-ID: test-call-id\r\n\
CSeq: 1 REGISTER\r\n\
WWW-Authenticate: Digest realm=\"example.com\", nonce=\"abc123\"\r\n\
Content-Length: 0\r\n\
\r\n";
let msg = SipMessage::parse(input).unwrap();
if let SipMessage::Response(res) = &msg {
assert_eq!(res.status, StatusCode::UNAUTHORIZED);
assert!(res
.headers
.get(&HeaderName::WwwAuthenticate)
.is_some());
} else {
panic!("Expected response");
}
}
}