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