1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum SipViaError {
9 Empty,
11 InvalidFormat(String),
13}
14
15impl fmt::Display for SipViaError {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 match self {
18 Self::Empty => write!(f, "Via header is empty"),
19 Self::InvalidFormat(msg) => write!(f, "Invalid Via format: {}", msg),
20 }
21 }
22}
23
24impl std::error::Error for SipViaError {}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28#[non_exhaustive]
29pub struct SipViaEntry {
30 protocol_name: String,
31 protocol_version: String,
32 transport: String,
33 host: String,
34 port: Option<u16>,
35 params: Vec<(String, Option<String>)>,
36 rport: Option<Option<u16>>,
37}
38
39impl SipViaEntry {
40 pub fn protocol(&self) -> &str {
42 &self.protocol_name
43 }
44
45 pub fn version(&self) -> &str {
47 &self.protocol_version
48 }
49
50 pub fn transport(&self) -> &str {
52 &self.transport
53 }
54
55 pub fn host(&self) -> &str {
57 &self.host
58 }
59
60 pub fn port(&self) -> Option<u16> {
62 self.port
63 }
64
65 pub fn params(&self) -> &[(String, Option<String>)] {
67 &self.params
68 }
69
70 pub fn param(&self, key: &str) -> Option<Option<&str>> {
72 let key_lower = key.to_ascii_lowercase();
73 self.params
74 .iter()
75 .find(|(k, _)| k == &key_lower)
76 .map(|(_, v)| v.as_deref())
77 }
78
79 pub fn branch(&self) -> Option<&str> {
81 self.param("branch")
82 .flatten()
83 }
84
85 pub fn received(&self) -> Option<&str> {
87 self.param("received")
88 .flatten()
89 }
90
91 pub fn rport(&self) -> Option<Option<u16>> {
100 self.rport
101 }
102
103 fn parse(entry: &str) -> Result<Self, SipViaError> {
104 let trimmed = entry.trim();
105 if trimmed.is_empty() {
106 return Err(SipViaError::InvalidFormat("empty Via entry".to_string()));
107 }
108
109 let (main_part, params_part) = if let Some(semi_idx) = trimmed.find(';') {
111 (&trimmed[..semi_idx], Some(&trimmed[semi_idx + 1..]))
112 } else {
113 (trimmed, None)
114 };
115
116 let (protocol_name, protocol_version, transport, sent_by) = parse_sent_protocol(main_part)?;
117 let (host, port) = parse_host_port(sent_by)?;
118
119 let params: Vec<(String, Option<String>)> = crate::parse_params(params_part.unwrap_or(""))
120 .into_iter()
121 .map(|p| {
122 (
123 p.key
124 .to_ascii_lowercase(),
125 p.value
126 .map(str::to_string),
127 )
128 })
129 .collect();
130
131 let rport = params
132 .iter()
133 .find(|(k, _)| k == "rport")
134 .map(|(_, v)| match v {
135 None => Ok(None),
136 Some(s) => s
137 .parse::<u16>()
138 .map(Some)
139 .map_err(|_| SipViaError::InvalidFormat("invalid rport value".to_string())),
140 })
141 .transpose()?;
142
143 Ok(Self {
144 protocol_name,
145 protocol_version,
146 transport,
147 host,
148 port,
149 params,
150 rport,
151 })
152 }
153}
154
155fn parse_sent_protocol(main: &str) -> Result<(String, String, String, &str), SipViaError> {
158 let malformed =
159 || SipViaError::InvalidFormat("expected 'protocol/version/transport host[:port]'".into());
160 let (name_raw, rest) = main
161 .split_once('/')
162 .ok_or_else(malformed)?;
163 let (version_raw, after_slash) = rest
164 .split_once('/')
165 .ok_or_else(malformed)?;
166 let (name, version) = (name_raw.trim(), version_raw.trim());
167 let rest = after_slash.trim_start();
168 let (mut transport, mut sent_by) = rest.split_at(
169 rest.find(char::is_whitespace)
170 .unwrap_or(rest.len()),
171 );
172 sent_by = sent_by.trim();
173 if sent_by.is_empty()
176 && after_slash.starts_with(char::is_whitespace)
177 && name == name_raw
178 && version == version_raw
179 {
180 (transport, sent_by) = ("", transport);
181 }
182 if sent_by.is_empty()
183 || [name, version, transport]
184 .iter()
185 .any(|p| p.contains(|c: char| c == '/' || c.is_whitespace()))
186 {
187 return Err(malformed());
188 }
189 Ok((name.into(), version.into(), transport.into(), sent_by))
190}
191
192impl fmt::Display for SipViaEntry {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 write!(
195 f,
196 "{}/{}/{}",
197 self.protocol_name, self.protocol_version, self.transport
198 )?;
199
200 if self
202 .host
203 .contains(':')
204 && !self
205 .host
206 .starts_with('[')
207 {
208 write!(f, " [{}]", self.host)?;
209 } else {
210 write!(f, " {}", self.host)?;
211 }
212
213 if let Some(port) = self.port {
214 write!(f, ":{}", port)?;
215 }
216
217 for (key, value) in &self.params {
218 crate::write_param(f, key, value.as_deref(), false)?;
219 }
220
221 Ok(())
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
227#[non_exhaustive]
228pub struct SipVia {
229 entries: Vec<SipViaEntry>,
230}
231
232impl SipVia {
233 pub fn parse(raw: &str) -> Result<Self, SipViaError> {
235 let raw = raw.trim();
236 if raw.is_empty() {
237 return Err(SipViaError::Empty);
238 }
239 Self::from_entries(crate::split_comma_entries(raw))
240 }
241
242 pub fn from_entries<'a>(
244 entries: impl IntoIterator<Item = &'a str>,
245 ) -> Result<Self, SipViaError> {
246 let entries = entries
247 .into_iter()
248 .map(SipViaEntry::parse)
249 .collect::<Result<Vec<_>, _>>()?;
250 if entries.is_empty() {
251 return Err(SipViaError::Empty);
252 }
253 Ok(Self { entries })
254 }
255
256 pub fn entries(&self) -> &[SipViaEntry] {
258 &self.entries
259 }
260
261 pub fn into_entries(self) -> Vec<SipViaEntry> {
263 self.entries
264 }
265
266 pub fn len(&self) -> usize {
268 self.entries
269 .len()
270 }
271
272 pub fn is_empty(&self) -> bool {
274 self.entries
275 .is_empty()
276 }
277}
278
279impl fmt::Display for SipVia {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 crate::fmt_joined(f, &self.entries, ", ")
282 }
283}
284
285impl_from_str_via_parse!(SipVia, SipViaError);
286
287impl IntoIterator for SipVia {
288 type Item = SipViaEntry;
289 type IntoIter = std::vec::IntoIter<SipViaEntry>;
290
291 fn into_iter(self) -> Self::IntoIter {
292 self.entries
293 .into_iter()
294 }
295}
296
297impl<'a> IntoIterator for &'a SipVia {
298 type Item = &'a SipViaEntry;
299 type IntoIter = std::slice::Iter<'a, SipViaEntry>;
300
301 fn into_iter(self) -> Self::IntoIter {
302 self.entries
303 .iter()
304 }
305}
306
307fn parse_host_port(sent_by: &str) -> Result<(String, Option<u16>), SipViaError> {
309 let invalid = |msg: &str| SipViaError::InvalidFormat(msg.to_string());
310 let (host, port) = if let Some(inner) = sent_by.strip_prefix('[') {
311 let close = inner
312 .find(']')
313 .ok_or_else(|| invalid("unclosed IPv6 bracket"))?;
314 let rest = inner[close + 1..].trim_start();
315 let port = if rest.is_empty() {
316 None
317 } else {
318 Some(
319 rest.strip_prefix(':')
320 .ok_or_else(|| invalid("unexpected characters after IPv6 reference"))?,
321 )
322 };
323 (&inner[..close], port)
324 } else {
325 match sent_by.split_once(':') {
326 Some((_, port)) if port.contains(':') => {
327 return Err(invalid("IPv6 sent-by must be bracketed"));
328 }
329 Some((host, port)) => (host.trim_end(), Some(port)),
330 None => (sent_by, None),
331 }
332 };
333 if host.contains(char::is_whitespace) {
334 return Err(invalid("whitespace inside sent-by host"));
335 }
336 let port = port
337 .map(|p| {
338 p.trim_start()
339 .parse::<u16>()
340 .map_err(|_| {
341 SipViaError::InvalidFormat(format!("invalid port ({} bytes)", p.len()))
342 })
343 })
344 .transpose()?;
345 Ok((host.to_string(), port))
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_single_via() {
354 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060").unwrap();
355 assert_eq!(via.len(), 1);
356
357 let entry = &via.entries()[0];
358 assert_eq!(entry.protocol(), "SIP");
359 assert_eq!(entry.version(), "2.0");
360 assert_eq!(entry.transport(), "UDP");
361 assert_eq!(entry.host(), "198.51.100.1");
362 assert_eq!(entry.port(), Some(5060));
363 assert!(entry
364 .params()
365 .is_empty());
366 }
367
368 #[test]
369 fn test_multiple_vias() {
370 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
371 assert_eq!(via.len(), 2);
372
373 let entry1 = &via.entries()[0];
374 assert_eq!(entry1.host(), "198.51.100.1");
375 assert_eq!(entry1.port(), Some(5060));
376 assert_eq!(entry1.transport(), "UDP");
377
378 let entry2 = &via.entries()[1];
379 assert_eq!(entry2.host(), "203.0.113.5");
380 assert_eq!(entry2.port(), None);
381 assert_eq!(entry2.transport(), "TCP");
382 }
383
384 #[test]
385 fn test_via_with_params() {
386 let via = SipVia::parse(
387 "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKnashds8;received=203.0.113.10;rport=5061",
388 )
389 .unwrap();
390
391 let entry = &via.entries()[0];
392 assert_eq!(entry.branch(), Some("z9hG4bKnashds8"));
393 assert_eq!(entry.received(), Some("203.0.113.10"));
394 assert_eq!(entry.rport(), Some(Some(5061)));
395 }
396
397 #[test]
398 fn test_via_with_rport_no_value() {
399 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;rport").unwrap();
400
401 let entry = &via.entries()[0];
402 assert_eq!(entry.rport(), Some(None));
403 }
404
405 #[test]
406 fn test_via_without_rport() {
407 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060").unwrap();
408
409 let entry = &via.entries()[0];
410 assert_eq!(entry.rport(), None);
411 }
412
413 #[test]
414 fn test_via_ipv6() {
415 let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]:5060").unwrap();
416
417 let entry = &via.entries()[0];
418 assert_eq!(entry.host(), "2001:db8::1");
419 assert_eq!(entry.port(), Some(5060));
420 }
421
422 #[test]
423 fn test_via_ipv6_no_port() {
424 let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]").unwrap();
425
426 let entry = &via.entries()[0];
427 assert_eq!(entry.host(), "2001:db8::1");
428 assert_eq!(entry.port(), None);
429 }
430
431 #[test]
432 fn test_via_hostname() {
433 let via = SipVia::parse("SIP/2.0/TLS example.com:5061").unwrap();
434
435 let entry = &via.entries()[0];
436 assert_eq!(entry.host(), "example.com");
437 assert_eq!(entry.port(), Some(5061));
438 assert_eq!(entry.transport(), "TLS");
439 }
440
441 #[test]
442 fn test_empty_via() {
443 let result = SipVia::parse("");
444 assert!(matches!(result, Err(SipViaError::Empty)));
445 }
446
447 #[test]
448 fn test_empty_via_whitespace() {
449 let result = SipVia::parse(" ");
450 assert!(matches!(result, Err(SipViaError::Empty)));
451 }
452
453 #[test]
454 fn test_invalid_format() {
455 let result = SipVia::parse("invalid");
456 assert!(matches!(result, Err(SipViaError::InvalidFormat(_))));
457 }
458
459 #[test]
460 fn test_rport_invalid_value_is_error() {
461 let result = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;rport=garbage");
462 assert!(result.is_err());
463 }
464
465 #[test]
466 fn test_display_roundtrip() {
467 let original =
468 "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKnashds8;received=203.0.113.10;rport";
469 let via = SipVia::parse(original).unwrap();
470 let displayed = via.to_string();
471
472 let reparsed = SipVia::parse(&displayed).unwrap();
473 assert_eq!(via, reparsed);
474 }
475
476 #[test]
477 fn test_display_multiple_vias() {
478 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
479 let displayed = via.to_string();
480 assert!(displayed.contains("198.51.100.1"));
481 assert!(displayed.contains("203.0.113.5"));
482 }
483
484 #[test]
485 fn test_into_iterator() {
486 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
487
488 let mut count = 0;
489 for entry in &via {
490 assert!(entry.host() == "198.51.100.1" || entry.host() == "203.0.113.5");
491 count += 1;
492 }
493 assert_eq!(count, 2);
494
495 let entries: Vec<_> = via
496 .into_iter()
497 .collect();
498 assert_eq!(entries.len(), 2);
499 }
500
501 #[test]
502 fn test_into_entries() {
503 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
504 let entries = via.into_entries();
505 assert_eq!(entries.len(), 2);
506 assert_eq!(entries[0].host(), "198.51.100.1");
507 assert_eq!(entries[1].host(), "203.0.113.5");
508 }
509
510 #[test]
511 fn test_from_str() {
512 let via: SipVia = "SIP/2.0/UDP 198.51.100.1:5060"
513 .parse()
514 .unwrap();
515 assert_eq!(via.len(), 1);
516 }
517
518 #[test]
519 fn test_param_case_insensitive() {
520 let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;Branch=test").unwrap();
521 let entry = &via.entries()[0];
522 assert_eq!(entry.param("branch"), Some(Some("test")));
523 assert_eq!(entry.param("BRANCH"), Some(Some("test")));
524 }
525
526 #[test]
527 fn test_display_ipv6() {
528 let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]:5060").unwrap();
529 let displayed = via.to_string();
530 assert!(displayed.contains("[2001:db8::1]"));
531 }
532
533 #[test]
534 fn from_entries_matches_parse() {
535 let a = "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bK1";
536 let b = "SIP/2.0/TCP 203.0.113.5";
537 let split = SipVia::from_entries([a, b]).unwrap();
538 let joined = SipVia::parse(&format!("{a}, {b}")).unwrap();
539 assert_eq!(split, joined);
540 assert_eq!(split.to_string(), format!("{a}, {b}"));
541 }
542
543 #[test]
544 fn from_entries_bad_entry_is_error() {
545 assert!(matches!(
546 SipVia::from_entries(["SIP/2.0/UDP 198.51.100.1", "invalid"]),
547 Err(SipViaError::InvalidFormat(_))
548 ));
549 }
550
551 #[test]
552 fn sws_around_protocol_slashes() {
553 let via = SipVia::parse("SIP / 2.0 / UDP example.com").unwrap();
554 let entry = &via.entries()[0];
555 assert_eq!(entry.protocol(), "SIP");
556 assert_eq!(entry.version(), "2.0");
557 assert_eq!(entry.transport(), "UDP");
558 assert_eq!(entry.host(), "example.com");
559 assert_eq!(entry.port(), None);
560 }
561
562 #[test]
563 fn sws_around_sent_by_colon() {
564 let via = SipVia::parse("SIP/2.0/UDP example.com : 5060;branch=z9hG4bK1").unwrap();
565 let entry = &via.entries()[0];
566 assert_eq!(entry.host(), "example.com");
567 assert_eq!(entry.port(), Some(5060));
568 assert_eq!(entry.branch(), Some("z9hG4bK1"));
569 }
570
571 #[test]
572 fn sws_around_ipv6_reference_colon() {
573 let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1] : 5060").unwrap();
574 let entry = &via.entries()[0];
575 assert_eq!(entry.host(), "2001:db8::1");
576 assert_eq!(entry.port(), Some(5060));
577 }
578
579 #[test]
580 fn empty_transport_before_sent_by_accepted() {
581 let via = SipVia::parse("SIP/2.0/ example.com:5060").unwrap();
582 let entry = &via.entries()[0];
583 assert_eq!(entry.transport(), "");
584 assert_eq!(entry.host(), "example.com");
585 assert_eq!(entry.port(), Some(5060));
586 }
587
588 #[test]
589 fn junk_after_sent_by_is_error() {
590 assert!(SipVia::parse("SIP/2.0/UDP example.com extra").is_err());
591 assert!(SipVia::parse("SIP/2.0/UDP/X example.com").is_err());
592 assert!(SipVia::parse("SIP/2.0/UDP").is_err());
593 }
594
595 #[test]
596 fn unbracketed_ipv6_is_error() {
597 assert!(matches!(
598 SipVia::parse("SIP/2.0/UDP 2001:db8::1:5060"),
599 Err(SipViaError::InvalidFormat(_))
600 ));
601 assert!(SipVia::parse("SIP/2.0/UDP 2001:db8::1").is_err());
602 }
603
604 #[test]
605 fn params_keep_trimmed_raw_values() {
606 let via = SipVia::parse("SIP/2.0/UDP example.com ; Branch = z9hG4bK1 ; rport ; x=\"a;b\"")
607 .unwrap();
608 let entry = &via.entries()[0];
609 assert_eq!(
610 entry.params(),
611 &[
612 ("branch".to_string(), Some("z9hG4bK1".to_string())),
613 ("rport".to_string(), None),
614 ("x".to_string(), Some("\"a;b\"".to_string())),
615 ]
616 );
617 assert_eq!(entry.rport(), Some(None));
618 assert_eq!(
619 via.to_string(),
620 "SIP/2.0/UDP example.com;branch=z9hG4bK1;rport;x=\"a;b\""
621 );
622 }
623
624 #[test]
625 fn error_display_omits_input() {
626 for raw in [
627 "SIP/2.0/UDP secret.example.com extra",
628 "SIP/2.0/UDP secret.example.com:99999",
629 "SIP/2.0/UDP [2001:db8::1]secret",
630 "SIP/2.0/UDP example.com;rport=secret",
631 "secret",
632 ] {
633 let err = SipVia::parse(raw).unwrap_err();
634 assert!(!err
635 .to_string()
636 .contains("secret"));
637 }
638 }
639
640 #[test]
641 fn from_entries_empty_is_empty_error() {
642 assert!(matches!(
643 SipVia::from_entries(std::iter::empty::<&str>()),
644 Err(SipViaError::Empty)
645 ));
646 }
647}