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