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 hangup_cause(&self) -> Result<Option<HangupCause>, ParseHangupCauseError> {
127 match self.header(EventHeader::HangupCause) {
128 Some(s) => Ok(Some(s.parse()?)),
129 None => Ok(None),
130 }
131 }
132
133 fn event_subclass(&self) -> Option<&str> {
135 self.header(EventHeader::EventSubclass)
136 }
137
138 fn pl_data(&self) -> Option<&str> {
144 self.header(EventHeader::PlData)
145 }
146
147 fn sip_event(&self) -> Option<&str> {
151 self.header(EventHeader::SipEvent)
152 }
153
154 fn gateway_name(&self) -> Option<&str> {
156 self.header(EventHeader::GatewayName)
157 }
158
159 fn channel_state(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
163 match self.header(EventHeader::ChannelState) {
164 Some(s) => Ok(Some(s.parse()?)),
165 None => Ok(None),
166 }
167 }
168
169 fn channel_state_number(&self) -> Result<Option<ChannelState>, ParseChannelStateError> {
173 match self.header(EventHeader::ChannelStateNumber) {
174 Some(s) => {
175 let n: u8 = s
176 .parse()
177 .map_err(|_| ParseChannelStateError(s.to_string()))?;
178 ChannelState::from_number(n)
179 .ok_or_else(|| ParseChannelStateError(s.to_string()))
180 .map(Some)
181 }
182 None => Ok(None),
183 }
184 }
185
186 fn call_state(&self) -> Result<Option<CallState>, ParseCallStateError> {
190 match self.header(EventHeader::ChannelCallState) {
191 Some(s) => Ok(Some(s.parse()?)),
192 None => Ok(None),
193 }
194 }
195
196 fn answer_state(&self) -> Result<Option<AnswerState>, ParseAnswerStateError> {
200 match self.header(EventHeader::AnswerState) {
201 Some(s) => Ok(Some(s.parse()?)),
202 None => Ok(None),
203 }
204 }
205
206 fn call_direction(&self) -> Result<Option<CallDirection>, ParseCallDirectionError> {
210 match self.header(EventHeader::CallDirection) {
211 Some(s) => Ok(Some(s.parse()?)),
212 None => Ok(None),
213 }
214 }
215
216 #[cfg(feature = "esl")]
220 fn priority(&self) -> Result<Option<EslEventPriority>, ParsePriorityError> {
221 match self.header(EventHeader::Priority) {
222 Some(s) => Ok(Some(s.parse()?)),
223 None => Ok(None),
224 }
225 }
226
227 fn timetable(&self, prefix: &str) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
232 ChannelTimetable::from_lookup(prefix, |key| self.header_str(key))
233 }
234
235 fn caller_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
237 self.timetable("Caller")
238 }
239
240 fn other_leg_timetable(&self) -> Result<Option<ChannelTimetable>, ParseTimetableError> {
242 self.timetable("Other-Leg")
243 }
244}
245
246impl<T: HeaderLookup> crate::sip_header::SipHeaderLookup for T {
247 fn sip_header_str(&self, name: &str) -> Option<&str> {
248 self.header_str(name)
249 }
250}
251
252impl HeaderLookup for std::collections::HashMap<String, String> {
253 fn header_str(&self, name: &str) -> Option<&str> {
254 self.get(name)
255 .map(|s| s.as_str())
256 }
257
258 fn variable_str(&self, name: &str) -> Option<&str> {
259 self.get(&format!("variable_{name}"))
260 .map(|s| s.as_str())
261 }
262}
263
264#[cfg(feature = "esl")]
265impl HeaderLookup for indexmap::IndexMap<String, String> {
266 fn header_str(&self, name: &str) -> Option<&str> {
267 self.get(name)
268 .map(|s| s.as_str())
269 }
270
271 fn variable_str(&self, name: &str) -> Option<&str> {
272 self.get(&format!("variable_{name}"))
273 .map(|s| s.as_str())
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::variables::ChannelVariable;
281 use std::collections::HashMap;
282
283 struct TestStore(HashMap<String, String>);
284
285 impl HeaderLookup for TestStore {
286 fn header_str(&self, name: &str) -> Option<&str> {
287 self.0
288 .get(name)
289 .map(|s| s.as_str())
290 }
291 fn variable_str(&self, name: &str) -> Option<&str> {
292 self.0
293 .get(&format!("variable_{}", name))
294 .map(|s| s.as_str())
295 }
296 }
297
298 fn store_with(pairs: &[(&str, &str)]) -> TestStore {
299 let map: HashMap<String, String> = pairs
300 .iter()
301 .map(|(k, v)| (k.to_string(), v.to_string()))
302 .collect();
303 TestStore(map)
304 }
305
306 #[test]
307 fn header_str_direct() {
308 let s = store_with(&[("Unique-ID", "abc-123")]);
309 assert_eq!(s.header_str("Unique-ID"), Some("abc-123"));
310 assert_eq!(s.header_str("Missing"), None);
311 }
312
313 #[test]
314 fn header_by_enum() {
315 let s = store_with(&[("Unique-ID", "abc-123")]);
316 assert_eq!(s.header(EventHeader::UniqueId), Some("abc-123"));
317 }
318
319 #[test]
320 fn variable_str_direct() {
321 let s = store_with(&[("variable_read_codec", "PCMU")]);
322 assert_eq!(s.variable_str("read_codec"), Some("PCMU"));
323 assert_eq!(s.variable_str("missing"), None);
324 }
325
326 #[test]
327 fn variable_by_enum() {
328 let s = store_with(&[("variable_read_codec", "PCMU")]);
329 assert_eq!(s.variable(ChannelVariable::ReadCodec), Some("PCMU"));
330 }
331
332 #[test]
333 fn unique_id_primary() {
334 let s = store_with(&[("Unique-ID", "uuid-1")]);
335 assert_eq!(s.unique_id(), Some("uuid-1"));
336 }
337
338 #[test]
339 fn unique_id_fallback() {
340 let s = store_with(&[("Caller-Unique-ID", "uuid-2")]);
341 assert_eq!(s.unique_id(), Some("uuid-2"));
342 }
343
344 #[test]
345 fn unique_id_none() {
346 let s = store_with(&[]);
347 assert_eq!(s.unique_id(), None);
348 }
349
350 #[test]
351 fn job_uuid() {
352 let s = store_with(&[("Job-UUID", "job-1")]);
353 assert_eq!(s.job_uuid(), Some("job-1"));
354 }
355
356 #[test]
357 fn channel_name() {
358 let s = store_with(&[("Channel-Name", "sofia/internal/1000@example.com")]);
359 assert_eq!(s.channel_name(), Some("sofia/internal/1000@example.com"));
360 }
361
362 #[test]
363 fn caller_id_number_and_name() {
364 let s = store_with(&[
365 ("Caller-Caller-ID-Number", "1000"),
366 ("Caller-Caller-ID-Name", "Alice"),
367 ]);
368 assert_eq!(s.caller_id_number(), Some("1000"));
369 assert_eq!(s.caller_id_name(), Some("Alice"));
370 }
371
372 #[test]
373 fn hangup_cause_typed() {
374 let s = store_with(&[("Hangup-Cause", "NORMAL_CLEARING")]);
375 assert_eq!(
376 s.hangup_cause()
377 .unwrap(),
378 Some(crate::channel::HangupCause::NormalClearing)
379 );
380 }
381
382 #[test]
383 fn hangup_cause_invalid_is_error() {
384 let s = store_with(&[("Hangup-Cause", "BOGUS_CAUSE")]);
385 assert!(s
386 .hangup_cause()
387 .is_err());
388 }
389
390 #[test]
391 fn destination_number() {
392 let s = store_with(&[("Caller-Destination-Number", "1000")]);
393 assert_eq!(s.destination_number(), Some("1000"));
394 }
395
396 #[test]
397 fn callee_id() {
398 let s = store_with(&[
399 ("Caller-Callee-ID-Number", "2000"),
400 ("Caller-Callee-ID-Name", "Bob"),
401 ]);
402 assert_eq!(s.callee_id_number(), Some("2000"));
403 assert_eq!(s.callee_id_name(), Some("Bob"));
404 }
405
406 #[test]
407 fn event_subclass() {
408 let s = store_with(&[("Event-Subclass", "sofia::register")]);
409 assert_eq!(s.event_subclass(), Some("sofia::register"));
410 }
411
412 #[test]
413 fn channel_state_typed() {
414 let s = store_with(&[("Channel-State", "CS_EXECUTE")]);
415 assert_eq!(
416 s.channel_state()
417 .unwrap(),
418 Some(ChannelState::CsExecute)
419 );
420 }
421
422 #[test]
423 fn channel_state_number_typed() {
424 let s = store_with(&[("Channel-State-Number", "4")]);
425 assert_eq!(
426 s.channel_state_number()
427 .unwrap(),
428 Some(ChannelState::CsExecute)
429 );
430 }
431
432 #[test]
433 fn call_state_typed() {
434 let s = store_with(&[("Channel-Call-State", "ACTIVE")]);
435 assert_eq!(
436 s.call_state()
437 .unwrap(),
438 Some(CallState::Active)
439 );
440 }
441
442 #[test]
443 fn answer_state_typed() {
444 let s = store_with(&[("Answer-State", "answered")]);
445 assert_eq!(
446 s.answer_state()
447 .unwrap(),
448 Some(AnswerState::Answered)
449 );
450 }
451
452 #[test]
453 fn call_direction_typed() {
454 let s = store_with(&[("Call-Direction", "inbound")]);
455 assert_eq!(
456 s.call_direction()
457 .unwrap(),
458 Some(CallDirection::Inbound)
459 );
460 }
461
462 #[test]
463 fn priority_typed() {
464 let s = store_with(&[("priority", "HIGH")]);
465 assert_eq!(
466 s.priority()
467 .unwrap(),
468 Some(EslEventPriority::High)
469 );
470 }
471
472 #[test]
473 fn timetable_extraction() {
474 let s = store_with(&[
475 ("Caller-Channel-Created-Time", "1700000001000000"),
476 ("Caller-Channel-Answered-Time", "1700000005000000"),
477 ]);
478 let tt = s
479 .caller_timetable()
480 .unwrap()
481 .expect("should have timetable");
482 assert_eq!(tt.created, Some(1700000001000000));
483 assert_eq!(tt.answered, Some(1700000005000000));
484 assert_eq!(tt.hungup, None);
485 }
486
487 #[test]
488 fn timetable_other_leg() {
489 let s = store_with(&[("Other-Leg-Channel-Created-Time", "1700000001000000")]);
490 let tt = s
491 .other_leg_timetable()
492 .unwrap()
493 .expect("should have timetable");
494 assert_eq!(tt.created, Some(1700000001000000));
495 }
496
497 #[test]
498 fn timetable_none_when_absent() {
499 let s = store_with(&[]);
500 assert_eq!(
501 s.caller_timetable()
502 .unwrap(),
503 None
504 );
505 }
506
507 #[test]
508 fn timetable_invalid_is_error() {
509 let s = store_with(&[("Caller-Channel-Created-Time", "not_a_number")]);
510 let err = s
511 .caller_timetable()
512 .unwrap_err();
513 assert_eq!(err.header, "Caller-Channel-Created-Time");
514 }
515
516 #[test]
517 fn missing_headers_return_none() {
518 let s = store_with(&[]);
519 assert_eq!(
520 s.channel_state()
521 .unwrap(),
522 None
523 );
524 assert_eq!(
525 s.channel_state_number()
526 .unwrap(),
527 None
528 );
529 assert_eq!(
530 s.call_state()
531 .unwrap(),
532 None
533 );
534 assert_eq!(
535 s.answer_state()
536 .unwrap(),
537 None
538 );
539 assert_eq!(
540 s.call_direction()
541 .unwrap(),
542 None
543 );
544 assert_eq!(
545 s.priority()
546 .unwrap(),
547 None
548 );
549 assert_eq!(
550 s.hangup_cause()
551 .unwrap(),
552 None
553 );
554 assert_eq!(s.channel_name(), None);
555 assert_eq!(s.caller_id_number(), None);
556 assert_eq!(s.caller_id_name(), None);
557 assert_eq!(s.destination_number(), None);
558 assert_eq!(s.callee_id_number(), None);
559 assert_eq!(s.callee_id_name(), None);
560 assert_eq!(s.event_subclass(), None);
561 assert_eq!(s.job_uuid(), None);
562 assert_eq!(s.pl_data(), None);
563 assert_eq!(s.sip_event(), None);
564 assert_eq!(s.gateway_name(), None);
565 }
566
567 #[test]
568 fn notify_in_headers() {
569 let s = store_with(&[
570 ("pl_data", r#"{"invite":"INVITE ..."}"#),
571 ("event", "emergency-AbandonedCall"),
572 ("gateway_name", "ng911-bcf"),
573 ]);
574 assert_eq!(s.pl_data(), Some(r#"{"invite":"INVITE ..."}"#));
575 assert_eq!(s.sip_event(), Some("emergency-AbandonedCall"));
576 assert_eq!(s.gateway_name(), Some("ng911-bcf"));
577 }
578
579 #[test]
580 fn invalid_values_return_err() {
581 let s = store_with(&[
582 ("Channel-State", "BOGUS"),
583 ("Channel-State-Number", "999"),
584 ("Channel-Call-State", "BOGUS"),
585 ("Answer-State", "bogus"),
586 ("Call-Direction", "bogus"),
587 ("priority", "BOGUS"),
588 ("Hangup-Cause", "BOGUS"),
589 ]);
590 assert!(s
591 .channel_state()
592 .is_err());
593 assert!(s
594 .channel_state_number()
595 .is_err());
596 assert!(s
597 .call_state()
598 .is_err());
599 assert!(s
600 .answer_state()
601 .is_err());
602 assert!(s
603 .call_direction()
604 .is_err());
605 assert!(s
606 .priority()
607 .is_err());
608 assert!(s
609 .hangup_cause()
610 .is_err());
611 }
612}