freeswitch_log_parser/message/
parts.rs1pub(crate) struct ExecuteParts<'a> {
7 pub(crate) depth: 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 depth = if rest.starts_with("[depth=") {
17 let end = rest.find(']').unwrap_or(0);
18 if end > 7 {
19 rest[7..end].parse::<u32>().unwrap_or(0)
20 } else {
21 0
22 }
23 } else {
24 return ExecuteParts {
25 depth: 0,
26 channel: "",
27 application: "",
28 arguments: rest,
29 };
30 };
31
32 let after_bracket = rest.find("] ").map(|p| &rest[p + 2..]).unwrap_or("");
33
34 let (channel, app_part) = match after_bracket.find(' ') {
38 Some(p) => {
39 let first_token = &after_bracket[..p];
40 if first_token.contains('/') {
41 (first_token, &after_bracket[p + 1..])
42 } else {
43 ("", after_bracket)
44 }
45 }
46 None => ("", after_bracket),
47 };
48
49 let (application, arguments) = match app_part.find('(') {
50 Some(p) => {
51 let app = &app_part[..p];
52 let args = if app_part.ends_with(')') {
53 &app_part[p + 1..app_part.len() - 1]
54 } else {
55 &app_part[p + 1..]
56 };
57 (app, args)
58 }
59 None => (app_part, ""),
60 };
61
62 ExecuteParts {
63 depth,
64 channel,
65 application,
66 arguments,
67 }
68}
69pub(crate) fn dialplan_parts(msg: &str) -> (&str, &str) {
71 let prefix_len = if msg.starts_with("Chatplan: ") {
72 "Chatplan: ".len()
73 } else {
74 "Dialplan: ".len()
75 };
76 let rest = &msg[prefix_len..];
77 match rest.find(' ') {
78 Some(p) => (&rest[..p], &rest[p + 1..]),
79 None => (rest, ""),
80 }
81}
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[non_exhaustive]
85pub struct RegexCondition<'a> {
86 pub field: &'a str,
88 pub value: &'a str,
90}
91
92pub fn regex_condition_parts(detail: &str) -> Option<RegexCondition<'_>> {
100 let after_exten = detail.strip_prefix("Regex (")?.split_once(") [")?.1;
101 let head = &after_exten[after_exten.find("] ")? + 2..];
102 let head = &head[..head.rfind(" =~ /")?];
104 let close = head.strip_suffix(')')?.len();
105
106 let bytes = head.as_bytes();
107 let mut depth = 1u32;
108 let mut i = close;
109 while i > 0 {
110 i -= 1;
111 match bytes[i] {
112 b')' => depth += 1,
113 b'(' => {
114 depth -= 1;
115 if depth == 0 {
116 return Some(RegexCondition {
117 field: &head[..i],
118 value: &head[i + 1..close],
119 });
120 }
121 }
122 _ => {}
123 }
124 }
125 None
126}
127
128pub(crate) fn parse_bracketed_value(s: &str, prefix_len: usize) -> Option<(&str, &str)> {
129 let after_prefix = &s[prefix_len..];
130 let colon = after_prefix.find(": ")?;
131 let name = &after_prefix[..colon];
132 let value_part = &after_prefix[colon + 2..];
133 if let Some(inner) = value_part.strip_prefix('[') {
134 if let Some(stripped) = inner.strip_suffix(']') {
135 Some((name, stripped))
136 } else {
137 Some((name, inner))
138 }
139 } else {
140 Some((name, value_part))
141 }
142}
143pub(crate) fn paren_channel(msg: &str) -> Option<&str> {
145 let inner = msg.strip_prefix('(')?;
146 let close = inner.find(')')?;
147 Some(&inner[..close]).filter(|c| !c.is_empty())
148}
149
150pub(crate) fn hangup_channel(msg: &str) -> Option<&str> {
152 let rest = msg.strip_prefix("Hangup ")?;
153 let bracket = rest.find(" [")?;
154 Some(&rest[..bracket]).filter(|c| !c.is_empty())
155}
156
157pub(crate) fn new_channel_name(msg: &str) -> Option<&str> {
159 let rest = msg.strip_prefix("New Channel ")?;
160 let bracket = rest.rfind(" [")?;
161 Some(&rest[..bracket]).filter(|c| !c.is_empty())
162}
163
164pub(crate) fn strip_channel_prefix(msg: &str) -> Option<(&str, &str)> {
165 if !msg.starts_with("sofia/") && !msg.starts_with("loopback/") {
166 return None;
167 }
168 let bytes = msg.as_bytes();
169 let mut i = 0;
170 let mut bracket_depth: u32 = 0;
171 while i < bytes.len() {
172 match bytes[i] {
173 b'[' => bracket_depth += 1,
174 b']' => {
175 bracket_depth = bracket_depth.saturating_sub(1);
176 }
177 b' ' if bracket_depth == 0 => {
178 return Some((&msg[..i], &msg[i + 1..]));
179 }
180 _ => {}
181 }
182 i += 1;
183 }
184 None
185}
186pub(crate) struct SetExportParts<'a> {
189 pub(crate) channel: Option<&'a str>,
190 pub(crate) name: &'a str,
191 pub(crate) value: &'a str,
192}
193
194fn value_close(msg: &str, from: usize) -> Option<usize> {
197 let mut depth = 1u32;
198 for (i, b) in msg.as_bytes().iter().enumerate().skip(from) {
199 match b {
200 b'[' => depth += 1,
201 b']' => {
202 depth -= 1;
203 if depth == 0 {
204 return Some(i);
205 }
206 }
207 _ => {}
208 }
209 }
210 None
211}
212
213pub(crate) fn set_export_parts(msg: &str) -> Option<SetExportParts<'_>> {
214 let sep_pos = msg.find("]=[")?;
220 let name_start = msg[..sep_pos].rfind('[')?;
221 let name = &msg[name_start + 1..sep_pos];
222 let val_start = sep_pos + 3; let val_end = value_close(msg, val_start).unwrap_or(msg.len());
224 let value = &msg[val_start..val_end];
225
226 let channel = msg
229 .strip_prefix("SET ")
230 .or_else(|| msg.strip_prefix("PUSH "))
231 .or_else(|| msg.strip_prefix("UNSHIFT "))
232 .and_then(|rest| {
233 let end = rest.find(' ').unwrap_or(rest.len());
234 Some(&rest[..end]).filter(|c| c.contains('/') && !c.starts_with('['))
235 });
236
237 Some(SetExportParts {
238 channel,
239 name,
240 value,
241 })
242}