sdp_nom/attributes/
candidate.rs

1//! Minimal Candidate parser
2//!
3//! read [RFC5245 Section 15.1](https://tools.ietf.org/html/rfc5245#section-15.1)
4
5use derive_into_owned::IntoOwned;
6use nom::{
7    branch::alt,
8    bytes::complete::tag,
9    combinator::{map, opt},
10    sequence::{preceded, tuple},
11    IResult,
12};
13
14use std::{borrow::Cow, net::IpAddr};
15
16use crate::parsers::{attribute, cowify, read_addr, read_number, read_string, wsf};
17
18#[derive(Copy, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "debug", derive(Debug))]
20#[cfg_attr(
21    feature = "serde",
22    derive(serde::Serialize, serde::Deserialize),
23    serde(rename_all = "camelCase")
24)]
25pub enum CandidateComponent {
26    Rtp,
27    Rtcp,
28}
29
30#[derive(Copy, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "debug", derive(Debug))]
32#[cfg_attr(
33    feature = "serde",
34    derive(serde::Serialize, serde::Deserialize),
35    serde(rename_all = "camelCase")
36)]
37#[non_exhaustive]
38pub enum CandidateProtocol {
39    Tcp,
40    Udp,
41    Dccp,
42}
43
44#[derive(Copy, Clone, PartialEq, Eq)]
45#[cfg_attr(feature = "debug", derive(Debug))]
46#[cfg_attr(
47    feature = "serde",
48    derive(serde::Serialize, serde::Deserialize),
49    serde(rename_all = "camelCase")
50)]
51#[non_exhaustive]
52pub enum CandidateType {
53    Host,
54    Relay,
55    Srflx,
56    Prflx,
57}
58
59/// Candidate
60///
61/// <https://tools.ietf.org/html/rfc5245#section-15.1>
62/// <https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidateInit/candidate>
63///
64///
65/// candidate:3348148302 1 udp 2113937151 192.0.2.1 56500 typ host
66/// candidate:3348148302 2 udp 2113937151 192.0.2.1 56501 typ host
67// "candidate:1853887674 2 udp 1518280447 47.61.61.61 36768 typ srflx raddr 192.168.0.196 rport 36768 generation 0"
68#[derive(Clone, IntoOwned, PartialEq, Eq)]
69#[cfg_attr(feature = "debug", derive(Debug))]
70#[cfg_attr(
71    feature = "serde",
72    derive(serde::Serialize, serde::Deserialize),
73    serde(rename_all = "camelCase")
74)]
75pub struct Candidate<'a> {
76    pub foundation: u32,
77    pub component: CandidateComponent,
78    pub protocol: CandidateProtocol,
79    pub priority: u32,         // 2043278322
80    pub addr: IpAddr,          // "192.168.0.56"
81    pub port: u32,             // 44323
82    pub r#type: CandidateType, // "host"
83    pub raddr: Option<IpAddr>, // "192.168.0.56"
84    pub rport: Option<u32>,    // 44323
85    pub tcptype: Option<Cow<'a, str>>,
86    pub generation: Option<u32>,
87    pub network_id: Option<u32>,
88}
89
90pub fn candidate(input: &str) -> IResult<&str, Candidate> {
91    map(
92        tuple((
93            wsf(read_number), // foundation
94            // component:
95            wsf(alt((
96                map(tag("1"), |_| CandidateComponent::Rtp),
97                map(tag("2"), |_| CandidateComponent::Rtcp),
98            ))),
99            // protocol:
100            wsf(alt((
101                map(alt((tag("UDP"), tag("udp"))), |_| CandidateProtocol::Udp),
102                map(alt((tag("TCP"), tag("tcp"))), |_| CandidateProtocol::Tcp),
103                map(alt((tag("DCCP"), tag("dccp"))), |_| CandidateProtocol::Dccp),
104            ))),
105            wsf(read_number), // priority
106            wsf(read_addr),   // addr
107            wsf(read_number), // port
108            preceded(
109                tag("typ"),
110                // typ:
111                wsf(alt((
112                    map(tag("host"), |_| CandidateType::Host),
113                    map(tag("relay"), |_| CandidateType::Relay),
114                    map(tag("srflx"), |_| CandidateType::Srflx),
115                    map(tag("prflx"), |_| CandidateType::Prflx),
116                ))),
117            ),
118            opt(preceded(wsf(tag("raddr")), read_addr)), // raddr
119            opt(preceded(wsf(tag("rport")), read_number)), // rport
120            opt(preceded(wsf(tag("tcptype")), cowify(read_string))), // tcptype
121            opt(preceded(wsf(tag("generation")), read_number)), // generation
122            opt(preceded(wsf(tag("network-id")), read_number)), // generation
123        )),
124        |(
125            foundation,
126            component,
127            protocol,
128            priority,
129            addr,
130            port,
131            r#type,
132            raddr,
133            rport,
134            tcptype,
135            generation,
136            network_id,
137        )| Candidate {
138            foundation,
139            component,
140            protocol,
141            priority,
142            addr,
143            port,
144            r#type,
145            raddr,
146            rport,
147            tcptype,
148            generation,
149            network_id,
150        },
151    )(input)
152}
153
154/// "a=Candidate"
155pub fn candidate_line(input: &str) -> IResult<&str, Candidate> {
156    attribute("candidate", candidate)(input)
157}
158
159#[cfg(test)]
160#[rustfmt::skip]
161mod tests {
162    use std::net::Ipv4Addr;
163
164    use crate::{assert_line, assert_line_print};
165
166    use super::*;
167
168    #[test]
169    fn parses_candidate_line() {
170        assert_line_print!(candidate_line, "a=candidate:3348148302 1 udp 2113937151 192.0.2.1 56500 typ host");
171        assert_line_print!(candidate_line, "a=candidate:3348148302 1 tcp 2113937151 192.0.2.1 56500 typ srflx");
172        assert_line_print!(candidate_line, "a=candidate:3348148302 2 tcp 2113937151 192.0.2.1 56500 typ srflx");
173        assert_line!(candidate_line, "a=candidate:1 1 TCP 2128609279 10.0.1.1 9 typ host tcptype active", Candidate {
174            foundation: 1, component: CandidateComponent::Rtp, protocol: CandidateProtocol::Tcp, priority: 2128609279, addr: Ipv4Addr::new(10,0,1,1).into(), port: 9,
175            r#type: CandidateType::Host, raddr: None, rport: None, tcptype: Some( Cow::from("active"),), generation: None, network_id: None, });
176        assert_line_print!(candidate_line, "a=candidate:2 1 tcp 2124414975 10.0.1.1 8998 typ host tcptype passive");
177        assert_line_print!(candidate_line, "a=candidate:3 1 tcp 2120220671 10.0.1.1 8999 typ host tcptype so");
178        assert_line_print!(candidate_line, "a=candidate:4 1 tcp 1688207359 192.0.2.3 9 typ srflx raddr 10.0.1.1 rport 9 tcptype active");
179        assert_line_print!(candidate_line, "a=candidate:5 1 tcp 1684013055 192.0.2.3 45664 typ srflx raddr 10.0.1.1 rport 8998 tcptype passive generation 5");
180        assert_line_print!(candidate_line, "a=candidate:6 1 tcp 1692401663 192.0.2.3 45687 typ srflx raddr 10.0.1.1 rport 8999 tcptype so");
181        assert_line!(candidate_line, "a=candidate:3348148302 1 UDP 2113937151 192.0.2.1 56500 typ relay");
182        assert_line!(candidate_line, "a=candidate:3348148302 1 UDP 2113937151 192.0.2.1 56500 typ srflx");
183        // assert_line!("a=candidate:3348148302 2 tcp 2113937151 ::1 56500 typ srflx ::1 1337", candidate_line); // FIXME: is this one compliant?
184        assert_line_print!(candidate_line, "a=candidate:2791055836 1 udp 2122262783 2001:9e8:b0b:8400:c5e3:8776:82fc:7704 58605 typ host generation 0 network-id 2");
185    }
186
187    #[test]
188    fn audio_lines() {
189
190        let lines =[
191            "a=candidate:1467250027 1 udp 2122260223 192.168.0.196 46243 typ host generation 0",
192            "a=candidate:1467250027 2 udp 2122260222 192.168.0.196 56280 typ host generation 0",
193            "a=candidate:435653019 1 tcp 1845501695 192.168.0.196 0 typ host tcptype active generation 0",
194            "a=candidate:435653019 2 tcp 1845501695 192.168.0.196 0 typ host tcptype active generation 0",
195            "a=candidate:1853887674 1 udp 1518280447 47.61.61.61 36768 typ srflx raddr 192.168.0.196 rport 36768 generation 0",
196            "a=candidate:1853887674 2 udp 1518280447 47.61.61.61 36768 typ srflx raddr 192.168.0.196 rport 36768 generation 0",
197            "a=candidate:750991856 2 udp 25108222 237.30.30.30 51472 typ relay raddr 47.61.61.61 rport 54763 generation 0",
198            "a=candidate:750991856 1 udp 25108223 237.30.30.30 58779 typ relay raddr 47.61.61.61 rport 54761 generation 0",
199            ];
200        for line in &lines {
201            assert_line!(*line, candidate_line);
202        }
203
204    }
205
206    #[test]
207    #[should_panic]
208    #[ignore]
209    fn fails_on_bad_ip() {
210        candidate("candidate:3348148302 1 udp 2113937151 293.0.2.1 56500 typ host\n").unwrap();
211    }
212}