use crate::codec::{CodecMedia, CodecOffer, CodecTrace};
use crate::message::{classify_message, parse_bracketed_value, MessageKind, SdpDirection, VarName};
use super::entry::{Block, ParseWarning};
const SDP_VERSION_LINE: &str = "v=0";
fn parse_field_line(msg: &str) -> Option<(String, String)> {
let (name, value) = parse_bracketed_value(msg, 0)?;
if name.is_empty() || name.contains(' ') {
return None;
}
Some((name.to_string(), value.to_string()))
}
pub(super) struct OpenVar {
name: VarName,
value: String,
cut: bool,
}
fn opens_own_name(msg: &str) -> bool {
matches!(
classify_message(msg),
MessageKind::ChannelField { .. } | MessageKind::Variable { .. }
)
}
pub(super) enum BlockBuilder {
Idle,
ChannelData {
fields: Vec<(String, String)>,
variables: Vec<(VarName, String)>,
open_var: Option<OpenVar>,
},
Sdp {
direction: SdpDirection,
body: Vec<String>,
},
Codec {
media: CodecMedia,
comparisons: Vec<(CodecOffer, CodecOffer)>,
matched: Vec<CodecOffer>,
near_matched: Vec<CodecOffer>,
},
}
impl BlockBuilder {
pub(super) fn open(message_kind: &MessageKind) -> Self {
match message_kind {
MessageKind::ChannelData => BlockBuilder::ChannelData {
fields: Vec::new(),
variables: Vec::new(),
open_var: None,
},
MessageKind::SdpMarker { direction } => BlockBuilder::Sdp {
direction: direction.clone(),
body: Vec::new(),
},
MessageKind::CodecNegotiation { media } => BlockBuilder::Codec {
media: *media,
comparisons: Vec::new(),
matched: Vec::new(),
near_matched: Vec::new(),
},
_ => BlockBuilder::Idle,
}
}
pub(super) fn codec_media(&self) -> Option<CodecMedia> {
match self {
BlockBuilder::Codec { media, .. } => Some(*media),
_ => None,
}
}
pub(super) fn push_continuation(&mut self, msg: &str, has_uuid: bool) -> Option<ParseWarning> {
match self {
BlockBuilder::ChannelData {
fields,
variables,
open_var,
} => {
if let Some(open) = open_var.as_mut() {
if !(open.cut && opens_own_name(msg)) {
open.value.push('\n');
open.value.push_str(msg);
if msg.ends_with(']') {
if let Some(open) = open_var.take() {
let closed = open
.value
.strip_suffix(']')
.unwrap_or(&open.value)
.to_string();
variables.push((open.name, closed));
}
}
return None;
}
}
if let Some(open) = open_var.take() {
variables.push((open.name, open.value));
}
match classify_message(msg) {
MessageKind::ChannelField { name, value } => fields.push((name, value)),
MessageKind::Variable { name, value } => {
if !msg.ends_with(']') && msg.contains(": [") {
*open_var = Some(OpenVar {
name,
value,
cut: false,
});
} else {
variables.push((name, value));
}
}
_ => match parse_field_line(msg) {
Some((name, value)) => fields.push((name, value)),
None => {
return Some(ParseWarning::UnparseableChannelData {
line: ParseWarning::excerpt(msg),
})
}
},
}
None
}
BlockBuilder::Sdp { body, .. } => {
body.push(msg.to_string());
None
}
BlockBuilder::Codec { .. } => Some(ParseWarning::UnexpectedCodecContinuation {
line: ParseWarning::excerpt(msg),
}),
BlockBuilder::Idle => {
if has_uuid && msg.trim() == SDP_VERSION_LINE {
*self = BlockBuilder::Sdp {
direction: SdpDirection::Unknown,
body: vec![msg.to_string()],
};
} else if let MessageKind::SdpMarker { direction } = classify_message(msg) {
*self = BlockBuilder::Sdp {
direction,
body: Vec::new(),
};
}
None
}
}
}
pub(super) fn mark_variable_cut(&mut self) -> Option<ParseWarning> {
let BlockBuilder::ChannelData { open_var, .. } = self else {
return None;
};
let open = open_var.as_mut()?;
if open.cut {
return None;
}
open.cut = true;
Some(ParseWarning::TruncatedVariable {
name: open.name.clone(),
})
}
pub(super) fn push_codec_trace(&mut self, msg: &str) -> Option<ParseWarning> {
let BlockBuilder::Codec {
media,
comparisons,
matched,
near_matched,
} = self
else {
return None;
};
match CodecTrace::parse(*media, msg) {
Ok(CodecTrace::Compared { offered, local }) => comparisons.push((offered, local)),
Ok(CodecTrace::Matched(c)) => matched.push(c),
Ok(CodecTrace::NearMatched(c)) => near_matched.push(c),
Ok(CodecTrace::Dropped(_)) => {}
Err(e) => {
return Some(ParseWarning::UnrecognizedCodecLine {
line: ParseWarning::excerpt(msg),
source: e.into(),
})
}
}
None
}
pub(super) fn finish(&mut self) -> (Option<Block>, Vec<ParseWarning>) {
let mut warnings = Vec::new();
let block = match std::mem::replace(self, BlockBuilder::Idle) {
BlockBuilder::Idle => None,
BlockBuilder::ChannelData {
fields,
mut variables,
open_var,
} => {
if let Some(open) = open_var {
if !open.cut {
warnings.push(ParseWarning::UnclosedVariable {
name: open.name.clone(),
});
}
variables.push((open.name, open.value));
}
Some(Block::ChannelData { fields, variables })
}
BlockBuilder::Sdp { direction, body } => Some(Block::Sdp { direction, body }),
BlockBuilder::Codec {
media,
comparisons,
matched,
near_matched,
} => Some(Block::CodecNegotiation {
media,
comparisons,
matched,
near_matched,
}),
};
(block, warnings)
}
}