Skip to main content

sipx_sip/
update.rs

1//! The UPDATE method (RFC 3311).
2//!
3//! A re-INVITE renegotiates a session that is already up. It cannot renegotiate one that is
4//! not: until the INVITE has a final response there is a transaction in progress, and a second
5//! INVITE inside it is not a thing SIP has. UPDATE is the request that fills that hole — an
6//! in-dialog renegotiation that runs alongside the INVITE transaction without disturbing it —
7//! and RFC 4028 §7.4 then reuses it as the cheaper way to refresh a session timer.
8//!
9//! Everything here is pure. What is written down is the offer/answer bookkeeping a dialog has
10//! to keep in order to decide whether an UPDATE may be sent or accepted, and the three
11//! different refusals §5.2 requires when it may not. The sending, the clock and the randomness
12//! live a layer up; see `docs/specs/sip-update.md`.
13
14use crate::headers::misc::Allow;
15use crate::message::{Headers, TypedHeader as _};
16use crate::name::HeaderName;
17
18/// The `Allow` value sipx advertises (RFC 3311 §4, RFC 3261 §20.5).
19///
20/// One constant rather than a literal at each site that writes the header, because §4 makes
21/// this list the *only* way a peer is permitted to decide it may send an UPDATE at all. A copy
22/// that drifts is a peer that silently falls back to a re-INVITE forever, on a path no test
23/// looks at — the failure is invisible from this side, which is exactly the kind that survives.
24pub const ALLOW: &str = "INVITE, ACK, CANCEL, BYE, OPTIONS, UPDATE";
25
26/// The largest `Retry-After` a §5.2 refusal may name.
27///
28/// §5.2 asks for "a randomly chosen value between 0 and 10 seconds". The number is drawn by the
29/// caller and passed in: this crate reads no clock and no entropy source, and reaching for one
30/// here would be the first I/O in a sans-IO core.
31pub const RETRY_AFTER_MAX_SECS: u64 = 10;
32
33/// Whether a peer's `Allow` lists UPDATE (RFC 3311 §4).
34///
35/// Absent means no. §4 is a `SHOULD` on the *sender*, so a peer that supports UPDATE and does
36/// not say so is indistinguishable from one that does not support it — and guessing wrong turns
37/// a session refresh into a request the far end answers 405, which is a call torn down for a
38/// capability nobody needed.
39#[must_use]
40pub fn peer_allows(headers: &Headers) -> bool {
41    // Every `Allow` row, not only the first: RFC 3261 §7.3 makes one row of `n` tokens and `n`
42    // rows of one token the same message, and a peer that writes the second form would
43    // otherwise be read as allowing whatever happened to land on line one.
44    headers
45        .get_all(&HeaderName::Allow)
46        .filter_map(|header| Allow::decode(&header.value()).ok())
47        .any(|allow| allow.contains(METHOD))
48}
49
50/// The method token, spelled once.
51const METHOD: &str = "UPDATE";
52
53/// Why an UPDATE cannot be processed now (RFC 3311 §5.2).
54///
55/// Three variants and not one, because the distinction is the whole value of the section: a
56/// peer's retry logic is built on it. 491 means the two sides collided and both should wait a
57/// randomised interval (RFC 3261 §14.1); 500 with `Retry-After` means the request was
58/// well-formed and badly timed, and the same one will work shortly. A peer told the wrong one
59/// either backs off when it did not need to or retries straight into the same wall.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum Refusal {
62    /// A previous UPDATE has not had its final response yet.
63    ///
64    /// §5.2's first rule, and the only one that applies to an UPDATE carrying no offer at all:
65    /// it is about the transaction, not about any description.
66    InProgress,
67    /// An offer arrived while this side's own offer is unanswered — glare.
68    Glare,
69    /// An offer arrived while this side still owes an answer to one already received.
70    AnswerOwed,
71}
72
73impl Refusal {
74    /// The status code to answer with.
75    #[must_use]
76    pub const fn status(self) -> u16 {
77        match self {
78            // §5.2: "MUST reject the UPDATE with a 491 response".
79            Self::Glare => 491,
80            // §5.2: both of the others are 500 with a `Retry-After`. They stay separate
81            // variants even so — the reason a request was too early is worth logging, and a
82            // caller that wants to distinguish them can, which a single `TooEarly` would have
83            // made impossible for everyone.
84            Self::InProgress | Self::AnswerOwed => 500,
85        }
86    }
87
88    /// The reason phrase for that status.
89    #[must_use]
90    pub const fn reason(self) -> &'static str {
91        match self {
92            Self::Glare => "Request Pending",
93            Self::InProgress | Self::AnswerOwed => "Server Internal Error",
94        }
95    }
96
97    /// Whether the response must carry a `Retry-After` (§5.2).
98    #[must_use]
99    pub const fn retry_after(self) -> bool {
100        match self {
101            Self::Glare => false,
102            Self::InProgress | Self::AnswerOwed => true,
103        }
104    }
105}
106
107/// What to do with an UPDATE that has arrived.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum Reception {
110    /// Process it: renegotiate if it carried an offer, and answer 2xx.
111    Accept,
112    /// Refuse it, without disturbing the dialog.
113    Refuse(Refusal),
114}
115
116/// An UPDATE that has been accepted and not yet answered, and what it brought with it.
117///
118/// The distinction is load-bearing rather than descriptive. Answering an UPDATE settles the
119/// debt *that UPDATE created* — and an offerless one created none. Forgetting which kind it was
120/// is how a session refresh comes to cancel the INVITE's outstanding offer; see
121/// [`Negotiation::answered`].
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123enum Pending {
124    /// It carried an offer, so its answer pays for that offer.
125    WithOffer,
126    /// It carried none — an RFC 4028 §7.4 refresh, say — so its 2xx settles nothing.
127    Offerless,
128}
129
130/// One dialog's offer/answer bookkeeping, as far as UPDATE is concerned (RFC 3264, RFC 3311 §5).
131///
132/// Three pieces of state, and the reason they are three rather than one is §5.2: a dialog that
133/// owes an answer and a dialog whose own offer is unanswered are different situations that
134/// produce different refusals, and an UPDATE already being processed is a third that has
135/// nothing to do with descriptions at all.
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
137pub struct Negotiation {
138    /// We have sent an offer and have not received its answer.
139    offered: bool,
140    /// We have received an offer and have not sent its answer.
141    ///
142    /// Set by an INVITE's offer as much as by an UPDATE's, which is why it cannot simply be
143    /// cleared whenever an UPDATE is answered.
144    owed: bool,
145    /// The UPDATE accepted and not yet answered, if there is one.
146    in_progress: Option<Pending>,
147}
148
149impl Negotiation {
150    /// Nothing outstanding in either direction.
151    #[must_use]
152    pub const fn idle() -> Self {
153        Self {
154            offered: false,
155            owed: false,
156            in_progress: None,
157        }
158    }
159
160    /// Whether no offer, answer, or accepted UPDATE remains outstanding.
161    ///
162    /// This is stricter than [`Self::may_offer`]: an offerless UPDATE in progress creates no
163    /// offer debt, but it is still a live transaction and therefore cannot cross a durable
164    /// dialog boundary.
165    #[must_use]
166    pub const fn is_idle(self) -> bool {
167        !self.offered && !self.owed && self.in_progress.is_none()
168    }
169
170    /// The state of a UAC that has just sent an INVITE carrying an offer.
171    #[must_use]
172    pub const fn offering() -> Self {
173        Self {
174            offered: true,
175            ..Self::idle()
176        }
177    }
178
179    /// The state of a UAS that has just received an INVITE carrying an offer.
180    #[must_use]
181    pub const fn owing() -> Self {
182        Self {
183            owed: true,
184            ..Self::idle()
185        }
186    }
187
188    /// Whether an offer of ours is unanswered.
189    #[must_use]
190    pub const fn is_offering(self) -> bool {
191        self.offered
192    }
193
194    /// Whether we owe the peer an answer.
195    #[must_use]
196    pub const fn owes_answer(self) -> bool {
197        self.owed
198    }
199
200    /// Record that we put an offer on the wire.
201    pub const fn sent_offer(&mut self) {
202        self.offered = true;
203    }
204
205    /// Record that the answer to our offer arrived.
206    pub const fn received_answer(&mut self) {
207        self.offered = false;
208    }
209
210    /// Record that an offer arrived and is unanswered.
211    pub const fn received_offer(&mut self) {
212        self.owed = true;
213    }
214
215    /// Record that we answered the offer we were holding.
216    pub const fn sent_answer(&mut self) {
217        self.owed = false;
218    }
219
220    /// Whether an UPDATE this side sends may carry an offer (RFC 3311 §5.1).
221    ///
222    /// RFC 3264's one-offer-at-a-time rule, seen from the sending end: not while ours is
223    /// unanswered, and not while we owe one. `in_progress` does not appear — that is the
224    /// *peer's* transaction, and an offer of ours is unrelated to it.
225    #[must_use]
226    pub const fn may_offer(self) -> bool {
227        !self.offered && !self.owed
228    }
229
230    /// Decide what to do with an incoming UPDATE (RFC 3311 §5.2), and record the decision.
231    ///
232    /// The order is normative rather than incidental: the in-progress rule is checked first
233    /// because it applies to *every* UPDATE, including one with no body, and answering a
234    /// second UPDATE 491 because the first one's offer is still open would tell the peer it
235    /// collided with us when what actually happened is that it was early.
236    ///
237    /// A refusal changes nothing. It is itself a final response, so there is no transaction
238    /// left in progress and no description has moved.
239    pub const fn receive(&mut self, has_offer: bool) -> Reception {
240        if self.in_progress.is_some() {
241            return Reception::Refuse(Refusal::InProgress);
242        }
243        if has_offer {
244            if self.offered {
245                return Reception::Refuse(Refusal::Glare);
246            }
247            if self.owed {
248                return Reception::Refuse(Refusal::AnswerOwed);
249            }
250            self.owed = true;
251            self.in_progress = Some(Pending::WithOffer);
252        } else {
253            self.in_progress = Some(Pending::Offerless);
254        }
255        Reception::Accept
256    }
257
258    /// Record that the final response to the accepted UPDATE has gone out.
259    ///
260    /// Clears **only the debt that UPDATE created**. When it carried an offer the 2xx carried
261    /// the answer (§5.2: the UAS "MUST ... generate an answer in the 2xx response") and the
262    /// debt is paid; when it carried none — the RFC 4028 §7.4 refresh, which is the most
263    /// ordinary UPDATE a peer sends — it created no debt and pays none.
264    ///
265    /// Clearing `owed` regardless was a real defect and not a tidiness point. An offerless
266    /// refresh arriving in an early dialog would wipe the INVITE's outstanding offer, and the
267    /// next UPDATE carrying one would then be *accepted* and answered 488 for a description
268    /// that was perfectly good — where §5.2 rule 3 requires 500 with `Retry-After`, which is
269    /// the difference between "your description is unusable" and "you are early".
270    ///
271    /// Calling this with nothing in progress is a no-op, so a caller that clears on an error
272    /// path cannot destroy state it did not create.
273    pub const fn answered(&mut self) {
274        if matches!(self.in_progress, Some(Pending::WithOffer)) {
275            self.owed = false;
276        }
277        self.in_progress = None;
278    }
279}
280
281#[cfg(test)]
282#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
283mod tests {
284    use super::*;
285    use crate::{Limits, Message, parse_datagram};
286
287    fn headers(allow: &str) -> Headers {
288        let text = format!(
289            "INVITE sip:b@example.com SIP/2.0\r\n\
290             Via: SIP/2.0/UDP 192.0.2.1;branch=z9hG4bKx\r\n\
291             To: <sip:b@example.com>\r\n\
292             From: <sip:a@example.net>;tag=1\r\n\
293             Call-ID: c\r\n\
294             CSeq: 1 INVITE\r\n\
295             {allow}\
296             Content-Length: 0\r\n\r\n"
297        );
298        match parse_datagram(bytes::Bytes::from(text), &Limits::datagram()).expect("parses") {
299            Message::Request(r) => r.headers,
300            Message::Response(_) => panic!("a request"),
301        }
302    }
303
304    /// §8.3 of the spec.
305    #[test]
306    fn the_peers_allow_is_the_only_permission_there_is() {
307        assert!(peer_allows(&headers(
308            "Allow: INVITE, ACK, CANCEL, BYE, OPTIONS, UPDATE\r\n"
309        )));
310        assert!(!peer_allows(&headers("Allow: INVITE, ACK, BYE\r\n")));
311        // RFC 3261 §7.3.1: tokens are case-insensitive and the spacing is free.
312        assert!(peer_allows(&headers("Allow: invite,update\r\n")));
313        // A token, not a substring. `UPDATEX` is a different method.
314        assert!(!peer_allows(&headers("Allow: INVITE, UPDATEX\r\n")));
315        // Silence means no. §4 is a SHOULD on the sender, so a peer that supports UPDATE and
316        // does not say so cannot be told apart from one that does not.
317        assert!(!peer_allows(&headers("")));
318        // Spread over two rows is still one list.
319        assert!(peer_allows(&headers("Allow: INVITE\r\nAllow: UPDATE\r\n")));
320    }
321
322    /// Our own advertisement has to contain the method, or §4 is unmet from this side.
323    #[test]
324    fn the_allow_we_advertise_lists_update() {
325        assert!(peer_allows(&headers(&format!("Allow: {ALLOW}\r\n"))));
326    }
327
328    /// §8.1 of the spec, row by row.
329    #[test]
330    fn the_three_refusals_are_three_different_answers() {
331        let accept = |mut state: Negotiation, offer| state.receive(offer);
332
333        // Idle: both forms are accepted.
334        assert_eq!(accept(Negotiation::idle(), true), Reception::Accept);
335        assert_eq!(accept(Negotiation::idle(), false), Reception::Accept);
336
337        // Rule 1 covers an UPDATE with no body at all — it is about the transaction.
338        let mut busy = Negotiation::idle();
339        assert_eq!(busy.receive(false), Reception::Accept);
340        assert_eq!(
341            busy.receive(false),
342            Reception::Refuse(Refusal::InProgress),
343            "a second UPDATE before the first was answered"
344        );
345        assert_eq!(busy.receive(true), Reception::Refuse(Refusal::InProgress));
346
347        // Rule 2: our offer is unanswered, so this is glare and the peer may retry after a
348        // randomised back-off.
349        assert_eq!(
350            accept(Negotiation::offering(), true),
351            Reception::Refuse(Refusal::Glare)
352        );
353        // ...but an offerless UPDATE collides with nothing.
354        assert_eq!(accept(Negotiation::offering(), false), Reception::Accept);
355
356        // Rule 3: we owe an answer. Not glare — nothing of ours is outstanding, the peer is
357        // simply early.
358        assert_eq!(
359            accept(Negotiation::owing(), true),
360            Reception::Refuse(Refusal::AnswerOwed)
361        );
362        assert_eq!(accept(Negotiation::owing(), false), Reception::Accept);
363    }
364
365    /// Order matters: rule 1 is checked before rule 2, so a peer that is early is told it is
366    /// early rather than told it collided with us.
367    #[test]
368    fn an_update_in_progress_outranks_glare() {
369        let mut state = Negotiation::offering();
370        assert_eq!(state.receive(false), Reception::Accept);
371        assert_eq!(state.receive(true), Reception::Refuse(Refusal::InProgress));
372    }
373
374    #[test]
375    fn durable_idle_is_stricter_than_permission_to_offer() {
376        assert!(Negotiation::idle().is_idle());
377        assert!(!Negotiation::offering().is_idle());
378        assert!(!Negotiation::owing().is_idle());
379
380        let mut busy = Negotiation::idle();
381        assert_eq!(busy.receive(false), Reception::Accept);
382        assert!(busy.may_offer());
383        assert!(!busy.is_idle());
384
385        busy.answered();
386        assert!(busy.is_idle());
387    }
388
389    #[test]
390    fn each_refusal_carries_what_the_peer_needs_to_act_on_it() {
391        assert_eq!(Refusal::Glare.status(), 491);
392        assert!(
393            !Refusal::Glare.retry_after(),
394            "491 is resolved by RFC 3261 §14.1's randomised wait, not by a header we choose"
395        );
396        for refusal in [Refusal::InProgress, Refusal::AnswerOwed] {
397            assert_eq!(refusal.status(), 500);
398            assert!(
399                refusal.retry_after(),
400                "§5.2 requires Retry-After on both 500s; without it the peer learns only that \
401                 it failed"
402            );
403        }
404        assert_ne!(Refusal::Glare.reason(), Refusal::InProgress.reason());
405    }
406
407    /// §8.2 of the spec.
408    #[test]
409    fn an_offer_may_only_go_out_when_nothing_is_outstanding() {
410        assert!(Negotiation::idle().may_offer());
411        assert!(!Negotiation::offering().may_offer(), "ours is unanswered");
412        assert!(!Negotiation::owing().may_offer(), "we owe theirs");
413
414        // An UPDATE we are processing is the peer's transaction. It does not stop us offering.
415        let mut busy = Negotiation::idle();
416        assert_eq!(busy.receive(false), Reception::Accept);
417        assert!(busy.may_offer());
418    }
419
420    /// The defect, stated as a test: an offerless UPDATE must not settle a debt it never took
421    /// on. RFC 4028 §7.4's refresh is exactly such an UPDATE and arrives on every timed call.
422    #[test]
423    fn an_offerless_update_does_not_pay_a_debt_it_never_incurred() {
424        // An early dialog: the INVITE's offer is in hand and unanswered.
425        let mut state = Negotiation::owing();
426
427        // A refresh comes through. Perfectly legal, and answered 200 with no description.
428        assert_eq!(state.receive(false), Reception::Accept);
429        state.answered();
430        assert!(
431            state.owes_answer(),
432            "an offerless refresh cancelled the INVITE's outstanding offer"
433        );
434
435        // So the next offer is still refused for the right reason. Without this the UPDATE
436        // would be accepted, renegotiated against a session whose first offer/answer never
437        // completed, and — when that failed — answered 488, telling the peer its description
438        // was unusable when the description was fine and the moment was not.
439        assert_eq!(
440            state.receive(true),
441            Reception::Refuse(Refusal::AnswerOwed),
442            "§5.2 rule 3 was lost to a refresh that arrived first"
443        );
444    }
445
446    /// The mirror: an UPDATE that *did* carry an offer settles that offer and nothing else.
447    #[test]
448    fn an_offer_carrying_update_settles_exactly_its_own_offer() {
449        let mut state = Negotiation::idle();
450        assert_eq!(state.receive(true), Reception::Accept);
451        assert!(state.owes_answer());
452        state.answered();
453        assert!(!state.owes_answer());
454
455        // And a stray `answered` with nothing in progress cannot clear a debt either, which is
456        // what makes it safe to call from an error path.
457        let mut owing = Negotiation::owing();
458        owing.answered();
459        assert!(owing.owes_answer());
460    }
461
462    #[test]
463    fn an_accepted_update_clears_when_it_is_answered() {
464        let mut state = Negotiation::idle();
465        assert_eq!(state.receive(true), Reception::Accept);
466        assert!(state.owes_answer(), "the offer it carried is unanswered");
467        assert!(!state.may_offer());
468
469        state.answered();
470        assert_eq!(state, Negotiation::idle());
471        assert!(state.may_offer());
472        // And the next one is accepted rather than refused as a duplicate.
473        assert_eq!(state.receive(true), Reception::Accept);
474    }
475
476    #[test]
477    fn the_two_directions_are_tracked_apart() {
478        let mut state = Negotiation::offering();
479        assert!(state.is_offering());
480        assert!(!state.owes_answer());
481        state.received_answer();
482        assert_eq!(state, Negotiation::idle());
483
484        state.received_offer();
485        assert!(state.owes_answer());
486        assert!(!state.is_offering());
487        state.sent_answer();
488        assert_eq!(state, Negotiation::idle());
489
490        // Both at once is possible — a re-INVITE crossing an UPDATE — and neither flag may
491        // clear the other.
492        state.sent_offer();
493        state.received_offer();
494        state.received_answer();
495        assert!(state.owes_answer(), "answering ours cleared theirs");
496    }
497}