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