use sip_uri::UriEquivalence;
use crate::dialog_id::DialogId;
use crate::{
HeaderParams, SipAuthValue, SipHeaderAddr, SipReason, SipReasonList, SipVia, SipViaEntry,
TokenList,
};
pub(crate) mod sealed {
pub trait Sealed {}
}
pub trait HeaderEquivalence: sealed::Sealed {
fn equivalent(&self, other: &Self) -> bool;
}
type Value<'a> = (Option<&'a str>, bool);
fn value_of<'a>(params: &'a HeaderParams, name: &str) -> Option<Value<'a>> {
params
.get(name)
.map(|v| (v, params.is_quoted(name)))
}
fn values_equivalent((a, a_quoted): Value<'_>, (b, b_quoted): Value<'_>) -> bool {
match (a, b) {
(Some(a), Some(b)) if !a_quoted && !b_quoted => a.eq_ignore_ascii_case(b),
(a, b) => a == b,
}
}
fn params_match(
a: &HeaderParams,
b: &HeaderParams,
same: impl Fn(Value<'_>, Value<'_>) -> bool,
may_lack: impl Fn(&str) -> bool,
) -> bool {
let one_way = |x: &HeaderParams, y: &HeaderParams| {
x.iter()
.all(|(name, _)| match (value_of(x, name), value_of(y, name)) {
(Some(xv), Some(yv)) => same(xv, yv),
_ => may_lack(name),
})
};
one_way(a, b) && one_way(b, a)
}
fn same_params(a: &HeaderParams, b: &HeaderParams) -> bool {
params_match(a, b, values_equivalent, |_| false)
}
fn entries_equivalent<T: HeaderEquivalence>(a: &[T], b: &[T]) -> bool {
a.len() == b.len()
&& a.iter()
.zip(b)
.all(|(a, b)| a.equivalent(b))
}
impl sealed::Sealed for SipHeaderAddr {}
impl HeaderEquivalence for SipHeaderAddr {
fn equivalent(&self, other: &Self) -> bool {
self.uri()
.equivalent(other.uri())
&& params_match(self.params(), other.params(), values_equivalent, |name| {
name != "tag"
})
}
}
impl sealed::Sealed for SipViaEntry {}
impl HeaderEquivalence for SipViaEntry {
fn equivalent(&self, other: &Self) -> bool {
self.protocol()
.eq_ignore_ascii_case(other.protocol())
&& self
.version()
.eq_ignore_ascii_case(other.version())
&& self
.transport()
.eq_ignore_ascii_case(other.transport())
&& self.host() == other.host()
&& self.port() == other.port()
&& same_params(self.params(), other.params())
}
}
impl sealed::Sealed for SipVia {}
impl HeaderEquivalence for SipVia {
fn equivalent(&self, other: &Self) -> bool {
entries_equivalent(self.entries(), other.entries())
}
}
impl sealed::Sealed for SipAuthValue {}
impl HeaderEquivalence for SipAuthValue {
fn equivalent(&self, other: &Self) -> bool {
self.scheme()
.eq_ignore_ascii_case(other.scheme())
&& self.token68() == other.token68()
&& params_match(
self.params(),
other.params(),
|(a, _), (b, _)| a == b,
|_| false,
)
}
}
impl sealed::Sealed for SipReason {}
impl HeaderEquivalence for SipReason {
fn equivalent(&self, other: &Self) -> bool {
self.protocol()
.eq_ignore_ascii_case(other.protocol())
&& self.cause() == other.cause()
&& self.text() == other.text()
&& same_params(self.params(), other.params())
}
}
impl sealed::Sealed for SipReasonList {}
impl HeaderEquivalence for SipReasonList {
fn equivalent(&self, other: &Self) -> bool {
entries_equivalent(self.entries(), other.entries())
}
}
pub(crate) fn dialogs_equivalent(a: &DialogId, b: &DialogId) -> bool {
a.call_id() == b.call_id()
&& a.first_tag()
.eq_ignore_ascii_case(b.first_tag())
&& a.second_tag()
.eq_ignore_ascii_case(b.second_tag())
&& a.early_only() == b.early_only()
&& same_params(a.params(), b.params())
}
impl sealed::Sealed for TokenList {}
impl HeaderEquivalence for TokenList {
fn equivalent(&self, other: &Self) -> bool {
let case_sensitive = self.is_case_sensitive();
case_sensitive == other.is_case_sensitive()
&& self.len() == other.len()
&& self
.iter()
.zip(other.iter())
.all(|(a, b)| {
if case_sensitive {
a == b
} else {
a.eq_ignore_ascii_case(b)
}
})
}
}