1use std::collections::HashMap;
5use std::str::FromStr;
6
7use freeswitch_types::variables::VariableName;
8use freeswitch_types::{CallDirection, CallState, ChannelState, EventHeader, HangupCause};
9
10use crate::line::parse_line;
11use crate::message::{classify_message, LifecycleEvent, MessageKind};
12use crate::stream::{Block, LogEntry, ParseWarning, SessionReading};
13use crate::uuid::is_uuid;
14
15use super::conference::ConferenceMembership;
16use super::media::SessionMedia;
17use super::parse::{
18 parse_bridge_args, parse_dialplan_context, parse_hangup, parse_new_channel,
19 parse_processing_line, parse_state_change, StateChange,
20};
21
22fn read<T: FromStr>(
27 slot: &mut Option<T>,
28 value: &str,
29 reading: SessionReading,
30 warnings: &mut Vec<ParseWarning>,
31) {
32 match T::from_str(value) {
33 Ok(parsed) => *slot = Some(parsed),
34 Err(_) => warnings.push(ParseWarning::UnreadableValue {
35 reading,
36 value: ParseWarning::excerpt(value),
37 }),
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43enum DumpSource {
44 Block,
47 AttachedLines,
49}
50
51#[derive(Debug, Clone, Default)]
57#[non_exhaustive]
58pub struct SessionState {
59 pub channel_name: Option<String>,
61 pub channel_state: Option<ChannelState>,
63 pub call_state: Option<CallState>,
66 pub initial_context: Option<String>,
68 pub initial_destination: Option<String>,
71 pub dialplan_context: Option<String>,
73 pub dialplan_from: Option<String>,
76 pub dialplan_to: Option<String>,
78 pub call_direction: Option<CallDirection>,
80 pub caller_id_number: Option<String>,
82 pub caller_id_name: Option<String>,
84 pub destination_number: Option<String>,
86 pub hangup_cause: Option<HangupCause>,
88 pub answered_at: Option<String>,
90 pub other_leg_uuid: Option<String>,
93 pub conference: Option<ConferenceMembership>,
95 pub media: SessionMedia,
97 pub(crate) pending_bridge_target: Option<String>,
99 pub variables: HashMap<String, String>,
101}
102#[derive(Debug, Clone)]
107#[non_exhaustive]
108pub struct SessionSnapshot {
109 pub channel_name: Option<String>,
110 pub channel_state: Option<ChannelState>,
111 pub call_state: Option<CallState>,
112 pub initial_context: Option<String>,
113 pub initial_destination: Option<String>,
114 pub dialplan_context: Option<String>,
115 pub dialplan_from: Option<String>,
116 pub dialplan_to: Option<String>,
117 pub call_direction: Option<CallDirection>,
118 pub caller_id_number: Option<String>,
119 pub caller_id_name: Option<String>,
120 pub destination_number: Option<String>,
121 pub hangup_cause: Option<HangupCause>,
122 pub answered_at: Option<String>,
123 pub other_leg_uuid: Option<String>,
124 pub conference: Option<ConferenceMembership>,
125 pub media: SessionMedia,
126}
127
128impl SessionState {
129 pub fn variable<V: VariableName>(&self, var: V) -> Option<&str> {
135 self.variables.get(var.as_str()).map(String::as_str)
136 }
137
138 pub(super) fn snapshot(&self) -> SessionSnapshot {
141 let SessionState {
142 channel_name,
143 channel_state,
144 call_state,
145 initial_context,
146 initial_destination,
147 dialplan_context,
148 dialplan_from,
149 dialplan_to,
150 call_direction,
151 caller_id_number,
152 caller_id_name,
153 destination_number,
154 hangup_cause,
155 answered_at,
156 other_leg_uuid,
157 conference,
158 media,
159 pending_bridge_target: _,
160 variables: _,
161 } = self;
162
163 SessionSnapshot {
164 channel_name: channel_name.clone(),
165 channel_state: *channel_state,
166 call_state: *call_state,
167 initial_context: initial_context.clone(),
168 initial_destination: initial_destination.clone(),
169 dialplan_context: dialplan_context.clone(),
170 dialplan_from: dialplan_from.clone(),
171 dialplan_to: dialplan_to.clone(),
172 call_direction: *call_direction,
173 caller_id_number: caller_id_number.clone(),
174 caller_id_name: caller_id_name.clone(),
175 destination_number: destination_number.clone(),
176 hangup_cause: *hangup_cause,
177 answered_at: answered_at.clone(),
178 other_leg_uuid: other_leg_uuid.clone(),
179 conference: conference.clone(),
180 media: media.clone(),
181 }
182 }
183
184 fn apply_channel_field(&mut self, name: &str, value: &str, warnings: &mut Vec<ParseWarning>) {
189 match EventHeader::from_str(name) {
190 Ok(EventHeader::ChannelName) => self.channel_name = Some(value.to_string()),
191 Ok(EventHeader::ChannelState) => read(
192 &mut self.channel_state,
193 value,
194 SessionReading::ChannelState,
195 warnings,
196 ),
197 Ok(EventHeader::CallDirection) => read(
198 &mut self.call_direction,
199 value,
200 SessionReading::CallDirection,
201 warnings,
202 ),
203 Ok(EventHeader::CallerCallerIdNumber) => {
204 self.caller_id_number = Some(value.to_string())
205 }
206 Ok(EventHeader::CallerCallerIdName) => self.caller_id_name = Some(value.to_string()),
207 Ok(EventHeader::CallerDestinationNumber) => {
208 self.destination_number = Some(value.to_string())
209 }
210 Ok(EventHeader::OtherLegUniqueId) => self.other_leg_uuid = Some(value.to_string()),
211 _ => {}
212 }
213 }
214
215 pub(super) fn is_terminal(&self) -> bool {
221 matches!(
222 self.channel_state,
223 Some(
224 ChannelState::CsHangup
225 | ChannelState::CsReporting
226 | ChannelState::CsDestroy
227 | ChannelState::CsNone
228 )
229 ) || matches!(self.call_state, Some(CallState::Hangup))
230 }
231
232 pub(super) fn update_from_entry(&mut self, entry: &LogEntry) -> Vec<ParseWarning> {
234 let mut warnings = Vec::new();
235 let dump = match entry.block {
236 Some(Block::ChannelData { .. }) => DumpSource::Block,
237 _ => DumpSource::AttachedLines,
238 };
239 if let Some(Block::ChannelData { fields, variables }) = &entry.block {
240 for (name, value) in fields {
241 self.apply_channel_field(name, value, &mut warnings);
242 }
243 for (name, value) in variables {
244 self.variables
245 .insert(name.bare().to_string(), value.clone());
246 }
247 }
248
249 match &entry.message_kind {
250 MessageKind::Execute {
251 application,
252 arguments,
253 ..
254 } => match application.as_str() {
255 "set" | "export" => {
256 if let Some((name, value)) = arguments.split_once('=') {
257 self.variables.insert(name.to_string(), value.to_string());
258 }
259 }
260 "bridge" => {
261 if let Some(info) = parse_bridge_args(arguments) {
262 if let Some(uuid) = &info.origination_uuid {
263 self.other_leg_uuid = Some(uuid.clone());
264 }
265 self.pending_bridge_target = Some(info.target_channel);
266 }
267 }
268 _ => {}
269 },
270 MessageKind::ChannelLifecycle { event, detail } => match event {
271 LifecycleEvent::NewChannel => {
272 if self.channel_name.is_none() {
273 self.channel_name = parse_new_channel(detail).map(str::to_string);
274 }
275 }
276 LifecycleEvent::Hangup => {
277 if let Some(cause) = parse_hangup(detail) {
278 read(
279 &mut self.hangup_cause,
280 cause,
281 SessionReading::HangupCause,
282 &mut warnings,
283 );
284 }
285 }
286 LifecycleEvent::Answered => {
287 self.answered_at
288 .get_or_insert_with(|| entry.timestamp.clone());
289 }
290 _ => {}
291 },
292 kind => self.apply_kind(kind, &mut warnings),
293 }
294
295 self.apply_processing(&entry.message);
296 self.media.update_from_entry(entry);
297
298 for attached in &entry.attached {
299 let parsed = parse_line(attached);
300 self.update_from_message(parsed.message, dump, &mut warnings);
301 }
302 warnings
303 }
304
305 fn apply_kind(&mut self, kind: &MessageKind, warnings: &mut Vec<ParseWarning>) {
309 match kind {
310 MessageKind::Dialplan { detail, .. } => {
311 if let Some(context) = parse_dialplan_context(detail) {
312 self.initial_context.get_or_insert(context.to_string());
313 self.dialplan_context = Some(context.to_string());
314 }
315 }
316 MessageKind::Variable { name, value } => {
317 self.variables
318 .insert(name.bare().to_string(), value.clone());
319 }
320 MessageKind::ChannelField { name, value } => {
321 self.apply_channel_field(name, value, warnings)
322 }
323 MessageKind::OriginateSuccess {
326 peer_uuid: Some(peer),
327 ..
328 } if !is_uuid(peer) => warnings.push(ParseWarning::UnreadableValue {
329 reading: SessionReading::PeerUuid,
330 value: ParseWarning::excerpt(peer),
331 }),
332 MessageKind::StateChange { detail } => match parse_state_change(detail) {
333 Some(StateChange::Channel(to)) => read(
334 &mut self.channel_state,
335 to,
336 SessionReading::ChannelState,
337 warnings,
338 ),
339 Some(StateChange::Call(to)) => read(
340 &mut self.call_state,
341 to,
342 SessionReading::CallState,
343 warnings,
344 ),
345 None => {}
346 },
347 _ => {}
348 }
349 }
350
351 fn apply_processing(&mut self, msg: &str) {
355 if let Some(dp) = parse_processing_line(msg) {
356 self.initial_context.get_or_insert(dp.context.clone());
357 self.initial_destination.get_or_insert(dp.to.clone());
358 self.dialplan_context = Some(dp.context);
359 self.dialplan_from = Some(dp.from);
360 self.dialplan_to = Some(dp.to);
361 }
362 }
363
364 fn update_from_message(
365 &mut self,
366 msg: &str,
367 dump: DumpSource,
368 warnings: &mut Vec<ParseWarning>,
369 ) {
370 let kind = classify_message(msg);
371 match &kind {
372 MessageKind::Variable { .. } | MessageKind::ChannelField { .. }
373 if dump == DumpSource::Block => {}
374 kind => self.apply_kind(kind, warnings),
375 }
376 self.apply_processing(msg);
377 }
378}