Skip to main content

sip_header/
via.rs

1//! SIP Via header parser (RFC 3261 ยง20.42).
2
3use std::fmt;
4
5/// Error parsing a SIP Via header.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum SipViaError {
9    /// The Via header value is empty.
10    Empty,
11    /// The Via header value has an invalid format.
12    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/// A single Via entry.
27#[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    /// Returns the protocol name (e.g., "SIP").
41    pub fn protocol(&self) -> &str {
42        &self.protocol_name
43    }
44
45    /// Returns the protocol version (e.g., "2.0").
46    pub fn version(&self) -> &str {
47        &self.protocol_version
48    }
49
50    /// Returns the transport protocol (e.g., "UDP", "TCP", "TLS").
51    pub fn transport(&self) -> &str {
52        &self.transport
53    }
54
55    /// Returns the host.
56    pub fn host(&self) -> &str {
57        &self.host
58    }
59
60    /// Returns the port, if present.
61    pub fn port(&self) -> Option<u16> {
62        self.port
63    }
64
65    /// Returns all parameters.
66    pub fn params(&self) -> &[(String, Option<String>)] {
67        &self.params
68    }
69
70    /// Returns a specific parameter value by key (case-insensitive).
71    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    /// Returns the `branch` parameter value, if present.
80    pub fn branch(&self) -> Option<&str> {
81        self.param("branch")
82            .flatten()
83    }
84
85    /// Returns the `received` parameter value, if present.
86    pub fn received(&self) -> Option<&str> {
87        self.param("received")
88            .flatten()
89    }
90
91    /// Returns the `rport` parameter.
92    ///
93    /// - `None` if the parameter is absent
94    /// - `Some(None)` if present without a value
95    /// - `Some(Some(port))` if present with a value
96    ///
97    /// Invalid rport values are rejected at parse time, so this accessor
98    /// is infallible.
99    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        // Split on first semicolon to separate sent-protocol/sent-by from params
110        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        // Parse sent-protocol and sent-by
117        let parts: Vec<&str> = main_part
118            .split_whitespace()
119            .collect();
120        if parts.len() != 2 {
121            return Err(SipViaError::InvalidFormat(format!(
122                "expected 'protocol/version/transport host[:port]', got '{}'",
123                main_part
124            )));
125        }
126
127        let sent_protocol = parts[0];
128        let sent_by = parts[1];
129
130        // Parse sent-protocol: protocol-name/version/transport
131        let protocol_parts: Vec<&str> = sent_protocol
132            .split('/')
133            .collect();
134        if protocol_parts.len() != 3 {
135            return Err(SipViaError::InvalidFormat(format!(
136                "expected 'protocol/version/transport', got '{}'",
137                sent_protocol
138            )));
139        }
140
141        let protocol_name = protocol_parts[0].to_string();
142        let protocol_version = protocol_parts[1].to_string();
143        let transport = protocol_parts[2].to_string();
144
145        // Parse sent-by: host[:port]
146        // Handle IPv6 bracket notation [::1]:port
147        let (host, port) = parse_host_port(sent_by)?;
148
149        // Parse params
150        let mut params = Vec::new();
151        if let Some(params_str) = params_part {
152            for param in params_str.split(';') {
153                let param = param.trim();
154                if param.is_empty() {
155                    continue;
156                }
157
158                if let Some(eq_idx) = param.find('=') {
159                    let key = param[..eq_idx]
160                        .trim()
161                        .to_ascii_lowercase();
162                    let value = param[eq_idx + 1..]
163                        .trim()
164                        .to_string();
165                    params.push((key, Some(value)));
166                } else {
167                    // Parameter without value (e.g., rport)
168                    params.push((param.to_ascii_lowercase(), None));
169                }
170            }
171        }
172
173        let rport = params
174            .iter()
175            .find(|(k, _)| k == "rport")
176            .map(|(_, v)| match v {
177                None => Ok(None),
178                Some(s) => s
179                    .parse::<u16>()
180                    .map(Some)
181                    .map_err(|_| SipViaError::InvalidFormat(format!("invalid rport value: {s}"))),
182            })
183            .transpose()?;
184
185        Ok(Self {
186            protocol_name,
187            protocol_version,
188            transport,
189            host,
190            port,
191            params,
192            rport,
193        })
194    }
195}
196
197impl fmt::Display for SipViaEntry {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(
200            f,
201            "{}/{}/{}",
202            self.protocol_name, self.protocol_version, self.transport
203        )?;
204
205        // Handle IPv6 addresses with brackets
206        if self
207            .host
208            .contains(':')
209            && !self
210                .host
211                .starts_with('[')
212        {
213            write!(f, " [{}]", self.host)?;
214        } else {
215            write!(f, " {}", self.host)?;
216        }
217
218        if let Some(port) = self.port {
219            write!(f, ":{}", port)?;
220        }
221
222        for (key, value) in &self.params {
223            if let Some(val) = value {
224                write!(f, ";{}={}", key, val)?;
225            } else {
226                write!(f, ";{}", key)?;
227            }
228        }
229
230        Ok(())
231    }
232}
233
234/// Parsed SIP Via header.
235#[derive(Debug, Clone, PartialEq, Eq)]
236#[non_exhaustive]
237pub struct SipVia {
238    entries: Vec<SipViaEntry>,
239}
240
241impl SipVia {
242    /// Parses a Via header value.
243    pub fn parse(raw: &str) -> Result<Self, SipViaError> {
244        let raw = raw.trim();
245        if raw.is_empty() {
246            return Err(SipViaError::Empty);
247        }
248        Self::from_entries(crate::split_comma_entries(raw))
249    }
250
251    /// Build from entries a transport already split; each is one `via-parm`.
252    pub fn from_entries<'a>(
253        entries: impl IntoIterator<Item = &'a str>,
254    ) -> Result<Self, SipViaError> {
255        let entries = entries
256            .into_iter()
257            .map(SipViaEntry::parse)
258            .collect::<Result<Vec<_>, _>>()?;
259        if entries.is_empty() {
260            return Err(SipViaError::Empty);
261        }
262        Ok(Self { entries })
263    }
264
265    /// Returns the Via entries.
266    pub fn entries(&self) -> &[SipViaEntry] {
267        &self.entries
268    }
269
270    /// Consume self and return entries as a `Vec`.
271    pub fn into_entries(self) -> Vec<SipViaEntry> {
272        self.entries
273    }
274
275    /// Returns the number of Via entries.
276    pub fn len(&self) -> usize {
277        self.entries
278            .len()
279    }
280
281    /// Returns `true` if there are no Via entries.
282    pub fn is_empty(&self) -> bool {
283        self.entries
284            .is_empty()
285    }
286}
287
288impl fmt::Display for SipVia {
289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290        crate::fmt_joined(f, &self.entries, ", ")
291    }
292}
293
294impl_from_str_via_parse!(SipVia, SipViaError);
295
296impl IntoIterator for SipVia {
297    type Item = SipViaEntry;
298    type IntoIter = std::vec::IntoIter<SipViaEntry>;
299
300    fn into_iter(self) -> Self::IntoIter {
301        self.entries
302            .into_iter()
303    }
304}
305
306impl<'a> IntoIterator for &'a SipVia {
307    type Item = &'a SipViaEntry;
308    type IntoIter = std::slice::Iter<'a, SipViaEntry>;
309
310    fn into_iter(self) -> Self::IntoIter {
311        self.entries
312            .iter()
313    }
314}
315
316fn parse_host_port(sent_by: &str) -> Result<(String, Option<u16>), SipViaError> {
317    // Handle IPv6 bracket notation [::1]:port
318    if sent_by.starts_with('[') {
319        // Find the closing bracket
320        if let Some(close_bracket) = sent_by.find(']') {
321            let host = sent_by[1..close_bracket].to_string();
322            let remainder = &sent_by[close_bracket + 1..];
323
324            if remainder.is_empty() {
325                return Ok((host, None));
326            }
327
328            if let Some(port_str) = remainder.strip_prefix(':') {
329                let port = port_str
330                    .parse::<u16>()
331                    .map_err(|_| {
332                        SipViaError::InvalidFormat(format!("invalid port: {}", port_str))
333                    })?;
334                return Ok((host, Some(port)));
335            }
336
337            return Err(SipViaError::InvalidFormat(format!(
338                "unexpected characters after IPv6 address: {}",
339                remainder
340            )));
341        } else {
342            return Err(SipViaError::InvalidFormat(
343                "unclosed IPv6 bracket".to_string(),
344            ));
345        }
346    }
347
348    // IPv4 or hostname with optional port
349    // Find the last colon (to handle IPv6 without brackets, though that's not valid in Via)
350    if let Some(colon_idx) = sent_by.rfind(':') {
351        let host = sent_by[..colon_idx].to_string();
352        let port_str = &sent_by[colon_idx + 1..];
353
354        // Check if this looks like an IPv6 address without brackets (invalid but handle gracefully)
355        if host.contains(':') {
356            // This is likely a bare IPv6 address, return as-is without port
357            return Ok((sent_by.to_string(), None));
358        }
359
360        let port = port_str
361            .parse::<u16>()
362            .map_err(|_| SipViaError::InvalidFormat(format!("invalid port: {}", port_str)))?;
363        Ok((host, Some(port)))
364    } else {
365        Ok((sent_by.to_string(), None))
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn test_single_via() {
375        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060").unwrap();
376        assert_eq!(via.len(), 1);
377
378        let entry = &via.entries()[0];
379        assert_eq!(entry.protocol(), "SIP");
380        assert_eq!(entry.version(), "2.0");
381        assert_eq!(entry.transport(), "UDP");
382        assert_eq!(entry.host(), "198.51.100.1");
383        assert_eq!(entry.port(), Some(5060));
384        assert!(entry
385            .params()
386            .is_empty());
387    }
388
389    #[test]
390    fn test_multiple_vias() {
391        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
392        assert_eq!(via.len(), 2);
393
394        let entry1 = &via.entries()[0];
395        assert_eq!(entry1.host(), "198.51.100.1");
396        assert_eq!(entry1.port(), Some(5060));
397        assert_eq!(entry1.transport(), "UDP");
398
399        let entry2 = &via.entries()[1];
400        assert_eq!(entry2.host(), "203.0.113.5");
401        assert_eq!(entry2.port(), None);
402        assert_eq!(entry2.transport(), "TCP");
403    }
404
405    #[test]
406    fn test_via_with_params() {
407        let via = SipVia::parse(
408            "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKnashds8;received=203.0.113.10;rport=5061",
409        )
410        .unwrap();
411
412        let entry = &via.entries()[0];
413        assert_eq!(entry.branch(), Some("z9hG4bKnashds8"));
414        assert_eq!(entry.received(), Some("203.0.113.10"));
415        assert_eq!(entry.rport(), Some(Some(5061)));
416    }
417
418    #[test]
419    fn test_via_with_rport_no_value() {
420        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;rport").unwrap();
421
422        let entry = &via.entries()[0];
423        assert_eq!(entry.rport(), Some(None));
424    }
425
426    #[test]
427    fn test_via_without_rport() {
428        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060").unwrap();
429
430        let entry = &via.entries()[0];
431        assert_eq!(entry.rport(), None);
432    }
433
434    #[test]
435    fn test_via_ipv6() {
436        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]:5060").unwrap();
437
438        let entry = &via.entries()[0];
439        assert_eq!(entry.host(), "2001:db8::1");
440        assert_eq!(entry.port(), Some(5060));
441    }
442
443    #[test]
444    fn test_via_ipv6_no_port() {
445        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]").unwrap();
446
447        let entry = &via.entries()[0];
448        assert_eq!(entry.host(), "2001:db8::1");
449        assert_eq!(entry.port(), None);
450    }
451
452    #[test]
453    fn test_via_hostname() {
454        let via = SipVia::parse("SIP/2.0/TLS example.com:5061").unwrap();
455
456        let entry = &via.entries()[0];
457        assert_eq!(entry.host(), "example.com");
458        assert_eq!(entry.port(), Some(5061));
459        assert_eq!(entry.transport(), "TLS");
460    }
461
462    #[test]
463    fn test_empty_via() {
464        let result = SipVia::parse("");
465        assert!(matches!(result, Err(SipViaError::Empty)));
466    }
467
468    #[test]
469    fn test_empty_via_whitespace() {
470        let result = SipVia::parse("   ");
471        assert!(matches!(result, Err(SipViaError::Empty)));
472    }
473
474    #[test]
475    fn test_invalid_format() {
476        let result = SipVia::parse("invalid");
477        assert!(matches!(result, Err(SipViaError::InvalidFormat(_))));
478    }
479
480    #[test]
481    fn test_rport_invalid_value_is_error() {
482        let result = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;rport=garbage");
483        assert!(result.is_err());
484    }
485
486    #[test]
487    fn test_display_roundtrip() {
488        let original =
489            "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bKnashds8;received=203.0.113.10;rport";
490        let via = SipVia::parse(original).unwrap();
491        let displayed = via.to_string();
492
493        let reparsed = SipVia::parse(&displayed).unwrap();
494        assert_eq!(via, reparsed);
495    }
496
497    #[test]
498    fn test_display_multiple_vias() {
499        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
500        let displayed = via.to_string();
501        assert!(displayed.contains("198.51.100.1"));
502        assert!(displayed.contains("203.0.113.5"));
503    }
504
505    #[test]
506    fn test_into_iterator() {
507        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
508
509        let mut count = 0;
510        for entry in &via {
511            assert!(entry.host() == "198.51.100.1" || entry.host() == "203.0.113.5");
512            count += 1;
513        }
514        assert_eq!(count, 2);
515
516        let entries: Vec<_> = via
517            .into_iter()
518            .collect();
519        assert_eq!(entries.len(), 2);
520    }
521
522    #[test]
523    fn test_into_entries() {
524        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060, SIP/2.0/TCP 203.0.113.5").unwrap();
525        let entries = via.into_entries();
526        assert_eq!(entries.len(), 2);
527        assert_eq!(entries[0].host(), "198.51.100.1");
528        assert_eq!(entries[1].host(), "203.0.113.5");
529    }
530
531    #[test]
532    fn test_from_str() {
533        let via: SipVia = "SIP/2.0/UDP 198.51.100.1:5060"
534            .parse()
535            .unwrap();
536        assert_eq!(via.len(), 1);
537    }
538
539    #[test]
540    fn test_param_case_insensitive() {
541        let via = SipVia::parse("SIP/2.0/UDP 198.51.100.1:5060;Branch=test").unwrap();
542        let entry = &via.entries()[0];
543        assert_eq!(entry.param("branch"), Some(Some("test")));
544        assert_eq!(entry.param("BRANCH"), Some(Some("test")));
545    }
546
547    #[test]
548    fn test_display_ipv6() {
549        let via = SipVia::parse("SIP/2.0/UDP [2001:db8::1]:5060").unwrap();
550        let displayed = via.to_string();
551        assert!(displayed.contains("[2001:db8::1]"));
552    }
553
554    #[test]
555    fn from_entries_matches_parse() {
556        let a = "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bK1";
557        let b = "SIP/2.0/TCP 203.0.113.5";
558        let split = SipVia::from_entries([a, b]).unwrap();
559        let joined = SipVia::parse(&format!("{a}, {b}")).unwrap();
560        assert_eq!(split, joined);
561        assert_eq!(split.to_string(), format!("{a}, {b}"));
562    }
563
564    #[test]
565    fn from_entries_bad_entry_is_error() {
566        assert!(matches!(
567            SipVia::from_entries(["SIP/2.0/UDP 198.51.100.1", "invalid"]),
568            Err(SipViaError::InvalidFormat(_))
569        ));
570    }
571
572    #[test]
573    fn from_entries_empty_is_empty_error() {
574        assert!(matches!(
575            SipVia::from_entries(std::iter::empty::<&str>()),
576            Err(SipViaError::Empty)
577        ));
578    }
579}