1use std::net::SocketAddr;
12
13use bytes::Bytes;
14use sipx_sip::headers::{Via, first_hop_end};
15use sipx_sip::{Header, HeaderName, Host, Request};
16
17pub fn apply_received_and_rport(request: &mut Request, source: SocketAddr) -> bool {
21 let Some(header) = request.headers.get(&HeaderName::Via) else {
22 return false;
23 };
24 let value = header.value().into_owned();
25 let hop_end = first_hop_end(&value);
26 let hop = value.get(..hop_end).unwrap_or(&value);
27 let Ok(via) = Via::parse_one(hop) else {
28 return false;
29 };
30
31 let mut updated_hop = hop.to_vec();
32 let mut changed = false;
33
34 if matches!(via.rport(), Some(None))
38 && let Some((start, end)) = param_span(&updated_hop, b"rport")
39 {
40 let replacement = format!(";rport={}", source.port());
41 updated_hop.splice(start..end, replacement.into_bytes());
42 changed = true;
43 }
44
45 let sent_by_matches = match &via.host {
54 Host::Ip(ip) => *ip == source.ip(),
55 Host::Name(_) => false,
56 };
57 let asked_for_rport = matches!(via.rport(), Some(None));
58 if (asked_for_rport || !sent_by_matches) && via.received().is_none() {
59 let addition = format!(";received={}", source.ip());
60 let insert_at =
64 param_span(&updated_hop, b"branch").map_or(updated_hop.len(), |(start, _)| start);
65 updated_hop.splice(insert_at..insert_at, addition.into_bytes());
66 changed = true;
67 }
68
69 if !changed {
70 return false;
71 }
72
73 let mut rebuilt = Vec::with_capacity(value.len() + 32);
74 rebuilt.extend_from_slice(&updated_hop);
75 rebuilt.extend_from_slice(value.get(hop_end..).unwrap_or(&[]));
76 replace_top_via(request, Bytes::from(rebuilt));
77 true
78}
79
80pub(crate) fn param_span(hop: &[u8], name: &[u8]) -> Option<(usize, usize)> {
82 let mut i = 0usize;
83 while i < hop.len() {
84 match hop.get(i) {
85 Some(b'"') => {
86 i = quoted_end(hop, i)?;
87 }
88 Some(b';') => {
89 let start = i;
90 let mut j = i + 1;
91 while matches!(hop.get(j), Some(b' ' | b'\t')) {
92 j += 1;
93 }
94 let name_start = j;
95 while hop
96 .get(j)
97 .is_some_and(|b| !matches!(b, b';' | b'=' | b' ' | b'\t'))
98 {
99 j += 1;
100 }
101 let found = hop.get(name_start..j).unwrap_or(&[]);
102 let mut end = j;
104 while matches!(hop.get(end), Some(b' ' | b'\t')) {
105 end += 1;
106 }
107 if hop.get(end) == Some(&b'=') {
108 end += 1;
109 while matches!(hop.get(end), Some(b' ' | b'\t')) {
110 end += 1;
111 }
112 if hop.get(end) == Some(&b'"') {
113 end = quoted_end(hop, end)?;
114 } else {
115 while hop.get(end).is_some_and(|&b| b != b';') {
116 end += 1;
117 }
118 }
119 }
120 if found.eq_ignore_ascii_case(name) {
121 return Some((start, end));
122 }
123 i = end;
124 }
125 Some(_) => i += 1,
126 None => break,
127 }
128 }
129 None
130}
131
132fn quoted_end(input: &[u8], at: usize) -> Option<usize> {
133 let mut i = at + 1;
134 while let Some(&b) = input.get(i) {
135 match b {
136 b'\\' if i + 1 < input.len() => i += 2,
137 b'"' => return Some(i + 1),
138 _ => i += 1,
139 }
140 }
141 None
142}
143
144fn replace_top_via(request: &mut Request, value: Bytes) {
150 let Ok(header) = Header::build(HeaderName::Via, value) else {
151 return;
152 };
153 if request.headers.remove_first(&HeaderName::Via).is_some() {
158 request.headers.push_front(header);
159 }
160}
161
162#[cfg(test)]
163#[allow(
164 clippy::unwrap_used,
165 clippy::expect_used,
166 clippy::panic,
167 clippy::indexing_slicing
168)]
169mod tests {
170 use super::*;
171 use sipx_sip::{Limits, Message, parse_datagram};
172
173 fn request_with(via: &str) -> Request {
174 let text = format!(
175 "OPTIONS sip:a@b.com SIP/2.0\r\n\
176 Via: {via}\r\n\
177 To: <sip:a@b.com>\r\n\
178 From: <sip:c@d.net>;tag=1\r\n\
179 Call-ID: x@y\r\n\
180 CSeq: 1 OPTIONS\r\n\
181 Max-Forwards: 70\r\n\
182 Content-Length: 0\r\n\r\n"
183 );
184 match parse_datagram(Bytes::from(text), &Limits::datagram()).expect("parses") {
185 Message::Request(r) => r,
186 Message::Response(_) => panic!("a request"),
187 }
188 }
189
190 fn source() -> SocketAddr {
191 "203.0.113.9:41234".parse().expect("valid")
192 }
193
194 fn top_via(request: &Request) -> String {
195 String::from_utf8_lossy(&request.headers.value(&HeaderName::Via).expect("a Via"))
196 .into_owned()
197 }
198
199 fn parsed_via(request: &Request) -> Via {
200 request
201 .headers
202 .typed::<Via>()
203 .expect("a Via")
204 .expect("it parses")
205 }
206
207 #[test]
208 fn received_is_added_when_the_source_differs_from_the_sent_by() {
209 let mut request = request_with("SIP/2.0/UDP 10.0.0.5:5060;branch=z9hG4bKx");
210 assert!(apply_received_and_rport(&mut request, source()));
211 assert_eq!(
212 parsed_via(&request).received().map(<[u8]>::to_vec),
213 Some(b"203.0.113.9".to_vec())
214 );
215 }
216
217 #[test]
218 fn received_is_not_added_when_the_sent_by_is_already_right() {
219 let mut request = request_with("SIP/2.0/UDP 203.0.113.9:41234;branch=z9hG4bKx");
220 assert!(!apply_received_and_rport(&mut request, source()));
221 assert!(!top_via(&request).contains("received"));
222 }
223
224 #[test]
227 fn an_empty_rport_is_replaced_not_duplicated() {
228 let mut request = request_with("SIP/2.0/UDP 10.0.0.5:5060;rport;branch=z9hG4bKx");
229 assert!(apply_received_and_rport(&mut request, source()));
230
231 let via = top_via(&request);
232 assert_eq!(via.matches("rport").count(), 1, "exactly one rport: {via}");
233 assert_eq!(
234 parsed_via(&request).rport().flatten().map(<[u8]>::to_vec),
235 Some(b"41234".to_vec())
236 );
237 assert_eq!(
238 parsed_via(&request).branch().map(<[u8]>::to_vec),
239 Some(b"z9hG4bKx".to_vec()),
240 "the branch must survive the surgery"
241 );
242 }
243
244 #[test]
250 fn rport_brings_received_with_it_even_when_the_sent_by_matches() {
251 let mut request = request_with("SIP/2.0/UDP 203.0.113.9:41234;rport;branch=z9hG4bKx");
252 assert!(apply_received_and_rport(&mut request, source()));
253 assert_eq!(
254 parsed_via(&request).rport().flatten().map(<[u8]>::to_vec),
255 Some(b"41234".to_vec())
256 );
257 assert_eq!(
258 parsed_via(&request).received().map(<[u8]>::to_vec),
259 Some(b"203.0.113.9".to_vec())
260 );
261 }
262
263 #[test]
264 fn an_absent_rport_is_not_invented() {
265 let mut request = request_with("SIP/2.0/UDP 10.0.0.5:5060;branch=z9hG4bKx");
266 apply_received_and_rport(&mut request, source());
267 assert!(!top_via(&request).contains("rport"));
268 }
269
270 #[test]
271 fn an_rport_that_already_has_a_value_is_left_alone() {
272 let mut request = request_with("SIP/2.0/UDP 203.0.113.9:5060;rport=9999;branch=z9hG4bKx");
273 apply_received_and_rport(&mut request, source());
274 assert_eq!(
275 parsed_via(&request).rport().flatten().map(<[u8]>::to_vec),
276 Some(b"9999".to_vec())
277 );
278 }
279
280 #[test]
282 fn only_the_topmost_hop_is_touched() {
283 let mut request = request_with(
284 "SIP/2.0/UDP 10.0.0.5:5060;rport;branch=z9hG4bK1, SIP/2.0/UDP 192.0.2.7:5060;branch=z9hG4bK2",
285 );
286 assert!(apply_received_and_rport(&mut request, source()));
287 let via = top_via(&request);
288 assert!(
289 via.ends_with("SIP/2.0/UDP 192.0.2.7:5060;branch=z9hG4bK2"),
290 "the second hop is untouched: {via}"
291 );
292 assert_eq!(via.matches("received").count(), 1);
293 assert_eq!(
294 parsed_via(&request).rport().flatten().map(<[u8]>::to_vec),
295 Some(b"41234".to_vec()),
296 "the top hop is the one that got the answer"
297 );
298 }
299
300 #[test]
301 fn every_other_header_keeps_its_place() {
302 let mut request = request_with("SIP/2.0/UDP 10.0.0.5:5060;branch=z9hG4bKx");
303 let before: Vec<String> = request
304 .headers
305 .iter()
306 .map(|h| h.name().to_string())
307 .collect();
308 apply_received_and_rport(&mut request, source());
309 let after: Vec<String> = request
310 .headers
311 .iter()
312 .map(|h| h.name().to_string())
313 .collect();
314 assert_eq!(before, after);
315 }
316
317 #[test]
318 fn parameter_spans_are_found_regardless_of_position_and_quoting() {
319 let hop = br#"SIP/2.0/UDP h;a=1;rport;note="x;y";branch=z"#;
320 let (start, end) = param_span(hop, b"rport").expect("rport is there");
321 assert_eq!(&hop[start..end], b";rport");
322 let (start, end) = param_span(hop, b"branch").expect("branch is there");
323 assert_eq!(&hop[start..end], b";branch=z");
324 let (start, end) = param_span(hop, b"note").expect("note is there");
325 assert_eq!(&hop[start..end], br#";note="x;y""#);
326 assert!(param_span(hop, b"absent").is_none());
327 }
328
329 #[test]
336 fn replacing_the_top_via_on_a_request_without_one_adds_nothing() {
337 let text = "OPTIONS sip:a@b.com SIP/2.0\r\n\
338 To: <sip:a@b.com>\r\n\
339 From: <sip:c@d.net>;tag=1\r\n\
340 Call-ID: x@y\r\n\
341 CSeq: 1 OPTIONS\r\n\
342 Max-Forwards: 70\r\n\
343 Content-Length: 0\r\n\r\n";
344 let mut request = match parse_datagram(Bytes::from(text.to_owned()), &Limits::datagram())
345 .expect("parses")
346 {
347 Message::Request(r) => r,
348 Message::Response(_) => panic!("a request"),
349 };
350 assert_eq!(request.headers.count(&HeaderName::Via), 0);
351
352 replace_top_via(
353 &mut request,
354 Bytes::from_static(b"SIP/2.0/UDP invented.example"),
355 );
356
357 assert_eq!(
358 request.headers.count(&HeaderName::Via),
359 0,
360 "a request with no Via must not acquire one; the topmost Via is where the response goes"
361 );
362 }
363}