1use 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")?;
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 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!(w, "i={session_description}\r")?;
187 }
188
189 if let Some(ref uri) = self.uri {
190 writeln!(w, "u={uri}\r")?;
191 }
192
193 for email in &self.emails {
194 writeln!(w, "e={email}\r")?;
195 }
196
197 for phone in &self.phones {
198 writeln!(w, "p={phone}\r")?;
199 }
200
201 if let Some(ref connection) = self.connection {
202 connection.write(w)?;
203 }
204
205 for bandwidth in &self.bandwidths {
206 bandwidth.write(w)?;
207 }
208
209 if !self.times.is_empty() {
210 for time in &self.times {
211 time.write(w)?;
212
213 for repeat in &time.repeats {
214 repeat.write(w)?;
215 }
216 }
217 } else {
218 writeln!(w, "t=0 0\r")?;
219 }
220
221 if !self.time_zones.is_empty() {
222 TimeZone::write(w, &self.time_zones)?;
223 }
224
225 if let Some(ref key) = self.key {
226 key.write(w)?;
227 }
228
229 for attribute in &self.attributes {
230 attribute.write(w)?;
231 }
232
233 for media in &self.medias {
234 media.write(w)?;
235 }
236
237 Ok(())
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn write_sdp() {
247 let sdp = Session {
248 origin: Origin {
249 username: Some("jdoe".into()),
250 sess_id: "2890844526".into(),
251 sess_version: 2890842807,
252 nettype: "IN".into(),
253 addrtype: "IP4".into(),
254 unicast_address: "10.47.16.5".into(),
255 },
256 session_name: "SDP Seminar".into(),
257 session_description: Some("A Seminar on the session description protocol".into()),
258 uri: Some("http://www.example.com/seminars/sdp.pdf".into()),
259 emails: vec!["j.doe@example.com (Jane Doe)".into()],
260 phones: vec!["+1 617 555-6011".into()],
261 connection: Some(Connection {
262 nettype: "IN".into(),
263 addrtype: "IP4".into(),
264 connection_address: "224.2.17.12/127".into(),
265 }),
266 bandwidths: vec![Bandwidth {
267 bwtype: "AS".into(),
268 bandwidth: 128,
269 }],
270 times: vec![Time {
271 start_time: 2873397496,
272 stop_time: 2873404696,
273 repeats: vec![Repeat {
274 repeat_interval: 604800,
275 active_duration: 3600,
276 offsets: vec![0, 90000],
277 }],
278 }],
279 time_zones: vec![
280 TimeZone {
281 adjustment_time: 2882844526,
282 offset: -3600,
283 },
284 TimeZone {
285 adjustment_time: 2898848070,
286 offset: 0,
287 },
288 ],
289 key: Some(Key {
290 method: "clear".into(),
291 encryption_key: Some("1234".into()),
292 }),
293 attributes: vec![Attribute {
294 attribute: "recvonly".into(),
295 value: None,
296 }],
297 medias: vec![
298 Media {
299 media: "audio".into(),
300 port: 49170,
301 num_ports: None,
302 proto: "RTP/AVP".into(),
303 fmt: "0".into(),
304 media_title: None,
305 connections: vec![],
306 bandwidths: vec![],
307 key: None,
308 attributes: vec![],
309 },
310 Media {
311 media: "video".into(),
312 port: 51372,
313 num_ports: Some(2),
314 proto: "RTP/AVP".into(),
315 fmt: "99".into(),
316 media_title: None,
317 connections: vec![],
318 bandwidths: vec![],
319 key: None,
320 attributes: vec![Attribute {
321 attribute: "rtpmap".into(),
322 value: Some("99 h263-1998/90000".into()),
323 }],
324 },
325 ],
326 };
327
328 let mut written = vec![];
329 sdp.write(&mut written).unwrap();
330
331 let expected = "v=0\r
332o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r
333s=SDP Seminar\r
334i=A Seminar on the session description protocol\r
335u=http://www.example.com/seminars/sdp.pdf\r
336e=j.doe@example.com (Jane Doe)\r
337p=+1 617 555-6011\r
338c=IN IP4 224.2.17.12/127\r
339b=AS:128\r
340t=2873397496 2873404696\r
341r=7d 1h 0 25h\r
342z=2882844526 -1h 2898848070 0\r
343k=clear:1234\r
344a=recvonly\r
345m=audio 49170 RTP/AVP 0\r
346m=video 51372/2 RTP/AVP 99\r
347a=rtpmap:99 h263-1998/90000\r
348";
349
350 assert_eq!(String::from_utf8_lossy(&written), expected);
351 }
352}