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::VariableName;
22
23pub trait HeaderLookup {
69 fn header_str(&self, name: &str) -> Option<&str>;
71
72 fn variable_str(&self, name: &str) -> Option<&str>;
76
77 fn header(&self, name: EventHeader) -> Option<&str> {
79 self.header_str(name.as_str())
80 }
81
82 fn variable(&self, name: impl VariableName) -> Option<&str> {
84 self.variable_str(name.as_str())
85 }
86
87 fn unique_id(&self) -> Option<&str> {
89 self.header(EventHeader::UniqueId)
90 .or_else(|| self.header(EventHeader::CallerUniqueId))
91 }
92
93 fn job_uuid(&self) -> Option<&str> {
95 self.header(EventHeader::JobUuid)
96 }
97
98 fn channel_name(&self) -> Option<&str> {
100 self.header(EventHeader::ChannelName)
101 }
102
103 fn caller_id_number(&self) -> Option<&str> {
105 self.header(EventHeader::CallerCallerIdNumber)
106 }
107
108 fn caller_id_name(&self) -> Option<&str> {
110 self.header(EventHeader::CallerCallerIdName)
111 }
112
113 fn destination_number(&self) -> Option<&str> {
115 self.header(EventHeader::CallerDestinationNumber)
116 }
117
118 fn callee_id_number(&self) -> Option<&str> {
120 self.header(EventHeader::CallerCalleeIdNumber)
121 }
122
123 fn callee_id_name(&self) -> Option<&str> {
125 self.header(EventHeader::CallerCalleeIdName)
126 }
127
128 fn channel_presence_id(&self) -> Option<&str> {
130 self.header(EventHeader::ChannelPresenceId)
131 }
132
133 fn presence_call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
135 match self.header(EventHeader::PresenceCallDirection) {
136 Some(s) => Ok(Some(s.parse()?)),
137 None => Ok(None),
138 }
139 }
140
141 fn event_date_timestamp(&self) -> Option<&str> {
143 self.header(EventHeader::EventDateTimestamp)
144 }
145
146 fn event_sequence(&self) -> Option<&str> {
148 self.header(EventHeader::EventSequence)
149 }
150
151 fn dtmf_duration(&self) -> Option<&str> {
153 self.header(EventHeader::DtmfDuration)
154 }
155
156 fn dtmf_source(&self) -> Option<&str> {
158 self.header(EventHeader::DtmfSource)
159 }
160
161 fn hangup_cause(&self) -> Result<Option<HangupCause>, ParseHangupCauseError> {
165 match self.header(EventHeader::HangupCause) {
166 Some(s) => Ok(Some(s.parse()?)),
167 None => Ok(None),
168 }
169 }
170
171 fn event_subclass(&self) -> Option<&str> {
173 self.header(EventHeader::EventSubclass)
174 }
175
176 fn sofia_event_subclass(
181 &self,
182 ) -> Result<Option<SofiaEventSubclass>, ParseSofiaEventSubclassError> {
183 match self.event_subclass() {
184 Some(s) => Ok(Some(s.parse()?)),
185 None => Ok(None),
186 }
187 }
188
189 fn gateway(&self) -> Option<&str> {
191 self.header(EventHeader::Gateway)
192 }
193
194 fn profile_name(&self) -> Option<&str> {
196 self.header(EventHeader::ProfileName)
197 }
198
199 fn phrase(&self) -> Option<&str> {
201 self.header(EventHeader::Phrase)
202 }
203
204 fn sip_status_code(&self) -> Result<Option<u16>, std::num::ParseIntError> {
207 match self.header(EventHeader::Status) {
208 Some(s) => Ok(Some(s.parse()?)),
209 None => Ok(None),
210 }
211 }
212
213 fn gateway_reg_state(&self) -> Result<Option<GatewayRegState>, ParseGatewayRegStateError> {
217 match self.header(EventHeader::State) {
218 Some(s) => Ok(Some(s.parse()?)),
219 None => Ok(None),
220 }
221 }
222
223 fn gateway_ping_status(
228 &self,
229 ) -> Result<Option<GatewayPingStatus>, ParseGatewayPingStatusError> {
230 match self.header(EventHeader::PingStatus) {
231 Some(s) => Ok(Some(s.parse()?)),
232 None => Ok(None),
233 }
234 }
235
236 fn sip_user_ping_status(
241 &self,
242 ) -> Result<Option<SipUserPingStatus>, ParseSipUserPingStatusError> {
243 match self.header(EventHeader::PingStatus) {
244 Some(s) => Ok(Some(s.parse()?)),
245 None => Ok(None),
246 }
247 }
248
249 fn pl_data(&self) -> Option<&str> {
255 self.header(EventHeader::PlData)
256 }
257
258 fn sip_event(&self) -> Option<&str> {
262 self.header(EventHeader::SipEvent)
263 }
264
265 fn gateway_name(&self) -> Option<&str> {
267 self.header(EventHeader::GatewayName)
268 }
269
270 fn channel_state(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
274 match self.header(EventHeader::ChannelState) {
275 Some(s) => Ok(Some(s.parse()?)),
276 None => Ok(None),
277 }
278 }
279
280 fn channel_state_number(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
284 match self.header(EventHeader::ChannelStateNumber) {
285 Some(s) => {
286 let n: u8 = s
287 .parse()
288 .map_err(|_| ParseChannelStateError(s.to_string()))?;
289 ChannelState::from_number(n)
290 .ok_or_else(|| ParseChannelStateError(s.to_string()))
291 .map(Some)
292 }
293 None => Ok(None),
294 }
295 }
296
297 fn call_state(&self) -> Result<Option<CallState>, ParseCallStateError> {
301 match self.header(EventHeader::ChannelCallState) {
302 Some(s) => Ok(Some(s.parse()?)),
303 None => Ok(None),
304 }
305 }
306
307 fn answer_state(&self) -> Result<Option<AnswerState>, ParseAnswerStateError> {
311 match self.header(EventHeader::AnswerState) {
312 Some(s) => Ok(Some(s.parse()?)),
313 None => Ok(None),
314 }
315 }
316
317 fn call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
321 match self.header(EventHeader::CallDirection) {
322 Some(s) => Ok(Some(s.parse()?)),
323 None => Ok(None),
324 }
325 }
326
327 #[cfg(feature = "esl")]
331 fn priority(&self) -> Result<Option<EslEventPriority>, ParsePriorityError> {
332 match self.header(EventHeader::Priority) {
333 Some(s) => Ok(Some(s.parse()?)),
334 None => Ok(None),
335 }
336 }
337
338 fn timetable(&self, prefix: &str) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
343 ChannelTimetable::from_lookup(prefix, |key| self.header_str(key))
344 }
345
346 fn caller_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
348 self.timetable("Caller")
349 }
350
351 fn other_leg_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
353 self.timetable("Other-Leg")
354 }
355}
356
357impl HeaderLookup for std::collections::HashMap<String, String> {
358 fn header_str(&self, name: &str) -> Option<&str> {
359 self.get(name)
360 .map(|s| s.as_str())
361 }
362
363 fn variable_str(&self, name: &str) -> Option<&str> {
364 self.get(&format!("variable_{name}"))
365 .map(|s| s.as_str())
366 }
367}
368
369#[cfg(feature = "esl")]
370impl HeaderLookup for indexmap::IndexMap<String, String> {
371 fn header_str(&self, name: &str) -> Option<&str> {
372 self.get(name)
373 .map(|s| s.as_str())
374 }
375
376 fn variable_str(&self, name: &str) -> Option<&str> {
377 self.get(&format!("variable_{name}"))
378 .map(|s| s.as_str())
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385 use crate::variables::ChannelVariable;
386 use std::collections::HashMap;
387
388 struct TestStore(HashMap<String, String>);
389
390 impl HeaderLookup for TestStore {
391 fn header_str(&self, name: &str) -> Option<&str> {
392 self.0
393 .get(name)
394 .map(|s| s.as_str())
395 }
396 fn variable_str(&self, name: &str) -> Option<&str> {
397 self.0
398 .get(&format!("variable_{}", name))
399 .map(|s| s.as_str())
400 }
401 }
402
403 fn store_with(pairs: &[(&str, &str)]) -> TestStore {
404 let map: HashMap<String, String> = pairs
405 .iter()
406 .map(|(k, v)| (k.to_string(), v.to_string()))
407 .collect();
408 TestStore(map)
409 }
410
411 #[test]
412 fn header_str_direct() {
413 let s = store_with(&[("Unique-ID", "abc-123")]);
414 assert_eq!(s.header_str("Unique-ID"), Some("abc-123"));
415 assert_eq!(s.header_str("Missing"), None);
416 }
417
418 #[test]
419 fn header_by_enum() {
420 let s = store_with(&[("Unique-ID", "abc-123")]);
421 assert_eq!(s.header(EventHeader::UniqueId), Some("abc-123"));
422 }
423
424 #[test]
425 fn variable_str_direct() {
426 let s = store_with(&[("variable_read_codec", "PCMU")]);
427 assert_eq!(s.variable_str("read_codec"), Some("PCMU"));
428 assert_eq!(s.variable_str("missing"), None);
429 }
430
431 #[test]
432 fn variable_by_enum() {
433 let s = store_with(&[("variable_read_codec", "PCMU")]);
434 assert_eq!(s.variable(ChannelVariable::ReadCodec), Some("PCMU"));
435 }
436
437 #[test]
438 fn unique_id_primary() {
439 let s = store_with(&[("Unique-ID", "uuid-1")]);
440 assert_eq!(s.unique_id(), Some("uuid-1"));
441 }
442
443 #[test]
444 fn unique_id_fallback() {
445 let s = store_with(&[("Caller-Unique-ID", "uuid-2")]);
446 assert_eq!(s.unique_id(), Some("uuid-2"));
447 }
448
449 #[test]
450 fn unique_id_none() {
451 let s = store_with(&[]);
452 assert_eq!(s.unique_id(), None);
453 }
454
455 #[test]
456 fn job_uuid() {
457 let s = store_with(&[("Job-UUID", "job-1")]);
458 assert_eq!(s.job_uuid(), Some("job-1"));
459 }
460
461 #[test]
462 fn channel_name() {
463 let s = store_with(&[("Channel-Name", "sofia/internal/1000@example.com")]);
464 assert_eq!(s.channel_name(), Some("sofia/internal/1000@example.com"));
465 }
466
467 #[test]
468 fn caller_id_number_and_name() {
469 let s = store_with(&[
470 ("Caller-Caller-ID-Number", "1000"),
471 ("Caller-Caller-ID-Name", "Alice"),
472 ]);
473 assert_eq!(s.caller_id_number(), Some("1000"));
474 assert_eq!(s.caller_id_name(), Some("Alice"));
475 }
476
477 #[test]
478 fn hangup_cause_typed() {
479 let s = store_with(&[("Hangup-Cause", "NORMAL_CLEARING")]);
480 assert_eq!(
481 s.hangup_cause()
482 .unwrap(),
483 Some(crate::channel::HangupCause::NormalClearing)
484 );
485 }
486
487 #[test]
488 fn hangup_cause_invalid_is_error() {
489 let s = store_with(&[("Hangup-Cause", "BOGUS_CAUSE")]);
490 assert!(s
491 .hangup_cause()
492 .is_err());
493 }
494
495 #[test]
496 fn destination_number() {
497 let s = store_with(&[("Caller-Destination-Number", "1000")]);
498 assert_eq!(s.destination_number(), Some("1000"));
499 }
500
501 #[test]
502 fn callee_id() {
503 let s = store_with(&[
504 ("Caller-Callee-ID-Number", "2000"),
505 ("Caller-Callee-ID-Name", "Bob"),
506 ]);
507 assert_eq!(s.callee_id_number(), Some("2000"));
508 assert_eq!(s.callee_id_name(), Some("Bob"));
509 }
510
511 #[test]
512 fn event_subclass() {
513 let s = store_with(&[("Event-Subclass", "sofia::register")]);
514 assert_eq!(s.event_subclass(), Some("sofia::register"));
515 }
516
517 #[test]
518 fn sofia_event_subclass_typed() {
519 let s = store_with(&[("Event-Subclass", "sofia::gateway_state")]);
520 assert_eq!(
521 s.sofia_event_subclass()
522 .unwrap(),
523 Some(crate::sofia::SofiaEventSubclass::GatewayState)
524 );
525 }
526
527 #[test]
528 fn sofia_event_subclass_absent() {
529 let s = store_with(&[]);
530 assert_eq!(
531 s.sofia_event_subclass()
532 .unwrap(),
533 None
534 );
535 }
536
537 #[test]
538 fn sofia_event_subclass_non_sofia_is_error() {
539 let s = store_with(&[("Event-Subclass", "conference::maintenance")]);
540 assert!(s
541 .sofia_event_subclass()
542 .is_err());
543 }
544
545 #[test]
546 fn gateway_reg_state_typed() {
547 let s = store_with(&[("State", "REGED")]);
548 assert_eq!(
549 s.gateway_reg_state()
550 .unwrap(),
551 Some(crate::sofia::GatewayRegState::Reged)
552 );
553 }
554
555 #[test]
556 fn gateway_reg_state_invalid_is_error() {
557 let s = store_with(&[("State", "BOGUS")]);
558 assert!(s
559 .gateway_reg_state()
560 .is_err());
561 }
562
563 #[test]
564 fn gateway_ping_status_typed() {
565 let s = store_with(&[("Ping-Status", "UP")]);
566 assert_eq!(
567 s.gateway_ping_status()
568 .unwrap(),
569 Some(crate::sofia::GatewayPingStatus::Up)
570 );
571 }
572
573 #[test]
574 fn sip_user_ping_status_typed() {
575 let s = store_with(&[("Ping-Status", "REACHABLE")]);
576 assert_eq!(
577 s.sip_user_ping_status()
578 .unwrap(),
579 Some(crate::sofia::SipUserPingStatus::Reachable)
580 );
581 }
582
583 #[test]
584 fn gateway_accessor() {
585 let s = store_with(&[("Gateway", "my-gateway")]);
586 assert_eq!(s.gateway(), Some("my-gateway"));
587 }
588
589 #[test]
590 fn profile_name_accessor() {
591 let s = store_with(&[("profile-name", "internal")]);
592 assert_eq!(s.profile_name(), Some("internal"));
593 }
594
595 #[test]
596 fn phrase_accessor() {
597 let s = store_with(&[("Phrase", "OK")]);
598 assert_eq!(s.phrase(), Some("OK"));
599 }
600
601 #[test]
602 fn channel_state_typed() {
603 let s = store_with(&[("Channel-State", "CS_EXECUTE")]);
604 assert_eq!(
605 s.channel_state()
606 .unwrap(),
607 Some(ChannelState::CsExecute)
608 );
609 }
610
611 #[test]
612 fn channel_state_number_typed() {
613 let s = store_with(&[("Channel-State-Number", "4")]);
614 assert_eq!(
615 s.channel_state_number()
616 .unwrap(),
617 Some(ChannelState::CsExecute)
618 );
619 }
620
621 #[test]
622 fn call_state_typed() {
623 let s = store_with(&[("Channel-Call-State", "ACTIVE")]);
624 assert_eq!(
625 s.call_state()
626 .unwrap(),
627 Some(CallState::Active)
628 );
629 }
630
631 #[test]
632 fn answer_state_typed() {
633 let s = store_with(&[("Answer-State", "answered")]);
634 assert_eq!(
635 s.answer_state()
636 .unwrap(),
637 Some(AnswerState::Answered)
638 );
639 }
640
641 #[test]
642 fn call_direction_typed() {
643 let s = store_with(&[("Call-Direction", "inbound")]);
644 assert_eq!(
645 s.call_direction()
646 .unwrap(),
647 Some(CallDirection::Inbound)
648 );
649 }
650
651 #[test]
652 fn priority_typed() {
653 let s = store_with(&[("priority", "HIGH")]);
654 assert_eq!(
655 s.priority()
656 .unwrap(),
657 Some(EslEventPriority::High)
658 );
659 }
660
661 #[test]
662 fn timetable_extraction() {
663 let s = store_with(&[
664 ("Caller-Channel-Created-Time", "1700000001000000"),
665 ("Caller-Channel-Answered-Time", "1700000005000000"),
666 ]);
667 let tt = s
668 .caller_timetable()
669 .unwrap()
670 .expect("should have timetable");
671 assert_eq!(tt.created, Some(1700000001000000));
672 assert_eq!(tt.answered, Some(1700000005000000));
673 assert_eq!(tt.hungup, None);
674 }
675
676 #[test]
677 fn timetable_other_leg() {
678 let s = store_with(&[("Other-Leg-Channel-Created-Time", "1700000001000000")]);
679 let tt = s
680 .other_leg_timetable()
681 .unwrap()
682 .expect("should have timetable");
683 assert_eq!(tt.created, Some(1700000001000000));
684 }
685
686 #[test]
687 fn timetable_none_when_absent() {
688 let s = store_with(&[]);
689 assert_eq!(
690 s.caller_timetable()
691 .unwrap(),
692 None
693 );
694 }
695
696 #[test]
697 fn timetable_invalid_is_error() {
698 let s = store_with(&[("Caller-Channel-Created-Time", "not_a_number")]);
699 let err = s
700 .caller_timetable()
701 .unwrap_err();
702 assert_eq!(err.header, "Caller-Channel-Created-Time");
703 }
704
705 #[test]
706 fn missing_headers_return_none() {
707 let s = store_with(&[]);
708 assert_eq!(
709 s.channel_state()
710 .unwrap(),
711 None
712 );
713 assert_eq!(
714 s.channel_state_number()
715 .unwrap(),
716 None
717 );
718 assert_eq!(
719 s.call_state()
720 .unwrap(),
721 None
722 );
723 assert_eq!(
724 s.answer_state()
725 .unwrap(),
726 None
727 );
728 assert_eq!(
729 s.call_direction()
730 .unwrap(),
731 None
732 );
733 assert_eq!(
734 s.priority()
735 .unwrap(),
736 None
737 );
738 assert_eq!(
739 s.hangup_cause()
740 .unwrap(),
741 None
742 );
743 assert_eq!(s.channel_name(), None);
744 assert_eq!(s.caller_id_number(), None);
745 assert_eq!(s.caller_id_name(), None);
746 assert_eq!(s.destination_number(), None);
747 assert_eq!(s.callee_id_number(), None);
748 assert_eq!(s.callee_id_name(), None);
749 assert_eq!(s.event_subclass(), None);
750 assert_eq!(s.job_uuid(), None);
751 assert_eq!(s.pl_data(), None);
752 assert_eq!(s.sip_event(), None);
753 assert_eq!(s.gateway_name(), None);
754 assert_eq!(s.channel_presence_id(), None);
755 assert_eq!(
756 s.presence_call_direction()
757 .unwrap(),
758 None
759 );
760 assert_eq!(s.event_date_timestamp(), None);
761 assert_eq!(s.event_sequence(), None);
762 assert_eq!(s.dtmf_duration(), None);
763 assert_eq!(s.dtmf_source(), None);
764 }
765
766 #[test]
767 fn notify_in_headers() {
768 let s = store_with(&[
769 ("pl_data", r#"{"invite":"INVITE ..."}"#),
770 ("event", "emergency-AbandonedCall"),
771 ("gateway_name", "ng911-bcf"),
772 ]);
773 assert_eq!(s.pl_data(), Some(r#"{"invite":"INVITE ..."}"#));
774 assert_eq!(s.sip_event(), Some("emergency-AbandonedCall"));
775 assert_eq!(s.gateway_name(), Some("ng911-bcf"));
776 }
777
778 #[test]
779 fn channel_presence_id() {
780 let s = store_with(&[("Channel-Presence-ID", "1000@example.com")]);
781 assert_eq!(s.channel_presence_id(), Some("1000@example.com"));
782 }
783
784 #[test]
785 fn presence_call_direction_typed() {
786 let s = store_with(&[("Presence-Call-Direction", "outbound")]);
787 assert_eq!(
788 s.presence_call_direction()
789 .unwrap(),
790 Some(CallDirection::Outbound)
791 );
792 }
793
794 #[test]
795 fn event_date_timestamp() {
796 let s = store_with(&[("Event-Date-Timestamp", "1700000001000000")]);
797 assert_eq!(s.event_date_timestamp(), Some("1700000001000000"));
798 }
799
800 #[test]
801 fn event_sequence() {
802 let s = store_with(&[("Event-Sequence", "12345")]);
803 assert_eq!(s.event_sequence(), Some("12345"));
804 }
805
806 #[test]
807 fn dtmf_duration() {
808 let s = store_with(&[("DTMF-Duration", "2000")]);
809 assert_eq!(s.dtmf_duration(), Some("2000"));
810 }
811
812 #[test]
813 fn dtmf_source() {
814 let s = store_with(&[("DTMF-Source", "rtp")]);
815 assert_eq!(s.dtmf_source(), Some("rtp"));
816 }
817
818 #[test]
819 fn invalid_values_return_err() {
820 let s = store_with(&[
821 ("Channel-State", "BOGUS"),
822 ("Channel-State-Number", "999"),
823 ("Channel-Call-State", "BOGUS"),
824 ("Answer-State", "bogus"),
825 ("Call-Direction", "bogus"),
826 ("Presence-Call-Direction", "bogus"),
827 ("priority", "BOGUS"),
828 ("Hangup-Cause", "BOGUS"),
829 ]);
830 assert!(s
831 .channel_state()
832 .is_err());
833 assert!(s
834 .channel_state_number()
835 .is_err());
836 assert!(s
837 .call_state()
838 .is_err());
839 assert!(s
840 .answer_state()
841 .is_err());
842 assert!(s
843 .call_direction()
844 .is_err());
845 assert!(s
846 .presence_call_direction()
847 .is_err());
848 assert!(s
849 .priority()
850 .is_err());
851 assert!(s
852 .hangup_cause()
853 .is_err());
854 }
855}