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