freeswitch_types/variables/
sip_passthrough.rs1use sip_header::SipHeader;
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct InvalidHeaderName(String);
25
26impl std::fmt::Display for InvalidHeaderName {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(
29 f,
30 "invalid SIP header name {:?}: contains \\n or \\r",
31 self.0
32 )
33 }
34}
35
36impl std::error::Error for InvalidHeaderName {}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44pub enum SipHeaderPrefix {
45 Invite,
47 Request,
49 Response,
51 Provisional,
53 Bye,
55 NoBye,
57}
58
59impl SipHeaderPrefix {
60 pub fn as_str(&self) -> &'static str {
62 match self {
63 Self::Invite => "sip_i_",
64 Self::Request => "sip_h_",
65 Self::Response => "sip_rh_",
66 Self::Provisional => "sip_ph_",
67 Self::Bye => "sip_bye_h_",
68 Self::NoBye => "sip_nobye_h_",
69 }
70 }
71}
72
73impl std::fmt::Display for SipHeaderPrefix {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.write_str(self.as_str())
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct ParseSipPassthroughError(pub String);
82
83impl std::fmt::Display for ParseSipPassthroughError {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "not a SIP passthrough variable: {}", self.0)
86 }
87}
88
89impl std::error::Error for ParseSipPassthroughError {}
90
91#[derive(Debug, Clone, PartialEq, Eq, Hash)]
120pub struct SipPassthroughHeader {
121 prefix: SipHeaderPrefix,
122 canonical_name: String,
123 wire: String,
124}
125
126const PREFIX_PATTERNS: &[SipHeaderPrefix] = &[
132 SipHeaderPrefix::NoBye,
133 SipHeaderPrefix::Bye,
134 SipHeaderPrefix::Provisional,
135 SipHeaderPrefix::Response,
136 SipHeaderPrefix::Invite,
137 SipHeaderPrefix::Request,
138];
139
140fn canonical_from_suffix(prefix: SipHeaderPrefix, suffix: &str) -> String {
147 match prefix {
148 SipHeaderPrefix::Invite => {
149 let with_hyphens = suffix.replace('_', "-");
152 match with_hyphens.parse::<SipHeader>() {
153 Ok(h) => h
154 .as_str()
155 .to_string(),
156 Err(_) => with_hyphens,
157 }
158 }
159 _ => match suffix.parse::<SipHeader>() {
160 Ok(h) => h
161 .as_str()
162 .to_string(),
163 Err(_) => suffix.to_string(),
164 },
165 }
166}
167
168fn validate_header_name(name: &str) -> Result<(), InvalidHeaderName> {
169 if name.is_empty() || crate::wire_safety::contains_wire_terminator(name) {
170 return Err(InvalidHeaderName(name.to_string()));
171 }
172 Ok(())
173}
174
175fn build_wire(prefix: SipHeaderPrefix, canonical: &str) -> String {
176 match prefix {
177 SipHeaderPrefix::Invite => {
178 let mut wire = String::with_capacity(6 + canonical.len());
179 wire.push_str("sip_i_");
180 for ch in canonical.chars() {
181 if ch == '-' {
182 wire.push('_');
183 } else {
184 wire.push(ch.to_ascii_lowercase());
185 }
186 }
187 wire
188 }
189 _ => {
190 let pfx = prefix.as_str();
191 let mut wire = String::with_capacity(pfx.len() + canonical.len());
192 wire.push_str(pfx);
193 wire.push_str(canonical);
194 wire
195 }
196 }
197}
198
199impl SipPassthroughHeader {
200 pub fn new(prefix: SipHeaderPrefix, header: SipHeader) -> Self {
202 let canonical = header
203 .as_str()
204 .to_string();
205 let wire = build_wire(prefix, &canonical);
206 Self {
207 prefix,
208 canonical_name: canonical,
209 wire,
210 }
211 }
212
213 pub fn new_raw(
217 prefix: SipHeaderPrefix,
218 name: impl Into<String>,
219 ) -> Result<Self, InvalidHeaderName> {
220 let canonical = name.into();
221 validate_header_name(&canonical)?;
222 let wire = build_wire(prefix, &canonical);
223 Ok(Self {
224 prefix,
225 canonical_name: canonical,
226 wire,
227 })
228 }
229
230 pub fn invite(header: SipHeader) -> Self {
232 Self::new(SipHeaderPrefix::Invite, header)
233 }
234
235 pub fn invite_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
237 Self::new_raw(SipHeaderPrefix::Invite, name)
238 }
239
240 pub fn request(header: SipHeader) -> Self {
242 Self::new(SipHeaderPrefix::Request, header)
243 }
244
245 pub fn request_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
247 Self::new_raw(SipHeaderPrefix::Request, name)
248 }
249
250 pub fn response(header: SipHeader) -> Self {
252 Self::new(SipHeaderPrefix::Response, header)
253 }
254
255 pub fn response_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
257 Self::new_raw(SipHeaderPrefix::Response, name)
258 }
259
260 pub fn provisional(header: SipHeader) -> Self {
262 Self::new(SipHeaderPrefix::Provisional, header)
263 }
264
265 pub fn provisional_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
267 Self::new_raw(SipHeaderPrefix::Provisional, name)
268 }
269
270 pub fn bye(header: SipHeader) -> Self {
272 Self::new(SipHeaderPrefix::Bye, header)
273 }
274
275 pub fn bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
277 Self::new_raw(SipHeaderPrefix::Bye, name)
278 }
279
280 pub fn no_bye(header: SipHeader) -> Self {
282 Self::new(SipHeaderPrefix::NoBye, header)
283 }
284
285 pub fn no_bye_raw(name: impl Into<String>) -> Result<Self, InvalidHeaderName> {
287 Self::new_raw(SipHeaderPrefix::NoBye, name)
288 }
289
290 pub fn prefix(&self) -> SipHeaderPrefix {
292 self.prefix
293 }
294
295 pub fn canonical_name(&self) -> &str {
297 &self.canonical_name
298 }
299
300 pub fn as_str(&self) -> &str {
302 &self.wire
303 }
304
305 pub fn extract_from(&self, message: &str) -> Vec<String> {
310 sip_header::extract_header(message, &self.canonical_name)
311 }
312
313 pub fn is_array_header(&self) -> bool {
319 if self.prefix != SipHeaderPrefix::Invite {
320 return false;
321 }
322 self.canonical_name
323 .parse::<SipHeader>()
324 .map(|h| h.is_multi_valued())
325 .unwrap_or(false)
326 }
327}
328
329impl super::VariableName for SipPassthroughHeader {
330 fn as_str(&self) -> &str {
331 &self.wire
332 }
333}
334
335impl std::fmt::Display for SipPassthroughHeader {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 f.write_str(&self.wire)
338 }
339}
340
341impl AsRef<str> for SipPassthroughHeader {
342 fn as_ref(&self) -> &str {
343 &self.wire
344 }
345}
346
347impl From<SipPassthroughHeader> for String {
348 fn from(h: SipPassthroughHeader) -> Self {
349 h.wire
350 }
351}
352
353impl std::str::FromStr for SipPassthroughHeader {
354 type Err = ParseSipPassthroughError;
355
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
357 for &prefix in PREFIX_PATTERNS {
358 if let Some(suffix) = s.strip_prefix(prefix.as_str()) {
359 if suffix.is_empty() {
360 return Err(ParseSipPassthroughError(s.to_string()));
361 }
362 let canonical = canonical_from_suffix(prefix, suffix);
363 return Ok(Self {
364 prefix,
365 canonical_name: canonical,
366 wire: s.to_string(),
367 });
368 }
369 }
370
371 let lower = s.to_ascii_lowercase();
374 if lower != s {
375 for &prefix in PREFIX_PATTERNS {
376 if let Some(suffix) = lower.strip_prefix(prefix.as_str()) {
377 if suffix.is_empty() {
378 return Err(ParseSipPassthroughError(s.to_string()));
379 }
380 let canonical = canonical_from_suffix(prefix, suffix);
381 let wire = build_wire(prefix, &canonical);
382 return Ok(Self {
383 prefix,
384 canonical_name: canonical,
385 wire,
386 });
387 }
388 }
389 }
390
391 Err(ParseSipPassthroughError(s.to_string()))
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
402 fn invite_typed_wire_format() {
403 assert_eq!(
404 SipPassthroughHeader::invite(SipHeader::CallInfo).as_str(),
405 "sip_i_call_info"
406 );
407 assert_eq!(
408 SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).as_str(),
409 "sip_i_p_asserted_identity"
410 );
411 assert_eq!(
412 SipPassthroughHeader::invite(SipHeader::Via).as_str(),
413 "sip_i_via"
414 );
415 assert_eq!(
416 SipPassthroughHeader::invite(SipHeader::CallId).as_str(),
417 "sip_i_call_id"
418 );
419 }
420
421 #[test]
422 fn request_typed_wire_format() {
423 assert_eq!(
424 SipPassthroughHeader::request(SipHeader::CallInfo).as_str(),
425 "sip_h_Call-Info"
426 );
427 assert_eq!(
428 SipPassthroughHeader::request(SipHeader::PAssertedIdentity).as_str(),
429 "sip_h_P-Asserted-Identity"
430 );
431 }
432
433 #[test]
434 fn response_typed_wire_format() {
435 assert_eq!(
436 SipPassthroughHeader::response(SipHeader::CallInfo).as_str(),
437 "sip_rh_Call-Info"
438 );
439 }
440
441 #[test]
442 fn provisional_typed_wire_format() {
443 assert_eq!(
444 SipPassthroughHeader::provisional(SipHeader::AlertInfo).as_str(),
445 "sip_ph_Alert-Info"
446 );
447 }
448
449 #[test]
450 fn bye_typed_wire_format() {
451 assert_eq!(
452 SipPassthroughHeader::bye(SipHeader::Reason).as_str(),
453 "sip_bye_h_Reason"
454 );
455 }
456
457 #[test]
458 fn no_bye_typed_wire_format() {
459 assert_eq!(
460 SipPassthroughHeader::no_bye(SipHeader::Reason).as_str(),
461 "sip_nobye_h_Reason"
462 );
463 }
464
465 #[test]
466 fn raw_custom_header() {
467 assert_eq!(
468 SipPassthroughHeader::request_raw("X-Tenant")
469 .unwrap()
470 .as_str(),
471 "sip_h_X-Tenant"
472 );
473 assert_eq!(
474 SipPassthroughHeader::invite_raw("X-Custom")
475 .unwrap()
476 .as_str(),
477 "sip_i_x_custom"
478 );
479 }
480
481 #[test]
482 fn raw_rejects_newlines() {
483 assert!(SipPassthroughHeader::request_raw("X-Bad\nHeader").is_err());
484 assert!(SipPassthroughHeader::request_raw("X-Bad\rHeader").is_err());
485 assert!(SipPassthroughHeader::request_raw("").is_err());
486 }
487
488 #[test]
491 fn display_matches_as_str() {
492 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
493 assert_eq!(h.to_string(), h.as_str());
494 }
495
496 #[test]
499 fn from_str_request() {
500 let parsed: SipPassthroughHeader = "sip_h_Call-Info"
501 .parse()
502 .unwrap();
503 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
504 assert_eq!(parsed.canonical_name(), "Call-Info");
505 assert_eq!(parsed.as_str(), "sip_h_Call-Info");
506 }
507
508 #[test]
509 fn from_str_invite() {
510 let parsed: SipPassthroughHeader = "sip_i_call_info"
511 .parse()
512 .unwrap();
513 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
514 assert_eq!(parsed.canonical_name(), "Call-Info");
515 assert_eq!(parsed.as_str(), "sip_i_call_info");
516 }
517
518 #[test]
519 fn from_str_invite_p_asserted_identity() {
520 let parsed: SipPassthroughHeader = "sip_i_p_asserted_identity"
521 .parse()
522 .unwrap();
523 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
524 assert_eq!(parsed.canonical_name(), "P-Asserted-Identity");
525 }
526
527 #[test]
528 fn from_str_response() {
529 let parsed: SipPassthroughHeader = "sip_rh_Call-Info"
530 .parse()
531 .unwrap();
532 assert_eq!(parsed.prefix(), SipHeaderPrefix::Response);
533 assert_eq!(parsed.canonical_name(), "Call-Info");
534 }
535
536 #[test]
537 fn from_str_bye() {
538 let parsed: SipPassthroughHeader = "sip_bye_h_Reason"
539 .parse()
540 .unwrap();
541 assert_eq!(parsed.prefix(), SipHeaderPrefix::Bye);
542 assert_eq!(parsed.canonical_name(), "Reason");
543 }
544
545 #[test]
546 fn from_str_no_bye() {
547 let parsed: SipPassthroughHeader = "sip_nobye_h_Reason"
548 .parse()
549 .unwrap();
550 assert_eq!(parsed.prefix(), SipHeaderPrefix::NoBye);
551 assert_eq!(parsed.canonical_name(), "Reason");
552 }
553
554 #[test]
555 fn from_str_unknown_custom_header() {
556 let parsed: SipPassthroughHeader = "sip_h_X-Tenant"
557 .parse()
558 .unwrap();
559 assert_eq!(parsed.prefix(), SipHeaderPrefix::Request);
560 assert_eq!(parsed.canonical_name(), "X-Tenant");
561 }
562
563 #[test]
564 fn from_str_invite_unknown_custom() {
565 let parsed: SipPassthroughHeader = "sip_i_x_custom"
566 .parse()
567 .unwrap();
568 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
569 assert_eq!(parsed.canonical_name(), "x-custom");
571 }
572
573 #[test]
574 fn from_str_case_insensitive_invite() {
575 let parsed: SipPassthroughHeader = "SIP_I_CALL_INFO"
576 .parse()
577 .unwrap();
578 assert_eq!(parsed.prefix(), SipHeaderPrefix::Invite);
579 assert_eq!(parsed.canonical_name(), "Call-Info");
580 }
581
582 #[test]
583 fn from_str_rejects_no_prefix() {
584 assert!("call_info"
585 .parse::<SipPassthroughHeader>()
586 .is_err());
587 assert!("sip_call_info"
588 .parse::<SipPassthroughHeader>()
589 .is_err());
590 }
591
592 #[test]
593 fn from_str_rejects_empty_suffix() {
594 assert!("sip_h_"
595 .parse::<SipPassthroughHeader>()
596 .is_err());
597 assert!("sip_i_"
598 .parse::<SipPassthroughHeader>()
599 .is_err());
600 }
601
602 #[test]
603 fn from_str_round_trip_all_prefixes() {
604 let headers = [
605 SipPassthroughHeader::invite(SipHeader::CallInfo),
606 SipPassthroughHeader::request(SipHeader::CallInfo),
607 SipPassthroughHeader::response(SipHeader::CallInfo),
608 SipPassthroughHeader::provisional(SipHeader::AlertInfo),
609 SipPassthroughHeader::bye(SipHeader::Reason),
610 SipPassthroughHeader::no_bye(SipHeader::Reason),
611 ];
612 for h in &headers {
613 let parsed: SipPassthroughHeader = h
614 .as_str()
615 .parse()
616 .unwrap();
617 assert_eq!(&parsed, h, "round-trip failed for {}", h.as_str());
618 }
619 }
620
621 #[test]
624 fn prefix_accessor() {
625 assert_eq!(
626 SipPassthroughHeader::invite(SipHeader::Via).prefix(),
627 SipHeaderPrefix::Invite
628 );
629 assert_eq!(
630 SipPassthroughHeader::request(SipHeader::Via).prefix(),
631 SipHeaderPrefix::Request
632 );
633 }
634
635 #[test]
636 fn canonical_name_accessor() {
637 assert_eq!(
638 SipPassthroughHeader::invite(SipHeader::CallInfo).canonical_name(),
639 "Call-Info"
640 );
641 assert_eq!(
642 SipPassthroughHeader::request_raw("X-Tenant")
643 .unwrap()
644 .canonical_name(),
645 "X-Tenant"
646 );
647 }
648
649 #[test]
652 fn is_array_header_invite_multi_valued() {
653 assert!(SipPassthroughHeader::invite(SipHeader::Via).is_array_header());
654 assert!(SipPassthroughHeader::invite(SipHeader::CallInfo).is_array_header());
655 assert!(SipPassthroughHeader::invite(SipHeader::PAssertedIdentity).is_array_header());
656 assert!(SipPassthroughHeader::invite(SipHeader::RecordRoute).is_array_header());
657 }
658
659 #[test]
660 fn is_array_header_invite_single_valued() {
661 assert!(!SipPassthroughHeader::invite(SipHeader::From).is_array_header());
662 assert!(!SipPassthroughHeader::invite(SipHeader::CallId).is_array_header());
663 assert!(!SipPassthroughHeader::invite(SipHeader::ContentType).is_array_header());
664 }
665
666 #[test]
667 fn is_array_header_non_invite_always_false() {
668 assert!(!SipPassthroughHeader::request(SipHeader::Via).is_array_header());
669 assert!(!SipPassthroughHeader::response(SipHeader::CallInfo).is_array_header());
670 }
671
672 #[test]
673 fn is_array_header_raw_unknown() {
674 assert!(!SipPassthroughHeader::invite_raw("X-Custom")
675 .unwrap()
676 .is_array_header());
677 }
678
679 #[test]
682 fn extract_from_sip_message() {
683 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
684 Call-Info: <sip:example.com>;answer-after=0\r\n\
685 \r\n";
686 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
687 assert_eq!(
688 h.extract_from(msg),
689 vec!["<sip:example.com>;answer-after=0"]
690 );
691 }
692
693 #[test]
694 fn extract_from_missing() {
695 let msg = "INVITE sip:bob@example.com SIP/2.0\r\n\
696 From: Alice <sip:alice@example.com>\r\n\
697 \r\n";
698 let h = SipPassthroughHeader::invite(SipHeader::CallInfo);
699 assert!(h
700 .extract_from(msg)
701 .is_empty());
702 }
703
704 #[test]
707 fn prefix_patterns_covers_all_variants_exactly_once() {
708 let all_variants = [
709 SipHeaderPrefix::Invite,
710 SipHeaderPrefix::Request,
711 SipHeaderPrefix::Response,
712 SipHeaderPrefix::Provisional,
713 SipHeaderPrefix::Bye,
714 SipHeaderPrefix::NoBye,
715 ];
716 for &variant in &all_variants {
717 let count = PREFIX_PATTERNS
718 .iter()
719 .filter(|&&p| p == variant)
720 .count();
721 assert_eq!(
722 count, 1,
723 "{:?} appears {count} times in PREFIX_PATTERNS (expected exactly 1)",
724 variant
725 );
726 }
727 assert_eq!(
728 PREFIX_PATTERNS.len(),
729 all_variants.len(),
730 "PREFIX_PATTERNS length differs from all-variants count"
731 );
732 }
733
734 #[test]
737 fn variable_name_trait() {
738 use crate::variables::VariableName;
739 let h = SipPassthroughHeader::request(SipHeader::CallInfo);
740 let name: &str = VariableName::as_str(&h);
741 assert_eq!(name, "sip_h_Call-Info");
742 }
743}