1use 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
24pub const BRANCH_MAGIC_COOKIE: &[u8] = b"z9hG4bK";
27
28const LABEL: &str = "Via";
29const OC_SEQUENCE_SCALE: u64 = 100_000;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum OcParameter {
34 Support,
36 Value(u64),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum OverloadAlgorithm {
43 Loss,
45 Rate,
47 Other(Vec<u8>),
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
53pub struct OverloadSequence(u64);
54
55impl OverloadSequence {
56 #[must_use]
58 pub fn from_integer(value: u64) -> Option<Self> {
59 value.checked_mul(OC_SEQUENCE_SCALE).map(Self)
60 }
61
62 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#[derive(Debug, Clone, PartialEq)]
112pub struct ViaOverload {
113 pub oc: Option<OcParameter>,
115 pub algorithms: Vec<OverloadAlgorithm>,
117 pub validity: Option<std::time::Duration>,
119 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#[derive(Debug, Clone)]
131pub struct Via {
132 pub protocol: Vec<u8>,
134 pub version: Vec<u8>,
136 pub transport: Vec<u8>,
139 pub host: Host,
141 pub port: Option<u16>,
143 pub params: Vec<HeaderParam>,
145}
146
147impl Via {
148 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 #[must_use]
212 pub fn branch(&self) -> Option<&[u8]> {
213 self.param("branch")
214 }
215
216 #[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 #[must_use]
229 pub fn received(&self) -> Option<&[u8]> {
230 self.param("received")
231 }
232
233 #[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 #[must_use]
242 pub fn maddr(&self) -> Option<&[u8]> {
243 self.param("maddr")
244 }
245
246 #[must_use]
248 pub fn ttl(&self) -> Option<&[u8]> {
249 self.param("ttl")
250 }
251
252 #[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 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 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 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 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#[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 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 #[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 assert!(!v.has_rfc3261_branch());
413 }
414
415 #[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 #[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 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"[..], b"SIP/2.0/UDP", b"/2.0/UDP host", b"SIP//UDP host", b"SIP/2.0/ host", ] {
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 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}