1use std::fmt;
4use std::str::Utf8Error;
5
6use percent_encoding::percent_decode_str;
7
8use crate::header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum HistoryInfoError {
14 Empty,
16 InvalidEntry(ParseSipHeaderAddrError),
18 Malformed(String),
21}
22
23impl fmt::Display for HistoryInfoError {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::Empty => write!(f, "empty History-Info header"),
27 Self::InvalidEntry(e) => write!(f, "invalid History-Info entry: {e}"),
28 Self::Malformed(reason) => write!(f, "malformed History-Info value: {reason}"),
29 }
30 }
31}
32
33impl std::error::Error for HistoryInfoError {
34 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
35 match self {
36 Self::InvalidEntry(e) => Some(e),
37 _ => None,
38 }
39 }
40}
41
42impl From<ParseSipHeaderAddrError> for HistoryInfoError {
43 fn from(e: ParseSipHeaderAddrError) -> Self {
44 Self::InvalidEntry(e)
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct HistoryInfoReason {
54 protocol: String,
55 cause: Option<u16>,
56 text: Option<String>,
57}
58
59impl HistoryInfoReason {
60 pub fn protocol(&self) -> &str {
62 &self.protocol
63 }
64
65 pub fn cause(&self) -> Option<u16> {
67 self.cause
68 }
69
70 pub fn text(&self) -> Option<&str> {
72 self.text
73 .as_deref()
74 }
75}
76
77fn parse_reason(decoded: &str) -> HistoryInfoReason {
81 let (protocol, rest) = decoded
82 .split_once(';')
83 .unwrap_or((decoded, ""));
84
85 let mut cause = None;
86 let mut text = None;
87
88 if let Some(idx) = rest.find("cause=") {
90 let val_start = idx + 6;
91 let val_end = rest[val_start..]
92 .find(';')
93 .map(|i| val_start + i)
94 .unwrap_or(rest.len());
95 cause = rest[val_start..val_end]
96 .trim()
97 .parse::<u16>()
98 .ok();
99 }
100
101 if let Some(idx) = rest.find("text=") {
103 let val_start = idx + 5;
104 let val = rest[val_start..].trim_start();
105 if let Some(inner) = val.strip_prefix('"') {
106 if let Some(end) = inner.find('"') {
107 text = Some(inner[..end].to_string());
108 } else {
109 text = Some(inner.to_string());
110 }
111 } else {
112 let end = val
113 .find(';')
114 .unwrap_or(val.len());
115 text = Some(val[..end].to_string());
116 }
117 }
118
119 HistoryInfoReason {
120 protocol: protocol
121 .trim()
122 .to_string(),
123 cause,
124 text,
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct HistoryInfoEntry {
134 addr: SipHeaderAddr,
135}
136
137impl HistoryInfoEntry {
138 pub fn addr(&self) -> &SipHeaderAddr {
140 &self.addr
141 }
142
143 pub fn uri(&self) -> &sip_uri::Uri {
145 self.addr
146 .uri()
147 }
148
149 pub fn sip_uri(&self) -> Option<&sip_uri::SipUri> {
151 self.addr
152 .sip_uri()
153 }
154
155 pub fn index(&self) -> Option<&str> {
157 self.addr
158 .param_raw("index")
159 .flatten()
160 }
161
162 pub fn reason_raw(&self) -> Option<&str> {
166 self.addr
167 .sip_uri()?
168 .header("Reason")
169 }
170
171 pub fn reason(&self) -> Option<Result<HistoryInfoReason, Utf8Error>> {
180 let raw = self.reason_raw()?;
181 let raw = raw.replace('+', " ");
185 Some(
186 percent_decode_str(&raw)
187 .decode_utf8()
188 .map(|decoded| parse_reason(&decoded)),
189 )
190 }
191}
192
193impl fmt::Display for HistoryInfoEntry {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 write!(f, "{}", self.addr)
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct HistoryInfo(Vec<HistoryInfoEntry>);
215
216impl HistoryInfo {
217 pub fn parse(raw: &str) -> Result<Self, HistoryInfoError> {
219 let raw = raw.trim();
220 if raw.is_empty() {
221 return Err(HistoryInfoError::Empty);
222 }
223 Self::from_entries(crate::split_comma_entries(raw))
224 }
225
226 pub fn from_entries<'a>(
231 entries: impl IntoIterator<Item = &'a str>,
232 ) -> Result<Self, HistoryInfoError> {
233 let entries: Vec<_> = entries
234 .into_iter()
235 .map(parse_entry)
236 .collect::<Result<_, _>>()?;
237 if entries.is_empty() {
238 return Err(HistoryInfoError::Empty);
239 }
240 Ok(Self(entries))
241 }
242
243 pub fn entries(&self) -> &[HistoryInfoEntry] {
245 &self.0
246 }
247
248 pub fn into_entries(self) -> Vec<HistoryInfoEntry> {
250 self.0
251 }
252
253 pub fn len(&self) -> usize {
255 self.0
256 .len()
257 }
258
259 pub fn is_empty(&self) -> bool {
261 self.0
262 .is_empty()
263 }
264}
265
266fn parse_entry(raw: &str) -> Result<HistoryInfoEntry, HistoryInfoError> {
267 let addr: SipHeaderAddr = raw
268 .trim()
269 .parse()?;
270 Ok(HistoryInfoEntry { addr })
271}
272
273impl fmt::Display for HistoryInfo {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 crate::fmt_joined(f, &self.0, ",")
276 }
277}
278
279impl<'a> IntoIterator for &'a HistoryInfo {
280 type Item = &'a HistoryInfoEntry;
281 type IntoIter = std::slice::Iter<'a, HistoryInfoEntry>;
282
283 fn into_iter(self) -> Self::IntoIter {
284 self.0
285 .iter()
286 }
287}
288
289impl IntoIterator for HistoryInfo {
290 type Item = HistoryInfoEntry;
291 type IntoIter = std::vec::IntoIter<HistoryInfoEntry>;
292
293 fn into_iter(self) -> Self::IntoIter {
294 self.0
295 .into_iter()
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 const EXAMPLE_1: &str = "\
304<sip:user1@esrp.example.com?Reason=RouteAction%3Bcause%3D200%3Btext%3D%22Normal+Next+Hop%22>;index=1,\
305<sip:sos@psap.example.com>;index=2";
306
307 const EXAMPLE_2: &str = "\
308<sip:lsrg.example.com?Reason=SIP%3Bcause%3D200%3Btext%3D%22Legacy+routing%22>;index=1,\
309<sip:user1@esrp2.example.com;lr;transport=udp?Reason=RouteAction%3Bcause%3D200%3Btext%3D%22Normal+Next+Hop%22>;index=1.1,\
310<sip:sos@psap.example.com>;index=1.2";
311
312 #[test]
315 fn parse_two_entries() {
316 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
317 assert_eq!(hi.len(), 2);
318 }
319
320 #[test]
321 fn parse_three_entries() {
322 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
323 assert_eq!(hi.len(), 3);
324 }
325
326 #[test]
327 fn parse_single_entry() {
328 let hi = HistoryInfo::parse("<sip:alice@example.com>;index=1").unwrap();
329 assert_eq!(hi.len(), 1);
330 }
331
332 #[test]
333 fn empty_input() {
334 assert!(matches!(
335 HistoryInfo::parse(""),
336 Err(HistoryInfoError::Empty)
337 ));
338 }
339
340 #[test]
343 fn index_simple() {
344 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
345 assert_eq!(hi.entries()[0].index(), Some("1"));
346 assert_eq!(hi.entries()[1].index(), Some("2"));
347 }
348
349 #[test]
350 fn index_hierarchical() {
351 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
352 assert_eq!(hi.entries()[0].index(), Some("1"));
353 assert_eq!(hi.entries()[1].index(), Some("1.1"));
354 assert_eq!(hi.entries()[2].index(), Some("1.2"));
355 }
356
357 #[test]
358 fn index_absent() {
359 let hi = HistoryInfo::parse("<sip:alice@example.com>").unwrap();
360 assert_eq!(hi.entries()[0].index(), None);
361 }
362
363 #[test]
366 fn uri_with_user() {
367 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
368 let sip = hi.entries()[0]
369 .sip_uri()
370 .unwrap();
371 assert_eq!(sip.user(), Some("user1"));
372 assert_eq!(
373 sip.host()
374 .to_string(),
375 "esrp.example.com"
376 );
377 }
378
379 #[test]
380 fn uri_without_user() {
381 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
382 let sip = hi.entries()[0]
383 .sip_uri()
384 .unwrap();
385 assert_eq!(sip.user(), None);
386 assert_eq!(
387 sip.host()
388 .to_string(),
389 "lsrg.example.com"
390 );
391 }
392
393 #[test]
394 fn uri_with_params() {
395 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
396 let sip = hi.entries()[1]
397 .sip_uri()
398 .unwrap();
399 assert_eq!(sip.user(), Some("user1"));
400 assert_eq!(
401 sip.host()
402 .to_string(),
403 "esrp2.example.com"
404 );
405 assert!(sip
406 .param("lr")
407 .is_some());
408 assert_eq!(sip.param("transport"), Some(&Some("udp".to_string())));
409 }
410
411 #[test]
414 fn reason_raw_present() {
415 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
416 assert_eq!(
417 hi.entries()[0].reason_raw(),
418 Some("RouteAction%3Bcause%3D200%3Btext%3D%22Normal+Next+Hop%22")
419 );
420 }
421
422 #[test]
423 fn reason_raw_absent() {
424 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
425 assert_eq!(hi.entries()[1].reason_raw(), None);
426 }
427
428 #[test]
429 fn reason_parsed_route_action() {
430 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
431 let reason = hi.entries()[0]
432 .reason()
433 .unwrap()
434 .unwrap();
435 assert_eq!(reason.protocol(), "RouteAction");
436 assert_eq!(reason.cause(), Some(200));
437 assert_eq!(reason.text(), Some("Normal Next Hop"));
438 }
439
440 #[test]
441 fn reason_parsed_sip() {
442 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
443 let reason = hi.entries()[0]
444 .reason()
445 .unwrap()
446 .unwrap();
447 assert_eq!(reason.protocol(), "SIP");
448 assert_eq!(reason.cause(), Some(200));
449 assert_eq!(reason.text(), Some("Legacy routing"));
450 }
451
452 #[test]
453 fn reason_absent_returns_none() {
454 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
455 assert!(hi.entries()[2]
456 .reason()
457 .is_none());
458 }
459
460 #[test]
461 fn reason_multiple_entries() {
462 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
463 let r0 = hi.entries()[0]
464 .reason()
465 .unwrap()
466 .unwrap();
467 let r1 = hi.entries()[1]
468 .reason()
469 .unwrap()
470 .unwrap();
471 assert_eq!(r0.protocol(), "SIP");
472 assert_eq!(r1.protocol(), "RouteAction");
473 assert!(hi.entries()[2]
474 .reason()
475 .is_none());
476 }
477
478 #[test]
481 fn display_roundtrip_simple() {
482 let raw = "<sip:alice@example.com>;index=1";
483 let hi = HistoryInfo::parse(raw).unwrap();
484 assert_eq!(hi.to_string(), raw);
485 }
486
487 #[test]
488 fn display_entry_count_matches_commas() {
489 let hi = HistoryInfo::parse(EXAMPLE_2).unwrap();
490 let s = hi.to_string();
491 assert_eq!(
492 s.matches(',')
493 .count()
494 + 1,
495 hi.len()
496 );
497 }
498
499 #[test]
500 fn display_roundtrip_real_world() {
501 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
502 let reparsed = HistoryInfo::parse(&hi.to_string()).unwrap();
503 assert_eq!(hi.len(), reparsed.len());
504 for (a, b) in hi
505 .entries()
506 .iter()
507 .zip(reparsed.entries())
508 {
509 assert_eq!(a.index(), b.index());
510 assert_eq!(a.reason_raw(), b.reason_raw());
511 }
512 }
513
514 #[test]
517 fn iter_by_ref() {
518 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
519 let indices: Vec<_> = hi
520 .entries()
521 .iter()
522 .map(|e| e.index())
523 .collect();
524 assert_eq!(indices, vec![Some("1"), Some("2")]);
525 }
526
527 #[test]
528 fn into_entries() {
529 let hi = HistoryInfo::parse(EXAMPLE_1).unwrap();
530 let entries = hi.into_entries();
531 assert_eq!(entries.len(), 2);
532 }
533
534 #[test]
537 fn parse_reason_full() {
538 let r = parse_reason("SIP;cause=302;text=\"Moved\"");
539 assert_eq!(r.protocol(), "SIP");
540 assert_eq!(r.cause(), Some(302));
541 assert_eq!(r.text(), Some("Moved"));
542 }
543
544 #[test]
545 fn parse_reason_no_text() {
546 let r = parse_reason("Q.850;cause=16");
547 assert_eq!(r.protocol(), "Q.850");
548 assert_eq!(r.cause(), Some(16));
549 assert_eq!(r.text(), None);
550 }
551
552 #[test]
553 fn parse_reason_protocol_only() {
554 let r = parse_reason("SIP");
555 assert_eq!(r.protocol(), "SIP");
556 assert_eq!(r.cause(), None);
557 assert_eq!(r.text(), None);
558 }
559
560 #[test]
561 fn parse_reason_unquoted_text() {
562 let r = parse_reason("SIP;cause=200;text=OK");
563 assert_eq!(r.text(), Some("OK"));
564 }
565
566 #[test]
569 fn malformed_display() {
570 let e = HistoryInfoError::Malformed("too many entries".to_string());
571 assert_eq!(
572 e.to_string(),
573 "malformed History-Info value: too many entries"
574 );
575 }
576
577 #[test]
578 fn malformed_no_source() {
579 use std::error::Error;
580 let e = HistoryInfoError::Malformed("framing failure".to_string());
581 assert!(e
582 .source()
583 .is_none());
584 }
585}