1use crate::channel::{
9 AnswerState, CallDirection, CallState, ChannelState, ChannelTimetable, HangupCause,
10 ParseAnswerStateError, ParseCallDirectionError, ParseCallStateError, ParseChannelStateError,
11 ParseHangupCauseError, ParseTimetableError,
12};
13#[cfg(feature = "esl")]
14use crate::event::{EslEventPriority, ParsePriorityError};
15use crate::headers::EventHeader;
16use crate::sofia::{
17 GatewayPingStatus, GatewayRegState, ParseGatewayPingStatusError, ParseGatewayRegStateError,
18 ParseSipUserPingStatusError, ParseSofiaEventSubclassError, SipUserPingStatus,
19 SofiaEventSubclass,
20};
21use crate::variables::{LoopbackResignation, LoopbackVariable, VariableName};
22use sip_header::SipHeaderLookup;
23use std::str::FromStr;
24
25#[inline]
30fn parse_opt<T: FromStr>(raw: Option<&str>) -> Result<Option<T>, T::Err> {
31 raw.map(str::parse)
32 .transpose()
33}
34
35pub trait HeaderLookup: SipHeaderLookup {
101 fn header_str(&self, name: &str) -> Option<&str>;
103
104 fn variable_str(&self, name: &str) -> Option<&str>;
108
109 fn header(&self, name: EventHeader) -> Option<&str> {
111 self.header_str(name.as_str())
112 }
113
114 fn variable(&self, name: impl VariableName) -> Option<&str> {
116 self.variable_str(name.as_str())
117 }
118
119 fn unique_id(&self) -> Option<&str> {
121 self.header(EventHeader::UniqueId)
122 .or_else(|| self.header(EventHeader::CallerUniqueId))
123 }
124
125 fn job_uuid(&self) -> Option<&str> {
131 self.header(EventHeader::JobUuid)
132 }
133
134 fn channel_name(&self) -> Option<&str> {
136 self.header(EventHeader::ChannelName)
137 }
138
139 fn caller_id_number(&self) -> Option<&str> {
141 self.header(EventHeader::CallerCallerIdNumber)
142 }
143
144 fn caller_id_name(&self) -> Option<&str> {
146 self.header(EventHeader::CallerCallerIdName)
147 }
148
149 fn destination_number(&self) -> Option<&str> {
151 self.header(EventHeader::CallerDestinationNumber)
152 }
153
154 fn callee_id_number(&self) -> Option<&str> {
156 self.header(EventHeader::CallerCalleeIdNumber)
157 }
158
159 fn callee_id_name(&self) -> Option<&str> {
161 self.header(EventHeader::CallerCalleeIdName)
162 }
163
164 fn channel_presence_id(&self) -> Option<&str> {
166 self.header(EventHeader::ChannelPresenceId)
167 }
168
169 fn presence_call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
171 parse_opt(self.header(EventHeader::PresenceCallDirection))
172 }
173
174 fn event_date_timestamp(&self) -> Option<&str> {
176 self.header(EventHeader::EventDateTimestamp)
177 }
178
179 fn event_sequence(&self) -> Option<&str> {
181 self.header(EventHeader::EventSequence)
182 }
183
184 fn dtmf_duration(&self) -> Option<&str> {
186 self.header(EventHeader::DtmfDuration)
187 }
188
189 fn dtmf_source(&self) -> Option<&str> {
191 self.header(EventHeader::DtmfSource)
192 }
193
194 fn hangup_cause(&self) -> Result<Option<HangupCause>, ParseHangupCauseError> {
198 parse_opt(self.header(EventHeader::HangupCause))
199 }
200
201 fn loopback_resignation(&self) -> Option<LoopbackResignation<'_>> {
213 self.variable(LoopbackVariable::LoopbackHangupCause)
214 .map(|cause| {
215 LoopbackResignation::new(
216 cause,
217 self.variable(LoopbackVariable::LoopbackBowoutOtherUuid),
218 )
219 })
220 }
221
222 fn event_subclass(&self) -> Option<&str> {
224 self.header(EventHeader::EventSubclass)
225 }
226
227 fn sofia_event_subclass(
232 &self,
233 ) -> Result<Option<SofiaEventSubclass>, ParseSofiaEventSubclassError> {
234 parse_opt(self.event_subclass())
235 }
236
237 fn gateway(&self) -> Option<&str> {
239 self.header(EventHeader::Gateway)
240 }
241
242 fn profile_name(&self) -> Option<&str> {
244 self.header(EventHeader::ProfileName)
245 }
246
247 fn phrase(&self) -> Option<&str> {
249 self.header(EventHeader::Phrase)
250 }
251
252 fn sip_status_code(&self) -> Result<Option<u16>, std::num::ParseIntError> {
255 parse_opt(self.header(EventHeader::Status))
256 }
257
258 fn gateway_reg_state(&self) -> Result<Option<GatewayRegState>, ParseGatewayRegStateError> {
262 parse_opt(self.header(EventHeader::State))
263 }
264
265 fn gateway_ping_status(
270 &self,
271 ) -> Result<Option<GatewayPingStatus>, ParseGatewayPingStatusError> {
272 parse_opt(self.header(EventHeader::PingStatus))
273 }
274
275 fn sip_user_ping_status(
280 &self,
281 ) -> Result<Option<SipUserPingStatus>, ParseSipUserPingStatusError> {
282 parse_opt(self.header(EventHeader::PingStatus))
283 }
284
285 fn pl_data(&self) -> Option<&str> {
291 self.header(EventHeader::PlData)
292 }
293
294 fn sip_event(&self) -> Option<&str> {
298 self.header(EventHeader::SipEvent)
299 }
300
301 fn gateway_name(&self) -> Option<&str> {
303 self.header(EventHeader::GatewayName)
304 }
305
306 fn channel_state(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
310 parse_opt(self.header(EventHeader::ChannelState))
311 }
312
313 fn channel_state_number(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
317 match self.header(EventHeader::ChannelStateNumber) {
318 Some(s) => {
319 let n: u8 = s
320 .parse()
321 .map_err(|_| ParseChannelStateError(s.to_string()))?;
322 ChannelState::from_number(n)
323 .ok_or_else(|| ParseChannelStateError(s.to_string()))
324 .map(Some)
325 }
326 None => Ok(None),
327 }
328 }
329
330 fn call_state(&self) -> Result<Option<CallState>, ParseCallStateError> {
334 parse_opt(self.header(EventHeader::ChannelCallState))
335 }
336
337 fn answer_state(&self) -> Result<Option<AnswerState>, ParseAnswerStateError> {
341 parse_opt(self.header(EventHeader::AnswerState))
342 }
343
344 fn call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
348 parse_opt(self.header(EventHeader::CallDirection))
349 }
350
351 #[cfg(feature = "esl")]
355 fn priority(&self) -> Result<Option<EslEventPriority>, ParsePriorityError> {
356 parse_opt(self.header(EventHeader::Priority))
357 }
358
359 fn timetable(&self, prefix: &str) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
364 ChannelTimetable::from_lookup(prefix, |key| self.header_str(key))
365 }
366
367 fn caller_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
369 self.timetable("Caller")
370 }
371
372 fn other_leg_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
374 self.timetable("Other-Leg")
375 }
376}
377
378impl HeaderLookup for std::collections::HashMap<String, String> {
379 fn header_str(&self, name: &str) -> Option<&str> {
380 self.get(name)
381 .map(|s| s.as_str())
382 }
383
384 fn variable_str(&self, name: &str) -> Option<&str> {
385 self.get(&format!("variable_{name}"))
386 .map(|s| s.as_str())
387 }
388}
389
390#[cfg(test)]
397mod tests {
398 use super::*;
399 use crate::variables::{ChannelVariable, LoopbackHangupCause};
400 use std::collections::HashMap;
401
402 struct TestStore(HashMap<String, String>);
403
404 impl SipHeaderLookup for TestStore {
405 fn sip_header_str(&self, name: &str) -> Option<&str> {
406 self.0
407 .get(name)
408 .map(|s| s.as_str())
409 }
410 }
411
412 impl HeaderLookup for TestStore {
413 fn header_str(&self, name: &str) -> Option<&str> {
414 self.0
415 .get(name)
416 .map(|s| s.as_str())
417 }
418 fn variable_str(&self, name: &str) -> Option<&str> {
419 self.0
420 .get(&format!("variable_{}", name))
421 .map(|s| s.as_str())
422 }
423 }
424
425 fn store_with(pairs: &[(&str, &str)]) -> TestStore {
426 let map: HashMap<String, String> = pairs
427 .iter()
428 .map(|(k, v)| (k.to_string(), v.to_string()))
429 .collect();
430 TestStore(map)
431 }
432
433 #[test]
434 fn header_str_direct() {
435 let s = store_with(&[("Unique-ID", "abc-123")]);
436 assert_eq!(s.header_str("Unique-ID"), Some("abc-123"));
437 assert_eq!(s.header_str("Missing"), None);
438 }
439
440 #[test]
441 fn header_by_enum() {
442 let s = store_with(&[("Unique-ID", "abc-123")]);
443 assert_eq!(s.header(EventHeader::UniqueId), Some("abc-123"));
444 }
445
446 #[test]
447 fn variable_str_direct() {
448 let s = store_with(&[("variable_read_codec", "PCMU")]);
449 assert_eq!(s.variable_str("read_codec"), Some("PCMU"));
450 assert_eq!(s.variable_str("missing"), None);
451 }
452
453 #[test]
454 fn variable_by_enum() {
455 let s = store_with(&[("variable_read_codec", "PCMU")]);
456 assert_eq!(s.variable(ChannelVariable::ReadCodec), Some("PCMU"));
457 }
458
459 #[test]
460 fn unique_id_primary() {
461 let s = store_with(&[("Unique-ID", "uuid-1")]);
462 assert_eq!(s.unique_id(), Some("uuid-1"));
463 }
464
465 #[test]
466 fn unique_id_fallback() {
467 let s = store_with(&[("Caller-Unique-ID", "uuid-2")]);
468 assert_eq!(s.unique_id(), Some("uuid-2"));
469 }
470
471 #[test]
472 fn unique_id_none() {
473 let s = store_with(&[]);
474 assert_eq!(s.unique_id(), None);
475 }
476
477 #[test]
478 fn job_uuid() {
479 let s = store_with(&[("Job-UUID", "job-1")]);
480 assert_eq!(s.job_uuid(), Some("job-1"));
481 }
482
483 #[test]
484 fn channel_name() {
485 let s = store_with(&[("Channel-Name", "sofia/internal/1000@example.com")]);
486 assert_eq!(s.channel_name(), Some("sofia/internal/1000@example.com"));
487 }
488
489 #[test]
490 fn caller_id_number_and_name() {
491 let s = store_with(&[
492 ("Caller-Caller-ID-Number", "1000"),
493 ("Caller-Caller-ID-Name", "Alice"),
494 ]);
495 assert_eq!(s.caller_id_number(), Some("1000"));
496 assert_eq!(s.caller_id_name(), Some("Alice"));
497 }
498
499 #[test]
500 fn hangup_cause_typed() {
501 let s = store_with(&[("Hangup-Cause", "NORMAL_CLEARING")]);
502 assert_eq!(
503 s.hangup_cause()
504 .unwrap(),
505 Some(crate::channel::HangupCause::NormalClearing)
506 );
507 }
508
509 #[test]
510 fn hangup_cause_invalid_is_error() {
511 let s = store_with(&[("Hangup-Cause", "BOGUS_CAUSE")]);
512 assert!(s
513 .hangup_cause()
514 .is_err());
515 }
516
517 #[test]
518 fn destination_number() {
519 let s = store_with(&[("Caller-Destination-Number", "1000")]);
520 assert_eq!(s.destination_number(), Some("1000"));
521 }
522
523 #[test]
524 fn callee_id() {
525 let s = store_with(&[
526 ("Caller-Callee-ID-Number", "2000"),
527 ("Caller-Callee-ID-Name", "Bob"),
528 ]);
529 assert_eq!(s.callee_id_number(), Some("2000"));
530 assert_eq!(s.callee_id_name(), Some("Bob"));
531 }
532
533 #[test]
534 fn event_subclass() {
535 let s = store_with(&[("Event-Subclass", "sofia::register")]);
536 assert_eq!(s.event_subclass(), Some("sofia::register"));
537 }
538
539 #[test]
540 fn sofia_event_subclass_typed() {
541 let s = store_with(&[("Event-Subclass", "sofia::gateway_state")]);
542 assert_eq!(
543 s.sofia_event_subclass()
544 .unwrap(),
545 Some(crate::sofia::SofiaEventSubclass::GatewayState)
546 );
547 }
548
549 #[test]
550 fn sofia_event_subclass_absent() {
551 let s = store_with(&[]);
552 assert_eq!(
553 s.sofia_event_subclass()
554 .unwrap(),
555 None
556 );
557 }
558
559 #[test]
560 fn sofia_event_subclass_non_sofia_is_error() {
561 let s = store_with(&[("Event-Subclass", "conference::maintenance")]);
562 assert!(s
563 .sofia_event_subclass()
564 .is_err());
565 }
566
567 #[test]
568 fn gateway_reg_state_typed() {
569 let s = store_with(&[("State", "REGED")]);
570 assert_eq!(
571 s.gateway_reg_state()
572 .unwrap(),
573 Some(crate::sofia::GatewayRegState::Reged)
574 );
575 }
576
577 #[test]
578 fn gateway_reg_state_invalid_is_error() {
579 let s = store_with(&[("State", "BOGUS")]);
580 assert!(s
581 .gateway_reg_state()
582 .is_err());
583 }
584
585 #[test]
586 fn gateway_ping_status_typed() {
587 let s = store_with(&[("Ping-Status", "UP")]);
588 assert_eq!(
589 s.gateway_ping_status()
590 .unwrap(),
591 Some(crate::sofia::GatewayPingStatus::Up)
592 );
593 }
594
595 #[test]
596 fn sip_user_ping_status_typed() {
597 let s = store_with(&[("Ping-Status", "REACHABLE")]);
598 assert_eq!(
599 s.sip_user_ping_status()
600 .unwrap(),
601 Some(crate::sofia::SipUserPingStatus::Reachable)
602 );
603 }
604
605 #[test]
606 fn gateway_accessor() {
607 let s = store_with(&[("Gateway", "my-gateway")]);
608 assert_eq!(s.gateway(), Some("my-gateway"));
609 }
610
611 #[test]
612 fn profile_name_accessor() {
613 let s = store_with(&[("profile-name", "internal")]);
614 assert_eq!(s.profile_name(), Some("internal"));
615 }
616
617 #[test]
618 fn phrase_accessor() {
619 let s = store_with(&[("Phrase", "OK")]);
620 assert_eq!(s.phrase(), Some("OK"));
621 }
622
623 #[test]
624 fn channel_state_typed() {
625 let s = store_with(&[("Channel-State", "CS_EXECUTE")]);
626 assert_eq!(
627 s.channel_state()
628 .unwrap(),
629 Some(ChannelState::CsExecute)
630 );
631 }
632
633 #[test]
634 fn channel_state_number_typed() {
635 let s = store_with(&[("Channel-State-Number", "4")]);
636 assert_eq!(
637 s.channel_state_number()
638 .unwrap(),
639 Some(ChannelState::CsExecute)
640 );
641 }
642
643 #[test]
644 fn call_state_typed() {
645 let s = store_with(&[("Channel-Call-State", "ACTIVE")]);
646 assert_eq!(
647 s.call_state()
648 .unwrap(),
649 Some(CallState::Active)
650 );
651 }
652
653 #[test]
654 fn answer_state_typed() {
655 let s = store_with(&[("Answer-State", "answered")]);
656 assert_eq!(
657 s.answer_state()
658 .unwrap(),
659 Some(AnswerState::Answered)
660 );
661 }
662
663 #[test]
664 fn call_direction_typed() {
665 let s = store_with(&[("Call-Direction", "inbound")]);
666 assert_eq!(
667 s.call_direction()
668 .unwrap(),
669 Some(CallDirection::Inbound)
670 );
671 }
672
673 #[test]
674 fn priority_typed() {
675 let s = store_with(&[("priority", "HIGH")]);
676 assert_eq!(
677 s.priority()
678 .unwrap(),
679 Some(EslEventPriority::High)
680 );
681 }
682
683 #[test]
684 fn timetable_extraction() {
685 let s = store_with(&[
686 ("Caller-Channel-Created-Time", "1700000001000000"),
687 ("Caller-Channel-Answered-Time", "1700000005000000"),
688 ]);
689 let tt = s
690 .caller_timetable()
691 .unwrap()
692 .expect("should have timetable");
693 assert_eq!(tt.created, Some(1700000001000000));
694 assert_eq!(tt.answered, Some(1700000005000000));
695 assert_eq!(tt.hungup, None);
696 }
697
698 #[test]
699 fn timetable_other_leg() {
700 let s = store_with(&[("Other-Leg-Channel-Created-Time", "1700000001000000")]);
701 let tt = s
702 .other_leg_timetable()
703 .unwrap()
704 .expect("should have timetable");
705 assert_eq!(tt.created, Some(1700000001000000));
706 }
707
708 #[test]
709 fn timetable_none_when_absent() {
710 let s = store_with(&[]);
711 assert_eq!(
712 s.caller_timetable()
713 .unwrap(),
714 None
715 );
716 }
717
718 #[test]
719 fn timetable_invalid_is_error() {
720 let s = store_with(&[("Caller-Channel-Created-Time", "not_a_number")]);
721 let err = s
722 .caller_timetable()
723 .unwrap_err();
724 assert_eq!(err.header, "Caller-Channel-Created-Time");
725 }
726
727 #[test]
730 fn loopback_resignation_absent_on_a_real_teardown() {
731 let s = store_with(&[("variable_hangup_cause", "NORMAL_CLEARING")]);
732 assert!(s
733 .loopback_resignation()
734 .is_none());
735 }
736
737 #[test]
738 fn loopback_resignation_on_execute_masquerade_path() {
739 let s = store_with(&[
740 ("variable_loopback_hangup_cause", "bowout"),
741 ("variable_loopback_bowout_other_uuid", "survivor-uuid"),
742 ]);
743 let r = s
744 .loopback_resignation()
745 .expect("marker present");
746 assert_eq!(r.cause(), Ok(LoopbackHangupCause::Bowout));
747 assert_eq!(r.cause_raw(), "bowout");
748 assert_eq!(r.other_uuid(), Some("survivor-uuid"));
749 }
750
751 #[test]
752 fn loopback_resignation_on_frame_count_path() {
753 let s = store_with(&[
754 ("variable_loopback_hangup_cause", "bridge"),
755 ("variable_loopback_bowout_other_uuid", "survivor-uuid"),
756 ]);
757 let r = s
758 .loopback_resignation()
759 .expect("marker present");
760 assert_eq!(r.cause(), Ok(LoopbackHangupCause::Bridge));
761 assert_eq!(r.other_uuid(), Some("survivor-uuid"));
762 }
763
764 #[test]
767 fn loopback_resignation_survives_an_unknown_token() {
768 let s = store_with(&[
769 ("variable_loopback_hangup_cause", "some_future_path"),
770 ("variable_loopback_bowout_other_uuid", "survivor-uuid"),
771 ]);
772 let r = s
773 .loopback_resignation()
774 .expect("presence is the signal, not the value");
775 assert_eq!(r.other_uuid(), Some("survivor-uuid"));
776 assert_eq!(r.cause_raw(), "some_future_path");
777 assert!(r
778 .cause()
779 .is_err());
780 }
781
782 #[test]
783 fn loopback_resignation_without_a_surviving_uuid() {
784 let s = store_with(&[("variable_loopback_hangup_cause", "bridge")]);
785 let r = s
786 .loopback_resignation()
787 .expect("marker present");
788 assert_eq!(r.other_uuid(), None);
789 }
790
791 #[test]
792 fn missing_headers_return_none() {
793 let s = store_with(&[]);
794 assert_eq!(
795 s.channel_state()
796 .unwrap(),
797 None
798 );
799 assert_eq!(
800 s.channel_state_number()
801 .unwrap(),
802 None
803 );
804 assert_eq!(
805 s.call_state()
806 .unwrap(),
807 None
808 );
809 assert_eq!(
810 s.answer_state()
811 .unwrap(),
812 None
813 );
814 assert_eq!(
815 s.call_direction()
816 .unwrap(),
817 None
818 );
819 assert_eq!(
820 s.priority()
821 .unwrap(),
822 None
823 );
824 assert_eq!(
825 s.hangup_cause()
826 .unwrap(),
827 None
828 );
829 assert_eq!(s.channel_name(), None);
830 assert_eq!(s.caller_id_number(), None);
831 assert_eq!(s.caller_id_name(), None);
832 assert_eq!(s.destination_number(), None);
833 assert_eq!(s.callee_id_number(), None);
834 assert_eq!(s.callee_id_name(), None);
835 assert_eq!(s.event_subclass(), None);
836 assert_eq!(s.job_uuid(), None);
837 assert_eq!(s.pl_data(), None);
838 assert_eq!(s.sip_event(), None);
839 assert_eq!(s.gateway_name(), None);
840 assert_eq!(s.channel_presence_id(), None);
841 assert_eq!(
842 s.presence_call_direction()
843 .unwrap(),
844 None
845 );
846 assert_eq!(s.event_date_timestamp(), None);
847 assert_eq!(s.event_sequence(), None);
848 assert_eq!(s.dtmf_duration(), None);
849 assert_eq!(s.dtmf_source(), None);
850 }
851
852 #[test]
853 fn notify_in_headers() {
854 let s = store_with(&[
855 ("pl_data", r#"{"invite":"INVITE ..."}"#),
856 ("event", "emergency-AbandonedCall"),
857 ("gateway_name", "ng911-bcf"),
858 ]);
859 assert_eq!(s.pl_data(), Some(r#"{"invite":"INVITE ..."}"#));
860 assert_eq!(s.sip_event(), Some("emergency-AbandonedCall"));
861 assert_eq!(s.gateway_name(), Some("ng911-bcf"));
862 }
863
864 #[test]
865 fn channel_presence_id() {
866 let s = store_with(&[("Channel-Presence-ID", "1000@example.com")]);
867 assert_eq!(s.channel_presence_id(), Some("1000@example.com"));
868 }
869
870 #[test]
871 fn presence_call_direction_typed() {
872 let s = store_with(&[("Presence-Call-Direction", "outbound")]);
873 assert_eq!(
874 s.presence_call_direction()
875 .unwrap(),
876 Some(CallDirection::Outbound)
877 );
878 }
879
880 #[test]
881 fn event_date_timestamp() {
882 let s = store_with(&[("Event-Date-Timestamp", "1700000001000000")]);
883 assert_eq!(s.event_date_timestamp(), Some("1700000001000000"));
884 }
885
886 #[test]
887 fn event_sequence() {
888 let s = store_with(&[("Event-Sequence", "12345")]);
889 assert_eq!(s.event_sequence(), Some("12345"));
890 }
891
892 #[test]
893 fn dtmf_duration() {
894 let s = store_with(&[("DTMF-Duration", "2000")]);
895 assert_eq!(s.dtmf_duration(), Some("2000"));
896 }
897
898 #[test]
899 fn dtmf_source() {
900 let s = store_with(&[("DTMF-Source", "rtp")]);
901 assert_eq!(s.dtmf_source(), Some("rtp"));
902 }
903
904 #[test]
905 fn invalid_values_return_err() {
906 let s = store_with(&[
907 ("Channel-State", "BOGUS"),
908 ("Channel-State-Number", "999"),
909 ("Channel-Call-State", "BOGUS"),
910 ("Answer-State", "bogus"),
911 ("Call-Direction", "bogus"),
912 ("Presence-Call-Direction", "bogus"),
913 ("priority", "BOGUS"),
914 ("Hangup-Cause", "BOGUS"),
915 ]);
916 assert!(s
917 .channel_state()
918 .is_err());
919 assert!(s
920 .channel_state_number()
921 .is_err());
922 assert!(s
923 .call_state()
924 .is_err());
925 assert!(s
926 .answer_state()
927 .is_err());
928 assert!(s
929 .call_direction()
930 .is_err());
931 assert!(s
932 .presence_call_direction()
933 .is_err());
934 assert!(s
935 .priority()
936 .is_err());
937 assert!(s
938 .hangup_cause()
939 .is_err());
940 }
941}