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}