ezk_sdp_types/attributes/
candidate.rs

1//! ICE Candidate (`a=candidate:...`)
2
3use crate::{ice_char, not_whitespace, probe_host6};
4use bytes::Bytes;
5use bytesstr::BytesStr;
6use internal::{ws, IResult};
7use nom::bytes::complete::{tag, take_while, take_while1, take_while_m_n};
8use nom::character::complete::digit1;
9use nom::combinator::{map, map_res};
10use nom::error::context;
11use nom::multi::many0;
12use nom::sequence::{preceded, tuple};
13use std::fmt;
14use std::net::IpAddr;
15use std::str::FromStr;
16
17/// Encountered invalid values for key-value params when parsing an [`IceCandidate`]
18#[derive(Debug, thiserror::Error)]
19#[error("failed to parse candidate")]
20pub struct InvalidCandidateParamError;
21
22/// Used by [`IceCandidate`]
23#[derive(Debug, Clone, Eq, PartialEq)]
24pub enum UntaggedAddress {
25    Fqdn(BytesStr),
26    IpAddress(IpAddr),
27}
28
29impl UntaggedAddress {
30    fn parse(src: &Bytes) -> impl FnMut(&str) -> IResult<&str, Self> + '_ {
31        move |i| {
32            context(
33                "parsing untagged address",
34                map(take_while(probe_host6), |address| {
35                    if let Ok(address) = IpAddr::from_str(address) {
36                        UntaggedAddress::IpAddress(address)
37                    } else {
38                        UntaggedAddress::Fqdn(BytesStr::from_parse(src, address))
39                    }
40                }),
41            )(i)
42        }
43    }
44}
45
46impl fmt::Display for UntaggedAddress {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        match &self {
49            UntaggedAddress::Fqdn(str) => str.fmt(f),
50            UntaggedAddress::IpAddress(addr) => addr.fmt(f),
51        }
52    }
53}
54
55/// ICE Candidate (`a=candidate`)
56///
57/// [RFC5245](https://tools.ietf.org/html/rfc5245#section-15.1)
58#[derive(Debug, Clone)]
59pub struct IceCandidate {
60    /// Session unique ID assigned to the candidate
61    pub foundation: BytesStr,
62
63    /// Identifies the specific component of the media stream for which this is a candidate.
64    ///
65    /// e.g. RTP is 1 and RTCP is 2
66    pub component: u32,
67
68    /// Transport protocol used by the candidate.
69    ///
70    /// Usually UDP or TCP
71    pub transport: BytesStr,
72
73    /// Candidate priority
74    pub priority: u64,
75
76    /// Address of the candidate
77    pub address: UntaggedAddress,
78
79    /// Port of the candidate
80    pub port: u16,
81
82    /// Candidate typ
83    ///
84    /// Defined are:  
85    /// - `host`: host
86    /// - `srflx`: server reflexive
87    /// - `prflx`: peer reflexive
88    /// - `relay`: relayed candidate
89    /// - or something entirely else
90    pub typ: BytesStr,
91
92    /// Required for candidate typ `srflx`, `prflx` and `relay`
93    ///
94    /// Transport address
95    pub rel_addr: Option<UntaggedAddress>,
96
97    /// Required for candidate typ `srflx`, `prflx` and `relay`
98    ///
99    /// Transport port
100    pub rel_port: Option<u16>,
101
102    /// Params that aren't known to this crate
103    pub unknown: Vec<(BytesStr, BytesStr)>,
104}
105
106impl IceCandidate {
107    pub fn parse<'i>(src: &Bytes, i: &'i str) -> IResult<&'i str, Self> {
108        context(
109    "parsing ice candidate",
110            map_res(
111                tuple((
112                    // foundation
113                    take_while_m_n(1, 32, ice_char),
114                    ws((
115                        // component id
116                        map_res(digit1, FromStr::from_str),
117                        // transport
118                        take_while(not_whitespace),
119                        // priority
120                        map_res(digit1, FromStr::from_str),
121                        // address
122                        UntaggedAddress::parse(src),
123                        // port
124                        map_res(digit1, FromStr::from_str),
125                        // candidate type
126                        preceded(tag("typ"), ws((take_while1(not_whitespace),))),
127                    )),
128                    // extensions
129                    many0(ws((
130                        // key
131                        take_while1(not_whitespace),
132                        // value
133                        take_while1(not_whitespace),
134                    ))),
135                )),
136            |(foundation, (component, transport, priority, address, port, type_), p_ext)| -> Result<IceCandidate, InvalidCandidateParamError> {
137                let mut unknown = vec![];
138
139                let mut rel_addr = None;
140                let mut rel_port = None;
141
142                for (key, value) in p_ext {
143                    match key {
144                        "raddr" => rel_addr = Some(UntaggedAddress::parse(src)(value).map_err(|_| InvalidCandidateParamError)?.1),
145                        "rport" => rel_port = Some(u16::from_str(value).map_err(|_| InvalidCandidateParamError)?),
146                        _ => unknown.push((
147                            BytesStr::from_parse(src, key),
148                            BytesStr::from_parse(src, value),
149                        )),
150                    }
151                }
152
153                Ok(IceCandidate {
154                    foundation: BytesStr::from_parse(src, foundation),
155                    component,
156                    transport: BytesStr::from_parse(src, transport),
157                    priority,
158                    address,
159                    port,
160                    typ: BytesStr::from_parse(src, type_.0),
161                    rel_addr,
162                    rel_port,
163                    unknown,
164                })
165            }
166            )
167        )(i)
168    }
169}
170
171impl fmt::Display for IceCandidate {
172    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173        write!(
174            f,
175            "{} {} {} {} {} {} typ {}",
176            self.foundation,
177            self.component,
178            self.transport,
179            self.priority,
180            self.address,
181            self.port,
182            self.typ
183        )?;
184
185        if let Some(rel_addr) = &self.rel_addr {
186            write!(f, " raddr {}", rel_addr)?;
187        }
188
189        if let Some(rel_port) = &self.rel_port {
190            write!(f, " rport {}", rel_port)?;
191        }
192
193        for (key, value) in &self.unknown {
194            write!(f, " {} {}", key, value)?;
195        }
196
197        Ok(())
198    }
199}
200
201#[cfg(test)]
202mod test {
203    use super::*;
204    use std::net::Ipv4Addr;
205
206    #[test]
207    fn candidate() {
208        let input = BytesStr::from_static(
209            "12 2 TCP 2105458942 192.168.56.1 9 typ host raddr 192.168.1.22 rport 123 tcptype active",
210        );
211
212        let (rem, candidate) = IceCandidate::parse(input.as_ref(), &input).unwrap();
213
214        assert_eq!(candidate.foundation, "12");
215        assert_eq!(candidate.component, 2);
216        assert_eq!(candidate.transport, "TCP");
217        assert_eq!(candidate.priority, 2105458942);
218        assert_eq!(
219            candidate.address,
220            UntaggedAddress::IpAddress(IpAddr::V4(Ipv4Addr::new(192, 168, 56, 1)))
221        );
222        assert_eq!(candidate.port, 9);
223        assert_eq!(candidate.typ, "host");
224        assert_eq!(
225            candidate.rel_addr,
226            Some(UntaggedAddress::IpAddress(IpAddr::V4(Ipv4Addr::new(
227                192, 168, 1, 22
228            ))))
229        );
230        assert_eq!(candidate.rel_port, Some(123));
231        assert_eq!(
232            candidate.unknown[0],
233            (
234                BytesStr::from_static("tcptype"),
235                BytesStr::from_static("active")
236            )
237        );
238
239        assert!(rem.is_empty());
240    }
241
242    #[test]
243    fn candidate_print() {
244        let candidate = IceCandidate {
245            foundation: "1".into(),
246            component: 1,
247            transport: "UDP".into(),
248            priority: 1,
249            address: UntaggedAddress::IpAddress(IpAddr::V4(Ipv4Addr::LOCALHOST)),
250            port: 9,
251            typ: "host".into(),
252            rel_addr: None,
253            rel_port: None,
254            unknown: vec![],
255        };
256
257        assert_eq!(candidate.to_string(), "1 1 UDP 1 127.0.0.1 9 typ host");
258    }
259}