1sip_header::define_header_enum! {
4 tests_mod: event_header_generated_tests,
5 error_type: ParseEventHeaderError => "unknown event header",
6 pub enum EventHeader {
12 EventName => "Event-Name",
13 EventSubclass => "Event-Subclass",
14 UniqueId => "Unique-ID",
15 CallerUniqueId => "Caller-Unique-ID",
16 OtherLegUniqueId => "Other-Leg-Unique-ID",
17 ChannelCallUuid => "Channel-Call-UUID",
18 JobUuid => "Job-UUID",
19 ChannelName => "Channel-Name",
20 ChannelState => "Channel-State",
21 ChannelStateNumber => "Channel-State-Number",
22 ChannelCallState => "Channel-Call-State",
23 AnswerState => "Answer-State",
24 CallDirection => "Call-Direction",
25 HangupCause => "Hangup-Cause",
26 CallerCallerIdName => "Caller-Caller-ID-Name",
27 CallerCallerIdNumber => "Caller-Caller-ID-Number",
28 CallerOrigCallerIdName => "Caller-Orig-Caller-ID-Name",
29 CallerOrigCallerIdNumber => "Caller-Orig-Caller-ID-Number",
30 CallerCalleeIdName => "Caller-Callee-ID-Name",
31 CallerCalleeIdNumber => "Caller-Callee-ID-Number",
32 CallerDestinationNumber => "Caller-Destination-Number",
33 CallerContext => "Caller-Context",
34 CallerDirection => "Caller-Direction",
35 CallerNetworkAddr => "Caller-Network-Addr",
36 CoreUuid => "Core-UUID",
37 DtmfDigit => "DTMF-Digit",
38 Priority => "priority",
39 LogLevel => "Log-Level",
40 PlData => "pl_data",
42 SipEvent => "event",
44 SipContentType => "sip_content_type",
46 GatewayName => "gateway_name",
48
49 ChannelReadCodecName => "Channel-Read-Codec-Name",
52 ChannelReadCodecRate => "Channel-Read-Codec-Rate",
53 ChannelReadCodecBitRate => "Channel-Read-Codec-Bit-Rate",
54 ChannelReportedReadCodecRate => "Channel-Reported-Read-Codec-Rate",
56 ChannelWriteCodecName => "Channel-Write-Codec-Name",
58 ChannelWriteCodecRate => "Channel-Write-Codec-Rate",
59 ChannelWriteCodecBitRate => "Channel-Write-Codec-Bit-Rate",
60 ChannelReportedWriteCodecRate => "Channel-Reported-Write-Codec-Rate",
62 ChannelVideoReadCodecName => "Channel-Video-Read-Codec-Name",
64 ChannelVideoReadCodecRate => "Channel-Video-Read-Codec-Rate",
65 ChannelVideoWriteCodecName => "Channel-Video-Write-Codec-Name",
66 ChannelVideoWriteCodecRate => "Channel-Video-Write-Codec-Rate",
67 SessionCount => "Session-Count",
69 FreeswitchHostname => "FreeSWITCH-Hostname",
70 FreeswitchSwitchname => "FreeSWITCH-Switchname",
71 FreeswitchIpv4 => "FreeSWITCH-IPv4",
72 FreeswitchIpv6 => "FreeSWITCH-IPv6",
73 FreeswitchVersion => "FreeSWITCH-Version",
74 FreeswitchDomain => "FreeSWITCH-Domain",
75 FreeswitchUser => "FreeSWITCH-User",
76
77 Application => "Application",
79 ApplicationData => "Application-Data",
80 ApplicationResponse => "Application-Response",
81 ApplicationUuid => "Application-UUID",
82
83 EventDateLocal => "Event-Date-Local",
85 EventDateGmt => "Event-Date-GMT",
86 EventDateTimestamp => "Event-Date-Timestamp",
87 EventCallingFile => "Event-Calling-File",
88 EventCallingFunction => "Event-Calling-Function",
89 EventCallingLineNumber => "Event-Calling-Line-Number",
90 EventSequence => "Event-Sequence",
91
92 ChannelPresenceId => "Channel-Presence-ID",
94 ChannelPresenceData => "Channel-Presence-Data",
95 PresenceDataCols => "Presence-Data-Cols",
96 PresenceCallDirection => "Presence-Call-Direction",
97 ChannelHitDialplan => "Channel-HIT-Dialplan",
98 SessionExternalId => "Session-External-ID",
99 OtherType => "Other-Type",
101
102 ChannelCallStateNumber => "Channel-Call-State-Number",
104 OriginalChannelCallState => "Original-Channel-Call-State",
105
106 DtmfDuration => "DTMF-Duration",
108 DtmfSource => "DTMF-Source",
109
110 CallerLogicalDirection => "Caller-Logical-Direction",
112 CallerUsername => "Caller-Username",
113 CallerDialplan => "Caller-Dialplan",
114 CallerAni => "Caller-ANI",
115 CallerAniii => "Caller-ANI-II",
116 CallerSource => "Caller-Source",
117 CallerTransferSource => "Caller-Transfer-Source",
118 CallerRdnis => "Caller-RDNIS",
119 CallerChannelName => "Caller-Channel-Name",
120 CallerProfileIndex => "Caller-Profile-Index",
121 CallerScreenBit => "Caller-Screen-Bit",
122 CallerPrivacyHideName => "Caller-Privacy-Hide-Name",
123 CallerPrivacyHideNumber => "Caller-Privacy-Hide-Number",
124
125 OtherLegDirection => "Other-Leg-Direction",
127 OtherLegLogicalDirection => "Other-Leg-Logical-Direction",
128 OtherLegUsername => "Other-Leg-Username",
129 OtherLegDialplan => "Other-Leg-Dialplan",
130 OtherLegCallerIdName => "Other-Leg-Caller-ID-Name",
131 OtherLegCallerIdNumber => "Other-Leg-Caller-ID-Number",
132 OtherLegOrigCallerIdName => "Other-Leg-Orig-Caller-ID-Name",
133 OtherLegOrigCallerIdNumber => "Other-Leg-Orig-Caller-ID-Number",
134 OtherLegCalleeIdName => "Other-Leg-Callee-ID-Name",
135 OtherLegCalleeIdNumber => "Other-Leg-Callee-ID-Number",
136 OtherLegNetworkAddr => "Other-Leg-Network-Addr",
137 OtherLegAni => "Other-Leg-ANI",
138 OtherLegAniii => "Other-Leg-ANI-II",
139 OtherLegDestinationNumber => "Other-Leg-Destination-Number",
140 OtherLegSource => "Other-Leg-Source",
141 OtherLegTransferSource => "Other-Leg-Transfer-Source",
142 OtherLegContext => "Other-Leg-Context",
143 OtherLegRdnis => "Other-Leg-RDNIS",
144 OtherLegChannelName => "Other-Leg-Channel-Name",
145 OtherLegProfileIndex => "Other-Leg-Profile-Index",
146 OtherLegScreenBit => "Other-Leg-Screen-Bit",
147 OtherLegPrivacyHideName => "Other-Leg-Privacy-Hide-Name",
148 OtherLegPrivacyHideNumber => "Other-Leg-Privacy-Hide-Number",
149
150 UpTime => "Up-Time",
153 UptimeMsec => "Uptime-msec",
155 MaxSessions => "Max-Sessions",
156 SessionPeakMax => "Session-Peak-Max",
157 SessionPeakFiveMin => "Session-Peak-FiveMin",
158 SessionPerSec => "Session-Per-Sec",
159 SessionPerSecFiveMin => "Session-Per-Sec-FiveMin",
160 SessionPerSecMax => "Session-Per-Sec-Max",
161 SessionPerSecLast => "Session-Per-Sec-Last",
162 SessionSinceStartup => "Session-Since-Startup",
163 IdleCpu => "Idle-CPU",
164 HeartbeatInterval => "Heartbeat-Interval",
165 EventInfo => "Event-Info",
166
167 LogData => "Log-Data",
169 LogFile => "Log-File",
170 LogFunction => "Log-Function",
171 LogLine => "Log-Line",
172 UserData => "User-Data",
173
174 ApplicationUuidName => "Application-UUID-Name",
176
177 Gateway => "Gateway",
179 State => "State",
180 PingStatus => "Ping-Status",
181 Phrase => "Phrase",
182 ProfileName => "profile-name",
183 Status => "Status",
185 }
186}
187
188pub fn case_alias_key(key: &str) -> Option<String> {
200 if key.contains('_') {
201 None
202 } else {
203 Some(key.to_ascii_lowercase())
204 }
205}
206
207pub fn normalize_header_key(raw: &str) -> String {
222 if let Ok(eh) = raw.parse::<EventHeader>() {
223 return eh
224 .as_str()
225 .to_string();
226 }
227 if case_alias_key(raw).is_none() {
228 raw.to_string()
229 } else {
230 title_case_dashes(raw)
231 }
232}
233
234fn title_case_dashes(s: &str) -> String {
235 let mut result = String::with_capacity(s.len());
236 let mut capitalize_next = true;
237 for c in s.chars() {
238 if c == '-' {
239 result.push('-');
240 capitalize_next = true;
241 } else if capitalize_next {
242 result.push(c.to_ascii_uppercase());
243 capitalize_next = false;
244 } else {
245 result.push(c.to_ascii_lowercase());
246 }
247 }
248 result
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254
255 #[test]
256 fn display_round_trip() {
257 assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
258 assert_eq!(
259 EventHeader::ChannelCallState.to_string(),
260 "Channel-Call-State"
261 );
262 assert_eq!(
263 EventHeader::CallerCallerIdName.to_string(),
264 "Caller-Caller-ID-Name"
265 );
266 assert_eq!(EventHeader::Priority.to_string(), "priority");
267 }
268
269 #[test]
270 fn as_ref_str() {
271 let h: &str = EventHeader::UniqueId.as_ref();
272 assert_eq!(h, "Unique-ID");
273 }
274
275 #[test]
276 fn from_str_case_insensitive() {
277 assert_eq!(
278 "unique-id".parse::<EventHeader>(),
279 Ok(EventHeader::UniqueId)
280 );
281 assert_eq!(
282 "UNIQUE-ID".parse::<EventHeader>(),
283 Ok(EventHeader::UniqueId)
284 );
285 assert_eq!(
286 "Unique-ID".parse::<EventHeader>(),
287 Ok(EventHeader::UniqueId)
288 );
289 assert_eq!(
290 "channel-call-state".parse::<EventHeader>(),
291 Ok(EventHeader::ChannelCallState)
292 );
293 }
294
295 #[test]
296 fn from_str_unknown() {
297 let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
298 assert!(err.is_err());
299 assert_eq!(
300 err.unwrap_err()
301 .to_string(),
302 "unknown event header: X-Custom-Not-In-Enum"
303 );
304 }
305
306 #[test]
314 fn normalize_known_enum_variants_return_canonical_form() {
315 assert_eq!(normalize_header_key("unique-id"), "Unique-ID");
317 assert_eq!(normalize_header_key("UNIQUE-ID"), "Unique-ID");
318 assert_eq!(normalize_header_key("Unique-ID"), "Unique-ID");
319 assert_eq!(normalize_header_key("dtmf-digit"), "DTMF-Digit");
320 assert_eq!(normalize_header_key("DTMF-DIGIT"), "DTMF-Digit");
321 assert_eq!(
322 normalize_header_key("channel-call-uuid"),
323 "Channel-Call-UUID"
324 );
325 assert_eq!(normalize_header_key("event-name"), "Event-Name");
326 }
327
328 #[test]
329 fn normalize_known_underscore_variants_return_canonical_form() {
330 assert_eq!(normalize_header_key("priority"), "priority");
332 assert_eq!(normalize_header_key("PRIORITY"), "priority");
333 assert_eq!(normalize_header_key("pl_data"), "pl_data");
334 assert_eq!(normalize_header_key("PL_DATA"), "pl_data");
335 assert_eq!(normalize_header_key("sip_content_type"), "sip_content_type");
336 assert_eq!(normalize_header_key("gateway_name"), "gateway_name");
337 assert_eq!(normalize_header_key("event"), "event");
338 assert_eq!(normalize_header_key("EVENT"), "event");
339 }
340
341 #[test]
342 fn normalize_codec_headers_from_switch_core_codec() {
343 assert_eq!(
346 normalize_header_key("channel-read-codec-bit-rate"),
347 "Channel-Read-Codec-Bit-Rate"
348 );
349 assert_eq!(
350 normalize_header_key("Channel-Read-Codec-Bit-Rate"),
351 "Channel-Read-Codec-Bit-Rate"
352 );
353 assert_eq!(
355 normalize_header_key("Channel-Write-codec-bit-rate"),
356 "Channel-Write-Codec-Bit-Rate"
357 );
358 assert_eq!(
359 normalize_header_key("channel-video-read-codec-name"),
360 "Channel-Video-Read-Codec-Name"
361 );
362 }
363
364 #[test]
365 fn normalize_unknown_underscore_keys_passthrough() {
366 assert_eq!(
368 normalize_header_key("variable_sip_call_id"),
369 "variable_sip_call_id"
370 );
371 assert_eq!(
372 normalize_header_key("variable_sip_h_X-My-CUSTOM-Header"),
373 "variable_sip_h_X-My-CUSTOM-Header"
374 );
375 assert_eq!(
376 normalize_header_key("variable_sip_h_Diversion"),
377 "variable_sip_h_Diversion"
378 );
379 }
380
381 #[test]
382 fn normalize_unknown_dash_keys_title_case() {
383 assert_eq!(normalize_header_key("content-type"), "Content-Type");
385 assert_eq!(normalize_header_key("Content-Type"), "Content-Type");
386 assert_eq!(normalize_header_key("CONTENT-TYPE"), "Content-Type");
387 assert_eq!(normalize_header_key("x-custom-header"), "X-Custom-Header");
388 assert_eq!(
389 normalize_header_key("Content-Disposition"),
390 "Content-Disposition"
391 );
392 assert_eq!(normalize_header_key("reply-text"), "Reply-Text");
393 }
394
395 #[test]
396 fn normalize_idempotent_for_all_enum_variants() {
397 let variants = [
399 EventHeader::EventName,
400 EventHeader::UniqueId,
401 EventHeader::ChannelCallUuid,
402 EventHeader::DtmfDigit,
403 EventHeader::Priority,
404 EventHeader::PlData,
405 EventHeader::SipEvent,
406 EventHeader::GatewayName,
407 EventHeader::SipContentType,
408 EventHeader::ChannelReadCodecBitRate,
409 EventHeader::ChannelVideoWriteCodecRate,
410 EventHeader::LogLevel,
411 ];
412 for v in variants {
413 let canonical = v.as_str();
414 assert_eq!(
415 normalize_header_key(canonical),
416 canonical,
417 "normalization not idempotent for {canonical}"
418 );
419 }
420 }
421
422 #[test]
423 fn parse_missing_event_default_headers() {
424 assert!("Event-Date-Local"
426 .parse::<EventHeader>()
427 .is_ok());
428 assert!("Event-Date-GMT"
429 .parse::<EventHeader>()
430 .is_ok());
431 assert!("Event-Date-Timestamp"
432 .parse::<EventHeader>()
433 .is_ok());
434 assert!("Event-Calling-File"
435 .parse::<EventHeader>()
436 .is_ok());
437 assert!("Event-Calling-Function"
438 .parse::<EventHeader>()
439 .is_ok());
440 assert!("Event-Calling-Line-Number"
441 .parse::<EventHeader>()
442 .is_ok());
443 assert!("Event-Sequence"
444 .parse::<EventHeader>()
445 .is_ok());
446 }
447
448 #[test]
449 fn parse_missing_channel_basic_data_headers() {
450 assert!("Channel-Presence-ID"
452 .parse::<EventHeader>()
453 .is_ok());
454 assert!("Channel-Presence-Data"
455 .parse::<EventHeader>()
456 .is_ok());
457 assert!("Presence-Data-Cols"
458 .parse::<EventHeader>()
459 .is_ok());
460 assert!("Presence-Call-Direction"
461 .parse::<EventHeader>()
462 .is_ok());
463 assert!("Channel-HIT-Dialplan"
464 .parse::<EventHeader>()
465 .is_ok());
466 assert!("Session-External-ID"
467 .parse::<EventHeader>()
468 .is_ok());
469 assert!("Other-Type"
470 .parse::<EventHeader>()
471 .is_ok());
472 }
473
474 #[test]
475 fn parse_missing_callstate_and_dtmf_headers() {
476 assert!("Channel-Call-State-Number"
478 .parse::<EventHeader>()
479 .is_ok());
480 assert!("Original-Channel-Call-State"
481 .parse::<EventHeader>()
482 .is_ok());
483 assert!("DTMF-Duration"
485 .parse::<EventHeader>()
486 .is_ok());
487 assert!("DTMF-Source"
488 .parse::<EventHeader>()
489 .is_ok());
490 }
491
492 #[test]
493 fn parse_missing_caller_profile_headers() {
494 assert!("Caller-Logical-Direction"
496 .parse::<EventHeader>()
497 .is_ok());
498 assert!("Caller-Username"
499 .parse::<EventHeader>()
500 .is_ok());
501 assert!("Caller-Dialplan"
502 .parse::<EventHeader>()
503 .is_ok());
504 assert!("Caller-ANI"
505 .parse::<EventHeader>()
506 .is_ok());
507 assert!("Caller-ANI-II"
508 .parse::<EventHeader>()
509 .is_ok());
510 assert!("Caller-Source"
511 .parse::<EventHeader>()
512 .is_ok());
513 assert!("Caller-Transfer-Source"
514 .parse::<EventHeader>()
515 .is_ok());
516 assert!("Caller-RDNIS"
517 .parse::<EventHeader>()
518 .is_ok());
519 assert!("Caller-Channel-Name"
520 .parse::<EventHeader>()
521 .is_ok());
522 assert!("Caller-Profile-Index"
523 .parse::<EventHeader>()
524 .is_ok());
525 assert!("Caller-Screen-Bit"
526 .parse::<EventHeader>()
527 .is_ok());
528 assert!("Caller-Privacy-Hide-Name"
529 .parse::<EventHeader>()
530 .is_ok());
531 assert!("Caller-Privacy-Hide-Number"
532 .parse::<EventHeader>()
533 .is_ok());
534 }
535
536 #[test]
537 fn parse_heartbeat_headers() {
538 assert!("Up-Time"
539 .parse::<EventHeader>()
540 .is_ok());
541 assert!("Uptime-msec"
542 .parse::<EventHeader>()
543 .is_ok());
544 assert!("Max-Sessions"
545 .parse::<EventHeader>()
546 .is_ok());
547 assert!("Session-Peak-Max"
548 .parse::<EventHeader>()
549 .is_ok());
550 assert!("Session-Peak-FiveMin"
551 .parse::<EventHeader>()
552 .is_ok());
553 assert!("Session-Per-Sec"
554 .parse::<EventHeader>()
555 .is_ok());
556 assert!("Session-Per-Sec-FiveMin"
557 .parse::<EventHeader>()
558 .is_ok());
559 assert!("Session-Per-Sec-Max"
560 .parse::<EventHeader>()
561 .is_ok());
562 assert!("Session-Per-Sec-Last"
563 .parse::<EventHeader>()
564 .is_ok());
565 assert!("Session-Since-Startup"
566 .parse::<EventHeader>()
567 .is_ok());
568 assert!("Idle-CPU"
569 .parse::<EventHeader>()
570 .is_ok());
571 assert!("Heartbeat-Interval"
572 .parse::<EventHeader>()
573 .is_ok());
574 assert!("Event-Info"
575 .parse::<EventHeader>()
576 .is_ok());
577 }
578
579 #[test]
580 fn parse_log_headers() {
581 assert!("Log-Data"
582 .parse::<EventHeader>()
583 .is_ok());
584 assert!("Log-File"
585 .parse::<EventHeader>()
586 .is_ok());
587 assert!("Log-Function"
588 .parse::<EventHeader>()
589 .is_ok());
590 assert!("Log-Line"
591 .parse::<EventHeader>()
592 .is_ok());
593 assert!("User-Data"
594 .parse::<EventHeader>()
595 .is_ok());
596 }
597
598 #[test]
599 fn parse_application_uuid_name() {
600 assert!("Application-UUID-Name"
601 .parse::<EventHeader>()
602 .is_ok());
603 }
604
605 #[test]
606 fn parse_sofia_event_headers() {
607 assert_eq!("Gateway".parse::<EventHeader>(), Ok(EventHeader::Gateway));
608 assert_eq!("State".parse::<EventHeader>(), Ok(EventHeader::State));
609 assert_eq!(
610 "Ping-Status".parse::<EventHeader>(),
611 Ok(EventHeader::PingStatus)
612 );
613 assert_eq!("Phrase".parse::<EventHeader>(), Ok(EventHeader::Phrase));
614 assert_eq!(
615 "profile-name".parse::<EventHeader>(),
616 Ok(EventHeader::ProfileName)
617 );
618 }
619
620 #[test]
621 fn parse_missing_other_leg_headers() {
622 assert!("Other-Leg-Direction"
624 .parse::<EventHeader>()
625 .is_ok());
626 assert!("Other-Leg-Logical-Direction"
627 .parse::<EventHeader>()
628 .is_ok());
629 assert!("Other-Leg-Username"
630 .parse::<EventHeader>()
631 .is_ok());
632 assert!("Other-Leg-Dialplan"
633 .parse::<EventHeader>()
634 .is_ok());
635 assert!("Other-Leg-Caller-ID-Name"
636 .parse::<EventHeader>()
637 .is_ok());
638 assert!("Other-Leg-Caller-ID-Number"
639 .parse::<EventHeader>()
640 .is_ok());
641 assert!("Other-Leg-Orig-Caller-ID-Name"
642 .parse::<EventHeader>()
643 .is_ok());
644 assert!("Other-Leg-Orig-Caller-ID-Number"
645 .parse::<EventHeader>()
646 .is_ok());
647 assert!("Other-Leg-Callee-ID-Name"
648 .parse::<EventHeader>()
649 .is_ok());
650 assert!("Other-Leg-Callee-ID-Number"
651 .parse::<EventHeader>()
652 .is_ok());
653 assert!("Other-Leg-Network-Addr"
654 .parse::<EventHeader>()
655 .is_ok());
656 assert!("Other-Leg-ANI"
657 .parse::<EventHeader>()
658 .is_ok());
659 assert!("Other-Leg-ANI-II"
660 .parse::<EventHeader>()
661 .is_ok());
662 assert!("Other-Leg-Destination-Number"
663 .parse::<EventHeader>()
664 .is_ok());
665 assert!("Other-Leg-Source"
666 .parse::<EventHeader>()
667 .is_ok());
668 assert!("Other-Leg-Transfer-Source"
669 .parse::<EventHeader>()
670 .is_ok());
671 assert!("Other-Leg-Context"
672 .parse::<EventHeader>()
673 .is_ok());
674 assert!("Other-Leg-RDNIS"
675 .parse::<EventHeader>()
676 .is_ok());
677 assert!("Other-Leg-Channel-Name"
678 .parse::<EventHeader>()
679 .is_ok());
680 assert!("Other-Leg-Profile-Index"
681 .parse::<EventHeader>()
682 .is_ok());
683 assert!("Other-Leg-Screen-Bit"
684 .parse::<EventHeader>()
685 .is_ok());
686 assert!("Other-Leg-Privacy-Hide-Name"
687 .parse::<EventHeader>()
688 .is_ok());
689 assert!("Other-Leg-Privacy-Hide-Number"
690 .parse::<EventHeader>()
691 .is_ok());
692 }
693
694 #[test]
695 fn case_alias_key_underscore_excluded() {
696 assert_eq!(case_alias_key("variable_foo"), None);
698 assert_eq!(case_alias_key("sip_h_Call-Info"), None);
699 assert_eq!(case_alias_key("sip_i_contact"), None);
700 }
701
702 #[test]
703 fn case_alias_key_dash_keys_lowercased() {
704 assert_eq!(
706 case_alias_key("Content-Type"),
707 Some("content-type".to_string())
708 );
709 assert_eq!(case_alias_key("UNIQUE-ID"), Some("unique-id".to_string()));
710 assert_eq!(case_alias_key("reply-text"), Some("reply-text".to_string()));
711 }
712}