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}