Skip to main content

sipx_call/
dialog.rs

1//! Dialogs (RFC 3261 §12).
2//!
3//! A dialog is the shared state two user agents keep between an INVITE and a BYE. Getting it
4//! wrong produces calls that establish and then cannot be ended, which is worse than a call
5//! that never establishes: the media keeps flowing.
6//!
7//! The three parts that matter, and the ways each goes wrong:
8//!
9//! - **The identifier** is `Call-ID` plus both tags. The local and remote tags swap places
10//!   depending on which side you are, and a UAS that builds the identifier as though it were
11//!   the UAC will fail to match its own dialog's BYE.
12//! - **The sequence numbers are independent.** Each side numbers its own requests. Sharing one
13//!   counter means the first in-dialog request from each side collides.
14//! - **The route set** is the `Record-Route` of the dialog-forming exchange, and it is
15//!   *reversed* for a UAC. Sending in the wrong order routes a BYE through the proxies
16//!   backwards, and it never arrives.
17
18use bytes::Bytes;
19use sipx_sip::headers::{From as FromHeader, To};
20use sipx_sip::{HeaderName, Request, Response, Uri};
21
22/// Which side of the dialog this is.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum Role {
25    /// We sent the INVITE.
26    Caller,
27    /// We received it.
28    Callee,
29}
30
31/// What identifies a dialog. `Call-ID` plus the two tags.
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub struct DialogId {
34    /// The `Call-ID`.
35    pub call_id: Vec<u8>,
36    /// Our tag.
37    pub local_tag: Vec<u8>,
38    /// Their tag.
39    pub remote_tag: Vec<u8>,
40}
41
42/// A dialog.
43#[derive(Debug, Clone)]
44pub struct Dialog {
45    /// Which side we are.
46    pub role: Role,
47    /// What identifies it.
48    pub id: DialogId,
49    /// Our address of record.
50    pub local_uri: String,
51    /// Theirs.
52    pub remote_uri: String,
53    /// Where to send in-dialog requests, from their `Contact`.
54    pub remote_target: Uri,
55    /// Our sequence number for requests we originate. Independent of theirs.
56    pub local_cseq: u32,
57    /// The highest sequence number we have seen from them.
58    pub remote_cseq: Option<u32>,
59    /// The route set, already in send order.
60    pub route_set: Vec<String>,
61}
62
63impl Dialog {
64    /// Build the caller's half from the request it sent and the response it got.
65    ///
66    /// Returns `None` if the response carries no `To` tag, which means no dialog was created —
67    /// a 100 Trying, for instance.
68    #[must_use]
69    pub fn from_response(request: &Request, response: &Response) -> Option<Self> {
70        let call_id = response.headers.value(&HeaderName::CallId)?.into_owned();
71        let local_tag = tag_of::<FromHeader>(&response.headers)?;
72        let remote_tag = tag_of::<To>(&response.headers)?;
73        let remote_target = contact_uri(&response.headers)?;
74
75        // RFC 3261 §12.1.2: for a UAC the route set is the Record-Route in *reverse* order.
76        // The response lists them outermost-first as seen from the callee; sending needs them
77        // in the order the request will traverse.
78        let mut route_set = record_routes(&response.headers);
79        route_set.reverse();
80
81        Some(Self {
82            role: Role::Caller,
83            id: DialogId {
84                call_id,
85                local_tag,
86                remote_tag,
87            },
88            local_uri: header_string(&request.headers, &HeaderName::From),
89            remote_uri: header_string(&request.headers, &HeaderName::To),
90            remote_target,
91            local_cseq: cseq_number(&request.headers).unwrap_or(1),
92            remote_cseq: None,
93            route_set,
94        })
95    }
96
97    /// Build the callee's half from the request that created it and the tag we chose.
98    #[must_use]
99    pub fn from_request(request: &Request, local_tag: &str) -> Option<Self> {
100        let call_id = request.headers.value(&HeaderName::CallId)?.into_owned();
101        let remote_tag = tag_of::<FromHeader>(&request.headers)?;
102        let remote_target = contact_uri(&request.headers)?;
103
104        // For a UAS the order is as received — the mirror of the caller's reversal, and the
105        // reason both are written out rather than shared.
106        let route_set = record_routes(&request.headers);
107
108        Some(Self {
109            role: Role::Callee,
110            id: DialogId {
111                call_id,
112                local_tag: local_tag.as_bytes().to_vec(),
113                remote_tag,
114            },
115            local_uri: header_string(&request.headers, &HeaderName::To),
116            remote_uri: header_string(&request.headers, &HeaderName::From),
117            remote_target,
118            // Our own numbering starts fresh; theirs is separate and recorded below.
119            local_cseq: 0,
120            remote_cseq: cseq_number(&request.headers),
121            route_set,
122        })
123    }
124
125    /// The `To` and `From` for a request we originate.
126    ///
127    /// They swap according to role, which is the detail a UAS most often gets wrong: a BYE
128    /// from the callee has the callee in `From`, not in `To`.
129    #[must_use]
130    pub fn local_and_remote(&self) -> (String, String) {
131        let local = format!(
132            "{};tag={}",
133            strip_header_params(&self.local_uri),
134            String::from_utf8_lossy(&self.id.local_tag)
135        );
136        let remote = format!(
137            "{};tag={}",
138            strip_header_params(&self.remote_uri),
139            String::from_utf8_lossy(&self.id.remote_tag)
140        );
141        (local, remote)
142    }
143
144    /// Take the next sequence number for a request we originate.
145    pub fn next_cseq(&mut self) -> u32 {
146        self.local_cseq = self.local_cseq.saturating_add(1);
147        self.local_cseq
148    }
149
150    /// The first entry of the route set, as a URI.
151    #[must_use]
152    pub fn first_route(&self) -> Option<Uri> {
153        uri_in(self.route_set.first()?)
154    }
155
156    /// Where an in-dialog request is *sent*, which is not always what its Request-URI says.
157    ///
158    /// RFC 3261 §12.2.1.1: with a route set the request goes to the first `Route` entry — the
159    /// proxy that record-routed itself into the dialog precisely so that it would see the rest
160    /// of it. Handing the request to the remote target instead bypasses that proxy, and where
161    /// it is the only element that can reach the far end — behind a NAT, or on a segment this
162    /// side cannot address — the request is simply lost. A BYE lost this way is the call that
163    /// cannot be hung up, with the media still running.
164    #[must_use]
165    pub fn hop(&self) -> Uri {
166        self.first_route()
167            .unwrap_or_else(|| self.remote_target.clone())
168    }
169
170    /// The Request-URI and `Route` headers for a request sent inside this dialog.
171    ///
172    /// RFC 3261 §12.2.1.1 describes two forms, chosen by the `lr` parameter on the first
173    /// route. A loose router leaves the Request-URI alone: the remote target addresses the
174    /// request and the route set travels in `Route` headers, in order. A strict router
175    /// predates that convention and rewrites the Request-URI at every hop, so the first route
176    /// becomes the Request-URI and the remote target moves to the *end* of the route set —
177    /// otherwise the far end receives a request addressed to a proxy it has never heard of.
178    #[must_use]
179    pub fn request_target(&self) -> (Uri, Vec<String>) {
180        let Some(first) = self.first_route() else {
181            return (self.remote_target.clone(), Vec::new());
182        };
183        if first.params().is_some_and(|params| params.contains("lr")) {
184            return (self.remote_target.clone(), self.route_set.clone());
185        }
186
187        let mut routes: Vec<String> = self.route_set.iter().skip(1).cloned().collect();
188        routes.push(format!(
189            "<{}>",
190            String::from_utf8_lossy(&self.remote_target.to_bytes())
191        ));
192        (as_request_uri(&first), routes)
193    }
194
195    /// Replace the remote target from a target refresh request or its response.
196    ///
197    /// RFC 3261 §12.2.2 and §12.2.1.2: a re-INVITE carrying a `Contact` moves the dialog's
198    /// remote target, in both directions. Keeping the original means every later request goes
199    /// to where the peer *used* to be — the BYE included, so a peer that re-homes mid-call can
200    /// never be told the call is over.
201    pub fn refresh_target(&mut self, headers: &sipx_sip::Headers) {
202        if let Some(contact) = contact_uri(headers) {
203            self.remote_target = contact;
204        }
205    }
206
207    /// Whether an in-dialog request arrived out of order (RFC 3261 §12.2.2).
208    ///
209    /// §12.2.2 rejects a request behind the dialog's sequence number with a 500 rather than
210    /// applying it, and §12.2.1.1 requires each new in-dialog request to *increment* the
211    /// number — so a repeat of the current one is a duplicate that has escaped the transaction
212    /// layer's absorption window, not a fresh request, and is refused on the same grounds.
213    ///
214    /// **The rule lives here, on the dialog, because every in-dialog path needs it and each one
215    /// that carries its own copy is a way around it.** That is not hypothetical: the early
216    /// dialog's UPDATE handler was written without it, and a replayed BYE arriving afterwards
217    /// ended a call that was still running — the exact failure the guard on the confirmed path
218    /// was put there to stop.
219    #[must_use]
220    pub fn is_out_of_order(&self, request: &Request) -> bool {
221        let Some(sequence) = cseq_number(&request.headers) else {
222            // A request whose `CSeq` will not parse cannot be placed in the sequence at all.
223            // Refusing it here would answer 500 to something the message layer has already
224            // judged; letting it through leaves the recorded number where it was.
225            return false;
226        };
227        self.remote_cseq.is_some_and(|last| sequence <= last)
228    }
229
230    /// Record the sequence number of an in-dialog request accepted here (RFC 3261 §12.2.2).
231    ///
232    /// Only ever forwards. A lower number never reaches this — [`Self::is_out_of_order`] has
233    /// refused it — but the `max` makes that a property of this function rather than of every
234    /// caller remembering to ask first.
235    pub fn record_remote_cseq(&mut self, request: &Request) {
236        if let Some(sequence) = cseq_number(&request.headers) {
237            self.remote_cseq = Some(self.remote_cseq.map_or(sequence, |last| last.max(sequence)));
238        }
239    }
240
241    /// Whether an in-dialog request belongs to this dialog.
242    #[must_use]
243    pub fn matches(&self, request: &Request) -> bool {
244        let Some(call_id) = request.headers.value(&HeaderName::CallId) else {
245            return false;
246        };
247        if call_id.as_ref() != self.id.call_id.as_slice() {
248            return false;
249        }
250        // The tags arrive swapped relative to how we hold them: their `From` is our remote.
251        let their_tag = tag_of::<FromHeader>(&request.headers);
252        let our_tag = tag_of::<To>(&request.headers);
253        their_tag.as_deref() == Some(self.id.remote_tag.as_slice())
254            && our_tag.as_deref() == Some(self.id.local_tag.as_slice())
255    }
256}
257
258/// The name-addr part of a `To` or `From`, without its header parameters.
259///
260/// Splitting on the first `;` is wrong: a URI inside angle brackets may carry its own
261/// parameters, so `<sip:alice@example.com;transport=tcp>;tag=abc` would be truncated to
262/// `<sip:alice@example.com` — an unterminated bracket the far end answers with 400, leaving a
263/// call that cannot be hung up. Parameters after the closing bracket belong to the header;
264/// those inside belong to the URI and stay.
265pub(crate) fn strip_header_params(value: &str) -> String {
266    if let Some(end) = value.rfind('>') {
267        return value.get(..=end).unwrap_or(value).trim().to_owned();
268    }
269    // Without brackets there can be no URI parameters — RFC 3261 §20.10 requires brackets
270    // exactly when the URI carries any — so the first `;` is the header's.
271    value.split(';').next().unwrap_or(value).trim().to_owned()
272}
273
274fn header_string(headers: &sipx_sip::Headers, name: &HeaderName) -> String {
275    headers
276        .value(name)
277        .map(|value| String::from_utf8_lossy(&value).into_owned())
278        .unwrap_or_default()
279}
280
281/// The `From` tag: whoever sent the request, seen from the receiving end.
282///
283/// Its own function because the dispatcher (`C-4`) keys routes on it and must read it the same
284/// way [`Dialog::matches`] does — a second reading of the same header is a second chance to read
285/// it differently, and the two disagreeing is a request routed to a call that then disowns it.
286pub(crate) fn from_tag(headers: &sipx_sip::Headers) -> Option<Vec<u8>> {
287    tag_of::<FromHeader>(headers)
288}
289
290/// The `To` tag, which is also how a request says whether it is inside a dialog at all
291/// (RFC 3261 §12.2.2).
292pub(crate) fn to_tag(headers: &sipx_sip::Headers) -> Option<Vec<u8>> {
293    tag_of::<To>(headers)
294}
295
296fn tag_of<T>(headers: &sipx_sip::Headers) -> Option<Vec<u8>>
297where
298    T: sipx_sip::TypedHeader,
299    T: HasTag,
300{
301    headers
302        .typed::<T>()
303        .and_then(Result::ok)
304        .and_then(|header| header.tag_bytes())
305}
306
307/// Both `To` and `From` carry a tag, and both are needed from either side.
308pub trait HasTag {
309    /// The `tag` parameter.
310    fn tag_bytes(&self) -> Option<Vec<u8>>;
311}
312
313impl HasTag for To {
314    fn tag_bytes(&self) -> Option<Vec<u8>> {
315        self.tag().map(<[u8]>::to_vec)
316    }
317}
318
319impl HasTag for FromHeader {
320    fn tag_bytes(&self) -> Option<Vec<u8>> {
321        self.tag().map(<[u8]>::to_vec)
322    }
323}
324
325fn contact_uri(headers: &sipx_sip::Headers) -> Option<Uri> {
326    let value = headers.value(&HeaderName::Contact)?;
327    uri_in(&String::from_utf8_lossy(&value))
328}
329
330/// A URI reduced to what may appear in a Request-URI.
331///
332/// RFC 3261 §12.2.1.1 says to place the first route into the Request-URI "stripping any
333/// parameters that are not allowed in a Request-URI", and §19.1.1 names them: the `method`
334/// parameter and the header component may not appear there. A strict router that is handed
335/// either sees a request it is entitled to reject, and the URI came off the wire from another
336/// element, so neither can be assumed absent.
337///
338/// The delimiters can be found by scanning because §19.1.2 requires a literal `?` or `;` in any
339/// earlier component to be escaped; an unescaped one is always the separator it looks like.
340fn as_request_uri(uri: &Uri) -> Uri {
341    let raw = uri.to_bytes();
342    let text = String::from_utf8_lossy(&raw);
343    let without_headers = text.split('?').next().unwrap_or(&text);
344
345    let mut parts = without_headers.split(';');
346    let Some(head) = parts.next() else {
347        return uri.clone();
348    };
349    let mut rebuilt = head.to_owned();
350    for param in parts {
351        let name = param.split('=').next().unwrap_or(param);
352        if name.eq_ignore_ascii_case("method") {
353            continue;
354        }
355        rebuilt.push(';');
356        rebuilt.push_str(param);
357    }
358
359    // A URI that came apart under this is left as it was: a Request-URI that is wrong in a way
360    // the far end may tolerate beats one this side has corrupted.
361    Uri::parse(Bytes::from(rebuilt)).unwrap_or_else(|_| uri.clone())
362}
363
364/// The URI inside a `name-addr` or bare `addr-spec`.
365///
366/// The angle brackets are what make a URI carrying its own parameters unambiguous, so when
367/// they are present the URI is exactly what they enclose; without them RFC 3261 §20.10 says
368/// there can be no URI parameters, and the first `;` begins the header's.
369fn uri_in(text: &str) -> Option<Uri> {
370    let inner = text
371        .split_once('<')
372        .and_then(|(_, rest)| rest.split_once('>'))
373        .map_or_else(
374            || text.split(';').next().unwrap_or(text).trim().to_owned(),
375            |(uri, _)| uri.to_owned(),
376        );
377    Uri::parse(Bytes::from(inner)).ok()
378}
379
380/// The route set, one entry per route.
381///
382/// Two things this must get right. `Record-Route` is a comma-separated list header, so one
383/// line may carry several routes, and the reversal a UAC performs has to reverse *routes*, not
384/// lines. And a malformed route must not take the well-formed ones with it: the route set is
385/// what a BYE travels along, and losing it silently is how a call becomes unhangupable.
386fn record_routes(headers: &sipx_sip::Headers) -> Vec<String> {
387    let mut routes = Vec::new();
388    for header in headers.get_all(&HeaderName::RecordRoute) {
389        routes.extend(split_routes(&header.value()));
390    }
391    routes
392}
393
394/// Split one `Record-Route` value into its routes, honouring angle brackets and quotes.
395///
396/// A comma inside `<...>` or inside a quoted display name is part of the route, not a
397/// separator between two of them.
398fn split_routes(value: &[u8]) -> Vec<String> {
399    let mut routes = Vec::new();
400    let mut depth = 0usize;
401    let mut quoted = false;
402    let mut start = 0usize;
403
404    for (index, &byte) in value.iter().enumerate() {
405        match byte {
406            b'"' => quoted = !quoted,
407            b'<' if !quoted => depth += 1,
408            b'>' if !quoted => depth = depth.saturating_sub(1),
409            b',' if !quoted && depth == 0 => {
410                push_route(&mut routes, value.get(start..index));
411                start = index + 1;
412            }
413            _ => {}
414        }
415    }
416    push_route(&mut routes, value.get(start..));
417    routes
418}
419
420fn push_route(routes: &mut Vec<String>, slice: Option<&[u8]>) {
421    let Some(slice) = slice else {
422        return;
423    };
424    let text = String::from_utf8_lossy(slice).trim().to_owned();
425    if !text.is_empty() {
426        routes.push(text);
427    }
428}
429
430/// The sequence number of a request, for RFC 3261 §8.2.2.2's third merged-request term.
431///
432/// `pub(crate)` for the dispatcher, which needs to read it the same way the dialog does — two
433/// readings of one header are two chances to read it differently.
434pub(crate) fn cseq_number(headers: &sipx_sip::Headers) -> Option<u32> {
435    headers
436        .typed::<sipx_sip::headers::CSeq>()
437        .and_then(Result::ok)
438        .map(|cseq| cseq.sequence)
439}
440
441#[cfg(test)]
442#[allow(
443    clippy::unwrap_used,
444    clippy::expect_used,
445    clippy::panic,
446    clippy::indexing_slicing
447)]
448mod tests {
449    use super::*;
450    use sipx_sip::{Limits, Message, parse_datagram};
451
452    fn request(text: &str) -> Request {
453        match parse_datagram(Bytes::from(text.to_owned()), &Limits::datagram()).expect("parses") {
454            Message::Request(r) => r,
455            Message::Response(_) => panic!("a request"),
456        }
457    }
458
459    fn response(text: &str) -> Response {
460        match parse_datagram(Bytes::from(text.to_owned()), &Limits::datagram()).expect("parses") {
461            Message::Response(r) => r,
462            Message::Request(_) => panic!("a response"),
463        }
464    }
465
466    fn invite() -> Request {
467        request(
468            "INVITE sip:bob@example.com SIP/2.0\r\n\
469             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
470             To: <sip:bob@example.com>\r\n\
471             From: <sip:alice@example.net>;tag=alicetag\r\n\
472             Call-ID: thecall@example.net\r\n\
473             CSeq: 1 INVITE\r\n\
474             Contact: <sip:alice@192.0.2.1:5060>\r\n\
475             Max-Forwards: 70\r\n\
476             Content-Length: 0\r\n\r\n",
477        )
478    }
479
480    fn ok() -> Response {
481        response(
482            "SIP/2.0 200 OK\r\n\
483             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
484             To: <sip:bob@example.com>;tag=bobtag\r\n\
485             From: <sip:alice@example.net>;tag=alicetag\r\n\
486             Call-ID: thecall@example.net\r\n\
487             CSeq: 1 INVITE\r\n\
488             Contact: <sip:bob@192.0.2.9:5060>\r\n\
489             Content-Length: 0\r\n\r\n",
490        )
491    }
492
493    #[test]
494    fn the_callers_dialog_takes_its_tags_from_the_right_places() {
495        let dialog = Dialog::from_response(&invite(), &ok()).expect("a dialog");
496        assert_eq!(dialog.role, Role::Caller);
497        assert_eq!(dialog.id.call_id, b"thecall@example.net");
498        assert_eq!(dialog.id.local_tag, b"alicetag", "our tag is in From");
499        assert_eq!(dialog.id.remote_tag, b"bobtag", "theirs is in To");
500        assert_eq!(
501            dialog.remote_target.to_bytes().as_ref(),
502            b"sip:bob@192.0.2.9:5060"
503        );
504    }
505
506    /// The callee's view is the mirror: the tags swap. A UAS that builds its dialog as though
507    /// it were the UAC fails to match its own dialog's BYE.
508    #[test]
509    fn the_callees_dialog_is_the_mirror_of_the_callers() {
510        let dialog = Dialog::from_request(&invite(), "bobtag").expect("a dialog");
511        assert_eq!(dialog.role, Role::Callee);
512        assert_eq!(dialog.id.local_tag, b"bobtag", "the tag we chose");
513        assert_eq!(dialog.id.remote_tag, b"alicetag", "theirs is in From");
514        assert_eq!(
515            dialog.remote_target.to_bytes().as_ref(),
516            b"sip:alice@192.0.2.1:5060"
517        );
518    }
519
520    /// Both halves must agree on the identity of the dialog, with local and remote swapped.
521    #[test]
522    fn both_halves_describe_the_same_dialog() {
523        let uac = Dialog::from_response(&invite(), &ok()).expect("a dialog");
524        let uas = Dialog::from_request(&invite(), "bobtag").expect("a dialog");
525
526        assert_eq!(uac.id.call_id, uas.id.call_id);
527        assert_eq!(uac.id.local_tag, uas.id.remote_tag);
528        assert_eq!(uac.id.remote_tag, uas.id.local_tag);
529    }
530
531    /// Each side numbers its own requests. A shared counter makes the first in-dialog request
532    /// from each side collide.
533    #[test]
534    fn the_two_sides_number_their_requests_independently() {
535        let mut uac = Dialog::from_response(&invite(), &ok()).expect("a dialog");
536        let mut uas = Dialog::from_request(&invite(), "bobtag").expect("a dialog");
537
538        assert_eq!(uac.next_cseq(), 2, "the INVITE was 1");
539        assert_eq!(uas.next_cseq(), 1, "the callee starts its own count");
540        assert_eq!(uac.next_cseq(), 3);
541        assert_eq!(uas.next_cseq(), 2);
542    }
543
544    /// A BYE from the callee has the callee in `From`. Getting this backwards produces a call
545    /// that establishes and cannot be ended, which is worse than one that never establishes:
546    /// the media keeps flowing.
547    #[test]
548    fn a_request_from_the_callee_puts_the_callee_in_from() {
549        let callee = Dialog::from_request(&invite(), "bobtag").expect("a dialog");
550        let (local, remote) = callee.local_and_remote();
551        assert!(local.contains("bob@example.com"), "{local}");
552        assert!(local.contains("tag=bobtag"), "{local}");
553        assert!(remote.contains("alice@example.net"), "{remote}");
554        assert!(remote.contains("tag=alicetag"), "{remote}");
555    }
556
557    #[test]
558    fn a_request_from_the_caller_puts_the_caller_in_from() {
559        let caller = Dialog::from_response(&invite(), &ok()).expect("a dialog");
560        let (local, remote) = caller.local_and_remote();
561        assert!(local.contains("alice@example.net"), "{local}");
562        assert!(local.contains("tag=alicetag"), "{local}");
563        assert!(remote.contains("bob@example.com"), "{remote}");
564    }
565
566    /// An in-dialog request arrives with the tags swapped relative to how we hold them.
567    #[test]
568    fn an_in_dialog_request_matches_its_dialog() {
569        let caller = Dialog::from_response(&invite(), &ok()).expect("a dialog");
570        let bye = request(
571            "BYE sip:alice@192.0.2.1:5060 SIP/2.0\r\n\
572             Via: SIP/2.0/UDP 192.0.2.9:5060;branch=z9hG4bKbye\r\n\
573             To: <sip:alice@example.net>;tag=alicetag\r\n\
574             From: <sip:bob@example.com>;tag=bobtag\r\n\
575             Call-ID: thecall@example.net\r\n\
576             CSeq: 1 BYE\r\n\
577             Max-Forwards: 70\r\n\
578             Content-Length: 0\r\n\r\n",
579        );
580        assert!(caller.matches(&bye));
581    }
582
583    #[test]
584    fn a_request_from_another_call_does_not_match() {
585        let caller = Dialog::from_response(&invite(), &ok()).expect("a dialog");
586        let other = request(
587            "BYE sip:alice@192.0.2.1:5060 SIP/2.0\r\n\
588             Via: SIP/2.0/UDP 192.0.2.9:5060;branch=z9hG4bKbye\r\n\
589             To: <sip:alice@example.net>;tag=alicetag\r\n\
590             From: <sip:bob@example.com>;tag=bobtag\r\n\
591             Call-ID: a-different-call@example.net\r\n\
592             CSeq: 1 BYE\r\n\
593             Max-Forwards: 70\r\n\
594             Content-Length: 0\r\n\r\n",
595        );
596        assert!(!caller.matches(&other));
597    }
598
599    /// A response with no `To` tag creates no dialog — a 100 Trying, for instance.
600    #[test]
601    fn a_response_without_a_to_tag_creates_no_dialog() {
602        let trying = response(
603            "SIP/2.0 100 Trying\r\n\
604             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
605             To: <sip:bob@example.com>\r\n\
606             From: <sip:alice@example.net>;tag=alicetag\r\n\
607             Call-ID: thecall@example.net\r\n\
608             CSeq: 1 INVITE\r\n\
609             Content-Length: 0\r\n\r\n",
610        );
611        assert!(Dialog::from_response(&invite(), &trying).is_none());
612    }
613
614    /// RFC 3261 §12.1.2: the caller reverses the route set and the callee does not. Sending in
615    /// the wrong order routes a BYE through the proxies backwards, and it never arrives.
616    #[test]
617    fn the_caller_reverses_the_route_set_and_the_callee_does_not() {
618        let routed_ok = response(
619            "SIP/2.0 200 OK\r\n\
620             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
621             Record-Route: <sip:proxy1.example.com;lr>\r\n\
622             Record-Route: <sip:proxy2.example.com;lr>\r\n\
623             To: <sip:bob@example.com>;tag=bobtag\r\n\
624             From: <sip:alice@example.net>;tag=alicetag\r\n\
625             Call-ID: thecall@example.net\r\n\
626             CSeq: 1 INVITE\r\n\
627             Contact: <sip:bob@192.0.2.9:5060>\r\n\
628             Content-Length: 0\r\n\r\n",
629        );
630        let caller = Dialog::from_response(&invite(), &routed_ok).expect("a dialog");
631        assert_eq!(caller.route_set.len(), 2);
632        assert!(
633            caller.route_set[0].contains("proxy2"),
634            "reversed for the caller: {:?}",
635            caller.route_set
636        );
637
638        let routed_invite = request(
639            "INVITE sip:bob@example.com SIP/2.0\r\n\
640             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
641             Record-Route: <sip:proxy1.example.com;lr>\r\n\
642             Record-Route: <sip:proxy2.example.com;lr>\r\n\
643             To: <sip:bob@example.com>\r\n\
644             From: <sip:alice@example.net>;tag=alicetag\r\n\
645             Call-ID: thecall@example.net\r\n\
646             CSeq: 1 INVITE\r\n\
647             Contact: <sip:alice@192.0.2.1:5060>\r\n\
648             Max-Forwards: 70\r\n\
649             Content-Length: 0\r\n\r\n",
650        );
651        let uas = Dialog::from_request(&routed_invite, "bobtag").expect("a dialog");
652        assert!(
653            uas.route_set[0].contains("proxy1"),
654            "as received for the callee: {:?}",
655            uas.route_set
656        );
657    }
658
659    /// Several routes on one line: `Record-Route` is a comma-separated list header. Treating a
660    /// line as a route means the caller's reversal reverses lines rather than routes, and a
661    /// BYE goes through the proxies backwards.
662    #[test]
663    fn several_routes_on_one_line_are_separate_routes() {
664        let routed = response(
665            "SIP/2.0 200 OK\r\n\
666             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
667             Record-Route: <sip:proxy1.example.com;lr>, <sip:proxy2.example.com;lr>\r\n\
668             Record-Route: <sip:proxy3.example.com;lr>\r\n\
669             To: <sip:bob@example.com>;tag=bobtag\r\n\
670             From: <sip:alice@example.net>;tag=alicetag\r\n\
671             Call-ID: thecall@example.net\r\n\
672             CSeq: 1 INVITE\r\n\
673             Contact: <sip:bob@192.0.2.9:5060>\r\n\
674             Content-Length: 0\r\n\r\n",
675        );
676        let caller = Dialog::from_response(&invite(), &routed).expect("a dialog");
677        assert_eq!(
678            caller.route_set.len(),
679            3,
680            "three routes: {:?}",
681            caller.route_set
682        );
683        assert!(
684            caller.route_set[0].contains("proxy3"),
685            "{:?}",
686            caller.route_set
687        );
688        assert!(
689            caller.route_set[1].contains("proxy2"),
690            "{:?}",
691            caller.route_set
692        );
693        assert!(
694            caller.route_set[2].contains("proxy1"),
695            "{:?}",
696            caller.route_set
697        );
698    }
699
700    /// A comma inside angle brackets or a quoted display name is part of the route.
701    #[test]
702    fn a_comma_inside_a_route_does_not_split_it() {
703        assert_eq!(
704            split_routes(br#""Proxy, Inc" <sip:p1.example.com;lr>, <sip:p2.example.com;lr>"#),
705            vec![
706                r#""Proxy, Inc" <sip:p1.example.com;lr>"#.to_owned(),
707                "<sip:p2.example.com;lr>".to_owned()
708            ]
709        );
710    }
711
712    /// A URI inside angle brackets may carry its own parameters. Splitting on the first `;`
713    /// truncates it to an unterminated bracket, which the far end answers 400 — leaving a call
714    /// that cannot be hung up.
715    #[test]
716    fn a_uri_with_parameters_survives_having_its_tag_stripped() {
717        assert_eq!(
718            strip_header_params("<sip:alice@example.com;transport=tcp>;tag=abc"),
719            "<sip:alice@example.com;transport=tcp>"
720        );
721        assert_eq!(
722            strip_header_params("<sip:bob@example.com>;tag=x"),
723            "<sip:bob@example.com>"
724        );
725        assert_eq!(
726            strip_header_params("sip:carol@example.com;tag=y"),
727            "sip:carol@example.com"
728        );
729        assert_eq!(
730            strip_header_params(r#""Alice" <sip:alice@example.com;user=phone>;tag=z"#),
731            r#""Alice" <sip:alice@example.com;user=phone>"#
732        );
733    }
734
735    /// And the whole way round: a dialog built from such a `From` produces a well-formed one.
736    #[test]
737    fn a_request_from_a_dialog_with_uri_parameters_is_well_formed() {
738        let with_params = request(
739            "INVITE sip:bob@example.com SIP/2.0\r\n\
740             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
741             To: <sip:bob@example.com;user=phone>\r\n\
742             From: <sip:alice@example.net;transport=tcp>;tag=alicetag\r\n\
743             Call-ID: thecall@example.net\r\n\
744             CSeq: 1 INVITE\r\n\
745             Contact: <sip:alice@192.0.2.1:5060>\r\n\
746             Max-Forwards: 70\r\n\
747             Content-Length: 0\r\n\r\n",
748        );
749        let callee = Dialog::from_request(&with_params, "bobtag").expect("a dialog");
750        let (local, remote) = callee.local_and_remote();
751        assert_eq!(local, "<sip:bob@example.com;user=phone>;tag=bobtag");
752        assert_eq!(remote, "<sip:alice@example.net;transport=tcp>;tag=alicetag");
753        assert_eq!(local.matches('<').count(), local.matches('>').count());
754        assert_eq!(remote.matches('<').count(), remote.matches('>').count());
755    }
756
757    /// A `Contact` may carry parameters; the angle brackets are what make the URI unambiguous.
758    #[test]
759    fn a_contact_with_parameters_yields_only_the_uri() {
760        let with_params = response(
761            "SIP/2.0 200 OK\r\n\
762             Via: SIP/2.0/UDP 192.0.2.1:5060;branch=z9hG4bKcall\r\n\
763             To: <sip:bob@example.com>;tag=bobtag\r\n\
764             From: <sip:alice@example.net>;tag=alicetag\r\n\
765             Call-ID: thecall@example.net\r\n\
766             CSeq: 1 INVITE\r\n\
767             Contact: \"Bob\" <sip:bob@192.0.2.9:5060>;expires=300\r\n\
768             Content-Length: 0\r\n\r\n",
769        );
770        let dialog = Dialog::from_response(&invite(), &with_params).expect("a dialog");
771        assert_eq!(
772            dialog.remote_target.to_bytes().as_ref(),
773            b"sip:bob@192.0.2.9:5060"
774        );
775    }
776}