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