1use std::collections::HashMap;
5use std::str::FromStr;
6
7use freeswitch_types::variables::{SofiaVariable, VariableName};
8use freeswitch_types::{CallDirection, CallState, ChannelState, EventHeader, HangupCause};
9
10use crate::line::parse_line;
11use crate::message::{
12 classify_message, parse_state_change, LifecycleEvent, MessageKind, StateChange,
13};
14use crate::stream::{Block, LogEntry, ParseWarning, SessionReading};
15use crate::uuid::is_uuid;
16
17use super::conference::ConferenceMembership;
18use super::media::SessionMedia;
19use super::parse::{
20 parse_bridge_args, parse_dialplan_context, parse_hangup, parse_new_channel,
21 parse_processing_line,
22};
23
24fn read<T: FromStr>(
29 slot: &mut Option<T>,
30 value: &str,
31 reading: SessionReading,
32 warnings: &mut Vec<ParseWarning>,
33) {
34 match T::from_str(value) {
35 Ok(parsed) => *slot = Some(parsed),
36 Err(_) => warnings.push(ParseWarning::UnreadableValue {
37 reading,
38 value: ParseWarning::excerpt(value),
39 }),
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45enum DumpSource {
46 Block,
49 AttachedLines,
51}
52
53#[derive(Debug, Clone, Default)]
59#[non_exhaustive]
60pub struct SessionState {
61 pub channel_name: Option<String>,
63 pub channel_state: Option<ChannelState>,
65 pub call_state: Option<CallState>,
68 pub initial_context: Option<String>,
70 pub initial_destination: Option<String>,
73 pub initial_caller_number: Option<String>,
77 pub initial_caller_name: Option<String>,
82 pub dialplan_context: Option<String>,
84 pub dialplan_from: Option<String>,
87 pub dialplan_to: Option<String>,
89 pub call_direction: Option<CallDirection>,
91 pub caller_id_number: Option<String>,
93 pub caller_id_name: Option<String>,
95 pub destination_number: Option<String>,
97 pub hangup_cause: Option<HangupCause>,
99 pub answered_at: Option<String>,
101 pub other_leg_uuid: Option<String>,
104 pub conference: Option<ConferenceMembership>,
106 pub media: SessionMedia,
108 pub(crate) pending_bridge_target: Option<String>,
110 pub variables: HashMap<String, String>,
112}
113#[derive(Debug, Clone)]
118#[non_exhaustive]
119pub struct SessionSnapshot {
120 pub channel_name: Option<String>,
121 pub channel_state: Option<ChannelState>,
122 pub call_state: Option<CallState>,
123 pub initial_context: Option<String>,
124 pub initial_destination: Option<String>,
125 pub initial_caller_number: Option<String>,
126 pub initial_caller_name: Option<String>,
127 pub dialplan_context: Option<String>,
128 pub dialplan_from: Option<String>,
129 pub dialplan_to: Option<String>,
130 pub call_direction: Option<CallDirection>,
131 pub caller_id_number: Option<String>,
132 pub caller_id_name: Option<String>,
133 pub destination_number: Option<String>,
134 pub hangup_cause: Option<HangupCause>,
135 pub answered_at: Option<String>,
136 pub other_leg_uuid: Option<String>,
137 pub conference: Option<ConferenceMembership>,
138 pub media: SessionMedia,
139}
140
141impl SessionState {
142 pub fn variable<V: VariableName>(&self, var: V) -> Option<&str> {
148 self.variables.get(var.as_str()).map(String::as_str)
149 }
150
151 pub fn caller_number(&self) -> Option<&str> {
161 self.caller_id_number
162 .as_deref()
163 .or_else(|| self.variable(SofiaVariable::SipFromUser))
164 .or(self.initial_caller_number.as_deref())
165 }
166
167 pub fn callee_number(&self) -> Option<&str> {
172 self.destination_number
173 .as_deref()
174 .or_else(|| self.variable(SofiaVariable::SipToUser))
175 .or(self.initial_destination.as_deref())
176 }
177
178 pub(super) fn snapshot(&self) -> SessionSnapshot {
181 let SessionState {
182 channel_name,
183 channel_state,
184 call_state,
185 initial_context,
186 initial_destination,
187 initial_caller_number,
188 initial_caller_name,
189 dialplan_context,
190 dialplan_from,
191 dialplan_to,
192 call_direction,
193 caller_id_number,
194 caller_id_name,
195 destination_number,
196 hangup_cause,
197 answered_at,
198 other_leg_uuid,
199 conference,
200 media,
201 pending_bridge_target: _,
202 variables: _,
203 } = self;
204
205 SessionSnapshot {
206 channel_name: channel_name.clone(),
207 channel_state: *channel_state,
208 call_state: *call_state,
209 initial_context: initial_context.clone(),
210 initial_destination: initial_destination.clone(),
211 initial_caller_number: initial_caller_number.clone(),
212 initial_caller_name: initial_caller_name.clone(),
213 dialplan_context: dialplan_context.clone(),
214 dialplan_from: dialplan_from.clone(),
215 dialplan_to: dialplan_to.clone(),
216 call_direction: *call_direction,
217 caller_id_number: caller_id_number.clone(),
218 caller_id_name: caller_id_name.clone(),
219 destination_number: destination_number.clone(),
220 hangup_cause: *hangup_cause,
221 answered_at: answered_at.clone(),
222 other_leg_uuid: other_leg_uuid.clone(),
223 conference: conference.clone(),
224 media: media.clone(),
225 }
226 }
227
228 fn apply_channel_field(&mut self, name: &str, value: &str, warnings: &mut Vec<ParseWarning>) {
233 match EventHeader::from_str(name) {
234 Ok(EventHeader::ChannelName) => self.channel_name = Some(value.to_string()),
235 Ok(EventHeader::ChannelState) => read(
236 &mut self.channel_state,
237 value,
238 SessionReading::ChannelState,
239 warnings,
240 ),
241 Ok(EventHeader::CallDirection) => read(
242 &mut self.call_direction,
243 value,
244 SessionReading::CallDirection,
245 warnings,
246 ),
247 Ok(EventHeader::CallerCallerIdNumber) => {
248 self.caller_id_number = Some(value.to_string())
249 }
250 Ok(EventHeader::CallerCallerIdName) => self.caller_id_name = Some(value.to_string()),
251 Ok(EventHeader::CallerDestinationNumber) => {
252 self.destination_number = Some(value.to_string())
253 }
254 Ok(EventHeader::OtherLegUniqueId) => self.other_leg_uuid = Some(value.to_string()),
255 _ => {}
256 }
257 }
258
259 pub(super) fn is_terminal(&self) -> bool {
265 matches!(
266 self.channel_state,
267 Some(
268 ChannelState::CsHangup
269 | ChannelState::CsReporting
270 | ChannelState::CsDestroy
271 | ChannelState::CsNone
272 )
273 ) || matches!(self.call_state, Some(CallState::Hangup))
274 }
275
276 pub(super) fn update_from_entry(&mut self, entry: &LogEntry) -> Vec<ParseWarning> {
278 let mut warnings = Vec::new();
279 let dump = match entry.block {
280 Some(Block::ChannelData { .. }) => DumpSource::Block,
281 _ => DumpSource::AttachedLines,
282 };
283 if let Some(Block::ChannelData { fields, variables }) = &entry.block {
284 for (name, value) in fields {
285 self.apply_channel_field(name, value, &mut warnings);
286 }
287 for (name, value) in variables {
288 self.variables
289 .insert(name.bare().to_string(), value.clone());
290 }
291 }
292
293 match &entry.message_kind {
294 MessageKind::Execute {
295 application,
296 arguments,
297 ..
298 } => match application.as_str() {
299 "set" | "export" => {
300 if let Some((name, value)) = arguments.split_once('=') {
301 self.variables.insert(name.to_string(), value.to_string());
302 }
303 }
304 "bridge" => {
305 if let Some(info) = parse_bridge_args(arguments) {
306 if let Some(uuid) = &info.origination_uuid {
307 self.other_leg_uuid = Some(uuid.clone());
308 }
309 self.pending_bridge_target = Some(info.target_channel);
310 }
311 }
312 _ => {}
313 },
314 MessageKind::ChannelLifecycle { event, detail } => match event {
315 LifecycleEvent::NewChannel => {
316 if self.channel_name.is_none() {
317 self.channel_name = parse_new_channel(detail).map(str::to_string);
318 }
319 }
320 LifecycleEvent::Hangup => {
321 if let Some(cause) = parse_hangup(detail) {
322 read(
323 &mut self.hangup_cause,
324 cause,
325 SessionReading::HangupCause,
326 &mut warnings,
327 );
328 }
329 }
330 LifecycleEvent::Answered => {
331 self.answered_at
332 .get_or_insert_with(|| entry.timestamp.clone());
333 }
334 _ => {}
335 },
336 kind => self.apply_kind(kind, &mut warnings),
337 }
338
339 self.apply_processing(&entry.message);
340 self.media.update_from_entry(entry);
341
342 for attached in &entry.attached {
343 let parsed = parse_line(attached);
344 self.update_from_message(parsed.message, dump, &mut warnings);
345 }
346 warnings
347 }
348
349 fn apply_kind(&mut self, kind: &MessageKind, warnings: &mut Vec<ParseWarning>) {
353 match kind {
354 MessageKind::Dialplan { detail, .. } => {
355 if let Some(context) = parse_dialplan_context(detail) {
356 self.initial_context.get_or_insert(context.to_string());
357 self.dialplan_context = Some(context.to_string());
358 }
359 }
360 MessageKind::Variable { name, value } => {
361 self.variables
362 .insert(name.bare().to_string(), value.clone());
363 }
364 MessageKind::ChannelField { name, value } => {
365 self.apply_channel_field(name, value, warnings)
366 }
367 MessageKind::OriginateSuccess {
370 peer_uuid: Some(peer),
371 ..
372 } if !is_uuid(peer) => warnings.push(ParseWarning::UnreadableValue {
373 reading: SessionReading::PeerUuid,
374 value: ParseWarning::excerpt(peer),
375 }),
376 MessageKind::StateChange { detail } => match parse_state_change(detail) {
377 Some(StateChange::Channel(to)) => read(
378 &mut self.channel_state,
379 to,
380 SessionReading::ChannelState,
381 warnings,
382 ),
383 Some(StateChange::Call(to)) => read(
384 &mut self.call_state,
385 to,
386 SessionReading::CallState,
387 warnings,
388 ),
389 None => {}
390 },
391 _ => {}
392 }
393 }
394
395 fn apply_processing(&mut self, msg: &str) {
399 if let Some(dp) = parse_processing_line(msg) {
400 self.initial_context.get_or_insert(dp.context.clone());
401 self.initial_destination.get_or_insert(dp.to.clone());
402 if let Some(number) = dp.from_number {
403 self.initial_caller_number.get_or_insert(number);
404 }
405 if let Some(name) = dp.from_name {
406 self.initial_caller_name.get_or_insert(name);
407 }
408 self.dialplan_context = Some(dp.context);
409 self.dialplan_from = Some(dp.from);
410 self.dialplan_to = Some(dp.to);
411 }
412 }
413
414 fn update_from_message(
415 &mut self,
416 msg: &str,
417 dump: DumpSource,
418 warnings: &mut Vec<ParseWarning>,
419 ) {
420 let kind = classify_message(msg);
421 match &kind {
422 MessageKind::Variable { .. } | MessageKind::ChannelField { .. }
423 if dump == DumpSource::Block => {}
424 kind => self.apply_kind(kind, warnings),
425 }
426 self.apply_processing(msg);
427 }
428}