commonlib/
http.rs

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    //used for webrtc(WHIP/WHEP)
18    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            //Because webrtc request uri does not contain the schema name, so here write empty string.
31            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    /*
52    RTSP HTTP header : "ANNOUNCE rtsp://127.0.0.1:5544/stream RTSP/1.0\r\n\
53    the uri rtsp://127.0.0.1:5544/stream is standard with schema://host:port/path?query.
54
55    WEBRTC is special:
56    "POST /whep?app=live&stream=test HTTP/1.1\r\n\
57     Host: localhost:3000\r\n\
58     Accept: \r\n\"
59    It only contains path?query after HTTP method, host:port is saved in the Host parameter.
60    In this function, for Webrtc we only parse path?query, host:port will be parsed in the HTTPRequest
61    unmarshal method.
62    */
63    fn unmarshal(url: &str) -> Option<Self> {
64        let mut uri = Uri::default();
65
66        /*first judge the correct schema */
67        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                        /*split host:port and path?query*/
81
82                        if let Some(index) = rtsp_url_without_prefix.find('/') {
83                            let path_with_query = &rtsp_url_without_prefix[index + 1..];
84                            /*parse host and port*/
85                            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        /*first pice path and query together*/
123        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    /*parse the query and save the results*/
149    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            /*parse the first line
179            POST /whip?app=live&stream=test HTTP/1.1*/
180            if let Some(request_first_line) = lines.next() {
181                let mut fields = request_first_line.split_ascii_whitespace();
182                /* method */
183                if let Some(method) = fields.next() {
184                    http_request.method = method.to_string();
185                }
186                /* url */
187                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                /* version */
208                if let Some(version) = fields.next() {
209                    http_request.version = version.to_string();
210                }
211            }
212            /*parse headers*/
213            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                    /*for schema: webrtc*/
218                    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            /*parse body*/
242            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            //parse the first line
297            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            //parse headers
313            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            //parse body
327            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]
665    // fn test_parse_rtsp_request_chatgpt() {
666    //     let data1 = "ANNOUNCE rtsp://127.0.0.1:5544/stream RTSP/1.0\r\n\
667    //     Content-Type: application/sdp\r\n\
668    //     CSeq: 2\r\n\
669    //     User-Agent: Lavf58.76.100\r\n\
670    //     Content-Length: 500\r\n\
671    //     \r\n\
672    //     v=0\r\n\
673    //     o=- 0 0 IN IP4 127.0.0.1\r\n\
674    //     s=No Name\r\n\
675    //     c=IN IP4 127.0.0.1\r\n\
676    //     t=0 0\r\n\
677    //     a=tool:libavformat 58.76.100\r\n\
678    //     m=video 0 RTP/AVP 96\r\n\
679    //     b=AS:284\r\n\
680    //     a=rtpmap:96 H264/90000
681    //     a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAHqzZQKAv+XARAAADAAEAAAMAMg8WLZY=,aOvjyyLA; profile-level-id=64001E\r\n\
682    //     a=control:streamid=0\r\n\
683    //     m=audio 0 RTP/AVP 97\r\n\
684    //     b=AS:128\r\n\
685    //     a=rtpmap:97 MPEG4-GENERIC/48000/2\r\n\
686    //     a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=119056E500\r\n\
687    //     a=control:streamid=1\r\n";
688
689    //     // v=0:SDP版本号,通常为0。
690    //     // o=- 0 0 IN IP4 127.0.0.1:会话的所有者和会话ID,以及会话开始时间和会话结束时间的信息。
691    //     // s=No Name:会话名称或标题。
692    //     // c=IN IP4 127.0.0.1:表示会话数据传输的地址类型(IPv4)和地址(127.0.0.1)。
693    //     // t=0 0:会话时间,包括会话开始时间和结束时间,这里的值都是0,表示会话没有预定义的结束时间。
694    //     // a=tool:libavformat 58.76.100:会话所使用的工具或软件名称和版本号。
695
696    //     // m=video 0 RTP/AVP 96:媒体类型(video或audio)、媒体格式(RTP/AVP)、媒体格式编号(96)和媒体流的传输地址。
697    //     // b=AS:284:视频流所使用的带宽大小。
698    //     // a=rtpmap:96 H264/90000:视频流所使用的编码方式(H.264)和时钟频率(90000)。
699    //     // a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAHqzZQKAv+XARAAADAAEAAAMAMg8WLZY=,aOvjyyLA; profile-level-id=64001E:视频流的格式参数,如分片方式、SPS和PPS等。
700    //     // a=control:streamid=0:指定视频流的流ID。
701
702    //     // m=audio 0 RTP/AVP 97:媒体类型(audio)、媒体格式(RTP/AVP)、媒体格式编号(97)和媒体流的传输地址。
703    //     // b=AS:128:音频流所使用的带宽大小。
704    //     // a=rtpmap:97 MPEG4-GENERIC/48000/2:音频流所使用的编码方式(MPEG4-GENERIC)、采样率(48000Hz)、和通道数(2)。
705    //     // a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=119056E500:音频流的格式参数,如编码方式、采样长度、索引长度等。
706    //     // a=control:streamid=1:指定音频流的流ID。
707
708    //     if let Some(request) = parse_request_bytes(&data1.as_bytes()) {
709    //         println!(" parser: {:?}", request);
710    //     }
711    // }
712
713    #[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}