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