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::variables::VariableName;
17
18pub trait HeaderLookup {
64 fn header_str(&self, name: &str) -> Option<&str>;
66
67 fn variable_str(&self, name: &str) -> Option<&str>;
71
72 fn header(&self, name: EventHeader) -> Option<&str> {
74 self.header_str(name.as_str())
75 }
76
77 fn variable(&self, name: impl VariableName) -> Option<&str> {
79 self.variable_str(name.as_str())
80 }
81
82 fn unique_id(&self) -> Option<&str> {
84 self.header(EventHeader::UniqueId)
85 .or_else(|| self.header(EventHeader::CallerUniqueId))
86 }
87
88 fn job_uuid(&self) -> Option<&str> {
90 self.header(EventHeader::JobUuid)
91 }
92
93 fn channel_name(&self) -> Option<&str> {
95 self.header(EventHeader::ChannelName)
96 }
97
98 fn caller_id_number(&self) -> Option<&str> {
100 self.header(EventHeader::CallerCallerIdNumber)
101 }
102
103 fn caller_id_name(&self) -> Option<&str> {
105 self.header(EventHeader::CallerCallerIdName)
106 }
107
108 fn destination_number(&self) -> Option<&str> {
110 self.header(EventHeader::CallerDestinationNumber)
111 }
112
113 fn callee_id_number(&self) -> Option<&str> {
115 self.header(EventHeader::CallerCalleeIdNumber)
116 }
117
118 fn callee_id_name(&self) -> Option<&str> {
120 self.header(EventHeader::CallerCalleeIdName)
121 }
122
123 fn channel_presence_id(&self) -> Option<&str> {
125 self.header(EventHeader::ChannelPresenceId)
126 }
127
128 fn presence_call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
130 match self.header(EventHeader::PresenceCallDirection) {
131 Some(s) => Ok(Some(s.parse()?)),
132 None => Ok(None),
133 }
134 }
135
136 fn event_date_timestamp(&self) -> Option<&str> {
138 self.header(EventHeader::EventDateTimestamp)
139 }
140
141 fn event_sequence(&self) -> Option<&str> {
143 self.header(EventHeader::EventSequence)
144 }
145
146 fn dtmf_duration(&self) -> Option<&str> {
148 self.header(EventHeader::DtmfDuration)
149 }
150
151 fn dtmf_source(&self) -> Option<&str> {
153 self.header(EventHeader::DtmfSource)
154 }
155
156 fn hangup_cause(&self) -> Result<Option<HangupCause>, ParseHangupCauseError> {
160 match self.header(EventHeader::HangupCause) {
161 Some(s) => Ok(Some(s.parse()?)),
162 None => Ok(None),
163 }
164 }
165
166 fn event_subclass(&self) -> Option<&str> {
168 self.header(EventHeader::EventSubclass)
169 }
170
171 fn pl_data(&self) -> Option<&str> {
177 self.header(EventHeader::PlData)
178 }
179
180 fn sip_event(&self) -> Option<&str> {
184 self.header(EventHeader::SipEvent)
185 }
186
187 fn gateway_name(&self) -> Option<&str> {
189 self.header(EventHeader::GatewayName)
190 }
191
192 fn channel_state(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
196 match self.header(EventHeader::ChannelState) {
197 Some(s) => Ok(Some(s.parse()?)),
198 None => Ok(None),
199 }
200 }
201
202 fn channel_state_number(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
206 match self.header(EventHeader::ChannelStateNumber) {
207 Some(s) => {
208 let n: u8 = s
209 .parse()
210 .map_err(|_| ParseChannelStateError(s.to_string()))?;
211 ChannelState::from_number(n)
212 .ok_or_else(|| ParseChannelStateError(s.to_string()))
213 .map(Some)
214 }
215 None => Ok(None),
216 }
217 }
218
219 fn call_state(&self) -> Result<Option<CallState>, ParseCallStateError> {
223 match self.header(EventHeader::ChannelCallState) {
224 Some(s) => Ok(Some(s.parse()?)),
225 None => Ok(None),
226 }
227 }
228
229 fn answer_state(&self) -> Result<Option<AnswerState>, ParseAnswerStateError> {
233 match self.header(EventHeader::AnswerState) {
234 Some(s) => Ok(Some(s.parse()?)),
235 None => Ok(None),
236 }
237 }
238
239 fn call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
243 match self.header(EventHeader::CallDirection) {
244 Some(s) => Ok(Some(s.parse()?)),
245 None => Ok(None),
246 }
247 }
248
249 #[cfg(feature = "esl")]
253 fn priority(&self) -> Result<Option<EslEventPriority>, ParsePriorityError> {
254 match self.header(EventHeader::Priority) {
255 Some(s) => Ok(Some(s.parse()?)),
256 None => Ok(None),
257 }
258 }
259
260 fn timetable(&self, prefix: &str) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
265 ChannelTimetable::from_lookup(prefix, |key| self.header_str(key))
266 }
267
268 fn caller_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
270 self.timetable("Caller")
271 }
272
273 fn other_leg_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
275 self.timetable("Other-Leg")
276 }
277}
278
279impl HeaderLookup for std::collections::HashMap<String, String> {
280 fn header_str(&self, name: &str) -> Option<&str> {
281 self.get(name)
282 .map(|s| s.as_str())
283 }
284
285 fn variable_str(&self, name: &str) -> Option<&str> {
286 self.get(&format!("variable_{name}"))
287 .map(|s| s.as_str())
288 }
289}
290
291#[cfg(feature = "esl")]
292impl HeaderLookup for indexmap::IndexMap<String, String> {
293 fn header_str(&self, name: &str) -> Option<&str> {
294 self.get(name)
295 .map(|s| s.as_str())
296 }
297
298 fn variable_str(&self, name: &str) -> Option<&str> {
299 self.get(&format!("variable_{name}"))
300 .map(|s| s.as_str())
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::variables::ChannelVariable;
308 use std::collections::HashMap;
309
310 struct TestStore(HashMap<String, String>);
311
312 impl HeaderLookup for TestStore {
313 fn header_str(&self, name: &str) -> Option<&str> {
314 self.0
315 .get(name)
316 .map(|s| s.as_str())
317 }
318 fn variable_str(&self, name: &str) -> Option<&str> {
319 self.0
320 .get(&format!("variable_{}", name))
321 .map(|s| s.as_str())
322 }
323 }
324
325 fn store_with(pairs: &[(&str, &str)]) -> TestStore {
326 let map: HashMap<String, String> = pairs
327 .iter()
328 .map(|(k, v)| (k.to_string(), v.to_string()))
329 .collect();
330 TestStore(map)
331 }
332
333 #[test]
334 fn header_str_direct() {
335 let s = store_with(&[("Unique-ID", "abc-123")]);
336 assert_eq!(s.header_str("Unique-ID"), Some("abc-123"));
337 assert_eq!(s.header_str("Missing"), None);
338 }
339
340 #[test]
341 fn header_by_enum() {
342 let s = store_with(&[("Unique-ID", "abc-123")]);
343 assert_eq!(s.header(EventHeader::UniqueId), Some("abc-123"));
344 }
345
346 #[test]
347 fn variable_str_direct() {
348 let s = store_with(&[("variable_read_codec", "PCMU")]);
349 assert_eq!(s.variable_str("read_codec"), Some("PCMU"));
350 assert_eq!(s.variable_str("missing"), None);
351 }
352
353 #[test]
354 fn variable_by_enum() {
355 let s = store_with(&[("variable_read_codec", "PCMU")]);
356 assert_eq!(s.variable(ChannelVariable::ReadCodec), Some("PCMU"));
357 }
358
359 #[test]
360 fn unique_id_primary() {
361 let s = store_with(&[("Unique-ID", "uuid-1")]);
362 assert_eq!(s.unique_id(), Some("uuid-1"));
363 }
364
365 #[test]
366 fn unique_id_fallback() {
367 let s = store_with(&[("Caller-Unique-ID", "uuid-2")]);
368 assert_eq!(s.unique_id(), Some("uuid-2"));
369 }
370
371 #[test]
372 fn unique_id_none() {
373 let s = store_with(&[]);
374 assert_eq!(s.unique_id(), None);
375 }
376
377 #[test]
378 fn job_uuid() {
379 let s = store_with(&[("Job-UUID", "job-1")]);
380 assert_eq!(s.job_uuid(), Some("job-1"));
381 }
382
383 #[test]
384 fn channel_name() {
385 let s = store_with(&[("Channel-Name", "sofia/internal/1000@example.com")]);
386 assert_eq!(s.channel_name(), Some("sofia/internal/1000@example.com"));
387 }
388
389 #[test]
390 fn caller_id_number_and_name() {
391 let s = store_with(&[
392 ("Caller-Caller-ID-Number", "1000"),
393 ("Caller-Caller-ID-Name", "Alice"),
394 ]);
395 assert_eq!(s.caller_id_number(), Some("1000"));
396 assert_eq!(s.caller_id_name(), Some("Alice"));
397 }
398
399 #[test]
400 fn hangup_cause_typed() {
401 let s = store_with(&[("Hangup-Cause", "NORMAL_CLEARING")]);
402 assert_eq!(
403 s.hangup_cause()
404 .unwrap(),
405 Some(crate::channel::HangupCause::NormalClearing)
406 );
407 }
408
409 #[test]
410 fn hangup_cause_invalid_is_error() {
411 let s = store_with(&[("Hangup-Cause", "BOGUS_CAUSE")]);
412 assert!(s
413 .hangup_cause()
414 .is_err());
415 }
416
417 #[test]
418 fn destination_number() {
419 let s = store_with(&[("Caller-Destination-Number", "1000")]);
420 assert_eq!(s.destination_number(), Some("1000"));
421 }
422
423 #[test]
424 fn callee_id() {
425 let s = store_with(&[
426 ("Caller-Callee-ID-Number", "2000"),
427 ("Caller-Callee-ID-Name", "Bob"),
428 ]);
429 assert_eq!(s.callee_id_number(), Some("2000"));
430 assert_eq!(s.callee_id_name(), Some("Bob"));
431 }
432
433 #[test]
434 fn event_subclass() {
435 let s = store_with(&[("Event-Subclass", "sofia::register")]);
436 assert_eq!(s.event_subclass(), Some("sofia::register"));
437 }
438
439 #[test]
440 fn channel_state_typed() {
441 let s = store_with(&[("Channel-State", "CS_EXECUTE")]);
442 assert_eq!(
443 s.channel_state()
444 .unwrap(),
445 Some(ChannelState::CsExecute)
446 );
447 }
448
449 #[test]
450 fn channel_state_number_typed() {
451 let s = store_with(&[("Channel-State-Number", "4")]);
452 assert_eq!(
453 s.channel_state_number()
454 .unwrap(),
455 Some(ChannelState::CsExecute)
456 );
457 }
458
459 #[test]
460 fn call_state_typed() {
461 let s = store_with(&[("Channel-Call-State", "ACTIVE")]);
462 assert_eq!(
463 s.call_state()
464 .unwrap(),
465 Some(CallState::Active)
466 );
467 }
468
469 #[test]
470 fn answer_state_typed() {
471 let s = store_with(&[("Answer-State", "answered")]);
472 assert_eq!(
473 s.answer_state()
474 .unwrap(),
475 Some(AnswerState::Answered)
476 );
477 }
478
479 #[test]
480 fn call_direction_typed() {
481 let s = store_with(&[("Call-Direction", "inbound")]);
482 assert_eq!(
483 s.call_direction()
484 .unwrap(),
485 Some(CallDirection::Inbound)
486 );
487 }
488
489 #[test]
490 fn priority_typed() {
491 let s = store_with(&[("priority", "HIGH")]);
492 assert_eq!(
493 s.priority()
494 .unwrap(),
495 Some(EslEventPriority::High)
496 );
497 }
498
499 #[test]
500 fn timetable_extraction() {
501 let s = store_with(&[
502 ("Caller-Channel-Created-Time", "1700000001000000"),
503 ("Caller-Channel-Answered-Time", "1700000005000000"),
504 ]);
505 let tt = s
506 .caller_timetable()
507 .unwrap()
508 .expect("should have timetable");
509 assert_eq!(tt.created, Some(1700000001000000));
510 assert_eq!(tt.answered, Some(1700000005000000));
511 assert_eq!(tt.hungup, None);
512 }
513
514 #[test]
515 fn timetable_other_leg() {
516 let s = store_with(&[("Other-Leg-Channel-Created-Time", "1700000001000000")]);
517 let tt = s
518 .other_leg_timetable()
519 .unwrap()
520 .expect("should have timetable");
521 assert_eq!(tt.created, Some(1700000001000000));
522 }
523
524 #[test]
525 fn timetable_none_when_absent() {
526 let s = store_with(&[]);
527 assert_eq!(
528 s.caller_timetable()
529 .unwrap(),
530 None
531 );
532 }
533
534 #[test]
535 fn timetable_invalid_is_error() {
536 let s = store_with(&[("Caller-Channel-Created-Time", "not_a_number")]);
537 let err = s
538 .caller_timetable()
539 .unwrap_err();
540 assert_eq!(err.header, "Caller-Channel-Created-Time");
541 }
542
543 #[test]
544 fn missing_headers_return_none() {
545 let s = store_with(&[]);
546 assert_eq!(
547 s.channel_state()
548 .unwrap(),
549 None
550 );
551 assert_eq!(
552 s.channel_state_number()
553 .unwrap(),
554 None
555 );
556 assert_eq!(
557 s.call_state()
558 .unwrap(),
559 None
560 );
561 assert_eq!(
562 s.answer_state()
563 .unwrap(),
564 None
565 );
566 assert_eq!(
567 s.call_direction()
568 .unwrap(),
569 None
570 );
571 assert_eq!(
572 s.priority()
573 .unwrap(),
574 None
575 );
576 assert_eq!(
577 s.hangup_cause()
578 .unwrap(),
579 None
580 );
581 assert_eq!(s.channel_name(), None);
582 assert_eq!(s.caller_id_number(), None);
583 assert_eq!(s.caller_id_name(), None);
584 assert_eq!(s.destination_number(), None);
585 assert_eq!(s.callee_id_number(), None);
586 assert_eq!(s.callee_id_name(), None);
587 assert_eq!(s.event_subclass(), None);
588 assert_eq!(s.job_uuid(), None);
589 assert_eq!(s.pl_data(), None);
590 assert_eq!(s.sip_event(), None);
591 assert_eq!(s.gateway_name(), None);
592 assert_eq!(s.channel_presence_id(), None);
593 assert_eq!(
594 s.presence_call_direction()
595 .unwrap(),
596 None
597 );
598 assert_eq!(s.event_date_timestamp(), None);
599 assert_eq!(s.event_sequence(), None);
600 assert_eq!(s.dtmf_duration(), None);
601 assert_eq!(s.dtmf_source(), None);
602 }
603
604 #[test]
605 fn notify_in_headers() {
606 let s = store_with(&[
607 ("pl_data", r#"{"invite":"INVITE ..."}"#),
608 ("event", "emergency-AbandonedCall"),
609 ("gateway_name", "ng911-bcf"),
610 ]);
611 assert_eq!(s.pl_data(), Some(r#"{"invite":"INVITE ..."}"#));
612 assert_eq!(s.sip_event(), Some("emergency-AbandonedCall"));
613 assert_eq!(s.gateway_name(), Some("ng911-bcf"));
614 }
615
616 #[test]
617 fn channel_presence_id() {
618 let s = store_with(&[("Channel-Presence-ID", "1000@example.com")]);
619 assert_eq!(s.channel_presence_id(), Some("1000@example.com"));
620 }
621
622 #[test]
623 fn presence_call_direction_typed() {
624 let s = store_with(&[("Presence-Call-Direction", "outbound")]);
625 assert_eq!(
626 s.presence_call_direction()
627 .unwrap(),
628 Some(CallDirection::Outbound)
629 );
630 }
631
632 #[test]
633 fn event_date_timestamp() {
634 let s = store_with(&[("Event-Date-Timestamp", "1700000001000000")]);
635 assert_eq!(s.event_date_timestamp(), Some("1700000001000000"));
636 }
637
638 #[test]
639 fn event_sequence() {
640 let s = store_with(&[("Event-Sequence", "12345")]);
641 assert_eq!(s.event_sequence(), Some("12345"));
642 }
643
644 #[test]
645 fn dtmf_duration() {
646 let s = store_with(&[("DTMF-Duration", "2000")]);
647 assert_eq!(s.dtmf_duration(), Some("2000"));
648 }
649
650 #[test]
651 fn dtmf_source() {
652 let s = store_with(&[("DTMF-Source", "rtp")]);
653 assert_eq!(s.dtmf_source(), Some("rtp"));
654 }
655
656 #[test]
657 fn invalid_values_return_err() {
658 let s = store_with(&[
659 ("Channel-State", "BOGUS"),
660 ("Channel-State-Number", "999"),
661 ("Channel-Call-State", "BOGUS"),
662 ("Answer-State", "bogus"),
663 ("Call-Direction", "bogus"),
664 ("Presence-Call-Direction", "bogus"),
665 ("priority", "BOGUS"),
666 ("Hangup-Cause", "BOGUS"),
667 ]);
668 assert!(s
669 .channel_state()
670 .is_err());
671 assert!(s
672 .channel_state_number()
673 .is_err());
674 assert!(s
675 .call_state()
676 .is_err());
677 assert!(s
678 .answer_state()
679 .is_err());
680 assert!(s
681 .call_direction()
682 .is_err());
683 assert!(s
684 .presence_call_direction()
685 .is_err());
686 assert!(s
687 .priority()
688 .is_err());
689 assert!(s
690 .hangup_cause()
691 .is_err());
692 }
693}