xref: /xiu/protocol/webrtc/src/http/mod.rs (revision 80f20d70)
1*80f20d70SHarlanC pub mod define;
2*80f20d70SHarlanC use indexmap::IndexMap;
3*80f20d70SHarlanC 
4*80f20d70SHarlanC macro_rules! scanf {
5*80f20d70SHarlanC     ( $string:expr, $sep:expr, $( $x:ty ),+ ) => {{
6*80f20d70SHarlanC         let mut iter = $string.split($sep);
7*80f20d70SHarlanC         ($(iter.next().and_then(|word| word.parse::<$x>().ok()),)*)
8*80f20d70SHarlanC     }}
9*80f20d70SHarlanC }
10*80f20d70SHarlanC 
11*80f20d70SHarlanC pub trait Unmarshal {
12*80f20d70SHarlanC     fn unmarshal(request_data: &str) -> Option<Self>
13*80f20d70SHarlanC     where
14*80f20d70SHarlanC         Self: Sized;
15*80f20d70SHarlanC }
16*80f20d70SHarlanC 
17*80f20d70SHarlanC pub trait Marshal {
18*80f20d70SHarlanC     fn marshal(&self) -> String;
19*80f20d70SHarlanC }
20*80f20d70SHarlanC 
21*80f20d70SHarlanC #[derive(Debug, Clone, Default)]
22*80f20d70SHarlanC pub struct HttpRequest {
23*80f20d70SHarlanC     pub method: String,
24*80f20d70SHarlanC     pub address: String,
25*80f20d70SHarlanC     pub port: u16,
26*80f20d70SHarlanC     pub path: String,
27*80f20d70SHarlanC     pub path_parameters: Option<String>,
28*80f20d70SHarlanC     //parse path_parameters and save the results
29*80f20d70SHarlanC     pub path_parameters_map: IndexMap<String, String>,
30*80f20d70SHarlanC     pub version: String,
31*80f20d70SHarlanC     pub headers: IndexMap<String, String>,
32*80f20d70SHarlanC     pub body: Option<String>,
33*80f20d70SHarlanC }
34*80f20d70SHarlanC 
35*80f20d70SHarlanC impl HttpRequest {
36*80f20d70SHarlanC     pub fn get_header(&self, header_name: &String) -> Option<&String> {
37*80f20d70SHarlanC         self.headers.get(header_name)
38*80f20d70SHarlanC     }
39*80f20d70SHarlanC }
40*80f20d70SHarlanC 
41*80f20d70SHarlanC pub fn parse_content_length(request_data: &str) -> Option<u32> {
42*80f20d70SHarlanC     let start = "Content-Length:";
43*80f20d70SHarlanC     let end = "\r\n";
44*80f20d70SHarlanC 
45*80f20d70SHarlanC     let start_index = request_data.find(start)? + start.len();
46*80f20d70SHarlanC     let end_index = request_data[start_index..].find(end)? + start_index;
47*80f20d70SHarlanC     let length_str = &request_data[start_index..end_index];
48*80f20d70SHarlanC 
49*80f20d70SHarlanC     length_str.trim().parse().ok()
50*80f20d70SHarlanC }
51*80f20d70SHarlanC 
52*80f20d70SHarlanC impl Unmarshal for HttpRequest {
53*80f20d70SHarlanC     fn unmarshal(request_data: &str) -> Option<Self> {
54*80f20d70SHarlanC         log::trace!("len: {} content: {}", request_data.len(), request_data);
55*80f20d70SHarlanC 
56*80f20d70SHarlanC         let mut http_request = HttpRequest::default();
57*80f20d70SHarlanC         let header_end_idx = if let Some(idx) = request_data.find("\r\n\r\n") {
58*80f20d70SHarlanC             let data_except_body = &request_data[..idx];
59*80f20d70SHarlanC             let mut lines = data_except_body.lines();
60*80f20d70SHarlanC             //parse the first line
61*80f20d70SHarlanC             //POST /whip?app=live&stream=test HTTP/1.1
62*80f20d70SHarlanC             if let Some(request_first_line) = lines.next() {
63*80f20d70SHarlanC                 let mut fields = request_first_line.split_ascii_whitespace();
64*80f20d70SHarlanC                 if let Some(method) = fields.next() {
65*80f20d70SHarlanC                     http_request.method = method.to_string();
66*80f20d70SHarlanC                 }
67*80f20d70SHarlanC                 if let Some(path) = fields.next() {
68*80f20d70SHarlanC                     let path_data: Vec<&str> = path.splitn(2, '?').collect();
69*80f20d70SHarlanC                     http_request.path = path_data[0].to_string();
70*80f20d70SHarlanC 
71*80f20d70SHarlanC                     if path_data.len() > 1 {
72*80f20d70SHarlanC                         let pars = path_data[1].to_string();
73*80f20d70SHarlanC                         let pars_array: Vec<&str> = pars.split('&').collect();
74*80f20d70SHarlanC 
75*80f20d70SHarlanC                         for ele in pars_array {
76*80f20d70SHarlanC                             let (k, v) = scanf!(ele, '=', String, String);
77*80f20d70SHarlanC                             if k.is_none() || v.is_none() {
78*80f20d70SHarlanC                                 continue;
79*80f20d70SHarlanC                             }
80*80f20d70SHarlanC                             http_request
81*80f20d70SHarlanC                                 .path_parameters_map
82*80f20d70SHarlanC                                 .insert(k.unwrap(), v.unwrap());
83*80f20d70SHarlanC                         }
84*80f20d70SHarlanC                         http_request.path_parameters = Some(pars);
85*80f20d70SHarlanC                     }
86*80f20d70SHarlanC                 }
87*80f20d70SHarlanC                 if let Some(version) = fields.next() {
88*80f20d70SHarlanC                     http_request.version = version.to_string();
89*80f20d70SHarlanC                 }
90*80f20d70SHarlanC             }
91*80f20d70SHarlanC             //parse headers
92*80f20d70SHarlanC             for line in lines {
93*80f20d70SHarlanC                 if let Some(index) = line.find(": ") {
94*80f20d70SHarlanC                     let name = line[..index].to_string();
95*80f20d70SHarlanC                     let value = line[index + 2..].to_string();
96*80f20d70SHarlanC                     if name == "Host" {
97*80f20d70SHarlanC                         let (address_val, port_val) = scanf!(value, ':', String, u16);
98*80f20d70SHarlanC                         if let Some(address) = address_val {
99*80f20d70SHarlanC                             http_request.address = address;
100*80f20d70SHarlanC                         }
101*80f20d70SHarlanC                         if let Some(port) = port_val {
102*80f20d70SHarlanC                             http_request.port = port;
103*80f20d70SHarlanC                         }
104*80f20d70SHarlanC                     }
105*80f20d70SHarlanC                     http_request.headers.insert(name, value);
106*80f20d70SHarlanC                 }
107*80f20d70SHarlanC             }
108*80f20d70SHarlanC             idx + 4
109*80f20d70SHarlanC         } else {
110*80f20d70SHarlanC             return None;
111*80f20d70SHarlanC         };
112*80f20d70SHarlanC         log::trace!(
113*80f20d70SHarlanC             "header_end_idx is: {} {}",
114*80f20d70SHarlanC             header_end_idx,
115*80f20d70SHarlanC             request_data.len()
116*80f20d70SHarlanC         );
117*80f20d70SHarlanC 
118*80f20d70SHarlanC         if request_data.len() > header_end_idx {
119*80f20d70SHarlanC             //parse body
120*80f20d70SHarlanC             http_request.body = Some(request_data[header_end_idx..].to_string());
121*80f20d70SHarlanC         }
122*80f20d70SHarlanC 
123*80f20d70SHarlanC         Some(http_request)
124*80f20d70SHarlanC     }
125*80f20d70SHarlanC }
126*80f20d70SHarlanC 
127*80f20d70SHarlanC impl Marshal for HttpRequest {
128*80f20d70SHarlanC     fn marshal(&self) -> String {
129*80f20d70SHarlanC         let full_path = if let Some(parameters) = &self.path_parameters {
130*80f20d70SHarlanC             format!("{}?{}", self.path, parameters)
131*80f20d70SHarlanC         } else {
132*80f20d70SHarlanC             self.path.clone()
133*80f20d70SHarlanC         };
134*80f20d70SHarlanC         let mut request_str = format!("{} {} {}\r\n", self.method, full_path, self.version);
135*80f20d70SHarlanC         for (header_name, header_value) in &self.headers {
136*80f20d70SHarlanC             if header_name == &"Content-Length".to_string() {
137*80f20d70SHarlanC                 if let Some(body) = &self.body {
138*80f20d70SHarlanC                     request_str += &format!("Content-Length: {}\r\n", body.len());
139*80f20d70SHarlanC                 }
140*80f20d70SHarlanC             } else {
141*80f20d70SHarlanC                 request_str += &format!("{header_name}: {header_value}\r\n");
142*80f20d70SHarlanC             }
143*80f20d70SHarlanC         }
144*80f20d70SHarlanC 
145*80f20d70SHarlanC         request_str += "\r\n";
146*80f20d70SHarlanC         if let Some(body) = &self.body {
147*80f20d70SHarlanC             request_str += body;
148*80f20d70SHarlanC         }
149*80f20d70SHarlanC         request_str
150*80f20d70SHarlanC     }
151*80f20d70SHarlanC }
152*80f20d70SHarlanC 
153*80f20d70SHarlanC #[derive(Debug, Clone, Default)]
154*80f20d70SHarlanC pub struct HttpResponse {
155*80f20d70SHarlanC     pub version: String,
156*80f20d70SHarlanC     pub status_code: u16,
157*80f20d70SHarlanC     pub reason_phrase: String,
158*80f20d70SHarlanC     pub headers: IndexMap<String, String>,
159*80f20d70SHarlanC     pub body: Option<String>,
160*80f20d70SHarlanC }
161*80f20d70SHarlanC 
162*80f20d70SHarlanC impl Unmarshal for HttpResponse {
163*80f20d70SHarlanC     fn unmarshal(request_data: &str) -> Option<Self> {
164*80f20d70SHarlanC         let mut http_response = HttpResponse::default();
165*80f20d70SHarlanC         let header_end_idx = if let Some(idx) = request_data.find("\r\n\r\n") {
166*80f20d70SHarlanC             let data_except_body = &request_data[..idx];
167*80f20d70SHarlanC             let mut lines = data_except_body.lines();
168*80f20d70SHarlanC             //parse the first line
169*80f20d70SHarlanC             if let Some(request_first_line) = lines.next() {
170*80f20d70SHarlanC                 let mut fields = request_first_line.split_ascii_whitespace();
171*80f20d70SHarlanC 
172*80f20d70SHarlanC                 if let Some(version) = fields.next() {
173*80f20d70SHarlanC                     http_response.version = version.to_string();
174*80f20d70SHarlanC                 }
175*80f20d70SHarlanC                 if let Some(status) = fields.next() {
176*80f20d70SHarlanC                     if let Ok(status) = status.parse::<u16>() {
177*80f20d70SHarlanC                         http_response.status_code = status;
178*80f20d70SHarlanC                     }
179*80f20d70SHarlanC                 }
180*80f20d70SHarlanC                 if let Some(reason_phrase) = fields.next() {
181*80f20d70SHarlanC                     http_response.reason_phrase = reason_phrase.to_string();
182*80f20d70SHarlanC                 }
183*80f20d70SHarlanC             }
184*80f20d70SHarlanC             //parse headers
185*80f20d70SHarlanC             for line in lines {
186*80f20d70SHarlanC                 if let Some(index) = line.find(": ") {
187*80f20d70SHarlanC                     let name = line[..index].to_string();
188*80f20d70SHarlanC                     let value = line[index + 2..].to_string();
189*80f20d70SHarlanC                     http_response.headers.insert(name, value);
190*80f20d70SHarlanC                 }
191*80f20d70SHarlanC             }
192*80f20d70SHarlanC             idx + 4
193*80f20d70SHarlanC         } else {
194*80f20d70SHarlanC             return None;
195*80f20d70SHarlanC         };
196*80f20d70SHarlanC 
197*80f20d70SHarlanC         if request_data.len() > header_end_idx {
198*80f20d70SHarlanC             //parse body
199*80f20d70SHarlanC             http_response.body = Some(request_data[header_end_idx..].to_string());
200*80f20d70SHarlanC         }
201*80f20d70SHarlanC 
202*80f20d70SHarlanC         Some(http_response)
203*80f20d70SHarlanC     }
204*80f20d70SHarlanC }
205*80f20d70SHarlanC 
206*80f20d70SHarlanC impl Marshal for HttpResponse {
207*80f20d70SHarlanC     fn marshal(&self) -> String {
208*80f20d70SHarlanC         let mut response_str = format!(
209*80f20d70SHarlanC             "{} {} {}\r\n",
210*80f20d70SHarlanC             self.version, self.status_code, self.reason_phrase
211*80f20d70SHarlanC         );
212*80f20d70SHarlanC 
213*80f20d70SHarlanC         for (header_name, header_value) in &self.headers {
214*80f20d70SHarlanC             if header_name == &"Content-Length".to_string() {
215*80f20d70SHarlanC                 if let Some(body) = &self.body {
216*80f20d70SHarlanC                     response_str += &format!("Content-Length: {}\r\n", body.len());
217*80f20d70SHarlanC                 }
218*80f20d70SHarlanC             } else {
219*80f20d70SHarlanC                 response_str += &format!("{header_name}: {header_value}\r\n");
220*80f20d70SHarlanC             }
221*80f20d70SHarlanC         }
222*80f20d70SHarlanC 
223*80f20d70SHarlanC         response_str += "\r\n";
224*80f20d70SHarlanC         if let Some(body) = &self.body {
225*80f20d70SHarlanC             response_str += body;
226*80f20d70SHarlanC         }
227*80f20d70SHarlanC         response_str
228*80f20d70SHarlanC     }
229*80f20d70SHarlanC }
230*80f20d70SHarlanC 
231*80f20d70SHarlanC #[cfg(test)]
232*80f20d70SHarlanC mod tests {
233*80f20d70SHarlanC 
234*80f20d70SHarlanC     use super::Marshal;
235*80f20d70SHarlanC     use super::Unmarshal;
236*80f20d70SHarlanC 
237*80f20d70SHarlanC     use super::HttpRequest;
238*80f20d70SHarlanC     use super::HttpResponse;
239*80f20d70SHarlanC 
240*80f20d70SHarlanC     use indexmap::IndexMap;
241*80f20d70SHarlanC     use std::io::BufRead;
242*80f20d70SHarlanC     #[allow(dead_code)]
243*80f20d70SHarlanC     fn read_headers(reader: &mut dyn BufRead) -> Option<IndexMap<String, String>> {
244*80f20d70SHarlanC         let mut headers = IndexMap::new();
245*80f20d70SHarlanC         loop {
246*80f20d70SHarlanC             let mut line = String::new();
247*80f20d70SHarlanC             match reader.read_line(&mut line) {
248*80f20d70SHarlanC                 Ok(0) => break,
249*80f20d70SHarlanC                 Ok(_) => {
250*80f20d70SHarlanC                     if let Some(index) = line.find(": ") {
251*80f20d70SHarlanC                         let name = line[..index].to_string();
252*80f20d70SHarlanC                         let value = line[index + 2..].trim().to_string();
253*80f20d70SHarlanC                         headers.insert(name, value);
254*80f20d70SHarlanC                     }
255*80f20d70SHarlanC                 }
256*80f20d70SHarlanC                 Err(_) => return None,
257*80f20d70SHarlanC             }
258*80f20d70SHarlanC         }
259*80f20d70SHarlanC         Some(headers)
260*80f20d70SHarlanC     }
261*80f20d70SHarlanC 
262*80f20d70SHarlanC     #[test]
263*80f20d70SHarlanC     fn test_parse_http_request() {
264*80f20d70SHarlanC         let request = "POST /whip/endpoint?app=live&stream=test HTTP/1.1\r\n\
265*80f20d70SHarlanC         Host: whip.example.com\r\n\
266*80f20d70SHarlanC         Content-Type: application/sdp\r\n\
267*80f20d70SHarlanC         Content-Length: 1326\r\n\
268*80f20d70SHarlanC         \r\n\
269*80f20d70SHarlanC         v=0\r\n\
270*80f20d70SHarlanC         o=- 5228595038118931041 2 IN IP4 127.0.0.1\r\n\
271*80f20d70SHarlanC         s=-\r\n\
272*80f20d70SHarlanC         t=0 0\r\n\
273*80f20d70SHarlanC         a=group:BUNDLE 0 1\r\n\
274*80f20d70SHarlanC         a=extmap-allow-mixed\r\n\
275*80f20d70SHarlanC         a=msid-semantic: WMS\r\n\
276*80f20d70SHarlanC         m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\
277*80f20d70SHarlanC         c=IN IP4 0.0.0.0\r\n\
278*80f20d70SHarlanC         a=rtcp:9 IN IP4 0.0.0.0\r\n\
279*80f20d70SHarlanC         a=ice-ufrag:EsAw\r\n\
280*80f20d70SHarlanC         a=ice-pwd:bP+XJMM09aR8AiX1jdukzR6Y\r\n\
281*80f20d70SHarlanC         a=ice-options:trickle\r\n\
282*80f20d70SHarlanC         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\
283*80f20d70SHarlanC         a=setup:actpass\r\n\
284*80f20d70SHarlanC         a=mid:0\r\n\
285*80f20d70SHarlanC         a=bundle-only\r\n\
286*80f20d70SHarlanC         a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
287*80f20d70SHarlanC         a=sendonly\r\n\
288*80f20d70SHarlanC         a=msid:- d46fb922-d52a-4e9c-aa87-444eadc1521b\r\n\
289*80f20d70SHarlanC         a=rtcp-mux\r\n\
290*80f20d70SHarlanC         a=rtpmap:111 opus/48000/2\r\n\
291*80f20d70SHarlanC         a=fmtp:111 minptime=10;useinbandfec=1\r\n\
292*80f20d70SHarlanC         m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n\
293*80f20d70SHarlanC         c=IN IP4 0.0.0.0\r\n\
294*80f20d70SHarlanC         a=rtcp:9 IN IP4 0.0.0.0\r\n\
295*80f20d70SHarlanC         a=ice-ufrag:EsAw\r\n\
296*80f20d70SHarlanC         a=ice-pwd:bP+XJMM09aR8AiX1jdukzR6Y\r\n\
297*80f20d70SHarlanC         a=ice-options:trickle\r\n\
298*80f20d70SHarlanC         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\
299*80f20d70SHarlanC         a=setup:actpass\r\n\
300*80f20d70SHarlanC         a=mid:1\r\n\
301*80f20d70SHarlanC         a=bundle-only\r\n\
302*80f20d70SHarlanC         a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
303*80f20d70SHarlanC         a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
304*80f20d70SHarlanC         a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
305*80f20d70SHarlanC         a=sendonly\r\n\
306*80f20d70SHarlanC         a=msid:- d46fb922-d52a-4e9c-aa87-444eadc1521b\r\n\
307*80f20d70SHarlanC         a=rtcp-mux\r\n\
308*80f20d70SHarlanC         a=rtcp-rsize\r\n\
309*80f20d70SHarlanC         a=rtpmap:96 VP8/90000\r\n\
310*80f20d70SHarlanC         a=rtcp-fb:96 ccm fir\r\n\
311*80f20d70SHarlanC         a=rtcp-fb:96 nack\r\n\
312*80f20d70SHarlanC         a=rtcp-fb:96 nack pli\r\n\
313*80f20d70SHarlanC         a=rtpmap:97 rtx/90000\r\n\
314*80f20d70SHarlanC         a=fmtp:97 apt=96\r\n";
315*80f20d70SHarlanC 
316*80f20d70SHarlanC         if let Some(parser) = HttpRequest::unmarshal(request) {
317*80f20d70SHarlanC             println!(" parser: {parser:?}");
318*80f20d70SHarlanC             let marshal_result = parser.marshal();
319*80f20d70SHarlanC             print!("marshal result: =={marshal_result}==");
320*80f20d70SHarlanC             assert_eq!(request, marshal_result);
321*80f20d70SHarlanC         }
322*80f20d70SHarlanC     }
323*80f20d70SHarlanC 
324*80f20d70SHarlanC     #[test]
325*80f20d70SHarlanC     fn test_whep_request() {
326*80f20d70SHarlanC         let request = "POST /whep?app=live&stream=test HTTP/1.1\r\n\
327*80f20d70SHarlanC         Host: localhost:3000\r\n\
328*80f20d70SHarlanC         Accept: */*\r\n\
329*80f20d70SHarlanC         Sec-Fetch-Site: same-origin\r\n\
330*80f20d70SHarlanC         Accept-Language: zh-cn\r\n\
331*80f20d70SHarlanC         Accept-Encoding: gzip, deflate\r\n\
332*80f20d70SHarlanC         Sec-Fetch-Mode: cors\r\n\
333*80f20d70SHarlanC         Content-Type: application/sdp\r\n\
334*80f20d70SHarlanC         Origin: http://localhost:3000\r\n\
335*80f20d70SHarlanC         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\
336*80f20d70SHarlanC         Referer: http://localhost:3000/\r\n\
337*80f20d70SHarlanC         Content-Length: 3895\r\n\
338*80f20d70SHarlanC         Connection: keep-alive\r\n\
339*80f20d70SHarlanC         Sec-Fetch-Dest: empty\r\n\
340*80f20d70SHarlanC         \r\n\
341*80f20d70SHarlanC         v=0\r\n\
342*80f20d70SHarlanC         o=- 6550659986740559335 2 IN IP4 127.0.0.1\r\n\
343*80f20d70SHarlanC         s=-\r\n\
344*80f20d70SHarlanC         t=0 0\r\n\
345*80f20d70SHarlanC         a=group:BUNDLE 0 1\r\n\
346*80f20d70SHarlanC         a=extmap-allow-mixed\r\n\
347*80f20d70SHarlanC         a=msid-semantic: WMS\r\n\
348*80f20d70SHarlanC         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\
349*80f20d70SHarlanC         c=IN IP4 0.0.0.0\r\n\
350*80f20d70SHarlanC         a=rtcp:9 IN IP4 0.0.0.0\r\n\
351*80f20d70SHarlanC         a=ice-ufrag:0mum\r\n\
352*80f20d70SHarlanC         a=ice-pwd:DD4LnAhZLQNLSzRZWZRh9Jm4\r\n\
353*80f20d70SHarlanC         a=ice-options:trickle\r\n\
354*80f20d70SHarlanC         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\
355*80f20d70SHarlanC         a=setup:actpass\r\n\
356*80f20d70SHarlanC         a=mid:0\r\n\
357*80f20d70SHarlanC         a=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\n\
358*80f20d70SHarlanC         a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n\
359*80f20d70SHarlanC         a=extmap:3 urn:3gpp:video-orientation\r\n\
360*80f20d70SHarlanC         a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n\
361*80f20d70SHarlanC         a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n\
362*80f20d70SHarlanC         a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n\
363*80f20d70SHarlanC         a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n\
364*80f20d70SHarlanC         a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n\
365*80f20d70SHarlanC         a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
366*80f20d70SHarlanC         a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
367*80f20d70SHarlanC         a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
368*80f20d70SHarlanC         a=recvonly\r\n\
369*80f20d70SHarlanC         a=rtcp-mux\r\n\
370*80f20d70SHarlanC         a=rtcp-rsize\r\n\
371*80f20d70SHarlanC         a=rtpmap:96 H264/90000\r\n\
372*80f20d70SHarlanC         a=rtcp-fb:96 goog-remb\r\n\
373*80f20d70SHarlanC         a=rtcp-fb:96 transport-cc\r\n\
374*80f20d70SHarlanC         a=rtcp-fb:96 ccm fir\r\n\
375*80f20d70SHarlanC         a=rtcp-fb:96 nack\r\n\
376*80f20d70SHarlanC         a=rtcp-fb:96 nack pli\r\n\
377*80f20d70SHarlanC         a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f\r\n\
378*80f20d70SHarlanC         a=rtpmap:97 rtx/90000\r\n\
379*80f20d70SHarlanC         a=fmtp:97 apt=96\r\n\
380*80f20d70SHarlanC         a=rtpmap:98 H264/90000\r\n\
381*80f20d70SHarlanC         a=rtcp-fb:98 goog-remb\r\n\
382*80f20d70SHarlanC         a=rtcp-fb:98 transport-cc\r\n\
383*80f20d70SHarlanC         a=rtcp-fb:98 ccm fir\r\n\
384*80f20d70SHarlanC         a=rtcp-fb:98 nack\r\n\
385*80f20d70SHarlanC         a=rtcp-fb:98 nack pli\r\n\
386*80f20d70SHarlanC         a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n\
387*80f20d70SHarlanC         a=rtpmap:99 rtx/90000\r\n\
388*80f20d70SHarlanC         a=fmtp:99 apt=98\r\n\
389*80f20d70SHarlanC         a=rtpmap:100 H264/90000\r\n\
390*80f20d70SHarlanC         a=rtcp-fb:100 goog-remb\r\n\
391*80f20d70SHarlanC         a=rtcp-fb:100 transport-cc\r\n\
392*80f20d70SHarlanC         a=rtcp-fb:100 ccm fir\r\n\
393*80f20d70SHarlanC         a=rtcp-fb:100 nack\r\n\
394*80f20d70SHarlanC         a=rtcp-fb:100 nack pli\r\n\
395*80f20d70SHarlanC         a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=640c1f\r\n\
396*80f20d70SHarlanC         a=rtpmap:101 rtx/90000\r\n\
397*80f20d70SHarlanC         a=fmtp:101 apt=100\r\n\
398*80f20d70SHarlanC         a=rtpmap:102 H264/90000\r\n\
399*80f20d70SHarlanC         a=rtcp-fb:102 goog-remb\r\n\
400*80f20d70SHarlanC         a=rtcp-fb:102 transport-cc\r\n\
401*80f20d70SHarlanC         a=rtcp-fb:102 ccm fir\r\n\
402*80f20d70SHarlanC         a=rtcp-fb:102 nack\r\n\
403*80f20d70SHarlanC         a=rtcp-fb:102 nack pli\r\n\
404*80f20d70SHarlanC         a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\n\
405*80f20d70SHarlanC         a=rtpmap:125 rtx/90000\r\n\
406*80f20d70SHarlanC         a=fmtp:125 apt=102\r\n\
407*80f20d70SHarlanC         a=rtpmap:104 VP8/90000\r\n\
408*80f20d70SHarlanC         a=rtcp-fb:104 goog-remb\r\n\
409*80f20d70SHarlanC         a=rtcp-fb:104 transport-cc\r\n\
410*80f20d70SHarlanC         a=rtcp-fb:104 ccm fir\r\n\
411*80f20d70SHarlanC         a=rtcp-fb:104 nack\r\n\
412*80f20d70SHarlanC         a=rtcp-fb:104 nack pli\r\n\
413*80f20d70SHarlanC         a=rtpmap:124 rtx/90000\r\n\
414*80f20d70SHarlanC         a=fmtp:124 apt=104\r\n\
415*80f20d70SHarlanC         a=rtpmap:106 VP9/90000\r\n\
416*80f20d70SHarlanC         a=rtcp-fb:106 goog-remb\r\n\
417*80f20d70SHarlanC         a=rtcp-fb:106 transport-cc\r\n\
418*80f20d70SHarlanC         a=rtcp-fb:106 ccm fir\r\n\
419*80f20d70SHarlanC         a=rtcp-fb:106 nack\r\n\
420*80f20d70SHarlanC         a=rtcp-fb:106 nack pli\r\n\
421*80f20d70SHarlanC         a=fmtp:106 profile-id=0\r\n\
422*80f20d70SHarlanC         a=rtpmap:107 rtx/90000\r\n\
423*80f20d70SHarlanC         a=fmtp:107 apt=106\r\n\
424*80f20d70SHarlanC         a=rtpmap:108 red/90000\r\n\
425*80f20d70SHarlanC         a=rtpmap:109 rtx/90000\r\n\
426*80f20d70SHarlanC         a=fmtp:109 apt=108\r\n\
427*80f20d70SHarlanC         a=rtpmap:127 ulpfec/90000\r\n\
428*80f20d70SHarlanC         a=rtpmap:35 flexfec-03/90000\r\n\
429*80f20d70SHarlanC         a=rtcp-fb:35 goog-remb\r\n\
430*80f20d70SHarlanC         a=rtcp-fb:35 transport-cc\r\n\
431*80f20d70SHarlanC         a=fmtp:35 repair-window=10000000\r\n\
432*80f20d70SHarlanC         m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 9 0 8 105 13 110 113 126\r\n\
433*80f20d70SHarlanC         c=IN IP4 0.0.0.0\r\n\
434*80f20d70SHarlanC         a=rtcp:9 IN IP4 0.0.0.0\r\n\
435*80f20d70SHarlanC         a=ice-ufrag:0mum\r\n\
436*80f20d70SHarlanC         a=ice-pwd:DD4LnAhZLQNLSzRZWZRh9Jm4\r\n\
437*80f20d70SHarlanC         a=ice-options:trickle\r\n\
438*80f20d70SHarlanC         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\
439*80f20d70SHarlanC         a=setup:actpass\r\n\
440*80f20d70SHarlanC         a=mid:1\r\n\
441*80f20d70SHarlanC         a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n\
442*80f20d70SHarlanC         a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n\
443*80f20d70SHarlanC         a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n\
444*80f20d70SHarlanC         a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
445*80f20d70SHarlanC         a=recvonly\r\n\
446*80f20d70SHarlanC         a=rtcp-mux\r\n\
447*80f20d70SHarlanC         a=rtpmap:111 opus/48000/2\r\n\
448*80f20d70SHarlanC         a=rtcp-fb:111 transport-cc\r\n\
449*80f20d70SHarlanC         a=fmtp:111 minptime=10;useinbandfec=1\r\n\
450*80f20d70SHarlanC         a=rtpmap:63 red/48000/2\r\n\
451*80f20d70SHarlanC         a=fmtp:63 111/111\r\n\
452*80f20d70SHarlanC         a=rtpmap:103 ISAC/16000\r\n\
453*80f20d70SHarlanC         a=rtpmap:9 G722/8000\r\n\
454*80f20d70SHarlanC         a=rtpmap:0 PCMU/8000\r\n\
455*80f20d70SHarlanC         a=rtpmap:8 PCMA/8000\r\n\
456*80f20d70SHarlanC         a=rtpmap:105 CN/16000\r\n\
457*80f20d70SHarlanC         a=rtpmap:13 CN/8000\r\n\
458*80f20d70SHarlanC         a=rtpmap:110 telephone-event/48000\r\n\
459*80f20d70SHarlanC         a=rtpmap:113 telephone-event/16000\r\n\
460*80f20d70SHarlanC         a=rtpmap:126 telephone-event/8000\r\n";
461*80f20d70SHarlanC 
462*80f20d70SHarlanC         if let Some(l) = super::parse_content_length(request) {
463*80f20d70SHarlanC             println!("content length is : {l}");
464*80f20d70SHarlanC         }
465*80f20d70SHarlanC 
466*80f20d70SHarlanC         if let Some(parser) = HttpRequest::unmarshal(request) {
467*80f20d70SHarlanC             println!(" parser: {parser:?}");
468*80f20d70SHarlanC             let marshal_result = parser.marshal();
469*80f20d70SHarlanC             print!("marshal result: =={marshal_result}==");
470*80f20d70SHarlanC             assert_eq!(request, marshal_result);
471*80f20d70SHarlanC         }
472*80f20d70SHarlanC     }
473*80f20d70SHarlanC 
474*80f20d70SHarlanC     #[test]
475*80f20d70SHarlanC     fn test_parse_http_response() {
476*80f20d70SHarlanC         let response = "HTTP/1.1 201 Created\r\n\
477*80f20d70SHarlanC         Content-Type: application/sdp\r\n\
478*80f20d70SHarlanC         Location: https://whip.example.com/resource/id\r\n\
479*80f20d70SHarlanC         Content-Length: 1392\r\n\
480*80f20d70SHarlanC         \r\n\
481*80f20d70SHarlanC         v=0\r\n\
482*80f20d70SHarlanC         o=- 1657793490019 1 IN IP4 127.0.0.1\r\n\
483*80f20d70SHarlanC         s=-\r\n\
484*80f20d70SHarlanC         t=0 0\r\n\
485*80f20d70SHarlanC         a=group:BUNDLE 0 1\r\n\
486*80f20d70SHarlanC         a=extmap-allow-mixed\r\n\
487*80f20d70SHarlanC         a=ice-lite\r\n\
488*80f20d70SHarlanC         a=msid-semantic: WMS *\r\n\
489*80f20d70SHarlanC         m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\
490*80f20d70SHarlanC         c=IN IP4 0.0.0.0\r\n\
491*80f20d70SHarlanC         a=rtcp:9 IN IP4 0.0.0.0\r\n\
492*80f20d70SHarlanC         a=ice-ufrag:38sdf4fdsf54\r\n\
493*80f20d70SHarlanC         a=ice-pwd:2e13dde17c1cb009202f627fab90cbec358d766d049c9697\r\n\
494*80f20d70SHarlanC         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\
495*80f20d70SHarlanC         a=candidate:1 1 UDP 2130706431 198.51.100.1 39132 typ host\r\n\
496*80f20d70SHarlanC         a=setup:passive\r\n\
497*80f20d70SHarlanC         a=mid:0\r\n\
498*80f20d70SHarlanC         a=bundle-only\r\n\
499*80f20d70SHarlanC         a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
500*80f20d70SHarlanC         a=recvonly\r\n\
501*80f20d70SHarlanC         a=rtcp-mux\r\n\
502*80f20d70SHarlanC         a=rtcp-rsize\r\n\
503*80f20d70SHarlanC         a=rtpmap:111 opus/48000/2\r\n\
504*80f20d70SHarlanC         a=fmtp:111 minptime=10;useinbandfec=1\r\n\
505*80f20d70SHarlanC         m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n\
506*80f20d70SHarlanC         c=IN IP4 0.0.0.0\r\n\
507*80f20d70SHarlanC         a=rtcp:9 IN IP4 0.0.0.0\r\n\
508*80f20d70SHarlanC         a=ice-ufrag:38sdf4fdsf54\r\n\
509*80f20d70SHarlanC         a=ice-pwd:2e13dde17c1cb009202f627fab90cbec358d766d049c9697\r\n\
510*80f20d70SHarlanC         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\
511*80f20d70SHarlanC         a=candidate:1 1 UDP 2130706431 198.51.100.1 39132 typ host\r\n\
512*80f20d70SHarlanC         a=setup:passive\r\n\
513*80f20d70SHarlanC         a=mid:1\r\n\
514*80f20d70SHarlanC         a=bundle-only\r\n\
515*80f20d70SHarlanC         a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
516*80f20d70SHarlanC         a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
517*80f20d70SHarlanC         a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
518*80f20d70SHarlanC         a=recvonly\r\n\
519*80f20d70SHarlanC         a=rtcp-mux\r\n\
520*80f20d70SHarlanC         a=rtcp-rsize\r\n\
521*80f20d70SHarlanC         a=rtpmap:96 VP8/90000\r\n\
522*80f20d70SHarlanC         a=rtcp-fb:96 ccm fir\r\n\
523*80f20d70SHarlanC         a=rtcp-fb:96 nack\r\n\
524*80f20d70SHarlanC         a=rtcp-fb:96 nack pli\r\n\
525*80f20d70SHarlanC         a=rtpmap:97 rtx/90000\r\n\
526*80f20d70SHarlanC         a=fmtp:97 apt=96\r\n";
527*80f20d70SHarlanC 
528*80f20d70SHarlanC         if let Some(parser) = HttpResponse::unmarshal(response) {
529*80f20d70SHarlanC             println!(" parser: {parser:?}");
530*80f20d70SHarlanC             let marshal_result = parser.marshal();
531*80f20d70SHarlanC             print!("marshal result: =={marshal_result}==");
532*80f20d70SHarlanC             assert_eq!(response, marshal_result);
533*80f20d70SHarlanC         }
534*80f20d70SHarlanC     }
535*80f20d70SHarlanC }
536