Skip to main content

sipx_sip/transaction/
key.rs

1//! Transaction matching keys (RFC 3261 §17.1.3, §17.2.3).
2//!
3//! Matching decides which transaction a message belongs to, and getting it wrong is not a
4//! subtle failure: a response matched to the wrong transaction answers the wrong request.
5//!
6//! There are two schemes. Senders that follow RFC 3261 put a magic cookie at the front of the
7//! `Via` `branch`, and the key is essentially that branch. Senders that predate it — still
8//! present on the public internet, and represented in the RFC 4475 corpus — do not, and the
9//! key has to be reconstructed from six other fields. The magic cookie is what tells the two
10//! apart, so its absence selects the fallback rather than causing a rejection.
11
12use crate::headers::{CSeq, Via};
13use crate::message::{Headers, Method, Request, Response};
14use crate::name::HeaderName;
15
16/// A key identifying a transaction.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum TransactionKey {
19    /// RFC 3261 matching: the branch carries the magic cookie.
20    Rfc3261 {
21        /// The `branch` parameter of the topmost `Via`.
22        branch: Vec<u8>,
23        /// The topmost `Via`'s sent-by, lowercased.
24        sent_by: Vec<u8>,
25        /// The method, with `ACK` folded to `INVITE`.
26        method: Vec<u8>,
27    },
28    /// RFC 2543 matching, reconstructed from the fields that were available before `branch`
29    /// meant anything.
30    Legacy {
31        /// The Request-URI, for a request; empty for a response.
32        request_uri: Vec<u8>,
33        /// The topmost `Via`, verbatim.
34        top_via: Vec<u8>,
35        /// The `From` tag.
36        from_tag: Vec<u8>,
37        /// The `To` tag, which RFC 3261 §17.2.3 makes part of the match: a forked INVITE
38        /// retried under a different tag, and an ACK belonging to another branch's response,
39        /// are separate transactions however alike the rest of their fields look.
40        to_tag: Vec<u8>,
41        /// The `Call-ID`.
42        call_id: Vec<u8>,
43        /// The `CSeq` number.
44        cseq: u32,
45        /// The method, with `ACK` folded to `INVITE`.
46        method: Vec<u8>,
47    },
48}
49
50/// An ACK is matched to the INVITE it acknowledges, so the two share a key.
51///
52/// **CANCEL is not.** It carries the branch of the request it cancels (RFC 3261 §9.1), which
53/// is how it names its target — but RFC 3261 §17.2.3 folds the method only for ACK, so a
54/// CANCEL runs in a transaction of its own. Folding it too makes a received CANCEL look like a
55/// retransmitted INVITE: it is absorbed, nobody is told, and the callee goes on ringing.
56/// [`TransactionKey::for_cancelled_invite`] is how the INVITE is found instead.
57fn match_method(method: &Method) -> Vec<u8> {
58    match method {
59        Method::Ack => Method::Invite.as_bytes().to_vec(),
60        other => other.as_bytes().to_vec(),
61    }
62}
63
64fn sent_by(via: &Via) -> Vec<u8> {
65    let mut out = via.host.to_bytes().to_ascii_lowercase();
66    if let Some(port) = via.port {
67        out.push(b':');
68        out.extend_from_slice(port.to_string().as_bytes());
69    }
70    out
71}
72
73/// The topmost `Via`, verbatim. A response echoes it back unchanged (RFC 3261 §8.2.6.2), which
74/// is what lets a client key derived from a request and one derived from its response agree.
75fn top_via(headers: &Headers) -> Vec<u8> {
76    headers
77        .value(&HeaderName::Via)
78        .map(|v| v.to_vec())
79        .unwrap_or_default()
80}
81
82/// The `From` tag, also echoed unchanged.
83fn from_tag(headers: &Headers) -> Vec<u8> {
84    headers
85        .typed::<crate::headers::From>()
86        .and_then(Result::ok)
87        .and_then(|f| f.tag().map(<[u8]>::to_vec))
88        .unwrap_or_default()
89}
90
91/// The `To` tag. Part of the *server* key only: a UAS adds a tag to the `To` of the response it
92/// sends, so the tag a request carries and the tag its response carries are different values.
93fn to_tag(headers: &Headers) -> Vec<u8> {
94    headers
95        .typed::<crate::headers::To>()
96        .and_then(Result::ok)
97        .and_then(|t| t.tag().map(<[u8]>::to_vec))
98        .unwrap_or_default()
99}
100
101/// The `Call-ID`, also echoed unchanged.
102fn call_id(headers: &Headers) -> Vec<u8> {
103    headers
104        .value(&HeaderName::CallId)
105        .map(|v| v.to_vec())
106        .unwrap_or_default()
107}
108
109impl TransactionKey {
110    /// The key a received request belongs to (RFC 3261 §17.2.3).
111    ///
112    /// Returns `None` if the request has no usable `Via`, which is not a transaction question
113    /// — a request without a `Via` cannot be answered at all, and validation reports it.
114    #[must_use]
115    pub fn from_request(request: &Request) -> Option<Self> {
116        let via = request.headers.typed::<Via>()?.ok()?;
117        let method = match_method(&request.method);
118
119        if via.has_rfc3261_branch() {
120            return Some(Self::Rfc3261 {
121                branch: via.branch()?.to_vec(),
122                sent_by: sent_by(&via),
123                method,
124            });
125        }
126
127        let cseq = request.headers.typed::<CSeq>()?.ok()?;
128        Some(Self::Legacy {
129            request_uri: request.uri.to_bytes().to_vec(),
130            top_via: top_via(&request.headers),
131            from_tag: from_tag(&request.headers),
132            to_tag: to_tag(&request.headers),
133            call_id: call_id(&request.headers),
134            cseq: cseq.sequence,
135            method,
136        })
137    }
138
139    /// The key a request sent by this element belongs to (RFC 3261 §17.1.3).
140    ///
141    /// **Not** [`Self::from_request`]'s derivation. §17.2.3 is the *server* rule, and it keys a
142    /// legacy transaction on the Request-URI and the `To` tag; a response has no Request-URI at
143    /// all, and carries the tag the UAS added rather than the one the request was sent with. A
144    /// client keyed by the server rule therefore never matches any of its own responses — it
145    /// does not fail, it retransmits until Timer F with the answer sitting in front of it.
146    ///
147    /// §17.1.3 is narrower on purpose (`docs/specs/sip-transaction.md` §6.2): the key is the
148    /// branch and the `CSeq` method, over fields a response echoes back unchanged. This agrees
149    /// with [`Self::from_response`] field for field, which is the property that matters.
150    ///
151    /// The legacy half of this is reached by an application that supplies its own cookieless
152    /// `Via`, not by an old peer: the topmost `Via` on a client transaction is ours, and the
153    /// transport gives it a `z9hG4bK` branch. [`Self::from_request`] is the one old peers meet.
154    #[must_use]
155    pub fn from_sent_request(request: &Request) -> Option<Self> {
156        let via = request.headers.typed::<Via>()?.ok()?;
157        let method = match_method(&request.method);
158
159        if via.has_rfc3261_branch() {
160            return Some(Self::Rfc3261 {
161                branch: via.branch()?.to_vec(),
162                sent_by: sent_by(&via),
163                method,
164            });
165        }
166
167        let cseq = request.headers.typed::<CSeq>()?.ok()?;
168        Some(Self::legacy_client(&request.headers, cseq.sequence, method))
169    }
170
171    /// The legacy client key of §17.1.3, from the headers a request and its responses share.
172    ///
173    /// The two fields of [`Self::Legacy`] that the server rule needs and this one must not use
174    /// are left empty rather than omitted, so that a client key and a server key for the same
175    /// legacy exchange stay distinguishable.
176    fn legacy_client(headers: &Headers, cseq: u32, method: Vec<u8>) -> Self {
177        Self::Legacy {
178            // A response has none to compare.
179            request_uri: Vec::new(),
180            top_via: top_via(headers),
181            from_tag: from_tag(headers),
182            // Deliberately not the response's tag: two 200s to one forked INVITE carry two
183            // different tags and must still reach the one client transaction that sent it.
184            to_tag: Vec::new(),
185            call_id: call_id(headers),
186            cseq,
187            method,
188        }
189    }
190
191    /// The key a received response belongs to (RFC 3261 §17.1.3).
192    ///
193    /// The branch of the topmost `Via` plus the `CSeq` method — and *only* those two. In
194    /// particular the sent-by is not part of it, because the response may come back from a
195    /// different address than the request went to.
196    #[must_use]
197    pub fn from_response(response: &Response) -> Option<Self> {
198        let via = response.headers.typed::<Via>()?.ok()?;
199        let cseq = response.headers.typed::<CSeq>()?.ok()?;
200        let method = match_method(&cseq.method);
201
202        if via.has_rfc3261_branch() {
203            return Some(Self::Rfc3261 {
204                branch: via.branch()?.to_vec(),
205                sent_by: sent_by(&via),
206                method,
207            });
208        }
209
210        Some(Self::legacy_client(
211            &response.headers,
212            cseq.sequence,
213            method,
214        ))
215    }
216
217    /// The key of the INVITE a CANCEL refers to (RFC 3261 §9.2).
218    ///
219    /// A CANCEL shares the INVITE's branch and differs only in method, so this is that key
220    /// with the method put back. Returns `None` for anything that is not a CANCEL, since no
221    /// other request cancels one.
222    #[must_use]
223    pub fn for_cancelled_invite(request: &Request) -> Option<Self> {
224        if request.method != Method::Cancel {
225            return None;
226        }
227        let key = Self::from_request(request)?;
228        Some(match key {
229            Self::Rfc3261 {
230                branch, sent_by, ..
231            } => Self::Rfc3261 {
232                branch,
233                sent_by,
234                method: Method::Invite.as_bytes().to_vec(),
235            },
236            Self::Legacy {
237                request_uri,
238                top_via,
239                from_tag,
240                to_tag,
241                call_id,
242                cseq,
243                ..
244            } => Self::Legacy {
245                request_uri,
246                top_via,
247                from_tag,
248                to_tag,
249                call_id,
250                cseq,
251                method: Method::Invite.as_bytes().to_vec(),
252            },
253        })
254    }
255
256    /// Whether this key was derived by the pre-RFC-3261 rules.
257    #[must_use]
258    pub fn is_legacy(&self) -> bool {
259        matches!(self, Self::Legacy { .. })
260    }
261}