sdp_types/
writer.rs

1// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
2//
3// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>
4
5use super::*;
6
7fn format_time<W: std::io::Write>(w: &mut W, t: u64) -> Result<(), std::io::Error> {
8    if t == 0 {
9        write!(w, "{}", t)?;
10    } else if t % 86_400 == 0 {
11        write!(w, "{}d", t / 86_400)?;
12    } else if t % 3_600 == 0 {
13        write!(w, "{}h", t / 3_600)?;
14    } else if t % 60 == 0 {
15        write!(w, "{}m", t / 60)?;
16    } else {
17        write!(w, "{}", t)?;
18    }
19
20    Ok(())
21}
22
23impl Origin {
24    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
25        writeln!(
26            w,
27            "o={username} {sess_id} {sess_version} {nettype} {addrtype} {unicast_address}\r",
28            username = if let Some(ref username) = self.username {
29                username
30            } else {
31                "-"
32            },
33            sess_id = self.sess_id,
34            sess_version = self.sess_version,
35            nettype = self.nettype,
36            addrtype = self.addrtype,
37            unicast_address = self.unicast_address,
38        )
39    }
40}
41
42impl Connection {
43    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
44        writeln!(
45            w,
46            "c={nettype} {addrtype} {connection_address}\r",
47            nettype = self.nettype,
48            addrtype = self.addrtype,
49            connection_address = self.connection_address
50        )
51    }
52}
53
54impl Bandwidth {
55    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
56        writeln!(
57            w,
58            "b={bwtype}:{bw}\r",
59            bwtype = self.bwtype,
60            bw = self.bandwidth
61        )
62    }
63}
64
65impl Time {
66    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
67        writeln!(
68            w,
69            "t={start_time} {stop_time}\r",
70            start_time = self.start_time,
71            stop_time = self.stop_time,
72        )
73    }
74}
75
76impl Repeat {
77    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
78        write!(w, "r=")?;
79        format_time(w, self.repeat_interval)?;
80        write!(w, " ")?;
81        format_time(w, self.active_duration)?;
82
83        for offset in &self.offsets {
84            write!(w, " ")?;
85            format_time(w, *offset)?;
86        }
87
88        writeln!(w, "\r")
89    }
90}
91
92impl TimeZone {
93    fn write<W: std::io::Write>(w: &mut W, zones: &[TimeZone]) -> Result<(), std::io::Error> {
94        write!(w, "z=")?;
95
96        let mut first = true;
97        for zone in zones {
98            if !first {
99                write!(w, " ")?;
100            }
101
102            write!(w, "{} ", zone.adjustment_time)?;
103            if zone.offset < 0 {
104                write!(w, "-")?;
105                format_time(w, (-zone.offset) as u64)?;
106            } else {
107                format_time(w, zone.offset as u64)?;
108            }
109            first = false;
110        }
111        writeln!(w, "\r")
112    }
113}
114
115impl Key {
116    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
117        if let Some(ref encryption_key) = self.encryption_key {
118            writeln!(
119                w,
120                "k={method}:{encryption_key}\r",
121                method = self.method,
122                encryption_key = encryption_key
123            )
124        } else {
125            writeln!(w, "k={method}\r", method = self.method)
126        }
127    }
128}
129
130impl Attribute {
131    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
132        if let Some(ref value) = self.value {
133            writeln!(
134                w,
135                "a={name}:{value}\r",
136                name = self.attribute,
137                value = value
138            )
139        } else {
140            writeln!(w, "a={name}\r", name = self.attribute)
141        }
142    }
143}
144
145impl Media {
146    fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
147        write!(w, "m={media} {port}", media = self.media, port = self.port)?;
148
149        if let Some(ref num_ports) = self.num_ports {
150            write!(w, "/{}", num_ports)?;
151        }
152        writeln!(w, " {} {}\r", self.proto, self.fmt)?;
153
154        if let Some(ref media_title) = self.media_title {
155            writeln!(w, "i={media_title}\r", media_title = media_title)?;
156        }
157
158        for connection in &self.connections {
159            connection.write(w)?;
160        }
161
162        for bandwidth in &self.bandwidths {
163            bandwidth.write(w)?;
164        }
165
166        if let Some(ref key) = self.key {
167            key.write(w)?;
168        }
169
170        for attribute in &self.attributes {
171            attribute.write(w)?;
172        }
173
174        Ok(())
175    }
176}
177
178impl Session {
179    /// Write the SDP session description to a `std::io::Write`.
180    pub fn write<W: std::io::Write>(&self, w: &mut W) -> Result<(), std::io::Error> {
181        writeln!(w, "v=0\r")?;
182        self.origin.write(w)?;
183        writeln!(w, "s={session_name}\r", session_name = self.session_name)?;
184
185        if let Some(ref session_description) = self.session_description {
186            writeln!(
187                w,
188                "i={session_description}\r",
189                session_description = session_description
190            )?;
191        }
192
193        if let Some(ref uri) = self.uri {
194            writeln!(w, "u={uri}\r", uri = uri)?;
195        }
196
197        for email in &self.emails {
198            writeln!(w, "e={email}\r", email = email)?;
199        }
200
201        for phone in &self.phones {
202            writeln!(w, "p={phone}\r", phone = phone)?;
203        }
204
205        if let Some(ref connection) = self.connection {
206            connection.write(w)?;
207        }
208
209        for bandwidth in &self.bandwidths {
210            bandwidth.write(w)?;
211        }
212
213        if !self.times.is_empty() {
214            for time in &self.times {
215                time.write(w)?;
216
217                for repeat in &time.repeats {
218                    repeat.write(w)?;
219                }
220            }
221        } else {
222            writeln!(w, "t=0 0\r")?;
223        }
224
225        if !self.time_zones.is_empty() {
226            TimeZone::write(w, &self.time_zones)?;
227        }
228
229        if let Some(ref key) = self.key {
230            key.write(w)?;
231        }
232
233        for attribute in &self.attributes {
234            attribute.write(w)?;
235        }
236
237        for media in &self.medias {
238            media.write(w)?;
239        }
240
241        Ok(())
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn write_sdp() {
251        let sdp = Session {
252            origin: Origin {
253                username: Some("jdoe".into()),
254                sess_id: "2890844526".into(),
255                sess_version: 2890842807,
256                nettype: "IN".into(),
257                addrtype: "IP4".into(),
258                unicast_address: "10.47.16.5".into(),
259            },
260            session_name: "SDP Seminar".into(),
261            session_description: Some("A Seminar on the session description protocol".into()),
262            uri: Some("http://www.example.com/seminars/sdp.pdf".into()),
263            emails: vec!["j.doe@example.com (Jane Doe)".into()],
264            phones: vec!["+1 617 555-6011".into()],
265            connection: Some(Connection {
266                nettype: "IN".into(),
267                addrtype: "IP4".into(),
268                connection_address: "224.2.17.12/127".into(),
269            }),
270            bandwidths: vec![Bandwidth {
271                bwtype: "AS".into(),
272                bandwidth: 128,
273            }],
274            times: vec![Time {
275                start_time: 2873397496,
276                stop_time: 2873404696,
277                repeats: vec![Repeat {
278                    repeat_interval: 604800,
279                    active_duration: 3600,
280                    offsets: vec![0, 90000],
281                }],
282            }],
283            time_zones: vec![
284                TimeZone {
285                    adjustment_time: 2882844526,
286                    offset: -3600,
287                },
288                TimeZone {
289                    adjustment_time: 2898848070,
290                    offset: 0,
291                },
292            ],
293            key: Some(Key {
294                method: "clear".into(),
295                encryption_key: Some("1234".into()),
296            }),
297            attributes: vec![Attribute {
298                attribute: "recvonly".into(),
299                value: None,
300            }],
301            medias: vec![
302                Media {
303                    media: "audio".into(),
304                    port: 49170,
305                    num_ports: None,
306                    proto: "RTP/AVP".into(),
307                    fmt: "0".into(),
308                    media_title: None,
309                    connections: vec![],
310                    bandwidths: vec![],
311                    key: None,
312                    attributes: vec![],
313                },
314                Media {
315                    media: "video".into(),
316                    port: 51372,
317                    num_ports: Some(2),
318                    proto: "RTP/AVP".into(),
319                    fmt: "99".into(),
320                    media_title: None,
321                    connections: vec![],
322                    bandwidths: vec![],
323                    key: None,
324                    attributes: vec![Attribute {
325                        attribute: "rtpmap".into(),
326                        value: Some("99 h263-1998/90000".into()),
327                    }],
328                },
329            ],
330        };
331
332        let mut written = vec![];
333        sdp.write(&mut written).unwrap();
334
335        let expected = "v=0\r
336o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
337s=SDP Seminar\r
338i=A Seminar on the session description protocol\r
339u=http://www.example.com/seminars/sdp.pdf\r
340e=j.doe@example.com (Jane Doe)\r
341p=+1 617 555-6011\r
342c=IN IP4 224.2.17.12/127\r
343b=AS:128\r
344t=2873397496 2873404696\r
345r=7d 1h 0 25h\r
346z=2882844526 -1h 2898848070 0\r
347k=clear:1234\r
348a=recvonly\r
349m=audio 49170 RTP/AVP 0\r
350m=video 51372/2 RTP/AVP 99\r
351a=rtpmap:99 h263-1998/90000\r
352";
353
354        assert_eq!(String::from_utf8_lossy(&written), expected);
355    }
356}