1use std::fmt;
7use std::str::FromStr;
8
9use super::endpoint::{DialString, Endpoint};
10use super::originate::OriginateError;
11use super::variables::{
12 installed_variables, read_error, BlockParse, DialStringCarrier, DialStringTarget, Variables,
13};
14use crate::switch_passes::expansion::expand_escapes;
15use crate::switch_passes::originate_legs::{scan_group, split_groups};
16use crate::switch_passes::separate::CBuffer;
17use crate::switch_passes::{pipeline, trace};
18
19const CARRIER: DialStringCarrier = DialStringCarrier::Dialplan;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[cfg_attr(feature = "serde", serde(try_from = "config::BridgeDialString"))]
36pub struct BridgeDialString {
37 #[cfg_attr(
38 feature = "serde",
39 serde(default, skip_serializing_if = "Option::is_none")
40 )]
41 variables: Option<Variables>,
42 groups: Vec<Vec<Endpoint>>,
43}
44
45impl BridgeDialString {
46 pub fn new(groups: Vec<Vec<Endpoint>>) -> Self {
48 Self {
49 variables: None,
50 groups,
51 }
52 }
53
54 pub fn with_variables(mut self, variables: Variables) -> Self {
56 self.variables = Some(variables);
57 self
58 }
59
60 pub fn variables(&self) -> Option<&Variables> {
62 self.variables
63 .as_ref()
64 }
65
66 pub fn groups(&self) -> &[Vec<Endpoint>] {
69 &self.groups
70 }
71
72 pub fn variables_mut(&mut self) -> &mut Option<Variables> {
74 &mut self.variables
75 }
76
77 pub fn groups_mut(&mut self) -> &mut Vec<Vec<Endpoint>> {
79 &mut self.groups
80 }
81}
82
83#[derive(Debug, Clone, Copy)]
86pub struct BridgeDialStringDisplay<'a> {
87 bridge: &'a BridgeDialString,
88 block_parse: BlockParse,
89}
90
91impl fmt::Display for BridgeDialStringDisplay<'_> {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 self.bridge
94 .write_with(f, self.block_parse)
95 }
96}
97
98impl fmt::Display for BridgeDialString {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 self.write_with(f, BlockParse::default())
101 }
102}
103
104impl FromStr for BridgeDialString {
105 type Err = OriginateError;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 Self::parse_with(s, BlockParse::default())
109 }
110}
111
112impl BridgeDialString {
113 pub fn display_with(&self, block_parse: BlockParse) -> BridgeDialStringDisplay<'_> {
116 BridgeDialStringDisplay {
117 bridge: self,
118 block_parse,
119 }
120 }
121
122 fn write_with(&self, f: &mut fmt::Formatter<'_>, block_parse: BlockParse) -> fmt::Result {
123 let target = DialStringTarget::new(CARRIER).with_block_parse(block_parse);
124 if let Some(vars) = &self.variables {
125 if !vars.is_empty() {
126 write!(f, "{}", vars.display_for(target))?;
127 }
128 }
129 for (gi, group) in self
130 .groups
131 .iter()
132 .enumerate()
133 {
134 if gi > 0 {
135 f.write_str("|")?;
136 }
137 for (ei, ep) in group
138 .iter()
139 .enumerate()
140 {
141 if ei > 0 {
142 f.write_str(",")?;
143 }
144 write!(f, "{}", ep.display_for(target))?;
145 }
146 }
147 Ok(())
148 }
149
150 pub fn parse_with(s: &str, block_parse: BlockParse) -> Result<Self, OriginateError> {
153 let target = DialStringTarget::new(CARRIER).with_block_parse(block_parse);
154 let s = s.trim_matches(' ');
155 if s.is_empty() {
156 return Err(OriginateError::ParseError(
157 "empty bridge dial string".into(),
158 ));
159 }
160
161 let list = pipeline::read(s, target).map_err(read_error)?;
162 let [thread] = &list.threads[..] else {
163 return Err(OriginateError::ParseError(
164 "a bridge dial string has no enterprise threads".into(),
165 ));
166 };
167 if !list
168 .blocks
169 .is_empty()
170 || thread
171 .blocks
172 .len()
173 > 1
174 {
175 return Err(OriginateError::ParseError(
176 "a bridge dial string carries one leading variable block".into(),
177 ));
178 }
179 let variables = installed_variables(
180 thread
181 .blocks
182 .first(),
183 )?;
184
185 let mut groups = Vec::new();
186 for group in &thread.groups {
187 let mut endpoints = Vec::new();
188 for leg in group {
189 if leg
190 .blocks
191 .is_empty()
192 && leg
193 .endpoint
194 .is_empty()
195 {
196 continue;
197 }
198 if leg
199 .blocks
200 .len()
201 > 1
202 {
203 return Err(OriginateError::ParseError(
204 "an endpoint carries one variable block".into(),
205 ));
206 }
207 let vars = installed_variables(
208 leg.blocks
209 .first(),
210 )?;
211 let mut endpoint = Endpoint::parse_bare(&leg.endpoint)?;
212 if vars.is_some() {
213 endpoint.set_variables(vars);
214 if endpoint
215 .variables()
216 .is_none()
217 {
218 return Err(OriginateError::VariablesNotSupported(endpoint.kind()));
219 }
220 }
221 endpoints.push(endpoint);
222 }
223 if !endpoints.is_empty() {
224 groups.push(endpoints);
225 }
226 }
227
228 let bridge = Self { variables, groups };
229 bridge.check_legs(block_parse)?;
230 Ok(bridge)
231 }
232
233 fn check_legs(&self, block_parse: BlockParse) -> Result<(), OriginateError> {
235 let target = DialStringTarget::new(CARRIER).with_block_parse(block_parse);
236 match self
237 .groups
238 .iter()
239 .position(|group| separator_merged(group, target))
240 {
241 Some(group) => Err(OriginateError::BracketSpansLegs { group }),
242 None => Ok(()),
243 }
244 }
245}
246
247fn separator_merged(group: &[Endpoint], target: DialStringTarget) -> bool {
251 let mut text = String::new();
252 let mut separators = Vec::new();
253 for (i, endpoint) in group
254 .iter()
255 .enumerate()
256 {
257 if i > 0 {
258 separators.push(text.len());
259 text.push(',');
260 }
261 text.push_str(
262 &endpoint
263 .display_for(target)
264 .to_string(),
265 );
266 }
267 let (expanded, _) = expand_escapes(&trace(&text));
268 let mut buffer = CBuffer::new(&expanded);
269 let split = split_groups(&mut buffer);
270 let [group] = &split.tokens[..] else {
271 return false;
272 };
273 scan_group(&mut buffer, group.start);
274 buffer
275 .c_str(group.start)
276 .iter()
277 .any(|&(c, start, _)| c != ',' && separators.contains(&start))
278}
279
280#[cfg(feature = "serde")]
281mod config {
282 use crate::commands::endpoint::Endpoint;
283 use crate::commands::variables::Variables;
284
285 #[derive(serde::Deserialize)]
286 pub(super) struct BridgeDialString {
287 #[serde(default)]
288 pub(super) variables: Option<Variables>,
289 pub(super) groups: Vec<Vec<Endpoint>>,
290 }
291}
292
293#[cfg(feature = "serde")]
294impl TryFrom<config::BridgeDialString> for BridgeDialString {
295 type Error = OriginateError;
296
297 fn try_from(config: config::BridgeDialString) -> Result<Self, Self::Error> {
298 let bridge = Self {
299 variables: config.variables,
300 groups: config.groups,
301 };
302 bridge.check_legs(BlockParse::default())?;
303 Ok(bridge)
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use crate::commands::endpoint::{
311 DialString, ErrorEndpoint, LoopbackEndpoint, SofiaEndpoint, SofiaGateway,
312 };
313 use crate::commands::variables::{BlockParse, VariablesType};
314
315 #[cfg(feature = "serde")]
318 #[test]
319 fn a_bracket_spanning_legs_after_expansion_is_refused() {
320 let bridge = BridgeDialString::new(vec![vec![
321 crate::commands::endpoint::UserEndpoint::new("['").into(),
322 SofiaGateway::new("", "]")
323 .with_profile(" ")
324 .into(),
325 ]]);
326 let json = serde_json::to_value(&bridge).unwrap();
327 assert!(matches!(
328 serde_json::from_value::<BridgeDialString>(json)
329 .map_err(|e| e.to_string()),
330 Err(e) if e.contains("group 0")
331 ));
332 }
333
334 #[test]
338 fn display_and_from_str_are_the_default_revision() {
339 let mut vars = Variables::new(VariablesType::Default);
340 vars.insert("cid", "it's");
341 let mut ep_vars = Variables::new(VariablesType::Channel);
342 ep_vars.insert("path", r"C:\path");
343 let bridge = BridgeDialString::new(vec![vec![SofiaGateway::new("gw", "1234")
344 .with_variables(ep_vars)
345 .into()]])
346 .with_variables(vars);
347
348 let rendered = bridge
349 .display_with(BlockParse::PairSplitCleans)
350 .to_string();
351 assert_eq!(rendered, bridge.to_string());
352 assert_eq!(
353 BridgeDialString::parse_with(&rendered, BlockParse::PairSplitCleans)
354 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}")),
355 bridge
356 );
357 }
358
359 #[test]
363 fn bridge_renders_one_level_shallower_than_the_default() {
364 let mut vars = Variables::new(VariablesType::Default);
365 vars.insert("cid", "it's");
366 let ep = SofiaGateway::new("gw", "18005551234");
367 let bridge = BridgeDialString::new(vec![vec![ep.into()]]).with_variables(vars.clone());
368
369 assert_eq!(
370 bridge.to_string(),
371 r"{cid=it\\\\\\'s}sofia/gateway/gw/18005551234"
372 );
373 assert_eq!(vars.to_string(), r"{cid=it\\\\\\\'s}");
374 }
375
376 #[test]
380 fn per_endpoint_escaped_values_round_trip() {
381 let mut ep_vars = Variables::new(VariablesType::Channel);
382 ep_vars.insert("path", r"C:\path");
383 ep_vars.insert("other", "a,b");
384 let bridge = BridgeDialString::new(vec![vec![SofiaGateway::new("gw", "1234")
385 .with_variables(ep_vars)
386 .into()]]);
387
388 let rendered = bridge.to_string();
389 let back: BridgeDialString = rendered
390 .parse()
391 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}"));
392 assert_eq!(back, bridge, "rendered {rendered}");
393 }
394
395 #[test]
399 fn per_endpoint_quoted_value_is_refused() {
400 let err = r"[cid=it\\\\\\'s]sofia/gateway/gw/1234"
401 .parse::<BridgeDialString>()
402 .unwrap_err()
403 .to_string();
404 assert!(
405 err.contains("cid") && err.contains("channel scope"),
406 "error does not name the variable and scope: {err}"
407 );
408 }
409
410 #[test]
411 fn display_single_endpoint() {
412 let bridge =
413 BridgeDialString::new(vec![vec![
414 SofiaGateway::new("my_provider", "18005551234").into()
415 ]]);
416 assert_eq!(bridge.to_string(), "sofia/gateway/my_provider/18005551234");
417 }
418
419 #[test]
420 fn display_simultaneous_ring() {
421 let bridge = BridgeDialString::new(vec![vec![
422 SofiaGateway::new("primary", "18005551234").into(),
423 SofiaGateway::new("secondary", "18005551234").into(),
424 ]]);
425 assert_eq!(
426 bridge.to_string(),
427 "sofia/gateway/primary/18005551234,sofia/gateway/secondary/18005551234"
428 );
429 }
430
431 #[test]
432 fn display_sequential_failover() {
433 let bridge = BridgeDialString::new(vec![
434 vec![SofiaGateway::new("primary", "18005551234").into()],
435 vec![SofiaGateway::new("backup", "18005551234").into()],
436 ]);
437 assert_eq!(
438 bridge.to_string(),
439 "sofia/gateway/primary/18005551234|sofia/gateway/backup/18005551234"
440 );
441 }
442
443 #[test]
444 fn display_mixed_simultaneous_and_sequential() {
445 let bridge = BridgeDialString::new(vec![
446 vec![
447 SofiaGateway::new("primary", "1234").into(),
448 SofiaGateway::new("secondary", "1234").into(),
449 ],
450 vec![SofiaGateway::new("backup", "1234").into()],
451 ]);
452 assert_eq!(
453 bridge.to_string(),
454 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234"
455 );
456 }
457
458 #[test]
459 fn display_with_global_variables() {
460 let mut vars = Variables::new(VariablesType::Default);
461 vars.insert("hangup_after_bridge", "true");
462 let bridge =
463 BridgeDialString::new(vec![vec![
464 SofiaEndpoint::new("internal", "1000@domain").into()
465 ]])
466 .with_variables(vars);
467 assert_eq!(
468 bridge.to_string(),
469 "{hangup_after_bridge=true}sofia/internal/1000@domain"
470 );
471 }
472
473 #[test]
474 fn display_with_per_endpoint_variables() {
475 let mut ep_vars = Variables::new(VariablesType::Channel);
476 ep_vars.insert("leg_timeout", "30");
477 let bridge = BridgeDialString::new(vec![vec![
478 SofiaGateway::new("gw1", "1234")
479 .with_variables(ep_vars)
480 .into(),
481 SofiaGateway::new("gw2", "1234").into(),
482 ]]);
483 assert_eq!(
484 bridge.to_string(),
485 "[leg_timeout=30]sofia/gateway/gw1/1234,sofia/gateway/gw2/1234"
486 );
487 }
488
489 #[test]
490 fn display_with_error_endpoint_failover() {
491 let bridge = BridgeDialString::new(vec![
492 vec![SofiaGateway::new("primary", "1234").into()],
493 vec![ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into()],
494 ]);
495 assert_eq!(
496 bridge.to_string(),
497 "sofia/gateway/primary/1234|error/USER_BUSY"
498 );
499 }
500
501 #[test]
502 fn display_with_loopback() {
503 let bridge = BridgeDialString::new(vec![vec![LoopbackEndpoint::new("9199")
504 .with_context("default")
505 .into()]]);
506 assert_eq!(bridge.to_string(), "loopback/9199/default");
507 }
508
509 #[test]
512 fn from_str_single_endpoint() {
513 let bridge: BridgeDialString = "sofia/gateway/my_provider/18005551234"
514 .parse()
515 .unwrap();
516 assert_eq!(
517 bridge
518 .groups()
519 .len(),
520 1
521 );
522 assert_eq!(bridge.groups()[0].len(), 1);
523 assert!(bridge
524 .variables()
525 .is_none());
526 }
527
528 #[test]
529 fn from_str_simultaneous_ring() {
530 let bridge: BridgeDialString = "sofia/gateway/primary/1234,sofia/gateway/secondary/1234"
531 .parse()
532 .unwrap();
533 assert_eq!(
534 bridge
535 .groups()
536 .len(),
537 1
538 );
539 assert_eq!(bridge.groups()[0].len(), 2);
540 }
541
542 #[test]
543 fn from_str_sequential_failover() {
544 let bridge: BridgeDialString = "sofia/gateway/primary/1234|sofia/gateway/backup/1234"
545 .parse()
546 .unwrap();
547 assert_eq!(
548 bridge
549 .groups()
550 .len(),
551 2
552 );
553 assert_eq!(bridge.groups()[0].len(), 1);
554 assert_eq!(bridge.groups()[1].len(), 1);
555 }
556
557 #[test]
558 fn from_str_mixed() {
559 let bridge: BridgeDialString =
560 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234"
561 .parse()
562 .unwrap();
563 assert_eq!(
564 bridge
565 .groups()
566 .len(),
567 2
568 );
569 assert_eq!(bridge.groups()[0].len(), 2);
570 assert_eq!(bridge.groups()[1].len(), 1);
571 }
572
573 #[test]
574 fn from_str_with_global_variables() {
575 let bridge: BridgeDialString = "{hangup_after_bridge=true}sofia/internal/1000@domain"
576 .parse()
577 .unwrap();
578 assert!(bridge
579 .variables()
580 .is_some());
581 assert_eq!(
582 bridge
583 .variables()
584 .unwrap()
585 .get("hangup_after_bridge"),
586 Some("true")
587 );
588 assert_eq!(
589 bridge
590 .groups()
591 .len(),
592 1
593 );
594 assert_eq!(bridge.groups()[0].len(), 1);
595 }
596
597 #[test]
598 fn from_str_with_per_endpoint_variables() {
599 let bridge: BridgeDialString =
600 "[leg_timeout=30]sofia/gateway/gw1/1234,sofia/gateway/gw2/1234"
601 .parse()
602 .unwrap();
603 assert_eq!(
604 bridge
605 .groups()
606 .len(),
607 1
608 );
609 assert_eq!(bridge.groups()[0].len(), 2);
610 let ep = &bridge.groups()[0][0];
611 if let Endpoint::SofiaGateway(gw) = ep {
612 assert!(gw
613 .variables
614 .is_some());
615 } else {
616 panic!("expected SofiaGateway");
617 }
618 }
619
620 #[test]
623 fn from_str_with_enterprise_global_variables() {
624 let bridge: BridgeDialString = "<originate_timeout=60>sofia/internal/1000@domain"
625 .parse()
626 .unwrap();
627 assert_eq!(
628 bridge
629 .variables()
630 .expect("enterprise block is global")
631 .get("originate_timeout"),
632 Some("60")
633 );
634 assert_eq!(bridge.groups()[0].len(), 1);
635 }
636
637 #[test]
640 fn from_str_leaves_a_channel_block_on_its_endpoint() {
641 let bridge: BridgeDialString = "[leg_timeout=30]sofia/gateway/gw1/1234"
642 .parse()
643 .unwrap();
644 assert!(bridge
645 .variables()
646 .is_none());
647 assert!(bridge.groups()[0][0]
648 .variables()
649 .is_some());
650 }
651
652 #[test]
653 fn from_str_round_trip_single() {
654 let input = "sofia/gateway/my_provider/18005551234";
655 let bridge: BridgeDialString = input
656 .parse()
657 .unwrap();
658 assert_eq!(bridge.to_string(), input);
659 }
660
661 #[test]
662 fn from_str_round_trip_mixed() {
663 let input =
664 "sofia/gateway/primary/1234,sofia/gateway/secondary/1234|sofia/gateway/backup/1234";
665 let bridge: BridgeDialString = input
666 .parse()
667 .unwrap();
668 assert_eq!(bridge.to_string(), input);
669 }
670
671 #[test]
672 fn from_str_round_trip_with_global_vars() {
673 let input = "{hangup_after_bridge=true}sofia/internal/1000@domain";
674 let bridge: BridgeDialString = input
675 .parse()
676 .unwrap();
677 assert_eq!(bridge.to_string(), input);
678 }
679
680 #[test]
683 fn serde_round_trip_single() {
684 let bridge =
685 BridgeDialString::new(vec![vec![
686 SofiaGateway::new("my_provider", "18005551234").into()
687 ]]);
688 let json = serde_json::to_string(&bridge).unwrap();
689 let parsed: BridgeDialString = serde_json::from_str(&json).unwrap();
690 assert_eq!(bridge, parsed);
691 }
692
693 #[test]
694 fn serde_round_trip_multi_group() {
695 let mut vars = Variables::new(VariablesType::Default);
696 vars.insert("hangup_after_bridge", "true");
697 let bridge = BridgeDialString::new(vec![
698 vec![
699 SofiaGateway::new("primary", "1234").into(),
700 SofiaGateway::new("secondary", "1234").into(),
701 ],
702 vec![ErrorEndpoint::new(crate::channel::HangupCause::UserBusy).into()],
703 ])
704 .with_variables(vars);
705 let json = serde_json::to_string(&bridge).unwrap();
706 let parsed: BridgeDialString = serde_json::from_str(&json).unwrap();
707 assert_eq!(bridge, parsed);
708 }
709
710 #[test]
713 fn from_str_empty_string_rejected() {
714 let result = "".parse::<BridgeDialString>();
715 assert!(result.is_err());
716 }
717
718 #[test]
719 fn from_str_whitespace_only_rejected() {
720 let result = " ".parse::<BridgeDialString>();
721 assert!(result.is_err());
722 }
723
724 #[test]
725 fn from_str_empty_groups_from_trailing_pipe() {
726 let bridge: BridgeDialString = "sofia/gateway/gw1/1234|"
728 .parse()
729 .unwrap();
730 assert_eq!(
731 bridge
732 .groups()
733 .len(),
734 1
735 );
736 }
737
738 #[test]
739 fn from_str_empty_variable_block() {
740 let bridge: BridgeDialString = "{}sofia/gateway/gw1/1234"
741 .parse()
742 .unwrap();
743 assert!(bridge
744 .variables()
745 .is_none());
746 assert_eq!(
747 bridge
748 .groups()
749 .len(),
750 1
751 );
752 }
753
754 #[test]
755 fn from_str_mismatched_bracket_rejected() {
756 let result = "{unclosed=true sofia/gateway/gw1/1234".parse::<BridgeDialString>();
757 assert!(result.is_err());
758 }
759
760 #[test]
762 fn a_separator_in_endpoint_text_round_trips() {
763 let bridge = BridgeDialString::new(vec![
764 vec![
765 SofiaEndpoint::new("internal", "a,b").into(),
766 SofiaEndpoint::new("internal", "c|d").into(),
767 ],
768 vec![LoopbackEndpoint::new("it's a").into()],
769 ]);
770 let rendered = bridge.to_string();
771 let back: BridgeDialString = rendered
772 .parse()
773 .unwrap_or_else(|e| panic!("{rendered} failed to parse: {e}"));
774 assert_eq!(back, bridge, "rendered {rendered}");
775 }
776
777 #[test]
780 fn a_bracket_spanning_legs_is_refused_at_config_load() {
781 let spanning = r#"{"groups":[[{"sofia":{"profile":"internal","destination":"x["}},{"sofia":{"profile":"internal","destination":"y]"}}]]}"#;
782 assert!(serde_json::from_str::<BridgeDialString>(spanning).is_err());
783 for fine in [
784 r#"{"groups":[[{"sofia":{"profile":"internal","destination":"x[]"}},{"sofia":{"profile":"internal","destination":"y]"}}]]}"#,
785 r#"{"groups":[[{"sofia":{"profile":"internal","destination":"x["}}],[{"sofia":{"profile":"internal","destination":"y]"}}]]}"#,
786 ] {
787 assert!(
788 serde_json::from_str::<BridgeDialString>(fine).is_ok(),
789 "{fine}"
790 );
791 }
792 }
793
794 #[test]
797 fn parse_reads_blocks_as_the_switch_installs_them() {
798 let bridge: BridgeDialString = r"{k=a\\\\b}[j=c\\\\\\\\d]loopback/9199/test"
799 .parse()
800 .unwrap_or_else(|e| panic!("{e}"));
801 assert_eq!(
802 bridge
803 .variables()
804 .and_then(|vars| vars.get("k")),
805 Some(r"a\b")
806 );
807 assert_eq!(
808 bridge.groups()[0][0]
809 .variables()
810 .and_then(|vars| vars.get("j")),
811 Some(r"c\d")
812 );
813 }
814
815 #[test]
816 fn serde_to_display_wire_format() {
817 let json = r#"{
818 "groups": [[{
819 "sofia_gateway": {
820 "gateway": "my_gw",
821 "destination": "18005551234"
822 }
823 }]]
824 }"#;
825 let bridge: BridgeDialString = serde_json::from_str(json).unwrap();
826 assert_eq!(bridge.to_string(), "sofia/gateway/my_gw/18005551234");
827 }
828}