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