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