#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IceCandidate {
pub mid: Option<String>,
pub ufrag: Option<String>,
pub candidate: String,
}
impl IceCandidate {
pub fn is_end_of_candidates(&self) -> bool {
self.candidate.is_empty()
}
}
pub fn parse_trickle(fragment: &str) -> Vec<IceCandidate> {
let mut out = Vec::new();
let mut mid: Option<String> = None;
let mut ufrag: Option<String> = None;
for line in fragment.lines() {
let line = line.trim();
if let Some(m) = line.strip_prefix("a=mid:") {
mid = Some(m.to_string());
} else if let Some(u) = line.strip_prefix("a=ice-ufrag:") {
ufrag = Some(u.to_string());
} else if let Some(c) = line.strip_prefix("a=candidate:") {
out.push(IceCandidate {
mid: mid.clone(),
ufrag: ufrag.clone(),
candidate: c.to_string(),
});
} else if line == "a=end-of-candidates" {
out.push(IceCandidate {
mid: mid.clone(),
ufrag: ufrag.clone(),
candidate: String::new(),
});
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_candidates_with_mid_context() {
let frag = "a=ice-ufrag:abcd\r\n\
a=ice-pwd:0123456789012345678901\r\n\
m=audio 9 UDP/TLS/RTP/SAVPF 0\r\n\
a=mid:0\r\n\
a=candidate:1 1 UDP 2122252543 192.0.2.1 51000 typ host\r\n\
a=candidate:2 1 UDP 1685987071 198.51.100.2 52000 typ srflx\r\n\
m=video 9 UDP/TLS/RTP/SAVPF 96\r\n\
a=mid:1\r\n\
a=candidate:3 1 UDP 2122252543 192.0.2.1 51002 typ host\r\n\
a=end-of-candidates\r\n";
let cands = parse_trickle(frag);
assert_eq!(cands.len(), 4);
assert_eq!(cands[0].mid.as_deref(), Some("0"));
assert_eq!(cands[0].ufrag.as_deref(), Some("abcd"));
assert!(cands[0].candidate.starts_with("1 1 UDP"));
assert_eq!(cands[2].mid.as_deref(), Some("1"));
assert!(cands[3].is_end_of_candidates());
}
#[test]
fn empty_fragment_yields_nothing() {
assert!(parse_trickle("").is_empty());
assert!(parse_trickle("a=ice-ufrag:x\r\n").is_empty());
}
}