1use std::collections::HashMap;
5use std::str::FromStr;
6
7use freeswitch_types::variables::VariableName;
8use freeswitch_types::{CallDirection, CallState, ChannelState, HangupCause};
9
10use crate::line::parse_line;
11use crate::message::{classify_message, MessageKind};
12use crate::stream::{Block, LogEntry, ParseWarning, SessionReading};
13
14use super::conference::ConferenceMembership;
15use super::media::SessionMedia;
16use super::parse::{
17 is_answered, parse_bridge_args, parse_dialplan_context, parse_hangup, parse_new_channel,
18 parse_processing_line, parse_state_change, StateChange,
19};
20
21fn read<T: FromStr>(
26 slot: &mut Option<T>,
27 value: &str,
28 reading: SessionReading,
29 warnings: &mut Vec<ParseWarning>,
30) {
31 match T::from_str(value) {
32 Ok(parsed) => *slot = Some(parsed),
33 Err(_) => warnings.push(ParseWarning::UnreadableValue {
34 reading,
35 value: ParseWarning::excerpt(value),
36 }),
37 }
38}
39
40#[derive(Debug, Clone, Default)]
46#[non_exhaustive]
47pub struct SessionState {
48 pub channel_name: Option<String>,
50 pub channel_state: Option<ChannelState>,
52 pub call_state: Option<CallState>,
55 pub initial_context: Option<String>,
57 pub initial_destination: Option<String>,
60 pub dialplan_context: Option<String>,
62 pub dialplan_from: Option<String>,
65 pub dialplan_to: Option<String>,
67 pub call_direction: Option<CallDirection>,
69 pub caller_id_number: Option<String>,
71 pub caller_id_name: Option<String>,
73 pub destination_number: Option<String>,
75 pub hangup_cause: Option<HangupCause>,
77 pub answered_at: Option<String>,
79 pub other_leg_uuid: Option<String>,
82 pub conference: Option<ConferenceMembership>,
84 pub media: SessionMedia,
86 pub(crate) pending_bridge_target: Option<String>,
88 pub variables: HashMap<String, String>,
90}
91#[derive(Debug, Clone)]
96#[non_exhaustive]
97pub struct SessionSnapshot {
98 pub channel_name: Option<String>,
99 pub channel_state: Option<ChannelState>,
100 pub call_state: Option<CallState>,
101 pub initial_context: Option<String>,
102 pub initial_destination: Option<String>,
103 pub dialplan_context: Option<String>,
104 pub dialplan_from: Option<String>,
105 pub dialplan_to: Option<String>,
106 pub call_direction: Option<CallDirection>,
107 pub caller_id_number: Option<String>,
108 pub caller_id_name: Option<String>,
109 pub destination_number: Option<String>,
110 pub hangup_cause: Option<HangupCause>,
111 pub answered_at: Option<String>,
112 pub other_leg_uuid: Option<String>,
113 pub conference: Option<ConferenceMembership>,
114 pub media: SessionMedia,
115}
116
117impl SessionState {
118 pub fn variable<V: VariableName>(&self, var: V) -> Option<&str> {
124 self.variables.get(var.as_str()).map(String::as_str)
125 }
126
127 pub(super) fn snapshot(&self) -> SessionSnapshot {
132 let SessionState {
133 channel_name,
134 channel_state,
135 call_state,
136 initial_context,
137 initial_destination,
138 dialplan_context,
139 dialplan_from,
140 dialplan_to,
141 call_direction,
142 caller_id_number,
143 caller_id_name,
144 destination_number,
145 hangup_cause,
146 answered_at,
147 other_leg_uuid,
148 conference,
149 media,
150 pending_bridge_target: _,
151 variables: _,
152 } = self;
153
154 SessionSnapshot {
155 channel_name: channel_name.clone(),
156 channel_state: *channel_state,
157 call_state: *call_state,
158 initial_context: initial_context.clone(),
159 initial_destination: initial_destination.clone(),
160 dialplan_context: dialplan_context.clone(),
161 dialplan_from: dialplan_from.clone(),
162 dialplan_to: dialplan_to.clone(),
163 call_direction: *call_direction,
164 caller_id_number: caller_id_number.clone(),
165 caller_id_name: caller_id_name.clone(),
166 destination_number: destination_number.clone(),
167 hangup_cause: *hangup_cause,
168 answered_at: answered_at.clone(),
169 other_leg_uuid: other_leg_uuid.clone(),
170 conference: conference.clone(),
171 media: media.clone(),
172 }
173 }
174
175 fn apply_channel_field(&mut self, name: &str, value: &str, warnings: &mut Vec<ParseWarning>) {
180 match name {
181 "Channel-Name" => self.channel_name = Some(value.to_string()),
182 "Channel-State" => read(
183 &mut self.channel_state,
184 value,
185 SessionReading::ChannelState,
186 warnings,
187 ),
188 "Call-Direction" => read(
189 &mut self.call_direction,
190 value,
191 SessionReading::CallDirection,
192 warnings,
193 ),
194 "Caller-Caller-ID-Number" => self.caller_id_number = Some(value.to_string()),
195 "Caller-Caller-ID-Name" => self.caller_id_name = Some(value.to_string()),
196 "Caller-Destination-Number" => self.destination_number = Some(value.to_string()),
197 "Other-Leg-Unique-ID" => self.other_leg_uuid = Some(value.to_string()),
198 _ => {}
199 }
200 }
201
202 pub(super) fn is_terminal(&self) -> bool {
208 matches!(
209 self.channel_state,
210 Some(
211 ChannelState::CsHangup
212 | ChannelState::CsReporting
213 | ChannelState::CsDestroy
214 | ChannelState::CsNone
215 )
216 ) || matches!(self.call_state, Some(CallState::Hangup))
217 }
218
219 pub(super) fn update_from_entry(&mut self, entry: &LogEntry) -> Vec<ParseWarning> {
221 let mut warnings = Vec::new();
222 let block_has_channel_data = matches!(entry.block, Some(Block::ChannelData { .. }));
223 if let Some(Block::ChannelData { fields, variables }) = &entry.block {
224 for (name, value) in fields {
225 self.apply_channel_field(name, value, &mut warnings);
226 }
227 for (name, value) in variables {
228 let var_name = name.strip_prefix("variable_").unwrap_or(name);
229 self.variables.insert(var_name.to_string(), value.clone());
230 }
231 }
232
233 match &entry.message_kind {
234 MessageKind::Execute {
235 application,
236 arguments,
237 ..
238 } => match application.as_str() {
239 "set" | "export" => {
240 if let Some((name, value)) = arguments.split_once('=') {
241 self.variables.insert(name.to_string(), value.to_string());
242 }
243 }
244 "bridge" => {
245 if let Some(info) = parse_bridge_args(arguments) {
246 if let Some(uuid) = &info.origination_uuid {
247 self.other_leg_uuid = Some(uuid.clone());
248 }
249 self.pending_bridge_target = Some(info.target_channel);
250 }
251 }
252 _ => {}
253 },
254 MessageKind::ChannelLifecycle { detail } => {
255 if let Some(name) = parse_new_channel(detail) {
256 if self.channel_name.is_none() {
257 self.channel_name = Some(name);
258 }
259 }
260 if let Some(cause) = parse_hangup(detail) {
261 read(
262 &mut self.hangup_cause,
263 &cause,
264 SessionReading::HangupCause,
265 &mut warnings,
266 );
267 }
268 if is_answered(detail) && self.answered_at.is_none() {
269 self.answered_at = Some(entry.timestamp.clone());
270 }
271 }
272 kind => self.apply_kind(kind, &mut warnings),
273 }
274
275 self.apply_processing(&entry.message);
276 self.media.update_from_entry(entry);
277
278 for attached in &entry.attached {
279 let parsed = parse_line(attached);
280 self.update_from_message(parsed.message, block_has_channel_data, &mut warnings);
281 }
282 warnings
283 }
284
285 fn apply_kind(&mut self, kind: &MessageKind, warnings: &mut Vec<ParseWarning>) {
289 match kind {
290 MessageKind::Dialplan { detail, .. } => {
291 if let Some(context) = parse_dialplan_context(detail) {
292 self.initial_context.get_or_insert(context.to_string());
293 self.dialplan_context = Some(context.to_string());
294 }
295 }
296 MessageKind::Variable { name, value } => {
297 let var_name = name.strip_prefix("variable_").unwrap_or(name);
298 self.variables.insert(var_name.to_string(), value.clone());
299 }
300 MessageKind::ChannelField { name, value } => {
301 self.apply_channel_field(name, value, warnings)
302 }
303 MessageKind::StateChange { detail } => match parse_state_change(detail) {
304 Some(StateChange::Channel(to)) => read(
305 &mut self.channel_state,
306 to,
307 SessionReading::ChannelState,
308 warnings,
309 ),
310 Some(StateChange::Call(to)) => read(
311 &mut self.call_state,
312 to,
313 SessionReading::CallState,
314 warnings,
315 ),
316 None => {}
317 },
318 _ => {}
319 }
320 }
321
322 fn apply_processing(&mut self, msg: &str) {
326 if msg.contains("Processing ") && msg.contains(" in context ") {
327 if let Some(dp) = parse_processing_line(msg) {
328 self.initial_context.get_or_insert(dp.context.clone());
329 self.initial_destination.get_or_insert(dp.to.clone());
330 self.dialplan_context = Some(dp.context);
331 self.dialplan_from = Some(dp.from);
332 self.dialplan_to = Some(dp.to);
333 }
334 }
335 }
336
337 fn update_from_message(
338 &mut self,
339 msg: &str,
340 block_provides_channel_data: bool,
341 warnings: &mut Vec<ParseWarning>,
342 ) {
343 let kind = classify_message(msg);
344 match &kind {
345 MessageKind::Variable { .. } | MessageKind::ChannelField { .. }
349 if block_provides_channel_data => {}
350 kind => self.apply_kind(kind, warnings),
351 }
352 self.apply_processing(msg);
353 }
354}