use crate::Uri;
pub const GR_PARAM: &str = "gr";
#[must_use]
pub fn is_gruu(uri: &Uri) -> bool {
uri.params().is_some_and(|params| params.contains(GR_PARAM))
}
#[must_use]
pub fn gr_value(uri: &Uri) -> Option<&[u8]> {
uri.params().and_then(|params| params.value(GR_PARAM))
}
#[must_use]
pub fn addressed_to(request_uri: &Uri, gruu: &Uri) -> bool {
is_gruu(request_uri) && is_gruu(gruu) && request_uri.equivalent(gruu)
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)]
mod tests {
use super::*;
use bytes::Bytes;
const INSTANCE: &str = "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6";
fn uri(text: &str) -> Uri {
Uri::parse(Bytes::from(text.to_owned())).unwrap_or_else(|e| panic!("{text:?}: {e}"))
}
#[test]
fn a_gruu_is_a_uri_carrying_the_gr_parameter() {
assert!(is_gruu(&uri(&format!(
"sip:alice@example.com;gr={INSTANCE}"
))));
assert!(is_gruu(&uri("sip:t7k2xq9f4m@example.com;gr")));
assert!(!is_gruu(&uri("sip:alice@example.com")));
assert!(!is_gruu(&uri("sip:alice@example.com;group=5")));
}
#[test]
fn the_gr_parameter_round_trips_through_the_parser_unchanged() {
for text in [
"sip:alice@example.com;gr=urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6",
"sip:t7k2xq9f4m@example.com;gr",
"sips:alice@example.com;transport=tls;gr=urn:uuid:abc",
] {
assert_eq!(
uri(text).to_bytes(),
Bytes::from(text.to_owned()),
"§4.2 has a UA treat a GRUU as opaque, so it must go back out as it came in"
);
}
assert_eq!(
gr_value(&uri(&format!("sip:alice@example.com;gr={INSTANCE}"))),
Some(INSTANCE.as_bytes())
);
assert_eq!(gr_value(&uri("sip:t7k2xq9f4m@example.com;gr")), None);
}
#[test]
fn an_address_of_record_is_rfc3261_equivalent_to_a_public_gruu_but_is_not_sent_to_it() {
let aor = uri("sip:alice@example.com");
let gruu = uri(&format!("sip:alice@example.com;gr={INSTANCE}"));
assert!(
aor.equivalent(&gruu),
"§5.4 says RFC 3261 §19.1.4 calls these the same URI, because it ignores a \
parameter present in only one of them"
);
assert!(
!addressed_to(&aor, &gruu),
"a request to the address of record names every device the user registered; \
answering it as though it named this instance is the whole bug §5.4 warns about"
);
assert!(addressed_to(&gruu, &gruu));
}
#[test]
fn one_instances_gruu_is_not_another_instances() {
let ours = uri(&format!("sip:alice@example.com;gr={INSTANCE}"));
let theirs = uri("sip:alice@example.com;gr=urn:uuid:00000000-0000-4000-8000-000000000000");
assert!(!addressed_to(&theirs, &ours));
assert!(!addressed_to(&ours, &theirs));
}
#[test]
fn a_valueless_temporary_gruu_matches_only_itself() {
let temp = uri("sip:t7k2xq9f4m@example.com;gr");
assert!(addressed_to(&temp, &temp));
assert!(!addressed_to(
&uri(&format!("sip:t7k2xq9f4m@example.com;gr={INSTANCE}")),
&temp
));
assert!(!addressed_to(&uri("sip:t7k2xq9f4m@example.com"), &temp));
assert!(!addressed_to(&uri("sip:h4d0s2p8v1@example.com;gr"), &temp));
}
#[test]
fn the_ordinary_uri_rules_still_decide_everything_else() {
let plain = uri(&format!("sip:alice@example.com;gr={INSTANCE}"));
let secure = uri(&format!("sips:alice@example.com;gr={INSTANCE}"));
assert!(!addressed_to(&secure, &plain));
assert!(addressed_to(
&uri(&format!("sip:alice@EXAMPLE.com;%67r={INSTANCE}")),
&plain
));
}
}