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",
189 Status => "Status",
191
192 ModuleName => "module_name",
195 ProfileNameSnake => "profile_name",
201 ProfileUri => "profile_uri",
205 }
206}
207
208pub fn case_alias_key(key: &str) -> Option<String> {
220 if key.contains('_') {
221 None
222 } else {
223 Some(key.to_ascii_lowercase())
224 }
225}
226
227pub fn normalize_header_key(raw: &str) -> String {
242 if let Ok(eh) = raw.parse::<EventHeader>() {
243 return eh
244 .as_str()
245 .to_string();
246 }
247 if case_alias_key(raw).is_none() {
248 raw.to_string()
249 } else {
250 title_case_dashes(raw)
251 }
252}
253
254fn title_case_dashes(s: &str) -> String {
255 let mut result = String::with_capacity(s.len());
256 let mut capitalize_next = true;
257 for c in s.chars() {
258 if c == '-' {
259 result.push('-');
260 capitalize_next = true;
261 } else if capitalize_next {
262 result.push(c.to_ascii_uppercase());
263 capitalize_next = false;
264 } else {
265 result.push(c.to_ascii_lowercase());
266 }
267 }
268 result
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn display_round_trip() {
277 assert_eq!(EventHeader::UniqueId.to_string(), "Unique-ID");
278 assert_eq!(
279 EventHeader::ChannelCallState.to_string(),
280 "Channel-Call-State"
281 );
282 assert_eq!(
283 EventHeader::CallerCallerIdName.to_string(),
284 "Caller-Caller-ID-Name"
285 );
286 assert_eq!(EventHeader::Priority.to_string(), "priority");
287 }
288
289 #[test]
290 fn as_ref_str() {
291 let h: &str = EventHeader::UniqueId.as_ref();
292 assert_eq!(h, "Unique-ID");
293 }
294
295 #[test]
296 fn from_str_case_insensitive() {
297 assert_eq!(
298 "unique-id".parse::<EventHeader>(),
299 Ok(EventHeader::UniqueId)
300 );
301 assert_eq!(
302 "UNIQUE-ID".parse::<EventHeader>(),
303 Ok(EventHeader::UniqueId)
304 );
305 assert_eq!(
306 "Unique-ID".parse::<EventHeader>(),
307 Ok(EventHeader::UniqueId)
308 );
309 assert_eq!(
310 "channel-call-state".parse::<EventHeader>(),
311 Ok(EventHeader::ChannelCallState)
312 );
313 }
314
315 #[test]
316 fn from_str_unknown() {
317 let err = "X-Custom-Not-In-Enum".parse::<EventHeader>();
318 assert!(err.is_err());
319 assert_eq!(
320 err.unwrap_err()
321 .to_string(),
322 "unknown event header (20 bytes)"
323 );
324 }
325
326 #[test]
334 fn normalize_known_enum_variants_return_canonical_form() {
335 assert_eq!(normalize_header_key("unique-id"), "Unique-ID");
337 assert_eq!(normalize_header_key("UNIQUE-ID"), "Unique-ID");
338 assert_eq!(normalize_header_key("Unique-ID"), "Unique-ID");
339 assert_eq!(normalize_header_key("dtmf-digit"), "DTMF-Digit");
340 assert_eq!(normalize_header_key("DTMF-DIGIT"), "DTMF-Digit");
341 assert_eq!(
342 normalize_header_key("channel-call-uuid"),
343 "Channel-Call-UUID"
344 );
345 assert_eq!(normalize_header_key("event-name"), "Event-Name");
346 }
347
348 #[test]
349 fn normalize_known_underscore_variants_return_canonical_form() {
350 assert_eq!(normalize_header_key("priority"), "priority");
352 assert_eq!(normalize_header_key("PRIORITY"), "priority");
353 assert_eq!(normalize_header_key("pl_data"), "pl_data");
354 assert_eq!(normalize_header_key("PL_DATA"), "pl_data");
355 assert_eq!(normalize_header_key("sip_content_type"), "sip_content_type");
356 assert_eq!(normalize_header_key("gateway_name"), "gateway_name");
357 assert_eq!(normalize_header_key("event"), "event");
358 assert_eq!(normalize_header_key("EVENT"), "event");
359 }
360
361 #[test]
362 fn normalize_codec_headers_from_switch_core_codec() {
363 assert_eq!(
366 normalize_header_key("channel-read-codec-bit-rate"),
367 "Channel-Read-Codec-Bit-Rate"
368 );
369 assert_eq!(
370 normalize_header_key("Channel-Read-Codec-Bit-Rate"),
371 "Channel-Read-Codec-Bit-Rate"
372 );
373 assert_eq!(
375 normalize_header_key("Channel-Write-codec-bit-rate"),
376 "Channel-Write-Codec-Bit-Rate"
377 );
378 assert_eq!(
379 normalize_header_key("channel-video-read-codec-name"),
380 "Channel-Video-Read-Codec-Name"
381 );
382 }
383
384 #[test]
385 fn normalize_unknown_underscore_keys_passthrough() {
386 assert_eq!(
388 normalize_header_key("variable_sip_call_id"),
389 "variable_sip_call_id"
390 );
391 assert_eq!(
392 normalize_header_key("variable_sip_h_X-My-CUSTOM-Header"),
393 "variable_sip_h_X-My-CUSTOM-Header"
394 );
395 assert_eq!(
396 normalize_header_key("variable_sip_h_Diversion"),
397 "variable_sip_h_Diversion"
398 );
399 }
400
401 #[test]
402 fn normalize_unknown_dash_keys_title_case() {
403 assert_eq!(normalize_header_key("content-type"), "Content-Type");
405 assert_eq!(normalize_header_key("Content-Type"), "Content-Type");
406 assert_eq!(normalize_header_key("CONTENT-TYPE"), "Content-Type");
407 assert_eq!(normalize_header_key("x-custom-header"), "X-Custom-Header");
408 assert_eq!(
409 normalize_header_key("Content-Disposition"),
410 "Content-Disposition"
411 );
412 assert_eq!(normalize_header_key("reply-text"), "Reply-Text");
413 }
414
415 #[test]
416 fn normalize_idempotent_for_all_enum_variants() {
417 let variants = [
419 EventHeader::EventName,
420 EventHeader::UniqueId,
421 EventHeader::ChannelCallUuid,
422 EventHeader::DtmfDigit,
423 EventHeader::Priority,
424 EventHeader::PlData,
425 EventHeader::SipEvent,
426 EventHeader::GatewayName,
427 EventHeader::SipContentType,
428 EventHeader::ChannelReadCodecBitRate,
429 EventHeader::ChannelVideoWriteCodecRate,
430 EventHeader::LogLevel,
431 ];
432 for v in variants {
433 let canonical = v.as_str();
434 assert_eq!(
435 normalize_header_key(canonical),
436 canonical,
437 "normalization not idempotent for {canonical}"
438 );
439 }
440 }
441
442 #[test]
443 fn parse_sofia_event_headers() {
444 assert_eq!("Gateway".parse::<EventHeader>(), Ok(EventHeader::Gateway));
445 assert_eq!("State".parse::<EventHeader>(), Ok(EventHeader::State));
446 assert_eq!(
447 "Ping-Status".parse::<EventHeader>(),
448 Ok(EventHeader::PingStatus)
449 );
450 assert_eq!("Phrase".parse::<EventHeader>(), Ok(EventHeader::Phrase));
451 }
452
453 #[test]
454 fn parse_sofia_profile_headers_are_spelling_exact() {
455 assert_eq!(
456 "profile-name".parse::<EventHeader>(),
457 Ok(EventHeader::ProfileName)
458 );
459 assert_eq!(
460 "profile_name".parse::<EventHeader>(),
461 Ok(EventHeader::ProfileNameSnake)
462 );
463 assert_eq!(
464 "module_name".parse::<EventHeader>(),
465 Ok(EventHeader::ModuleName)
466 );
467 assert_eq!(
468 "profile_uri".parse::<EventHeader>(),
469 Ok(EventHeader::ProfileUri)
470 );
471 }
472
473 #[test]
474 fn normalize_keeps_profile_name_spellings_apart() {
475 assert_eq!(normalize_header_key("profile-name"), "profile-name");
476 assert_eq!(normalize_header_key("PROFILE-NAME"), "profile-name");
477 assert_eq!(normalize_header_key("profile_name"), "profile_name");
478 }
479
480 #[test]
481 fn case_alias_key_underscore_excluded() {
482 assert_eq!(case_alias_key("variable_foo"), None);
484 assert_eq!(case_alias_key("sip_h_Call-Info"), None);
485 assert_eq!(case_alias_key("sip_i_contact"), None);
486 }
487
488 #[test]
489 fn case_alias_key_dash_keys_lowercased() {
490 assert_eq!(
492 case_alias_key("Content-Type"),
493 Some("content-type".to_string())
494 );
495 assert_eq!(case_alias_key("UNIQUE-ID"), Some("unique-id".to_string()));
496 assert_eq!(case_alias_key("reply-text"), Some("reply-text".to_string()));
497 }
498}