#![allow(dead_code)]
use rsiprtp::sip::parser::{Header as OurHeader, Message as OurMessage};
use rsiprtp::sip::SipUri;
use rsiprtp::sip::parser::typed::{From as OurFrom, To as OurTo};
use rsiprtp::sip::parser::typed::{CSeq as OurCSeq, Contact as OurContact, Via as OurVia};
fn find_header_separator(bytes: &[u8]) -> Option<usize> {
bytes
.windows(4)
.position(|w| w == b"\r\n\r\n")
.or_else(|| bytes.windows(2).position(|w| w == b"\n\n"))
}
fn has_bare_lf_in_start_line(bytes: &[u8]) -> bool {
let crlf_pos = bytes
.windows(2)
.position(|w| w == b"\r\n")
.unwrap_or(bytes.len());
let start_region = &bytes[..crlf_pos];
for (i, &b) in start_region.iter().enumerate() {
if b == b'\n' && (i == 0 || start_region[i - 1] != b'\r') {
return true;
}
}
false
}
#[derive(Debug, PartialEq, Eq)]
pub struct NormalizedUri {
pub scheme: String,
pub user: Option<String>,
pub host: String,
pub port: Option<u16>,
pub params: Vec<(String, Option<String>)>,
pub headers: Vec<(String, String)>,
pub raw_fallback: Option<String>,
}
impl NormalizedUri {
pub fn from_str(s: &str) -> Self {
match SipUri::parse(s) {
Ok(uri) => {
let mut params: Vec<(String, Option<String>)> = uri
.params()
.map(|(k, v)| (k.to_ascii_lowercase(), v.map(|s| s.to_string())))
.collect();
params.sort_by(|a, b| a.0.cmp(&b.0));
let mut headers: Vec<(String, String)> = uri
.headers()
.map(|(k, v)| (k.to_ascii_lowercase(), v.to_string()))
.collect();
headers.sort_by(|a, b| a.0.cmp(&b.0));
NormalizedUri {
scheme: uri.scheme().to_string(),
user: uri.user().map(|u| u.to_string()),
host: uri.host().to_ascii_lowercase(),
port: uri.port(),
params,
headers,
raw_fallback: None,
}
}
Err(_) => NormalizedUri {
scheme: String::new(),
user: None,
host: String::new(),
port: None,
params: Vec::new(),
headers: Vec::new(),
raw_fallback: Some(s.to_string()),
},
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum DiffKind {
Request { method: String, uri: NormalizedUri },
Response { status: u16 },
}
#[derive(Debug, PartialEq, Eq)]
pub struct DiffMessage {
pub kind: DiffKind,
pub headers: Vec<(
String, /* name_lc */
String, /* value_normalized */
)>,
pub body: Vec<u8>,
}
pub fn normalize_value(s: &str) -> String {
let bytes = s.as_bytes();
let mut out = String::with_capacity(s.len());
let mut i = 0;
let mut in_quoted = false;
let mut paren_depth: u32 = 0;
while i < bytes.len() {
let b = bytes[i];
if in_quoted {
out.push(b as char);
if b == b'\\' && i + 1 < bytes.len() {
out.push(bytes[i + 1] as char);
i += 2;
continue;
}
if b == b'"' {
in_quoted = false;
}
i += 1;
continue;
}
if paren_depth > 0 {
if b == b'\\' && i + 1 < bytes.len() {
i += 2;
continue;
}
if b == b'(' {
paren_depth += 1;
} else if b == b')' {
paren_depth -= 1;
}
i += 1;
continue;
}
match b {
b'"' => {
in_quoted = true;
out.push('"');
}
b'(' => {
paren_depth = 1;
}
b'\\' => {
out.push('\\');
if i + 1 < bytes.len() {
out.push(bytes[i + 1] as char);
i += 2;
continue;
}
}
b' ' | b'\t' => {
if !out.ends_with(' ') {
out.push(' ');
}
}
_ => out.push(b as char),
}
i += 1;
}
out.trim().to_string()
}
pub fn rsip_to_diff(bytes: &[u8]) -> Result<DiffMessage, String> {
use rsip::SipMessage;
let msg = SipMessage::try_from(bytes).map_err(|e| format!("rsip: {e}"))?;
let (kind, headers, body) = match msg {
SipMessage::Request(req) => {
let method = req.method.to_string();
let uri = NormalizedUri::from_str(&req.uri.to_string());
let headers = collect_rsip_headers(&req.headers);
(DiffKind::Request { method, uri }, headers, req.body.clone())
}
SipMessage::Response(resp) => {
let status: u16 = resp.status_code.clone().into();
let headers = collect_rsip_headers(&resp.headers);
(DiffKind::Response { status }, headers, resp.body.clone())
}
};
Ok(DiffMessage {
kind,
headers,
body,
})
}
fn collect_rsip_headers(hs: &rsip::Headers) -> Vec<(String, String)> {
hs.iter().map(rsip_header_pair).collect()
}
fn resolve_compact_name(name: &str) -> String {
let lc = name.trim().to_ascii_lowercase();
if lc.len() != 1 {
return lc;
}
match lc.as_bytes()[0] {
b'i' => "call-id".into(),
b'm' => "contact".into(),
b'f' => "from".into(),
b't' => "to".into(),
b'v' => "via".into(),
b'c' => "content-type".into(),
b'l' => "content-length".into(),
b's' => "subject".into(),
b'k' => "supported".into(),
b'e' => "content-encoding".into(),
b'r' => "refer-to".into(),
b'b' => "referred-by".into(),
b'd' => "content-disposition".into(),
b'o' => "event".into(),
b'u' => "allow-events".into(),
_ => lc,
}
}
fn rsip_header_pair(h: &rsip::Header) -> (String, String) {
use rsip::headers::UntypedHeader;
use rsip::Header as H;
match h {
H::Accept(v) => ("accept".into(), normalize_value(v.value())),
H::AcceptEncoding(v) => ("accept-encoding".into(), normalize_value(v.value())),
H::AcceptLanguage(v) => ("accept-language".into(), normalize_value(v.value())),
H::AlertInfo(v) => ("alert-info".into(), normalize_value(v.value())),
H::Allow(v) => ("allow".into(), normalize_value(v.value())),
H::AuthenticationInfo(v) => ("authentication-info".into(), normalize_value(v.value())),
H::Authorization(v) => ("authorization".into(), normalize_value(v.value())),
H::CSeq(v) => ("cseq".into(), normalize_value(v.value())),
H::CallId(v) => ("call-id".into(), normalize_value(v.value())),
H::CallInfo(v) => ("call-info".into(), normalize_value(v.value())),
H::Contact(v) => ("contact".into(), normalize_value(v.value())),
H::ContentDisposition(v) => ("content-disposition".into(), normalize_value(v.value())),
H::ContentEncoding(v) => ("content-encoding".into(), normalize_value(v.value())),
H::ContentLanguage(v) => ("content-language".into(), normalize_value(v.value())),
H::ContentLength(v) => ("content-length".into(), normalize_value(v.value())),
H::ContentType(v) => ("content-type".into(), normalize_value(v.value())),
H::Date(v) => ("date".into(), normalize_value(v.value())),
H::ErrorInfo(v) => ("error-info".into(), normalize_value(v.value())),
H::Event(v) => ("event".into(), normalize_value(v.value())),
H::Expires(v) => ("expires".into(), normalize_value(v.value())),
H::From(v) => ("from".into(), normalize_value(v.value())),
H::InReplyTo(v) => ("in-reply-to".into(), normalize_value(v.value())),
H::MaxForwards(v) => ("max-forwards".into(), normalize_value(v.value())),
H::MimeVersion(v) => ("mime-version".into(), normalize_value(v.value())),
H::MinExpires(v) => ("min-expires".into(), normalize_value(v.value())),
H::Organization(v) => ("organization".into(), normalize_value(v.value())),
H::Other(name, value) => (resolve_compact_name(name), normalize_value(value)),
H::Priority(v) => ("priority".into(), normalize_value(v.value())),
H::ProxyAuthenticate(v) => ("proxy-authenticate".into(), normalize_value(v.value())),
H::ProxyAuthorization(v) => ("proxy-authorization".into(), normalize_value(v.value())),
H::ProxyRequire(v) => ("proxy-require".into(), normalize_value(v.value())),
H::RecordRoute(v) => ("record-route".into(), normalize_value(v.value())),
H::ReplyTo(v) => ("reply-to".into(), normalize_value(v.value())),
H::Require(v) => ("require".into(), normalize_value(v.value())),
H::RetryAfter(v) => ("retry-after".into(), normalize_value(v.value())),
H::Route(v) => ("route".into(), normalize_value(v.value())),
H::Server(v) => ("server".into(), normalize_value(v.value())),
H::Subject(v) => ("subject".into(), normalize_value(v.value())),
H::SubscriptionState(v) => ("subscription-state".into(), normalize_value(v.value())),
H::Supported(v) => ("supported".into(), normalize_value(v.value())),
H::Timestamp(v) => ("timestamp".into(), normalize_value(v.value())),
H::To(v) => ("to".into(), normalize_value(v.value())),
H::Unsupported(v) => ("unsupported".into(), normalize_value(v.value())),
H::UserAgent(v) => ("user-agent".into(), normalize_value(v.value())),
H::Via(v) => ("via".into(), normalize_value(v.value())),
H::Warning(v) => ("warning".into(), normalize_value(v.value())),
H::WwwAuthenticate(v) => ("www-authenticate".into(), normalize_value(v.value())),
}
}
pub fn ours_to_diff(bytes: &[u8]) -> Result<DiffMessage, String> {
let msg = OurMessage::parse(bytes).map_err(|e| format!("ours: {e}"))?;
let (kind, headers, body) = match msg {
OurMessage::Request(req) => {
let method = req.method.as_str().to_string();
let uri = NormalizedUri::from_str(&req.uri);
let headers = collect_our_headers(&req.headers);
(DiffKind::Request { method, uri }, headers, req.body.clone())
}
OurMessage::Response(resp) => {
let status = resp.status_code.as_u16();
let headers = collect_our_headers(&resp.headers);
(DiffKind::Response { status }, headers, resp.body.clone())
}
};
Ok(DiffMessage {
kind,
headers,
body,
})
}
fn collect_our_headers(hs: &rsiprtp::sip::parser::Headers) -> Vec<(String, String)> {
hs.iter()
.map(|h| {
let name = match h {
OurHeader::Other(n, _) => n.to_ascii_lowercase(),
_ => h.name().to_ascii_lowercase(),
};
(name, normalize_value(h.value()))
})
.collect()
}
#[derive(Debug, PartialEq, Eq)]
pub struct DiffNameAddr {
pub display_name: Option<String>,
pub uri: NormalizedUri,
pub parameters: Vec<(String, Option<String>)>,
}
pub fn unquote_display_name(s: &str) -> String {
let bytes = s.as_bytes();
if bytes.len() >= 2 && bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"' {
let inner = &s[1..s.len() - 1];
let mut out = String::with_capacity(inner.len());
let inb = inner.as_bytes();
let mut i = 0;
while i < inb.len() {
if inb[i] == b'\\' && i + 1 < inb.len() {
out.push(inb[i + 1] as char);
i += 2;
} else {
out.push(inb[i] as char);
i += 1;
}
}
out
} else {
s.to_string()
}
}
fn normalize_params(params: Vec<(String, Option<String>)>) -> Vec<(String, Option<String>)> {
let mut out: Vec<(String, Option<String>)> = params
.into_iter()
.map(|(k, v)| (k.to_ascii_lowercase(), v))
.collect();
out.sort_by(|a, b| a.0.cmp(&b.0));
out
}
pub fn rsip_from_to_diff(value: &str) -> Result<DiffNameAddr, String> {
use rsip::headers::untyped::{ToTypedHeader, UntypedHeader};
let untyped = rsip::headers::From::new(value);
let typed = untyped.typed().map_err(|e| format!("rsip From: {e}"))?;
let params = typed
.params
.iter()
.map(rsip_param_to_pair)
.collect::<Vec<_>>();
Ok(DiffNameAddr {
display_name: typed.display_name.as_deref().map(unquote_display_name),
uri: NormalizedUri::from_str(&typed.uri.to_string()),
parameters: normalize_params(params),
})
}
pub fn rsip_to_to_diff(value: &str) -> Result<DiffNameAddr, String> {
use rsip::headers::untyped::{ToTypedHeader, UntypedHeader};
let untyped = rsip::headers::To::new(value);
let typed = untyped.typed().map_err(|e| format!("rsip To: {e}"))?;
let params = typed
.params
.iter()
.map(rsip_param_to_pair)
.collect::<Vec<_>>();
Ok(DiffNameAddr {
display_name: typed.display_name.as_deref().map(unquote_display_name),
uri: NormalizedUri::from_str(&typed.uri.to_string()),
parameters: normalize_params(params),
})
}
fn rsip_param_to_pair(p: &rsip::common::uri::Param) -> (String, Option<String>) {
use rsip::common::uri::Param;
match p {
Param::Lr => ("lr".to_string(), None),
Param::Tag(t) => ("tag".to_string(), Some(t.value().to_string())),
Param::Branch(b) => ("branch".to_string(), Some(b.value().to_string())),
Param::Received(r) => ("received".to_string(), Some(r.value().to_string())),
Param::Expires(e) => ("expires".to_string(), Some(e.value().to_string())),
Param::Q(q) => ("q".to_string(), Some(q.value().to_string())),
Param::Ttl(t) => ("ttl".to_string(), Some(t.value().to_string())),
Param::Maddr(m) => ("maddr".to_string(), Some(m.value().to_string())),
Param::User(u) => ("user".to_string(), Some(u.value().to_string())),
Param::Method(m) => ("method".to_string(), Some(m.to_string())),
Param::Transport(t) => ("transport".to_string(), Some(t.to_string())),
Param::Other(name, Some(v)) => (name.value().to_string(), Some(v.value().to_string())),
Param::Other(name, None) => (name.value().to_string(), None),
}
}
pub fn ours_from_to_diff(value: &str) -> Result<DiffNameAddr, String> {
let f = OurFrom::parse(value).map_err(|e| format!("ours From: {e}"))?;
Ok(DiffNameAddr {
display_name: f.display_name.clone(),
uri: NormalizedUri::from_str(&f.uri.to_string()),
parameters: normalize_params(f.params.clone()),
})
}
pub fn ours_to_to_diff(value: &str) -> Result<DiffNameAddr, String> {
let t = OurTo::parse(value).map_err(|e| format!("ours To: {e}"))?;
Ok(DiffNameAddr {
display_name: t.display_name.clone(),
uri: NormalizedUri::from_str(&t.uri.to_string()),
parameters: normalize_params(t.params.clone()),
})
}
fn assert_typed_from_to_equivalent(bytes: &[u8]) {
use rsip::SipMessage as RsipMsg;
let rs_msg = match RsipMsg::try_from(bytes) {
Ok(m) => m,
Err(_) => return,
};
let our_msg = match OurMessage::parse(bytes) {
Ok(m) => m,
Err(_) => return,
};
let rs_headers: &rsip::Headers = match &rs_msg {
RsipMsg::Request(r) => &r.headers,
RsipMsg::Response(r) => &r.headers,
};
let our_headers = match &our_msg {
OurMessage::Request(r) => &r.headers,
OurMessage::Response(r) => &r.headers,
};
use rsip::headers::untyped::UntypedHeader as _;
let rsip_from_value = rs_headers.iter().find_map(|h| match h {
rsip::Header::From(v) => Some(v.value().to_string()),
_ => None,
});
let our_from_value = our_headers.iter().find_map(|h| match h {
OurHeader::From(v) => Some(v.clone()),
_ => None,
});
if let (Some(rs_v), Some(our_v)) = (rsip_from_value.as_deref(), our_from_value.as_deref()) {
let rs = rsip_from_to_diff(rs_v);
let ours = ours_from_to_diff(our_v);
match (rs, ours) {
(Ok(a), Ok(b)) => {
if a != b {
panic!(
"TYPED-FROM DIVERGENCE.\n\
rsip-value: {rs_v:?}\n\
our-value: {our_v:?}\n\
rsip:\n{a:#?}\n\
ours:\n{b:#?}",
);
}
}
(Err(_), Err(_)) => {}
(Ok(_), Err(e)) => panic!(
"rsip accepted typed From but ours rejected.\n\
value: {our_v:?}\n\
ours error: {e}",
),
(Err(e), Ok(_)) => panic!(
"ours accepted typed From but rsip rejected.\n\
value: {rs_v:?}\n\
rsip error: {e}",
),
}
}
let rsip_to_value = rs_headers.iter().find_map(|h| match h {
rsip::Header::To(v) => Some(v.value().to_string()),
_ => None,
});
let our_to_value = our_headers.iter().find_map(|h| match h {
OurHeader::To(v) => Some(v.clone()),
_ => None,
});
if let (Some(rs_v), Some(our_v)) = (rsip_to_value.as_deref(), our_to_value.as_deref()) {
let rs = rsip_to_to_diff(rs_v);
let ours = ours_to_to_diff(our_v);
match (rs, ours) {
(Ok(a), Ok(b)) => {
if a != b {
panic!(
"TYPED-TO DIVERGENCE.\n\
rsip-value: {rs_v:?}\n\
our-value: {our_v:?}\n\
rsip:\n{a:#?}\n\
ours:\n{b:#?}",
);
}
}
(Err(_), Err(_)) => {}
(Ok(_), Err(e)) => panic!(
"rsip accepted typed To but ours rejected.\n\
value: {our_v:?}\n\
ours error: {e}",
),
(Err(e), Ok(_)) => panic!(
"ours accepted typed To but rsip rejected.\n\
value: {rs_v:?}\n\
rsip error: {e}",
),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct DiffVia {
pub protocol: String,
pub transport: String,
pub sent_by: String,
pub parameters: Vec<(String, Option<String>)>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct DiffCSeq {
pub seq: u32,
pub method: String,
}
#[derive(Debug, PartialEq, Eq)]
pub enum DiffContact {
Wildcard,
Addr(DiffNameAddr),
}
pub fn rsip_via_diff(value: &str) -> Result<DiffVia, String> {
use rsip::headers::untyped::{ToTypedHeader, UntypedHeader};
let untyped = rsip::headers::Via::new(value);
let typed = untyped.typed().map_err(|e| format!("rsip Via: {e}"))?;
let params: Vec<(String, Option<String>)> =
typed.params.iter().map(rsip_param_to_pair).collect();
let host = typed.uri.host_with_port.host.to_string();
let sent_by = match &typed.uri.host_with_port.port {
Some(p) => format!("{}:{}", host, p),
None => host,
};
Ok(DiffVia {
protocol: format!("{}", typed.version),
transport: typed.transport.to_string().to_ascii_uppercase(),
sent_by: normalize_sent_by(&sent_by),
parameters: normalize_params(params),
})
}
pub fn ours_via_diff(value: &str) -> Result<DiffVia, String> {
let v = OurVia::parse(value).map_err(|e| format!("ours Via: {e}"))?;
Ok(DiffVia {
protocol: v.protocol.clone(),
transport: v.transport.to_ascii_uppercase(),
sent_by: normalize_sent_by(&v.sent_by),
parameters: normalize_params(v.params.clone()),
})
}
fn normalize_sent_by(s: &str) -> String {
let bytes = s.as_bytes();
let mut depth = 0i32;
let mut last_colon: Option<usize> = None;
for (i, &b) in bytes.iter().enumerate() {
match b {
b'[' => depth += 1,
b']' => depth -= 1,
b':' if depth == 0 => last_colon = Some(i),
_ => {}
}
}
match last_colon {
Some(idx) => {
let host = s[..idx].to_ascii_lowercase();
let port = &s[idx..];
format!("{}{}", host, port)
}
None => s.to_ascii_lowercase(),
}
}
pub fn rsip_cseq_diff(value: &str) -> Result<DiffCSeq, String> {
use rsip::headers::untyped::{ToTypedHeader, UntypedHeader};
let untyped = rsip::headers::CSeq::new(value);
let typed = untyped.typed().map_err(|e| format!("rsip CSeq: {e}"))?;
Ok(DiffCSeq {
seq: typed.seq,
method: typed.method.to_string(),
})
}
pub fn ours_cseq_diff(value: &str) -> Result<DiffCSeq, String> {
let c = OurCSeq::parse(value).map_err(|e| format!("ours CSeq: {e}"))?;
Ok(DiffCSeq {
seq: c.seq,
method: c.method.as_str().to_string(),
})
}
pub fn rsip_contact_diff(value: &str) -> Result<DiffContact, String> {
let trimmed = value.trim();
if trimmed == "*" {
return Ok(DiffContact::Wildcard);
}
use rsip::headers::untyped::{ToTypedHeader, UntypedHeader};
let untyped = rsip::headers::Contact::new(value);
let typed = untyped.typed().map_err(|e| format!("rsip Contact: {e}"))?;
let params: Vec<(String, Option<String>)> =
typed.params.iter().map(rsip_param_to_pair).collect();
Ok(DiffContact::Addr(DiffNameAddr {
display_name: typed.display_name.as_deref().map(unquote_display_name),
uri: NormalizedUri::from_str(&typed.uri.to_string()),
parameters: normalize_params(params),
}))
}
pub fn ours_contact_diff(value: &str) -> Result<DiffContact, String> {
let c = OurContact::parse(value).map_err(|e| format!("ours Contact: {e}"))?;
match c {
OurContact::Wildcard { .. } => Ok(DiffContact::Wildcard),
OurContact::Addr(a) => Ok(DiffContact::Addr(DiffNameAddr {
display_name: a.display_name.clone(),
uri: NormalizedUri::from_str(&a.uri.to_string()),
parameters: normalize_params(a.params.clone()),
})),
}
}
fn assert_typed_via_cseq_contact_equivalent(bytes: &[u8]) {
use rsip::headers::untyped::UntypedHeader as _;
use rsip::SipMessage as RsipMsg;
let rs_msg = match RsipMsg::try_from(bytes) {
Ok(m) => m,
Err(_) => return,
};
let our_msg = match OurMessage::parse(bytes) {
Ok(m) => m,
Err(_) => return,
};
let rs_headers: &rsip::Headers = match &rs_msg {
RsipMsg::Request(r) => &r.headers,
RsipMsg::Response(r) => &r.headers,
};
let our_headers = match &our_msg {
OurMessage::Request(r) => &r.headers,
OurMessage::Response(r) => &r.headers,
};
let rs_vias: Vec<String> = rs_headers
.iter()
.filter_map(|h| match h {
rsip::Header::Via(v) => Some(v.value().to_string()),
rsip::Header::Other(name, value) if name.eq_ignore_ascii_case("v") => {
Some(value.clone())
}
_ => None,
})
.collect();
let our_vias: Vec<String> = our_headers
.iter()
.filter_map(|h| match h {
OurHeader::Via(v) => Some(v.clone()),
_ => None,
})
.collect();
assert_eq!(
rs_vias.len(),
our_vias.len(),
"Via header count differs: rsip {} vs ours {} ({:?} vs {:?})",
rs_vias.len(),
our_vias.len(),
rs_vias,
our_vias,
);
for (rs_v, our_v) in rs_vias.iter().zip(our_vias.iter()) {
let rs = rsip_via_diff(rs_v);
let ours = ours_via_diff(our_v);
match (rs, ours) {
(Ok(a), Ok(b)) => {
if a != b {
panic!(
"TYPED-VIA DIVERGENCE.\n\
rsip-value: {rs_v:?}\n\
our-value: {our_v:?}\n\
rsip:\n{a:#?}\n\
ours:\n{b:#?}",
);
}
}
(Err(_), Err(_)) => {}
(Ok(_), Err(e)) => panic!(
"rsip accepted typed Via but ours rejected.\n\
value: {our_v:?}\n\
ours error: {e}",
),
(Err(e), Ok(_)) => panic!(
"ours accepted typed Via but rsip rejected.\n\
value: {rs_v:?}\n\
rsip error: {e}",
),
}
}
let rs_cseq = rs_headers.iter().find_map(|h| match h {
rsip::Header::CSeq(v) => Some(v.value().to_string()),
_ => None,
});
let our_cseq = our_headers.iter().find_map(|h| match h {
OurHeader::CSeq(v) => Some(v.clone()),
_ => None,
});
if let (Some(rs_v), Some(our_v)) = (rs_cseq.as_deref(), our_cseq.as_deref()) {
let rs = rsip_cseq_diff(rs_v);
let ours = ours_cseq_diff(our_v);
match (rs, ours) {
(Ok(a), Ok(b)) => {
if a != b {
panic!(
"TYPED-CSEQ DIVERGENCE.\n\
rsip-value: {rs_v:?}\n\
our-value: {our_v:?}\n\
rsip:\n{a:#?}\n\
ours:\n{b:#?}",
);
}
}
(Err(_), Err(_)) => {}
(Ok(_), Err(e)) => panic!(
"rsip accepted typed CSeq but ours rejected.\n\
value: {our_v:?}\n\
ours error: {e}",
),
(Err(e), Ok(_)) => panic!(
"ours accepted typed CSeq but rsip rejected.\n\
value: {rs_v:?}\n\
rsip error: {e}",
),
}
}
let rs_contacts: Vec<String> = rs_headers
.iter()
.filter_map(|h| match h {
rsip::Header::Contact(v) => Some(v.value().to_string()),
rsip::Header::Other(name, value) if name.eq_ignore_ascii_case("m") => {
Some(value.clone())
}
_ => None,
})
.collect();
let our_contacts: Vec<String> = our_headers
.iter()
.filter_map(|h| match h {
OurHeader::Contact(v) => Some(v.clone()),
_ => None,
})
.collect();
assert_eq!(
rs_contacts.len(),
our_contacts.len(),
"Contact header count differs: rsip {} vs ours {}",
rs_contacts.len(),
our_contacts.len(),
);
for (rs_v, our_v) in rs_contacts.iter().zip(our_contacts.iter()) {
let rs = rsip_contact_diff(rs_v);
let ours = ours_contact_diff(our_v);
match (rs, ours) {
(Ok(a), Ok(b)) => {
if a != b {
panic!(
"TYPED-CONTACT DIVERGENCE.\n\
rsip-value: {rs_v:?}\n\
our-value: {our_v:?}\n\
rsip:\n{a:#?}\n\
ours:\n{b:#?}",
);
}
}
(Err(_), Err(_)) => {}
(Ok(_), Err(e)) => panic!(
"rsip accepted typed Contact but ours rejected.\n\
value: {our_v:?}\n\
ours error: {e}",
),
(Err(e), Ok(_)) => panic!(
"ours accepted typed Contact but rsip rejected.\n\
value: {rs_v:?}\n\
rsip error: {e}",
),
}
}
}
pub fn assert_equivalent(bytes: &[u8]) {
let rs = rsip_to_diff(bytes);
let ours = ours_to_diff(bytes);
match (rs, ours) {
(Ok(a), Ok(b)) => {
if a == b {
} else if a.kind == b.kind
&& a.headers == b.headers
&& b.body.starts_with(b"\r\n")
&& b.body[2..] == a.body
{
} else if a.kind == b.kind && has_bare_lf_in_start_line(bytes) {
} else {
panic!(
"DIVERGENCE on parse-success.\n\
rsip:\n{a:#?}\n\
ours:\n{b:#?}",
);
}
}
(Err(_), Err(_)) => { }
(Ok(a), Err(e)) => {
let msg = e.to_string();
if msg.contains("invalid SIP version")
|| msg.contains("status code out of range")
|| msg.contains("invalid Request-URI")
|| msg.contains("missing ':'")
|| msg.contains("reason phrase contains forbidden control byte")
|| msg.contains("no header/body separator")
{
return;
}
panic!(
"rsip accepted but ours rejected:\n\
{a:#?}\n\
ours error: {e}",
);
}
(Err(e), Ok(b)) => {
let header_section = match find_header_separator(bytes) {
Some(idx) => &bytes[..idx],
None => bytes,
};
let has_unusual_byte = header_section
.iter()
.any(|&b| !(b == 0x09 || b == 0x0A || b == 0x0D || (0x20..=0x7E).contains(&b)));
let rsip_err = e.to_string();
if has_unusual_byte && rsip_err.contains("Tokenizer error") {
return;
}
let has_bare_lf = bytes.iter().enumerate().any(|(i, &b)| {
b == 0x0A && (i == 0 || bytes[i - 1] != 0x0D)
});
if has_bare_lf && rsip_err.contains("Tokenizer error") {
return;
}
if rsip_err.contains("invalid utf8") || rsip_err.contains("invalid UTF-8") {
return;
}
panic!(
"ours accepted but rsip rejected:\n\
{b:#?}\n\
rsip error: {e}",
);
}
}
assert_typed_from_to_equivalent(bytes);
assert_typed_via_cseq_contact_equivalent(bytes);
}
pub fn assert_both_reject(label: &str, bytes: &[u8]) {
let rs = rsip::SipMessage::try_from(bytes);
let ours = OurMessage::parse(bytes);
match (rs.is_ok(), ours.is_ok()) {
(false, false) => { }
(true, false) => panic!(
"{label}: rsip ACCEPTED but ours rejected — \
RFC 4475 §4 says this is invalid; rsip is wrong.\n\
rsip parsed: {:#?}",
rs.unwrap()
),
(false, true) => panic!(
"{label}: ours ACCEPTED but rsip rejected — \
RFC 4475 §4 says this is invalid; we are too lenient.\n\
ours parsed: {:#?}",
ours.unwrap()
),
(true, true) => panic!(
"{label}: BOTH parsers accepted — RFC 4475 §4 says this \
is invalid; both are too lenient."
),
}
}