freeswitch_log_parser/message/
classify.rs1use freeswitch_types::VARIABLE_PREFIX;
5
6use crate::codec::CodecMedia;
7
8use super::dtmf::parse_dtmf;
9use super::kind::MessageKind;
10use super::lifecycle::{
11 channel_lifecycle, classify_channel_prefixed, detect_channel_lifecycle, originate_success,
12};
13use super::media::{detect_media, detect_sdp_direction};
14use super::parts::{
15 dialplan_parts, execute_parts, parse_bracketed_value, set_export_parts, strip_channel_prefix,
16};
17use super::varname::VarName;
18
19fn parse_execute(msg: &str) -> MessageKind {
20 let parts = execute_parts(msg);
21 MessageKind::Execute {
22 depth: parts.depth,
23 channel: parts.channel.to_string(),
24 application: parts.application.to_string(),
25 arguments: parts.arguments.to_string(),
26 }
27}
28fn parse_dialplan(msg: &str) -> MessageKind {
29 let (channel, detail) = dialplan_parts(msg);
30 MessageKind::Dialplan {
31 channel: channel.to_string(),
32 detail: detail.to_string(),
33 }
34}
35pub fn classify_message(msg: &str) -> MessageKind {
40 if msg.starts_with("EXECUTE ") || msg.starts_with("Execute ") {
41 return parse_execute(msg);
42 }
43
44 if msg.starts_with("RECV DTMF ")
45 || msg.starts_with("RTP RECV DTMF ")
46 || msg.starts_with("INFO DTMF(")
47 {
48 if let Some(dtmf) = parse_dtmf(msg) {
49 return dtmf;
50 }
51 }
52
53 if msg.starts_with("Dialplan: ") || msg.starts_with("Chatplan: ") {
54 return parse_dialplan(msg);
55 }
56
57 if msg.starts_with("Processing ")
58 && (msg.contains(" in context ") || msg.contains("recursive conditions"))
59 {
60 return parse_dialplan_processing(msg);
61 }
62
63 if msg.contains("CHANNEL_DATA") {
64 return MessageKind::ChannelData;
65 }
66
67 if msg.starts_with(VARIABLE_PREFIX) {
68 if let Some((name, value)) = parse_bracketed_value(msg, VARIABLE_PREFIX.len()) {
69 return MessageKind::Variable {
70 name: VarName::new(name),
71 value: value.to_string(),
72 };
73 }
74 }
75
76 if let Some(direction) = detect_sdp_direction(msg) {
77 return MessageKind::SdpMarker { direction };
78 }
79
80 if msg.contains("State Change") || msg.contains("Callstate Change") {
81 return MessageKind::StateChange {
82 detail: msg.to_string(),
83 };
84 }
85
86 if msg.starts_with("SET ")
88 || msg.starts_with("EXPORT ")
89 || msg.starts_with("PUSH ")
90 || msg.starts_with("UNSHIFT ")
91 {
92 if let Some(sv) = parse_set_or_export(msg) {
93 return sv;
94 }
95 }
96
97 if msg.starts_with("Audio Codec Compare ") {
98 return MessageKind::CodecNegotiation {
99 media: CodecMedia::Audio,
100 };
101 }
102
103 if msg.starts_with("Video Codec Compare ") {
104 return MessageKind::CodecNegotiation {
105 media: CodecMedia::Video,
106 };
107 }
108
109 if msg.starts_with("CoreSession::setVariable(") {
110 return parse_core_session_set_variable(msg);
111 }
112
113 if msg.starts_with("UNSET ") {
114 return parse_unset(msg);
115 }
116
117 if let Some(rest) = msg.strip_prefix("set variable ") {
118 if let Some((name, value)) = rest.split_once('=') {
119 return MessageKind::Variable {
120 name: VarName::new(name),
121 value: value.to_string(),
122 };
123 }
124 }
125
126 if msg.starts_with("Transfer ") {
127 return MessageKind::Dialplan {
128 channel: String::new(),
129 detail: msg.to_string(),
130 };
131 }
132
133 if msg.starts_with('(') {
134 if msg.contains(") State ") {
135 return MessageKind::StateChange {
136 detail: msg.to_string(),
137 };
138 }
139 return channel_lifecycle(msg);
140 }
141
142 if msg.starts_with("SOFIA ") {
143 return MessageKind::StateChange {
144 detail: msg.to_string(),
145 };
146 }
147
148 if msg.starts_with("checking condition") || msg.starts_with("action(") {
149 return channel_lifecycle(msg);
150 }
151
152 if msg.starts_with("Event Socket Command") {
153 return MessageKind::EventSocket {
154 detail: msg.to_string(),
155 };
156 }
157
158 if let Some(kind) = detect_media(msg) {
159 return kind;
160 }
161
162 if let Some(kind) = originate_success(msg) {
163 return kind;
164 }
165
166 if let Some(kind) = detect_channel_lifecycle(msg) {
167 return kind;
168 }
169
170 if let Some((channel_part, rest)) = strip_channel_prefix(msg) {
171 return classify_channel_prefixed(channel_part, rest);
172 }
173
174 if let Some((name, value)) = parse_bracketed_value(msg, 0) {
177 let name_bytes = name.as_bytes();
178 if !name_bytes.is_empty()
179 && !name.contains(' ')
180 && name_bytes[0].is_ascii_alphabetic()
181 && (name.contains('-') || name.starts_with("Channel-"))
182 {
183 return MessageKind::ChannelField {
184 name: name.to_string(),
185 value: value.to_string(),
186 };
187 }
188 }
189
190 MessageKind::General
191}
192fn parse_core_session_set_variable(msg: &str) -> MessageKind {
193 let rest = &msg["CoreSession::setVariable(".len()..];
194 if let Some(end) = rest.strip_suffix(')') {
195 if let Some(comma) = end.find(", ") {
196 return MessageKind::Variable {
197 name: VarName::new(&end[..comma]),
198 value: end[comma + 2..].to_string(),
199 };
200 }
201 }
202 MessageKind::Variable {
203 name: VarName::new(""),
204 value: msg.to_string(),
205 }
206}
207
208fn parse_unset(msg: &str) -> MessageKind {
209 let rest = &msg["UNSET ".len()..];
210 let name = if let Some(inner) = rest.strip_prefix('[') {
211 inner.strip_suffix(']').unwrap_or(inner)
212 } else {
213 rest
214 };
215 MessageKind::Variable {
216 name: VarName::new(name),
217 value: String::new(),
218 }
219}
220
221fn parse_dialplan_processing(msg: &str) -> MessageKind {
222 let rest = &msg["Processing ".len()..];
223 MessageKind::Dialplan {
224 channel: String::new(),
225 detail: rest.to_string(),
226 }
227}
228fn parse_set_or_export(msg: &str) -> Option<MessageKind> {
229 let parts = set_export_parts(msg)?;
230 Some(MessageKind::Variable {
231 name: VarName::new(parts.name),
232 value: parts.value.to_string(),
233 })
234}