use std::net::IpAddr;
use std::ops::Range;
use crate::message::{
classify_message, dialplan_parts, execute_parts, hangup_channel, new_channel_name,
paren_channel, parse_bracketed_value, set_export_parts, sip_invite_direction,
strip_channel_prefix, MessageKind, SipInviteDirection,
};
use crate::uuid::find_uuids;
use super::kind::{kind_rank, Field, FieldKind, FieldLocation};
use super::processing::processing_parts;
use super::subslice_range;
fn bracketed_ip(token: &str) -> Option<&str> {
let inner = token.strip_prefix('[')?;
let close = inner.find(']')?;
let addr = &inner[..close];
addr.parse::<IpAddr>().ok().map(|_| addr)
}
fn channel_host_ip(channel: &str) -> Option<&str> {
let host = channel.rsplit_once('@')?.1;
if host.starts_with('[') {
return bracketed_ip(host);
}
if host.parse::<IpAddr>().is_ok() {
return Some(host);
}
let addr = host.rsplit_once(':')?.0;
addr.parse::<std::net::Ipv4Addr>().ok().map(|_| addr)
}
fn invite_source_addr(rest: &str) -> Option<&str> {
let after = rest.split_once("receiving invite from ")?.1;
let token = after.split_whitespace().next()?;
if token.starts_with('[') {
return bracketed_ip(token);
}
let addr = token.rsplit_once(':').map(|(a, _)| a).unwrap_or(token);
addr.parse::<IpAddr>().ok().map(|_| addr)
}
pub fn message_fields(msg: &str) -> Vec<Field> {
let mut out = Vec::new();
collect_typed(msg, &mut out);
for (start, uuid) in find_uuids(msg) {
let range = start..start + uuid.len();
if !out.iter().any(|f: &Field| intersects(&f.range, &range)) {
push(&mut out, FieldKind::Uuid, range);
}
}
sort_spans(&mut out);
out
}
fn sort_spans(fields: &mut [Field]) {
fields.sort_by(|a, b| {
(
a.range.start,
std::cmp::Reverse(a.range.end),
kind_rank(a.kind),
)
.cmp(&(
b.range.start,
std::cmp::Reverse(b.range.end),
kind_rank(b.kind),
))
});
}
fn intersects(a: &Range<usize>, b: &Range<usize>) -> bool {
a.start < b.end && b.start < a.end
}
pub(super) fn raw_line_fields(line: &str, at: FieldLocation) -> Vec<Field> {
let message = crate::line::parse_line(line).message;
debug_assert!(line.ends_with(message), "message is a suffix of its line");
let offset = line.len() - message.len();
let mut out: Vec<Field> = message_fields(message)
.into_iter()
.map(|f| Field {
kind: f.kind,
at,
range: f.range.start + offset..f.range.end + offset,
})
.collect();
for (start, uuid) in find_uuids(&line[..offset]) {
out.push(Field {
kind: FieldKind::Uuid,
at,
range: start..start + uuid.len(),
});
}
sort_spans(&mut out);
out
}
fn push(out: &mut Vec<Field>, kind: FieldKind, range: Range<usize>) {
if !range.is_empty() {
out.push(Field {
kind,
at: FieldLocation::Message,
range,
});
}
}
fn push_channel(out: &mut Vec<Field>, msg: &str, channel: &str) {
let Some(range) = subslice_range(msg, channel) else {
return;
};
if let Some(host) = channel_host_ip(channel).and_then(|h| subslice_range(msg, h)) {
push(out, FieldKind::IpAddr, host);
}
push(out, FieldKind::ChannelName, range);
}
fn collect_typed(msg: &str, out: &mut Vec<Field>) {
match classify_message(msg) {
MessageKind::Execute { .. } => push_channel(out, msg, execute_parts(msg).channel),
MessageKind::Dialplan { .. } => collect_dialplan(msg, out),
MessageKind::Variable { .. } => {
if let Some(channel) = set_export_parts(msg).and_then(|p| p.channel) {
push_channel(out, msg, channel);
}
}
MessageKind::ChannelField { name, .. } => collect_channel_field(msg, &name, out),
MessageKind::SipInvite { direction, .. } => collect_invite(msg, direction, out),
MessageKind::StateChange { .. } | MessageKind::Media { .. } => {
collect_channel_prefixed(msg, out);
}
MessageKind::ChannelLifecycle { .. } if !collect_channel_prefixed(msg, out) => {
if let Some(channel) = hangup_channel(msg).or_else(|| new_channel_name(msg)) {
push_channel(out, msg, channel);
}
}
_ => {}
}
}
fn collect_channel_prefixed(msg: &str, out: &mut Vec<Field>) -> bool {
match strip_channel_prefix(msg) {
Some((channel, _)) => {
push_channel(out, msg, channel);
true
}
None => match paren_channel(msg) {
Some(channel) => {
push_channel(out, msg, channel);
true
}
None => false,
},
}
}
fn collect_dialplan(msg: &str, out: &mut Vec<Field>) {
if msg.starts_with("Dialplan: ") || msg.starts_with("Chatplan: ") {
push_channel(out, msg, dialplan_parts(msg).0);
return;
}
if let Some(parts) = processing_parts(msg) {
if let Some(name) = parts.name {
push(out, FieldKind::CallerIdName, name);
}
if let Some(number) = parts.number {
push(out, FieldKind::CallerIdNumber, number);
}
push(out, FieldKind::DestinationNumber, parts.dest);
}
}
fn collect_channel_field(msg: &str, name: &str, out: &mut Vec<Field>) {
let kind = match name {
"Channel-Name" => FieldKind::ChannelName,
"Caller-Caller-ID-Name" => FieldKind::CallerIdName,
"Caller-Caller-ID-Number" => FieldKind::CallerIdNumber,
"Caller-Destination-Number" => FieldKind::DestinationNumber,
_ => return,
};
let Some((_, value)) = parse_bracketed_value(msg, 0) else {
return;
};
if kind == FieldKind::ChannelName {
push_channel(out, msg, value);
} else if let Some(range) = subslice_range(msg, value) {
push(out, kind, range);
}
}
fn collect_invite(msg: &str, direction: SipInviteDirection, out: &mut Vec<Field>) {
let Some((channel, rest)) = strip_channel_prefix(msg) else {
return;
};
push_channel(out, msg, channel);
if sip_invite_direction(rest).is_none() {
return;
}
if let Some(range) = crate::message::call_id_token(rest).and_then(|t| subslice_range(msg, t)) {
push(out, FieldKind::CallId, range);
}
if direction == SipInviteDirection::Receiving {
if let Some(range) = invite_source_addr(rest).and_then(|a| subslice_range(msg, a)) {
push(out, FieldKind::IpAddr, range);
}
}
}