freeswitch_log_parser/
peer.rs1use std::str::FromStr;
10
11use freeswitch_types::ChannelVariable;
12
13use crate::message::MessageKind;
14use crate::session::parse_bridge_args;
15use crate::stream::{Block, LogEntry};
16use crate::uuid::find_uuids;
17
18pub const PEER_UUID_VARS: &[ChannelVariable] = &[
20 ChannelVariable::BridgeUuid,
21 ChannelVariable::SignalBond,
22 ChannelVariable::SignalBridge,
23 ChannelVariable::LastBridgeTo,
24 ChannelVariable::OriginatingLegUuid,
25 ChannelVariable::OriginationUuid,
26 ChannelVariable::OriginatedLegs,
27 ChannelVariable::TransferSource,
28 ChannelVariable::TransferHistory,
29];
30
31pub fn is_peer_uuid_var(name: &str) -> bool {
34 ChannelVariable::from_str(name)
35 .map(|v| PEER_UUID_VARS.contains(&v))
36 .unwrap_or(false)
37}
38
39pub fn for_each_peer_uuid<F: FnMut(&str)>(entry: &LogEntry, f: F) {
44 for_each_peer_uuid_with(entry, |_| false, f);
45}
46
47pub fn for_each_peer_uuid_with<F: FnMut(&str)>(
55 entry: &LogEntry,
56 extra_var: impl Fn(&str) -> bool,
57 mut f: F,
58) {
59 let wanted = |name: &str| {
60 let name = name.strip_prefix("variable_").unwrap_or(name);
61 is_peer_uuid_var(name) || extra_var(name)
62 };
63 let mut harvest = |value: &str| {
64 for (_, uuid) in find_uuids(value) {
65 f(uuid);
66 }
67 };
68
69 if let Some(Block::ChannelData { variables, .. }) = &entry.block {
70 for (name, value) in variables {
71 if wanted(name) {
72 harvest(value);
73 }
74 }
75 }
76
77 match &entry.message_kind {
78 MessageKind::Variable { name, value } => {
79 if wanted(name) {
80 harvest(value);
81 }
82 }
83 MessageKind::Execute {
84 application,
85 arguments,
86 ..
87 } => match application.as_str() {
88 "set" | "export" => {
89 if let Some((name, value)) = arguments.split_once('=') {
90 if wanted(name) {
91 harvest(value);
92 }
93 }
94 }
95 "bridge" | "att_xfer" => {
98 if let Some(uuid) = parse_bridge_args(arguments).and_then(|i| i.origination_uuid) {
99 f(&uuid);
100 }
101 }
102 _ => {}
103 },
104 _ => {}
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::attached::AttachedLines;
112 use crate::line::LineKind;
113
114 const PEER: &str = "11111111-2222-3333-4444-555555555555";
115
116 fn entry(message_kind: MessageKind, block: Option<Block>) -> LogEntry {
117 LogEntry {
118 uuid: "self".to_string(),
119 timestamp: String::new(),
120 level: None,
121 idle_pct: None,
122 source: None,
123 message: String::new(),
124 kind: LineKind::Full,
125 message_kind,
126 block,
127 attached: AttachedLines::new(),
128 line_number: 0,
129 warnings: Vec::new(),
130 }
131 }
132
133 fn var(name: &str, value: &str) -> LogEntry {
134 entry(
135 MessageKind::Variable {
136 name: name.to_string(),
137 value: value.to_string(),
138 },
139 None,
140 )
141 }
142
143 fn collect(entry: &LogEntry) -> Vec<String> {
144 let mut out = Vec::new();
145 for_each_peer_uuid(entry, |u| out.push(u.to_string()));
146 out
147 }
148
149 #[test]
150 fn harvests_from_allowlisted_var() {
151 assert_eq!(collect(&var("bridge_uuid", PEER)), vec![PEER]);
152 }
153
154 #[test]
155 fn harvests_uppercase_hex() {
156 let upper = "AAAABBBB-2222-3333-4444-5555CCCCDDDD";
157 assert_eq!(collect(&var("bridge_uuid", upper)), vec![upper]);
158 }
159
160 #[test]
161 fn ignores_shared_context_var() {
162 assert!(collect(&var("domain_uuid", PEER)).is_empty());
163 }
164
165 #[test]
166 fn strips_variable_prefix_in_channel_data() {
167 let block = Block::ChannelData {
168 fields: Vec::new(),
169 variables: vec![("variable_signal_bond".to_string(), PEER.to_string())],
170 };
171 assert_eq!(
172 collect(&entry(MessageKind::General, Some(block))),
173 vec![PEER]
174 );
175 }
176
177 #[test]
178 fn harvests_from_set_and_bridge() {
179 let set = entry(
180 MessageKind::Execute {
181 depth: 0,
182 channel: "sofia/internal/1001".to_string(),
183 application: "set".to_string(),
184 arguments: format!("last_bridge_to={PEER}"),
185 },
186 None,
187 );
188 assert_eq!(collect(&set), vec![PEER]);
189
190 let bridge = |args: String| {
191 entry(
192 MessageKind::Execute {
193 depth: 0,
194 channel: "sofia/internal/1001".to_string(),
195 application: "bridge".to_string(),
196 arguments: args,
197 },
198 None,
199 )
200 };
201 assert_eq!(
203 collect(&bridge(format!(
204 "[origination_uuid={PEER}]sofia/gateway/gw/5551234"
205 ))),
206 vec![PEER]
207 );
208 assert_eq!(
209 collect(&bridge(format!(
210 "{{origination_uuid={PEER}}}sofia/gateway/gw/5551234"
211 ))),
212 vec![PEER]
213 );
214 }
215
216 #[test]
217 fn att_xfer_names_its_leg_the_same_way() {
218 let e = entry(
219 MessageKind::Execute {
220 depth: 0,
221 channel: "sofia/internal/1001".to_string(),
222 application: "att_xfer".to_string(),
223 arguments: format!("[origination_uuid={PEER}]sofia/internal/1002"),
224 },
225 None,
226 );
227 assert_eq!(collect(&e), vec![PEER]);
228 }
229
230 #[test]
231 fn extra_var_extends_the_allowlist() {
232 let e = var("my_deployment_peer_id", PEER);
233 assert!(collect(&e).is_empty());
234
235 let mut out = Vec::new();
236 for_each_peer_uuid_with(
237 &e,
238 |n| n == "my_deployment_peer_id",
239 |u| out.push(u.to_string()),
240 );
241 assert_eq!(out, vec![PEER]);
242 }
243
244 #[test]
245 fn harvests_every_uuid_in_a_multi_valued_var() {
246 let second = "aaaabbbb-cccc-dddd-eeee-ffff00001111";
247 let e = var("originated_legs", &format!("{PEER},{second}"));
248 assert_eq!(collect(&e), vec![PEER, second]);
249 }
250}