1use crate::scanf;
2use indexmap::IndexMap;
3use std::fmt;
4
5pub trait Unmarshal {
6 fn unmarshal(request_data: &str) -> Option<Self>
7 where
8 Self: Sized;
9}
10
11pub trait Marshal {
12 fn marshal(&self) -> String;
13}
14
15#[derive(Debug, Clone, Default)]
16pub enum Schema {
17 WEBRTC,
19 RTSP,
20 #[default]
21 UNKNOWN,
22}
23
24impl fmt::Display for Schema {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 match self {
27 Schema::RTSP => {
28 write!(f, "rtsp")
29 }
30 Schema::WEBRTC => {
32 write!(f, "")
33 }
34 Schema::UNKNOWN => {
35 write!(f, "unknown")
36 }
37 }
38 }
39}
40
41#[derive(Debug, Clone, Default)]
42pub struct Uri {
43 pub schema: Schema,
44 pub host: String,
45 pub port: Option<u16>,
46 pub path: String,
47 pub query: Option<String>,
48}
49
50impl Unmarshal for Uri {
51 fn unmarshal(url: &str) -> Option<Self> {
64 let mut uri = Uri::default();
65
66 if url.starts_with("rtsp://") {
68 uri.schema = Schema::RTSP;
69 } else if url.starts_with("/whip") || url.starts_with("/whep") {
70 uri.schema = Schema::WEBRTC;
71 } else {
72 log::warn!("cannot judge the schema: {}", url);
73 uri.schema = Schema::UNKNOWN;
74 }
75
76 let path_with_query = match uri.schema {
77 Schema::RTSP => {
78 let rtsp_path_with_query =
79 if let Some(rtsp_url_without_prefix) = url.strip_prefix("rtsp://") {
80 if let Some(index) = rtsp_url_without_prefix.find('/') {
83 let path_with_query = &rtsp_url_without_prefix[index + 1..];
84 let host_with_port = &rtsp_url_without_prefix[..index];
86 let (host_val, port_val) = scanf!(host_with_port, ':', String, u16);
87 if let Some(host) = host_val {
88 uri.host = host;
89 }
90 if let Some(port) = port_val {
91 uri.port = Some(port);
92 }
93
94 path_with_query
95 } else {
96 log::error!("cannot find split '/' for host:port and path?query.");
97 return None;
98 }
99 } else {
100 log::error!("cannot find RTSP prefix.");
101 return None;
102 };
103 rtsp_path_with_query
104 }
105 Schema::WEBRTC => url,
106 Schema::UNKNOWN => url,
107 };
108
109 let path_data: Vec<&str> = path_with_query.splitn(2, '?').collect();
110 uri.path = path_data[0].to_string();
111
112 if path_data.len() > 1 {
113 uri.query = Some(path_data[1].to_string());
114 }
115
116 Some(uri)
117 }
118}
119
120impl Marshal for Uri {
121 fn marshal(&self) -> String {
122 let path_with_query = if let Some(query) = &self.query {
124 format!("{}?{}", self.path, query)
125 } else {
126 self.path.clone()
127 };
128
129 match self.schema {
130 Schema::RTSP => {
131 let host_with_port = if let Some(port) = &self.port {
132 format!("{}:{}", self.host, port)
133 } else {
134 self.host.clone()
135 };
136 format!("{}://{}/{}", self.schema, host_with_port, path_with_query)
137 }
138 Schema::WEBRTC => path_with_query,
139 Schema::UNKNOWN => path_with_query,
140 }
141 }
142}
143
144#[derive(Debug, Clone, Default)]
145pub struct HttpRequest {
146 pub method: String,
147 pub uri: Uri,
148 pub query_pairs: IndexMap<String, String>,
150 pub version: String,
151 pub headers: IndexMap<String, String>,
152 pub body: Option<String>,
153}
154
155impl HttpRequest {
156 pub fn get_header(&self, header_name: &String) -> Option<&String> {
157 self.headers.get(header_name)
158 }
159}
160
161pub fn parse_content_length(request_data: &str) -> Option<u32> {
162 let start = "Content-Length:";
163 let end = "\r\n";
164
165 let start_index = request_data.find(start)? + start.len();
166 let end_index = request_data[start_index..].find(end)? + start_index;
167 let length_str = &request_data[start_index..end_index];
168
169 length_str.trim().parse().ok()
170}
171
172impl Unmarshal for HttpRequest {
173 fn unmarshal(request_data: &str) -> Option<Self> {
174 let mut http_request = HttpRequest::default();
175 let header_end_idx = if let Some(idx) = request_data.find("\r\n\r\n") {
176 let data_except_body = &request_data[..idx];
177 let mut lines = data_except_body.lines();
178 if let Some(request_first_line) = lines.next() {
181 let mut fields = request_first_line.split_ascii_whitespace();
182 if let Some(method) = fields.next() {
184 http_request.method = method.to_string();
185 }
186 if let Some(url) = fields.next() {
188 if let Some(uri) = Uri::unmarshal(url) {
189 http_request.uri = uri;
190
191 if let Some(query) = &http_request.uri.query {
192 let pars_array: Vec<&str> = query.split('&').collect();
193
194 for ele in pars_array {
195 let (k, v) = scanf!(ele, '=', String, String);
196 if k.is_none() || v.is_none() {
197 continue;
198 }
199 http_request.query_pairs.insert(k.unwrap(), v.unwrap());
200 }
201 }
202 } else {
203 log::error!("cannot get a Uri.");
204 return None;
205 }
206 }
207 if let Some(version) = fields.next() {
209 http_request.version = version.to_string();
210 }
211 }
212 for line in lines {
214 if let Some(index) = line.find(": ") {
215 let name = line[..index].to_string();
216 let value = line[index + 2..].to_string();
217 if name == "Host" {
219 let (address_val, port_val) = scanf!(value, ':', String, u16);
220 if let Some(address) = address_val {
221 http_request.uri.host = address;
222 }
223 if let Some(port) = port_val {
224 http_request.uri.port = Some(port);
225 }
226 }
227 http_request.headers.insert(name, value);
228 }
229 }
230 idx + 4
231 } else {
232 return None;
233 };
234 log::trace!(
235 "header_end_idx is: {} {}",
236 header_end_idx,
237 request_data.len()
238 );
239
240 if request_data.len() > header_end_idx {
241 http_request.body = Some(request_data[header_end_idx..].to_string());
243 }
244
245 Some(http_request)
246 }
247}
248
249impl Marshal for HttpRequest {
250 fn marshal(&self) -> String {
251 let mut request_str = format!(
252 "{} {} {}\r\n",
253 self.method,
254 self.uri.marshal(),
255 self.version
256 );
257 for (header_name, header_value) in &self.headers {
258 if header_name == &"Content-Length".to_string() {
259 if let Some(body) = &self.body {
260 request_str += &format!("Content-Length: {}\r\n", body.len());
261 }
262 } else {
263 request_str += &format!("{header_name}: {header_value}\r\n");
264 }
265 }
266
267 request_str += "\r\n";
268 if let Some(body) = &self.body {
269 request_str += body;
270 }
271 request_str
272 }
273}
274
275#[derive(Debug, Clone, Default)]
276pub struct HttpResponse {
277 pub version: String,
278 pub status_code: u16,
279 pub reason_phrase: String,
280 pub headers: IndexMap<String, String>,
281 pub body: Option<String>,
282}
283
284impl HttpResponse {
285 pub fn get_header(&self, header_name: &String) -> Option<&String> {
286 self.headers.get(header_name)
287 }
288}
289
290impl Unmarshal for HttpResponse {
291 fn unmarshal(request_data: &str) -> Option<Self> {
292 let mut http_response = HttpResponse::default();
293 let header_end_idx = if let Some(idx) = request_data.find("\r\n\r\n") {
294 let data_except_body = &request_data[..idx];
295 let mut lines = data_except_body.lines();
296 if let Some(request_first_line) = lines.next() {
298 let mut fields = request_first_line.split_ascii_whitespace();
299
300 if let Some(version) = fields.next() {
301 http_response.version = version.to_string();
302 }
303 if let Some(status) = fields.next() {
304 if let Ok(status) = status.parse::<u16>() {
305 http_response.status_code = status;
306 }
307 }
308 if let Some(reason_phrase) = fields.next() {
309 http_response.reason_phrase = reason_phrase.to_string();
310 }
311 }
312 for line in lines {
314 if let Some(index) = line.find(": ") {
315 let name = line[..index].to_string();
316 let value = line[index + 2..].to_string();
317 http_response.headers.insert(name, value);
318 }
319 }
320 idx + 4
321 } else {
322 return None;
323 };
324
325 if request_data.len() > header_end_idx {
326 http_response.body = Some(request_data[header_end_idx..].to_string());
328 }
329
330 Some(http_response)
331 }
332}
333
334impl Marshal for HttpResponse {
335 fn marshal(&self) -> String {
336 let mut response_str = format!(
337 "{} {} {}\r\n",
338 self.version, self.status_code, self.reason_phrase
339 );
340
341 for (header_name, header_value) in &self.headers {
342 if header_name != &"Content-Length".to_string() {
343 response_str += &format!("{header_name}: {header_value}\r\n");
344 }
345 }
346
347 if let Some(body) = &self.body {
348 response_str += &format!("Content-Length: {}\r\n", body.len());
349 }
350
351 response_str += "\r\n";
352 if let Some(body) = &self.body {
353 response_str += body;
354 }
355 response_str
356 }
357}
358
359#[cfg(test)]
360mod tests {
361
362 use super::Marshal;
363 use super::Unmarshal;
364
365 use super::HttpRequest;
366 use super::HttpResponse;
367
368 use indexmap::IndexMap;
369 use std::io::BufRead;
370 #[allow(dead_code)]
371 fn read_headers(reader: &mut dyn BufRead) -> Option<IndexMap<String, String>> {
372 let mut headers = IndexMap::new();
373 loop {
374 let mut line = String::new();
375 match reader.read_line(&mut line) {
376 Ok(0) => break,
377 Ok(_) => {
378 if let Some(index) = line.find(": ") {
379 let name = line[..index].to_string();
380 let value = line[index + 2..].trim().to_string();
381 headers.insert(name, value);
382 }
383 }
384 Err(_) => return None,
385 }
386 }
387 Some(headers)
388 }
389
390 #[test]
391 fn test_parse_http_request() {
392 let request = "POST /whip/endpoint?app=live&stream=test HTTP/1.1\r\n\
393 Host: whip.example.com\r\n\
394 Content-Type: application/sdp\r\n\
395 Content-Length: 1326\r\n\
396 \r\n\
397 v=0\r\n\
398 o=- 5228595038118931041 2 IN IP4 127.0.0.1\r\n\
399 s=-\r\n\
400 t=0 0\r\n\
401 a=group:BUNDLE 0 1\r\n\
402 a=extmap-allow-mixed\r\n\
403 a=msid-semantic: WMS\r\n\
404 m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\
405 c=IN IP4 0.0.0.0\r\n\
406 a=rtcp:9 IN IP4 0.0.0.0\r\n\
407 a=ice-ufrag:EsAw\r\n\
408 a=ice-pwd:bP+XJMM09aR8AiX1jdukzR6Y\r\n\
409 a=ice-options:trickle\r\n\
410 a=fingerprint:sha-256 DA:7B:57:DC:28:CE:04:4F:31:79:85:C4:31:67:EB:27:58:29:ED:77:2A:0D:24:AE:ED:AD:30:BC:BD:F1:9C:02\r\n\
411 a=setup:actpass\r\n\
412 a=mid:0\r\n\
413 a=bundle-only\r\n\
414 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
415 a=sendonly\r\n\
416 a=msid:- d46fb922-d52a-4e9c-aa87-444eadc1521b\r\n\
417 a=rtcp-mux\r\n\
418 a=rtpmap:111 opus/48000/2\r\n\
419 a=fmtp:111 minptime=10;useinbandfec=1\r\n\
420 m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n\
421 c=IN IP4 0.0.0.0\r\n\
422 a=rtcp:9 IN IP4 0.0.0.0\r\n\
423 a=ice-ufrag:EsAw\r\n\
424 a=ice-pwd:bP+XJMM09aR8AiX1jdukzR6Y\r\n\
425 a=ice-options:trickle\r\n\
426 a=fingerprint:sha-256 DA:7B:57:DC:28:CE:04:4F:31:79:85:C4:31:67:EB:27:58:29:ED:77:2A:0D:24:AE:ED:AD:30:BC:BD:F1:9C:02\r\n\
427 a=setup:actpass\r\n\
428 a=mid:1\r\n\
429 a=bundle-only\r\n\
430 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
431 a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
432 a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
433 a=sendonly\r\n\
434 a=msid:- d46fb922-d52a-4e9c-aa87-444eadc1521b\r\n\
435 a=rtcp-mux\r\n\
436 a=rtcp-rsize\r\n\
437 a=rtpmap:96 VP8/90000\r\n\
438 a=rtcp-fb:96 ccm fir\r\n\
439 a=rtcp-fb:96 nack\r\n\
440 a=rtcp-fb:96 nack pli\r\n\
441 a=rtpmap:97 rtx/90000\r\n\
442 a=fmtp:97 apt=96\r\n";
443
444 if let Some(parser) = HttpRequest::unmarshal(request) {
445 println!(" parser: {parser:?}");
446 let marshal_result = parser.marshal();
447 print!("marshal result: =={marshal_result}==");
448 assert_eq!(request, marshal_result);
449 }
450 }
451
452 #[test]
453 fn test_whep_request() {
454 let request = "POST /whep?app=live&stream=test HTTP/1.1\r\n\
455 Host: localhost:3000\r\n\
456 Accept: */*\r\n\
457 Sec-Fetch-Site: same-origin\r\n\
458 Accept-Language: zh-cn\r\n\
459 Accept-Encoding: gzip, deflate\r\n\
460 Sec-Fetch-Mode: cors\r\n\
461 Content-Type: application/sdp\r\n\
462 Origin: http://localhost:3000\r\n\
463 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15\r\n\
464 Referer: http://localhost:3000/\r\n\
465 Content-Length: 3895\r\n\
466 Connection: keep-alive\r\n\
467 Sec-Fetch-Dest: empty\r\n\
468 \r\n\
469 v=0\r\n\
470 o=- 6550659986740559335 2 IN IP4 127.0.0.1\r\n\
471 s=-\r\n\
472 t=0 0\r\n\
473 a=group:BUNDLE 0 1\r\n\
474 a=extmap-allow-mixed\r\n\
475 a=msid-semantic: WMS\r\n\
476 m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 125 104 124 106 107 108 109 127 35\r\n\
477 c=IN IP4 0.0.0.0\r\n\
478 a=rtcp:9 IN IP4 0.0.0.0\r\n\
479 a=ice-ufrag:0mum\r\n\
480 a=ice-pwd:DD4LnAhZLQNLSzRZWZRh9Jm4\r\n\
481 a=ice-options:trickle\r\n\
482 a=fingerprint:sha-256 6C:61:89:FF:9D:2F:BA:0A:A4:80:0D:98:C3:CA:43:05:82:EB:59:13:BC:C8:DE:33:2F:26:4A:27:D8:D0:D1:3D\r\n\
483 a=setup:actpass\r\n\
484 a=mid:0\r\n\
485 a=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\n\
486 a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n\
487 a=extmap:3 urn:3gpp:video-orientation\r\n\
488 a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n\
489 a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n\
490 a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n\
491 a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n\
492 a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n\
493 a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
494 a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
495 a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
496 a=recvonly\r\n\
497 a=rtcp-mux\r\n\
498 a=rtcp-rsize\r\n\
499 a=rtpmap:96 H264/90000\r\n\
500 a=rtcp-fb:96 goog-remb\r\n\
501 a=rtcp-fb:96 transport-cc\r\n\
502 a=rtcp-fb:96 ccm fir\r\n\
503 a=rtcp-fb:96 nack\r\n\
504 a=rtcp-fb:96 nack pli\r\n\
505 a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f\r\n\
506 a=rtpmap:97 rtx/90000\r\n\
507 a=fmtp:97 apt=96\r\n\
508 a=rtpmap:98 H264/90000\r\n\
509 a=rtcp-fb:98 goog-remb\r\n\
510 a=rtcp-fb:98 transport-cc\r\n\
511 a=rtcp-fb:98 ccm fir\r\n\
512 a=rtcp-fb:98 nack\r\n\
513 a=rtcp-fb:98 nack pli\r\n\
514 a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n\
515 a=rtpmap:99 rtx/90000\r\n\
516 a=fmtp:99 apt=98\r\n\
517 a=rtpmap:100 H264/90000\r\n\
518 a=rtcp-fb:100 goog-remb\r\n\
519 a=rtcp-fb:100 transport-cc\r\n\
520 a=rtcp-fb:100 ccm fir\r\n\
521 a=rtcp-fb:100 nack\r\n\
522 a=rtcp-fb:100 nack pli\r\n\
523 a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=640c1f\r\n\
524 a=rtpmap:101 rtx/90000\r\n\
525 a=fmtp:101 apt=100\r\n\
526 a=rtpmap:102 H264/90000\r\n\
527 a=rtcp-fb:102 goog-remb\r\n\
528 a=rtcp-fb:102 transport-cc\r\n\
529 a=rtcp-fb:102 ccm fir\r\n\
530 a=rtcp-fb:102 nack\r\n\
531 a=rtcp-fb:102 nack pli\r\n\
532 a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\n\
533 a=rtpmap:125 rtx/90000\r\n\
534 a=fmtp:125 apt=102\r\n\
535 a=rtpmap:104 VP8/90000\r\n\
536 a=rtcp-fb:104 goog-remb\r\n\
537 a=rtcp-fb:104 transport-cc\r\n\
538 a=rtcp-fb:104 ccm fir\r\n\
539 a=rtcp-fb:104 nack\r\n\
540 a=rtcp-fb:104 nack pli\r\n\
541 a=rtpmap:124 rtx/90000\r\n\
542 a=fmtp:124 apt=104\r\n\
543 a=rtpmap:106 VP9/90000\r\n\
544 a=rtcp-fb:106 goog-remb\r\n\
545 a=rtcp-fb:106 transport-cc\r\n\
546 a=rtcp-fb:106 ccm fir\r\n\
547 a=rtcp-fb:106 nack\r\n\
548 a=rtcp-fb:106 nack pli\r\n\
549 a=fmtp:106 profile-id=0\r\n\
550 a=rtpmap:107 rtx/90000\r\n\
551 a=fmtp:107 apt=106\r\n\
552 a=rtpmap:108 red/90000\r\n\
553 a=rtpmap:109 rtx/90000\r\n\
554 a=fmtp:109 apt=108\r\n\
555 a=rtpmap:127 ulpfec/90000\r\n\
556 a=rtpmap:35 flexfec-03/90000\r\n\
557 a=rtcp-fb:35 goog-remb\r\n\
558 a=rtcp-fb:35 transport-cc\r\n\
559 a=fmtp:35 repair-window=10000000\r\n\
560 m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 9 0 8 105 13 110 113 126\r\n\
561 c=IN IP4 0.0.0.0\r\n\
562 a=rtcp:9 IN IP4 0.0.0.0\r\n\
563 a=ice-ufrag:0mum\r\n\
564 a=ice-pwd:DD4LnAhZLQNLSzRZWZRh9Jm4\r\n\
565 a=ice-options:trickle\r\n\
566 a=fingerprint:sha-256 6C:61:89:FF:9D:2F:BA:0A:A4:80:0D:98:C3:CA:43:05:82:EB:59:13:BC:C8:DE:33:2F:26:4A:27:D8:D0:D1:3D\r\n\
567 a=setup:actpass\r\n\
568 a=mid:1\r\n\
569 a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n\
570 a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n\
571 a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n\
572 a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
573 a=recvonly\r\n\
574 a=rtcp-mux\r\n\
575 a=rtpmap:111 opus/48000/2\r\n\
576 a=rtcp-fb:111 transport-cc\r\n\
577 a=fmtp:111 minptime=10;useinbandfec=1\r\n\
578 a=rtpmap:63 red/48000/2\r\n\
579 a=fmtp:63 111/111\r\n\
580 a=rtpmap:103 ISAC/16000\r\n\
581 a=rtpmap:9 G722/8000\r\n\
582 a=rtpmap:0 PCMU/8000\r\n\
583 a=rtpmap:8 PCMA/8000\r\n\
584 a=rtpmap:105 CN/16000\r\n\
585 a=rtpmap:13 CN/8000\r\n\
586 a=rtpmap:110 telephone-event/48000\r\n\
587 a=rtpmap:113 telephone-event/16000\r\n\
588 a=rtpmap:126 telephone-event/8000\r\n";
589
590 if let Some(l) = super::parse_content_length(request) {
591 println!("content length is : {l}");
592 }
593
594 if let Some(parser) = HttpRequest::unmarshal(request) {
595 println!(" parser: {parser:?}");
596 let marshal_result = parser.marshal();
597 print!("marshal result: =={marshal_result}==");
598 assert_eq!(request, marshal_result);
599 }
600 }
601
602 #[test]
603 fn test_parse_http_response() {
604 let response = "HTTP/1.1 201 Created\r\n\
605 Content-Type: application/sdp\r\n\
606 Location: https://whip.example.com/resource/id\r\n\
607 Content-Length: 1392\r\n\
608 \r\n\
609 v=0\r\n\
610 o=- 1657793490019 1 IN IP4 127.0.0.1\r\n\
611 s=-\r\n\
612 t=0 0\r\n\
613 a=group:BUNDLE 0 1\r\n\
614 a=extmap-allow-mixed\r\n\
615 a=ice-lite\r\n\
616 a=msid-semantic: WMS *\r\n\
617 m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\
618 c=IN IP4 0.0.0.0\r\n\
619 a=rtcp:9 IN IP4 0.0.0.0\r\n\
620 a=ice-ufrag:38sdf4fdsf54\r\n\
621 a=ice-pwd:2e13dde17c1cb009202f627fab90cbec358d766d049c9697\r\n\
622 a=fingerprint:sha-256 F7:EB:F3:3E:AC:D2:EA:A7:C1:EC:79:D9:B3:8A:35:DA:70:86:4F:46:D9:2D:CC:D0:BC:81:9F:67:EF:34:2E:BD\r\n\
623 a=candidate:1 1 UDP 2130706431 198.51.100.1 39132 typ host\r\n\
624 a=setup:passive\r\n\
625 a=mid:0\r\n\
626 a=bundle-only\r\n\
627 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
628 a=recvonly\r\n\
629 a=rtcp-mux\r\n\
630 a=rtcp-rsize\r\n\
631 a=rtpmap:111 opus/48000/2\r\n\
632 a=fmtp:111 minptime=10;useinbandfec=1\r\n\
633 m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n\
634 c=IN IP4 0.0.0.0\r\n\
635 a=rtcp:9 IN IP4 0.0.0.0\r\n\
636 a=ice-ufrag:38sdf4fdsf54\r\n\
637 a=ice-pwd:2e13dde17c1cb009202f627fab90cbec358d766d049c9697\r\n\
638 a=fingerprint:sha-256 F7:EB:F3:3E:AC:D2:EA:A7:C1:EC:79:D9:B3:8A:35:DA:70:86:4F:46:D9:2D:CC:D0:BC:81:9F:67:EF:34:2E:BD\r\n\
639 a=candidate:1 1 UDP 2130706431 198.51.100.1 39132 typ host\r\n\
640 a=setup:passive\r\n\
641 a=mid:1\r\n\
642 a=bundle-only\r\n\
643 a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
644 a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
645 a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
646 a=recvonly\r\n\
647 a=rtcp-mux\r\n\
648 a=rtcp-rsize\r\n\
649 a=rtpmap:96 VP8/90000\r\n\
650 a=rtcp-fb:96 ccm fir\r\n\
651 a=rtcp-fb:96 nack\r\n\
652 a=rtcp-fb:96 nack pli\r\n\
653 a=rtpmap:97 rtx/90000\r\n\
654 a=fmtp:97 apt=96\r\n";
655
656 if let Some(parser) = HttpResponse::unmarshal(response) {
657 println!(" parser: {parser:?}");
658 let marshal_result = parser.marshal();
659 print!("marshal result: =={marshal_result}==");
660 assert_eq!(response, marshal_result);
661 }
662 }
663
664 #[test]
714 fn test_parse_rtsp_request() {
715 let data1 = "SETUP rtsp://127.0.0.1/stream/streamid=0 RTSP/1.0\r\n\
716 Transport: RTP/AVP/TCP;unicast;interleaved=0-1;mode=record\r\n\
717 CSeq: 3\r\n\
718 User-Agent: Lavf58.76.100\r\n\
719 \r\n";
720
721 if let Some(parser) = HttpRequest::unmarshal(data1) {
722 println!(" parser: {parser:?}");
723 let marshal_result = parser.marshal();
724 print!("marshal result: =={marshal_result}==");
725 assert_eq!(data1, marshal_result);
726 }
727
728 let data2 = "ANNOUNCE rtsp://127.0.0.1/stream RTSP/1.0\r\n\
729 Content-Type: application/sdp\r\n\
730 CSeq: 2\r\n\
731 User-Agent: Lavf58.76.100\r\n\
732 Content-Length: 500\r\n\
733 \r\n\
734 v=0\r\n\
735 o=- 0 0 IN IP4 127.0.0.1\r\n\
736 s=No Name\r\n\
737 c=IN IP4 127.0.0.1\r\n\
738 t=0 0\r\n\
739 a=tool:libavformat 58.76.100\r\n\
740 m=video 0 RTP/AVP 96\r\n\
741 b=AS:284\r\n\
742 a=rtpmap:96 H264/90000\r\n\
743 a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAHqzZQKAv+XARAAADAAEAAAMAMg8WLZY=,aOvjyyLA; profile-level-id=64001E\r\n\
744 a=control:streamid=0\r\n\
745 m=audio 0 RTP/AVP 97\r\n\
746 b=AS:128\r\n\
747 a=rtpmap:97 MPEG4-GENERIC/48000/2\r\n\
748 a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=119056E500\r\n\
749 a=control:streamid=1\r\n";
750
751 if let Some(parser) = HttpRequest::unmarshal(data2) {
752 println!(" parser: {parser:?}");
753 let marshal_result = parser.marshal();
754 print!("marshal result: =={marshal_result}==");
755 assert_eq!(data2, marshal_result);
756 }
757 }
758}