use crate::headers::misc::Allow;
use crate::message::{Headers, TypedHeader as _};
use crate::name::HeaderName;
pub const ALLOW: &str = "INVITE, ACK, CANCEL, BYE, OPTIONS, UPDATE";
pub const RETRY_AFTER_MAX_SECS: u64 = 10;
#[must_use]
pub fn peer_allows(headers: &Headers) -> bool {
headers
.get_all(&HeaderName::Allow)
.filter_map(|header| Allow::decode(&header.value()).ok())
.any(|allow| allow.contains(METHOD))
}
const METHOD: &str = "UPDATE";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Refusal {
InProgress,
Glare,
AnswerOwed,
}
impl Refusal {
#[must_use]
pub const fn status(self) -> u16 {
match self {
Self::Glare => 491,
Self::InProgress | Self::AnswerOwed => 500,
}
}
#[must_use]
pub const fn reason(self) -> &'static str {
match self {
Self::Glare => "Request Pending",
Self::InProgress | Self::AnswerOwed => "Server Internal Error",
}
}
#[must_use]
pub const fn retry_after(self) -> bool {
match self {
Self::Glare => false,
Self::InProgress | Self::AnswerOwed => true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reception {
Accept,
Refuse(Refusal),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Pending {
WithOffer,
Offerless,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Negotiation {
offered: bool,
owed: bool,
in_progress: Option<Pending>,
}
impl Negotiation {
#[must_use]
pub const fn idle() -> Self {
Self {
offered: false,
owed: false,
in_progress: None,
}
}
#[must_use]
pub const fn is_idle(self) -> bool {
!self.offered && !self.owed && self.in_progress.is_none()
}
#[must_use]
pub const fn offering() -> Self {
Self {
offered: true,
..Self::idle()
}
}
#[must_use]
pub const fn owing() -> Self {
Self {
owed: true,
..Self::idle()
}
}
#[must_use]
pub const fn is_offering(self) -> bool {
self.offered
}
#[must_use]
pub const fn owes_answer(self) -> bool {
self.owed
}
pub const fn sent_offer(&mut self) {
self.offered = true;
}
pub const fn received_answer(&mut self) {
self.offered = false;
}
pub const fn received_offer(&mut self) {
self.owed = true;
}
pub const fn sent_answer(&mut self) {
self.owed = false;
}
#[must_use]
pub const fn may_offer(self) -> bool {
!self.offered && !self.owed
}
pub const fn receive(&mut self, has_offer: bool) -> Reception {
if self.in_progress.is_some() {
return Reception::Refuse(Refusal::InProgress);
}
if has_offer {
if self.offered {
return Reception::Refuse(Refusal::Glare);
}
if self.owed {
return Reception::Refuse(Refusal::AnswerOwed);
}
self.owed = true;
self.in_progress = Some(Pending::WithOffer);
} else {
self.in_progress = Some(Pending::Offerless);
}
Reception::Accept
}
pub const fn answered(&mut self) {
if matches!(self.in_progress, Some(Pending::WithOffer)) {
self.owed = false;
}
self.in_progress = None;
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use crate::{Limits, Message, parse_datagram};
fn headers(allow: &str) -> Headers {
let text = format!(
"INVITE sip:b@example.com SIP/2.0\r\n\
Via: SIP/2.0/UDP 192.0.2.1;branch=z9hG4bKx\r\n\
To: <sip:b@example.com>\r\n\
From: <sip:a@example.net>;tag=1\r\n\
Call-ID: c\r\n\
CSeq: 1 INVITE\r\n\
{allow}\
Content-Length: 0\r\n\r\n"
);
match parse_datagram(bytes::Bytes::from(text), &Limits::datagram()).expect("parses") {
Message::Request(r) => r.headers,
Message::Response(_) => panic!("a request"),
}
}
#[test]
fn the_peers_allow_is_the_only_permission_there_is() {
assert!(peer_allows(&headers(
"Allow: INVITE, ACK, CANCEL, BYE, OPTIONS, UPDATE\r\n"
)));
assert!(!peer_allows(&headers("Allow: INVITE, ACK, BYE\r\n")));
assert!(peer_allows(&headers("Allow: invite,update\r\n")));
assert!(!peer_allows(&headers("Allow: INVITE, UPDATEX\r\n")));
assert!(!peer_allows(&headers("")));
assert!(peer_allows(&headers("Allow: INVITE\r\nAllow: UPDATE\r\n")));
}
#[test]
fn the_allow_we_advertise_lists_update() {
assert!(peer_allows(&headers(&format!("Allow: {ALLOW}\r\n"))));
}
#[test]
fn the_three_refusals_are_three_different_answers() {
let accept = |mut state: Negotiation, offer| state.receive(offer);
assert_eq!(accept(Negotiation::idle(), true), Reception::Accept);
assert_eq!(accept(Negotiation::idle(), false), Reception::Accept);
let mut busy = Negotiation::idle();
assert_eq!(busy.receive(false), Reception::Accept);
assert_eq!(
busy.receive(false),
Reception::Refuse(Refusal::InProgress),
"a second UPDATE before the first was answered"
);
assert_eq!(busy.receive(true), Reception::Refuse(Refusal::InProgress));
assert_eq!(
accept(Negotiation::offering(), true),
Reception::Refuse(Refusal::Glare)
);
assert_eq!(accept(Negotiation::offering(), false), Reception::Accept);
assert_eq!(
accept(Negotiation::owing(), true),
Reception::Refuse(Refusal::AnswerOwed)
);
assert_eq!(accept(Negotiation::owing(), false), Reception::Accept);
}
#[test]
fn an_update_in_progress_outranks_glare() {
let mut state = Negotiation::offering();
assert_eq!(state.receive(false), Reception::Accept);
assert_eq!(state.receive(true), Reception::Refuse(Refusal::InProgress));
}
#[test]
fn durable_idle_is_stricter_than_permission_to_offer() {
assert!(Negotiation::idle().is_idle());
assert!(!Negotiation::offering().is_idle());
assert!(!Negotiation::owing().is_idle());
let mut busy = Negotiation::idle();
assert_eq!(busy.receive(false), Reception::Accept);
assert!(busy.may_offer());
assert!(!busy.is_idle());
busy.answered();
assert!(busy.is_idle());
}
#[test]
fn each_refusal_carries_what_the_peer_needs_to_act_on_it() {
assert_eq!(Refusal::Glare.status(), 491);
assert!(
!Refusal::Glare.retry_after(),
"491 is resolved by RFC 3261 §14.1's randomised wait, not by a header we choose"
);
for refusal in [Refusal::InProgress, Refusal::AnswerOwed] {
assert_eq!(refusal.status(), 500);
assert!(
refusal.retry_after(),
"§5.2 requires Retry-After on both 500s; without it the peer learns only that \
it failed"
);
}
assert_ne!(Refusal::Glare.reason(), Refusal::InProgress.reason());
}
#[test]
fn an_offer_may_only_go_out_when_nothing_is_outstanding() {
assert!(Negotiation::idle().may_offer());
assert!(!Negotiation::offering().may_offer(), "ours is unanswered");
assert!(!Negotiation::owing().may_offer(), "we owe theirs");
let mut busy = Negotiation::idle();
assert_eq!(busy.receive(false), Reception::Accept);
assert!(busy.may_offer());
}
#[test]
fn an_offerless_update_does_not_pay_a_debt_it_never_incurred() {
let mut state = Negotiation::owing();
assert_eq!(state.receive(false), Reception::Accept);
state.answered();
assert!(
state.owes_answer(),
"an offerless refresh cancelled the INVITE's outstanding offer"
);
assert_eq!(
state.receive(true),
Reception::Refuse(Refusal::AnswerOwed),
"§5.2 rule 3 was lost to a refresh that arrived first"
);
}
#[test]
fn an_offer_carrying_update_settles_exactly_its_own_offer() {
let mut state = Negotiation::idle();
assert_eq!(state.receive(true), Reception::Accept);
assert!(state.owes_answer());
state.answered();
assert!(!state.owes_answer());
let mut owing = Negotiation::owing();
owing.answered();
assert!(owing.owes_answer());
}
#[test]
fn an_accepted_update_clears_when_it_is_answered() {
let mut state = Negotiation::idle();
assert_eq!(state.receive(true), Reception::Accept);
assert!(state.owes_answer(), "the offer it carried is unanswered");
assert!(!state.may_offer());
state.answered();
assert_eq!(state, Negotiation::idle());
assert!(state.may_offer());
assert_eq!(state.receive(true), Reception::Accept);
}
#[test]
fn the_two_directions_are_tracked_apart() {
let mut state = Negotiation::offering();
assert!(state.is_offering());
assert!(!state.owes_answer());
state.received_answer();
assert_eq!(state, Negotiation::idle());
state.received_offer();
assert!(state.owes_answer());
assert!(!state.is_offering());
state.sent_answer();
assert_eq!(state, Negotiation::idle());
state.sent_offer();
state.received_offer();
state.received_answer();
assert!(state.owes_answer(), "answering ours cleared theirs");
}
}