Skip to main content

sipx_sip/headers/
via.rs

1//! The `Via` header (RFC 3261 §20.42).
2//!
3//! ```abnf
4//! via-parm      = sent-protocol LWS sent-by *( SEMI via-params )
5//! sent-protocol = protocol-name SLASH protocol-version SLASH transport
6//! sent-by       = host [ COLON port ]
7//! SLASH         = SWS "/" SWS
8//! ```
9//!
10//! `SLASH` and `LWS` mean whitespace is legal almost everywhere, including across a line
11//! fold: RFC 4475 §3.1.1.1 sends `Via  : SIP  /   2.0` with the `/UDP` on the next line. The
12//! top `Via` decides where a response goes and its `branch` identifies the transaction, so
13//! this is the header the transaction layer leans on hardest.
14
15use bytes::Bytes;
16use std::fmt;
17
18use crate::error::HeaderError;
19use crate::headers::grammar::{self, HeaderParam, find_param_start, skip_ws, trim};
20use crate::message::TypedHeader;
21use crate::name::HeaderName;
22use crate::uri::Host;
23
24/// The magic cookie that marks a branch parameter as RFC 3261 rather than RFC 2543
25/// (RFC 3261 §8.1.1.7). Its absence is what selects the legacy matching rules.
26pub const BRANCH_MAGIC_COOKIE: &[u8] = b"z9hG4bK";
27
28const LABEL: &str = "Via";
29const OC_SEQUENCE_SCALE: u64 = 100_000;
30
31/// The overload-control capability or value carried by `Via`'s `oc` parameter (RFC 7339 §4.1).
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum OcParameter {
34    /// A valueless parameter: the client supports overload control.
35    Support,
36    /// A server report. Its units are selected by [`OverloadAlgorithm`].
37    Value(u64),
38}
39
40/// One algorithm token from `oc-algo` (RFC 7339 §4.2, RFC 7415 §3.3).
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum OverloadAlgorithm {
43    /// Percentage loss control.
44    Loss,
45    /// Requests-per-second rate control.
46    Rate,
47    /// An extension a peer advertised. Kept so negotiation can ignore rather than corrupt it.
48    Other(Vec<u8>),
49}
50
51/// RFC 7339's decimal `oc-seq`, normalized to five fractional decimal places.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
53pub struct OverloadSequence(u64);
54
55impl OverloadSequence {
56    /// Construct an integral sequence value for a locally generated report.
57    #[must_use]
58    pub fn from_integer(value: u64) -> Option<Self> {
59        value.checked_mul(OC_SEQUENCE_SCALE).map(Self)
60    }
61
62    /// Parse `1*12DIGIT "." 1*5DIGIT` from RFC 7339 §13.1.
63    pub fn parse(value: &[u8]) -> Result<Self, HeaderError> {
64        let Some(dot) = value.iter().position(|byte| *byte == b'.') else {
65            return Err(HeaderError::Syntax { header: LABEL });
66        };
67        let whole = value.get(..dot).unwrap_or(&[]);
68        let fraction = value.get(dot.saturating_add(1)..).unwrap_or(&[]);
69        if whole.is_empty()
70            || whole.len() > 12
71            || fraction.is_empty()
72            || fraction.len() > 5
73            || !whole.iter().all(u8::is_ascii_digit)
74            || !fraction.iter().all(u8::is_ascii_digit)
75        {
76            return Err(HeaderError::Syntax { header: LABEL });
77        }
78        let whole = decimal_u64(whole)?;
79        let fraction_value = decimal_u64(fraction)?;
80        let missing = 5usize.saturating_sub(fraction.len());
81        let scale = 10u64
82            .checked_pow(u32::try_from(missing).unwrap_or(0))
83            .ok_or(HeaderError::Syntax { header: LABEL })?;
84        let scaled_fraction = fraction_value
85            .checked_mul(scale)
86            .ok_or(HeaderError::Syntax { header: LABEL })?;
87        whole
88            .checked_mul(OC_SEQUENCE_SCALE)
89            .and_then(|base| base.checked_add(scaled_fraction))
90            .map(Self)
91            .ok_or(HeaderError::Syntax { header: LABEL })
92    }
93}
94
95impl fmt::Display for OverloadSequence {
96    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97        let whole = self.0 / OC_SEQUENCE_SCALE;
98        let fraction = self.0 % OC_SEQUENCE_SCALE;
99        if fraction == 0 {
100            return write!(formatter, "{whole}.0");
101        }
102        let mut digits = format!("{fraction:05}");
103        while digits.ends_with('0') {
104            digits.pop();
105        }
106        write!(formatter, "{whole}.{digits}")
107    }
108}
109
110/// The four typed overload-control parameters on one `Via` hop.
111#[derive(Debug, Clone, PartialEq)]
112pub struct ViaOverload {
113    /// `None` when `oc` is absent; otherwise capability or server value.
114    pub oc: Option<OcParameter>,
115    /// The offered list in a request or the single selected algorithm in a response.
116    pub algorithms: Vec<OverloadAlgorithm>,
117    /// Server-only validity. Absence is interpreted by the transport as 500 ms.
118    pub validity: Option<std::time::Duration>,
119    /// Server-only report sequence.
120    pub sequence: Option<OverloadSequence>,
121}
122
123fn decimal_u64(value: &[u8]) -> Result<u64, HeaderError> {
124    let text = std::str::from_utf8(value).map_err(|_| HeaderError::Syntax { header: LABEL })?;
125    text.parse()
126        .map_err(|_| HeaderError::Syntax { header: LABEL })
127}
128
129/// One `Via` value.
130#[derive(Debug, Clone)]
131pub struct Via {
132    /// The protocol name, normally `SIP`.
133    pub protocol: Vec<u8>,
134    /// The protocol version, normally `2.0`.
135    pub version: Vec<u8>,
136    /// The transport: `UDP`, `TCP`, `TLS`, `SCTP`, `WS`, `WSS`, or anything else a peer sends
137    /// — RFC 4475 §3.1.1.10 requires unknown transports to parse.
138    pub transport: Vec<u8>,
139    /// The host this hop wants responses sent to.
140    pub host: Host,
141    /// The port, if stated.
142    pub port: Option<u16>,
143    /// The via parameters.
144    pub params: Vec<HeaderParam>,
145}
146
147impl Via {
148    /// Decode the RFC 7339/RFC 7415 overload-control parameters on this hop.
149    ///
150    /// Presence and value of `oc` stay distinct because the client sends the former and only a
151    /// server may send the latter. Invalid numbers are errors rather than zero: zero has protocol
152    /// meaning for both loss/rate and validity.
153    pub fn overload(&self) -> Result<ViaOverload, HeaderError> {
154        let oc = match grammar::param(&self.params, "oc") {
155            None => None,
156            Some(parameter) => match parameter.value.as_deref() {
157                None => Some(OcParameter::Support),
158                Some(value) => Some(OcParameter::Value(decimal_u64(value)?)),
159            },
160        };
161        let algorithms = match grammar::param(&self.params, "oc-algo") {
162            None => Vec::new(),
163            Some(parameter) => {
164                let value = parameter
165                    .value
166                    .as_deref()
167                    .ok_or(HeaderError::Syntax { header: LABEL })?;
168                let algorithms: Vec<_> = value
169                    .split(|byte| *byte == b',')
170                    .map(|token| match grammar::trim(token) {
171                        token if token.eq_ignore_ascii_case(b"loss") => OverloadAlgorithm::Loss,
172                        token if token.eq_ignore_ascii_case(b"rate") => OverloadAlgorithm::Rate,
173                        token => OverloadAlgorithm::Other(token.to_vec()),
174                    })
175                    .collect();
176                if algorithms.iter().any(|algorithm| {
177                    matches!(algorithm, OverloadAlgorithm::Other(token) if token.is_empty() || !token.iter().all(u8::is_ascii_alphanumeric))
178                }) {
179                    return Err(HeaderError::Syntax { header: LABEL });
180                }
181                algorithms
182            }
183        };
184        let validity = grammar::param(&self.params, "oc-validity")
185            .map(|parameter| {
186                let value = parameter
187                    .value
188                    .as_deref()
189                    .ok_or(HeaderError::Syntax { header: LABEL })?;
190                decimal_u64(value).map(std::time::Duration::from_millis)
191            })
192            .transpose()?;
193        let sequence = grammar::param(&self.params, "oc-seq")
194            .map(|parameter| {
195                parameter
196                    .value
197                    .as_deref()
198                    .ok_or(HeaderError::Syntax { header: LABEL })
199                    .and_then(OverloadSequence::parse)
200            })
201            .transpose()?;
202        Ok(ViaOverload {
203            oc,
204            algorithms,
205            validity,
206            sequence,
207        })
208    }
209
210    /// The `branch` parameter, which identifies the transaction.
211    #[must_use]
212    pub fn branch(&self) -> Option<&[u8]> {
213        self.param("branch")
214    }
215
216    /// Whether the branch carries the RFC 3261 magic cookie.
217    ///
218    /// When it does not, the sender predates RFC 3261 and transaction matching must fall back
219    /// to the rules in §17.2.3.
220    #[must_use]
221    pub fn has_rfc3261_branch(&self) -> bool {
222        self.branch()
223            .is_some_and(|b| b.starts_with(BRANCH_MAGIC_COOKIE))
224    }
225
226    /// The `received` parameter: the source address the previous hop was actually seen from
227    /// (RFC 3261 §18.2.1).
228    #[must_use]
229    pub fn received(&self) -> Option<&[u8]> {
230        self.param("received")
231    }
232
233    /// The `rport` parameter (RFC 3581). Present with no value in a request means "tell me
234    /// what port you saw"; present with a value in a response is that port.
235    #[must_use]
236    pub fn rport(&self) -> Option<Option<&[u8]>> {
237        grammar::param(&self.params, "rport").map(|p| p.value.as_deref())
238    }
239
240    /// The `maddr` parameter.
241    #[must_use]
242    pub fn maddr(&self) -> Option<&[u8]> {
243        self.param("maddr")
244    }
245
246    /// The `ttl` parameter.
247    #[must_use]
248    pub fn ttl(&self) -> Option<&[u8]> {
249        self.param("ttl")
250    }
251
252    /// Any via parameter, by name.
253    #[must_use]
254    pub fn param(&self, name: &str) -> Option<&[u8]> {
255        grammar::param(&self.params, name).and_then(|p| p.value.as_deref())
256    }
257
258    /// Parse one `Via` value — a single hop, not a comma-separated list.
259    pub fn parse_one(value: &[u8]) -> Result<Self, HeaderError> {
260        let value = trim(value);
261        if value.is_empty() {
262            return Err(HeaderError::Syntax { header: LABEL });
263        }
264
265        let (before_params, params_tail) = match find_param_start(value) {
266            Some(semi) => (
267                value.get(..semi).unwrap_or(&[]),
268                value.get(semi..).unwrap_or(&[]),
269            ),
270            None => (value, &[][..]),
271        };
272
273        // sent-protocol: three fields separated by slashes, with whitespace permitted around
274        // each slash.
275        let mut fields: Vec<&[u8]> = Vec::with_capacity(3);
276        let mut start = 0usize;
277        let mut split_count = 0usize;
278        for (i, &b) in before_params.iter().enumerate() {
279            if b == b'/' && split_count < 2 {
280                fields.push(trim(before_params.get(start..i).unwrap_or(&[])));
281                start = i + 1;
282                split_count += 1;
283            }
284        }
285        let tail = trim(before_params.get(start..).unwrap_or(&[]));
286        if split_count != 2 {
287            return Err(HeaderError::Syntax { header: LABEL });
288        }
289
290        // The third slash-separated field is `transport LWS sent-by`; the whitespace between
291        // them is the only separator, and there may be a lot of it.
292        let space = tail
293            .iter()
294            .position(|&b| matches!(b, b' ' | b'\t'))
295            .ok_or(HeaderError::Syntax { header: LABEL })?;
296        let transport = trim(tail.get(..space).unwrap_or(&[]));
297        let sent_by = trim(tail.get(skip_ws(tail, space)..).unwrap_or(&[]));
298
299        let protocol = fields.first().copied().unwrap_or(&[]);
300        let version = fields.get(1).copied().unwrap_or(&[]);
301        if protocol.is_empty() || version.is_empty() || transport.is_empty() || sent_by.is_empty() {
302            return Err(HeaderError::Syntax { header: LABEL });
303        }
304
305        let (host, port) =
306            Host::parse_hostport(&Bytes::copy_from_slice(sent_by)).map_err(|source| {
307                HeaderError::Uri {
308                    header: LABEL,
309                    source,
310                }
311            })?;
312
313        Ok(Self {
314            protocol: protocol.to_vec(),
315            version: version.to_vec(),
316            transport: transport.to_vec(),
317            host,
318            port,
319            params: grammar::parse_params(trim(params_tail), LABEL)?,
320        })
321    }
322
323    /// Parse a header value that may carry several comma-separated hops.
324    pub fn parse_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
325        grammar::split_list(value, LABEL)?
326            .into_iter()
327            .map(Self::parse_one)
328            .collect()
329    }
330}
331
332/// The index just past the first `via-parm` in a header value that may carry several
333/// comma-separated hops.
334///
335/// A server has to add `received` and `rport` to the **topmost** hop and only that one, so it
336/// needs to know where the first hop ends without reserializing the rest — the other hops
337/// belong to other elements and must go back out exactly as they arrived.
338///
339/// Commas inside quoted parameter values are not separators, which is why this is not a call
340/// to `position`.
341#[must_use]
342pub fn first_hop_end(value: &[u8]) -> usize {
343    let mut i = 0usize;
344    while i < value.len() {
345        match value.get(i) {
346            Some(b'"') => match grammar::quoted_string_end(value, i) {
347                Some(end) => i = end,
348                None => return value.len(),
349            },
350            Some(b',') => return i,
351            Some(_) => i += 1,
352            None => break,
353        }
354    }
355    value.len()
356}
357
358impl TypedHeader for Via {
359    const NAME: HeaderName = HeaderName::Via;
360
361    /// Decodes the **first** hop in the value.
362    ///
363    /// A single `Via` header line may carry several comma-separated hops, so `typed::<Via>()`
364    /// gives the topmost one — which is the one that matters for routing a response. Use
365    /// [`Via::parse_list`] when every hop is needed.
366    fn decode(value: &[u8]) -> Result<Self, HeaderError> {
367        let parts = grammar::split_list(value, LABEL)?;
368        let first = parts.first().copied().unwrap_or(&[]);
369        Self::parse_one(first)
370    }
371
372    fn decode_list(value: &[u8]) -> Result<Vec<Self>, HeaderError> {
373        Self::parse_list(value)
374    }
375}
376
377#[cfg(test)]
378#[allow(
379    clippy::unwrap_used,
380    clippy::expect_used,
381    clippy::panic,
382    clippy::indexing_slicing
383)]
384mod tests {
385    use super::*;
386
387    fn via(value: &[u8]) -> Via {
388        Via::parse_one(value).unwrap_or_else(|e| panic!("{value:?} should parse: {e}"))
389    }
390
391    #[test]
392    fn parses_an_ordinary_via() {
393        let v = via(b"SIP/2.0/UDP host5.example.com:5060;branch=z9hG4bKkdjuw");
394        assert_eq!(v.protocol, b"SIP");
395        assert_eq!(v.version, b"2.0");
396        assert_eq!(v.transport, b"UDP");
397        assert_eq!(v.port, Some(5060));
398        assert_eq!(v.branch(), Some(&b"z9hG4bKkdjuw"[..]));
399        assert!(v.has_rfc3261_branch());
400    }
401
402    /// RFC 4475 3.1.1.1 sends this, folded across three lines. Once unfolded the whitespace
403    /// is still everywhere the grammar allows it.
404    #[test]
405    fn tolerates_whitespace_around_every_slash() {
406        let v = via(b"SIP  /   2.0   /UDP     192.0.2.2;branch=390skdjuw");
407        assert_eq!(v.protocol, b"SIP");
408        assert_eq!(v.version, b"2.0");
409        assert_eq!(v.transport, b"UDP");
410        assert_eq!(v.branch(), Some(&b"390skdjuw"[..]));
411        // No magic cookie: this sender predates RFC 3261 and matching must fall back.
412        assert!(!v.has_rfc3261_branch());
413    }
414
415    /// RFC 4475 3.1.1.10: unknown transports are legal and must not be rejected.
416    #[test]
417    fn accepts_unknown_transports() {
418        for transport in [&b"TLS"[..], b"SCTP", b"UNKNOWN", b"ws"] {
419            let mut value = b"SIP/2.0/".to_vec();
420            value.extend_from_slice(transport);
421            value.extend_from_slice(b" host.example.com;branch=z9hG4bKx");
422            assert_eq!(via(&value).transport, transport);
423        }
424    }
425
426    /// RFC 4475 3.1.2.1: the stray separators make this invalid — but only because it is a
427    /// `Via`. The same value under an unknown header name is legal.
428    #[test]
429    fn rejects_extraneous_separators() {
430        assert!(Via::parse_list(b"SIP/2.0/UDP 192.0.2.15;;,;,,").is_err());
431    }
432
433    #[test]
434    fn parses_several_hops_on_one_line() {
435        let hops = Via::parse_list(
436            b"SIP  / 2.0  / TCP     spindle.example.com   ;  branch  =   z9hG4bK9ikj8  , \
437              SIP  /    2.0   / UDP  192.168.255.111   ; branch=z9hG4bK30239",
438        )
439        .expect("should parse");
440        assert_eq!(hops.len(), 2);
441        assert_eq!(hops[0].transport, b"TCP");
442        assert_eq!(hops[1].branch(), Some(&b"z9hG4bK30239"[..]));
443    }
444
445    #[test]
446    fn reports_rport_presence_separately_from_its_value() {
447        // In a request rport is present and empty; in a response it carries the port.
448        let asking = via(b"SIP/2.0/UDP h.example.com;rport;branch=z9hG4bKx");
449        assert_eq!(asking.rport(), Some(None));
450
451        let answered = via(b"SIP/2.0/UDP h.example.com;rport=1234;branch=z9hG4bKx");
452        assert_eq!(answered.rport(), Some(Some(&b"1234"[..])));
453
454        let absent = via(b"SIP/2.0/UDP h.example.com;branch=z9hG4bKx");
455        assert_eq!(absent.rport(), None);
456    }
457
458    #[test]
459    fn overload_parameters_are_typed_for_both_parties() {
460        let offered = via(b"SIP/2.0/UDP client.example;branch=z9hG4bKx;oc;oc-algo=\"loss,rate\"")
461            .overload()
462            .expect("valid overload offer");
463        assert_eq!(offered.oc, Some(OcParameter::Support));
464        assert_eq!(
465            offered.algorithms,
466            vec![OverloadAlgorithm::Loss, OverloadAlgorithm::Rate]
467        );
468        assert_eq!(offered.validity, None);
469        assert_eq!(offered.sequence, None);
470
471        let report = via(
472            b"SIP/2.0/UDP server.example;branch=z9hG4bKy;oc=37;oc-algo=rate;\
473              oc-validity=750;oc-seq=42.125",
474        )
475        .overload()
476        .expect("valid overload report");
477        assert_eq!(report.oc, Some(OcParameter::Value(37)));
478        assert_eq!(report.algorithms, vec![OverloadAlgorithm::Rate]);
479        assert_eq!(report.validity, Some(std::time::Duration::from_millis(750)));
480        assert_eq!(
481            report.sequence,
482            Some(OverloadSequence::parse(b"42.125").expect("sequence"))
483        );
484    }
485
486    #[test]
487    fn malformed_overload_numbers_are_not_zero() {
488        for value in [
489            b"SIP/2.0/UDP h;oc=not-a-number;oc-algo=loss".as_slice(),
490            b"SIP/2.0/UDP h;oc=1.5;oc-algo=rate",
491            b"SIP/2.0/UDP h;oc=10;oc-algo=loss;oc-validity=-1",
492            b"SIP/2.0/UDP h;oc=10;oc-algo=loss;oc-seq=1",
493        ] {
494            assert!(via(value).overload().is_err(), "accepted {value:?}");
495        }
496    }
497
498    #[test]
499    fn rejects_malformed_sent_protocol() {
500        for value in [
501            &b"SIP/2.0 host.example.com"[..], // only one slash
502            b"SIP/2.0/UDP",                   // no sent-by
503            b"/2.0/UDP host",                 // empty protocol
504            b"SIP//UDP host",                 // empty version
505            b"SIP/2.0/ host",                 // empty transport
506        ] {
507            assert!(
508                Via::parse_one(value).is_err(),
509                "{value:?} should be rejected"
510            );
511        }
512    }
513
514    #[test]
515    fn the_first_hop_ends_at_the_first_real_comma() {
516        let single = b"SIP/2.0/UDP a;branch=x";
517        assert_eq!(first_hop_end(single), single.len());
518        let two = b"SIP/2.0/UDP a;branch=x, SIP/2.0/UDP b;branch=y";
519        assert_eq!(&two[..first_hop_end(two)], b"SIP/2.0/UDP a;branch=x");
520        // A comma inside a quoted parameter value is not a separator.
521        let quoted = br#"SIP/2.0/UDP a;note="one, two";branch=x"#;
522        assert_eq!(first_hop_end(quoted), quoted.len());
523    }
524
525    #[test]
526    fn rejects_a_sent_by_with_a_bad_host() {
527        assert!(Via::parse_one(b"SIP/2.0/UDP host:99999;branch=z9hG4bKx").is_err());
528    }
529}