freeswitch_log_parser/message/
parts.rs1pub(crate) struct ExecuteParts<'a> {
7 pub(crate) depth: Option<u32>,
8 pub(crate) channel: &'a str,
9 pub(crate) application: &'a str,
10 pub(crate) arguments: &'a str,
11}
12
13pub(crate) fn execute_parts(msg: &str) -> ExecuteParts<'_> {
14 let rest = &msg["EXECUTE ".len()..];
15
16 let Some(after) = rest.strip_prefix("[depth=") else {
17 return ExecuteParts {
18 depth: None,
19 channel: "",
20 application: "",
21 arguments: rest,
22 };
23 };
24 let depth = after
25 .find(']')
26 .and_then(|end| after[..end].parse::<u32>().ok());
27
28 let after_bracket = rest.find("] ").map(|p| &rest[p + 2..]).unwrap_or("");
29
30 let (channel, app_part) = match after_bracket.find(' ') {
33 Some(p) => {
34 let first_token = &after_bracket[..p];
35 if first_token.contains('/') {
36 (first_token, &after_bracket[p + 1..])
37 } else {
38 ("", after_bracket)
39 }
40 }
41 None => ("", after_bracket),
42 };
43
44 let (application, arguments) = match app_part.find('(') {
45 Some(p) => {
46 let app = &app_part[..p];
47 let args = if app_part.ends_with(')') {
48 &app_part[p + 1..app_part.len() - 1]
49 } else {
50 &app_part[p + 1..]
51 };
52 (app, args)
53 }
54 None => (app_part, ""),
55 };
56
57 ExecuteParts {
58 depth,
59 channel,
60 application,
61 arguments,
62 }
63}
64pub(crate) fn dialplan_parts(msg: &str) -> (&str, &str) {
66 let prefix_len = if msg.starts_with("Chatplan: ") {
67 "Chatplan: ".len()
68 } else {
69 "Dialplan: ".len()
70 };
71 let rest = &msg[prefix_len..];
72 match rest.find(' ') {
73 Some(p) => (&rest[..p], &rest[p + 1..]),
74 None => (rest, ""),
75 }
76}
77fn matching_close(bytes: &[u8], from: usize, open: u8, close: u8) -> Option<usize> {
80 let mut depth = 1u32;
81 for (i, &b) in bytes.iter().enumerate().skip(from) {
82 if b == open {
83 depth += 1;
84 } else if b == close {
85 depth -= 1;
86 if depth == 0 {
87 return Some(i);
88 }
89 }
90 }
91 None
92}
93
94fn matching_open(bytes: &[u8], from: usize, open: u8, close: u8) -> Option<usize> {
96 let mut depth = 1u32;
97 let mut i = from;
98 while i > 0 {
99 i -= 1;
100 if bytes[i] == close {
101 depth += 1;
102 } else if bytes[i] == open {
103 depth -= 1;
104 if depth == 0 {
105 return Some(i);
106 }
107 }
108 }
109 None
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114#[non_exhaustive]
115pub struct RegexCondition<'a> {
116 pub field: &'a str,
118 pub value: &'a str,
120}
121
122pub fn regex_condition_parts(detail: &str) -> Option<RegexCondition<'_>> {
130 let after_exten = detail.strip_prefix("Regex (")?.split_once(") [")?.1;
131 let head = &after_exten[after_exten.find("] ")? + 2..];
132 let head = &head[..head.rfind(" =~ /")?];
134 let close = head.strip_suffix(')')?.len();
135 let open = matching_open(head.as_bytes(), close, b'(', b')')?;
136 Some(RegexCondition {
137 field: &head[..open],
138 value: &head[open + 1..close],
139 })
140}
141
142pub(crate) fn parse_bracketed_value(s: &str, prefix_len: usize) -> Option<(&str, &str)> {
143 let after_prefix = &s[prefix_len..];
144 let colon = after_prefix.find(": ")?;
145 let name = &after_prefix[..colon];
146 let value_part = &after_prefix[colon + 2..];
147 if let Some(inner) = value_part.strip_prefix('[') {
148 if let Some(stripped) = inner.strip_suffix(']') {
149 Some((name, stripped))
150 } else {
151 Some((name, inner))
152 }
153 } else {
154 Some((name, value_part))
155 }
156}
157pub(crate) fn paren_channel(msg: &str) -> Option<&str> {
159 let inner = msg.strip_prefix('(')?;
160 let close = inner.find(')')?;
161 Some(&inner[..close]).filter(|c| !c.is_empty())
162}
163
164pub(crate) fn hangup_channel(msg: &str) -> Option<&str> {
166 let rest = msg.strip_prefix("Hangup ")?;
167 let bracket = rest.find(" [")?;
168 Some(&rest[..bracket]).filter(|c| !c.is_empty())
169}
170
171pub(crate) enum StateChange<'a> {
173 Channel(&'a str),
174 Call(&'a str),
175}
176
177pub(crate) fn parse_state_change(detail: &str) -> Option<StateChange<'_>> {
180 let arrow = detail.rfind(" -> ")?;
182 let to = detail[arrow + 4..].trim();
183 if detail.contains("Callstate Change") {
184 Some(StateChange::Call(to))
185 } else {
186 Some(StateChange::Channel(to))
187 }
188}
189
190pub(crate) fn new_channel_name(msg: &str) -> Option<&str> {
192 let rest = msg.strip_prefix("New Channel ")?;
193 let bracket = rest.rfind(" [")?;
194 Some(&rest[..bracket]).filter(|c| !c.is_empty())
195}
196
197pub(crate) fn strip_channel_prefix(msg: &str) -> Option<(&str, &str)> {
198 if !msg.starts_with("sofia/") && !msg.starts_with("loopback/") {
199 return None;
200 }
201 let bytes = msg.as_bytes();
202 let mut i = 0;
203 while i < bytes.len() {
204 match bytes[i] {
205 b'[' => i = matching_close(bytes, i + 1, b'[', b']')?,
206 b' ' => return Some((&msg[..i], &msg[i + 1..])),
207 _ => {}
208 }
209 i += 1;
210 }
211 None
212}
213pub(crate) struct SetExportParts<'a> {
216 pub(crate) channel: Option<&'a str>,
217 pub(crate) name: &'a str,
218 pub(crate) value: &'a str,
219}
220
221pub(crate) fn set_export_parts(msg: &str) -> Option<SetExportParts<'_>> {
222 let sep_pos = msg.find("]=[")?;
225 let name_start = msg[..sep_pos].rfind('[')?;
226 let name = &msg[name_start + 1..sep_pos];
227 let val_start = sep_pos + 3; let val_end = matching_close(msg.as_bytes(), val_start, b'[', b']').unwrap_or(msg.len());
229 let value = &msg[val_start..val_end];
230
231 let channel = msg
234 .strip_prefix("SET ")
235 .or_else(|| msg.strip_prefix("PUSH "))
236 .or_else(|| msg.strip_prefix("UNSHIFT "))
237 .and_then(|rest| {
238 let end = rest.find(' ').unwrap_or(rest.len());
239 Some(&rest[..end]).filter(|c| c.contains('/') && !c.starts_with('['))
240 });
241
242 Some(SetExportParts {
243 channel,
244 name,
245 value,
246 })
247}