180f20d70SHarlanC pub mod define;
280f20d70SHarlanC use indexmap::IndexMap;
380f20d70SHarlanC
480f20d70SHarlanC macro_rules! scanf {
580f20d70SHarlanC ( $string:expr, $sep:expr, $( $x:ty ),+ ) => {{
680f20d70SHarlanC let mut iter = $string.split($sep);
780f20d70SHarlanC ($(iter.next().and_then(|word| word.parse::<$x>().ok()),)*)
880f20d70SHarlanC }}
980f20d70SHarlanC }
1080f20d70SHarlanC
1180f20d70SHarlanC pub trait Unmarshal {
unmarshal(request_data: &str) -> Option<Self> where Self: Sized1280f20d70SHarlanC fn unmarshal(request_data: &str) -> Option<Self>
1380f20d70SHarlanC where
1480f20d70SHarlanC Self: Sized;
1580f20d70SHarlanC }
1680f20d70SHarlanC
1780f20d70SHarlanC pub trait Marshal {
marshal(&self) -> String1880f20d70SHarlanC fn marshal(&self) -> String;
1980f20d70SHarlanC }
2080f20d70SHarlanC
2180f20d70SHarlanC #[derive(Debug, Clone, Default)]
2280f20d70SHarlanC pub struct HttpRequest {
2380f20d70SHarlanC pub method: String,
2480f20d70SHarlanC pub address: String,
2580f20d70SHarlanC pub port: u16,
2680f20d70SHarlanC pub path: String,
2780f20d70SHarlanC pub path_parameters: Option<String>,
2880f20d70SHarlanC //parse path_parameters and save the results
2980f20d70SHarlanC pub path_parameters_map: IndexMap<String, String>,
3080f20d70SHarlanC pub version: String,
3180f20d70SHarlanC pub headers: IndexMap<String, String>,
3280f20d70SHarlanC pub body: Option<String>,
3380f20d70SHarlanC }
3480f20d70SHarlanC
3580f20d70SHarlanC impl HttpRequest {
get_header(&self, header_name: &String) -> Option<&String>3680f20d70SHarlanC pub fn get_header(&self, header_name: &String) -> Option<&String> {
3780f20d70SHarlanC self.headers.get(header_name)
3880f20d70SHarlanC }
3980f20d70SHarlanC }
4080f20d70SHarlanC
parse_content_length(request_data: &str) -> Option<u32>4180f20d70SHarlanC pub fn parse_content_length(request_data: &str) -> Option<u32> {
4280f20d70SHarlanC let start = "Content-Length:";
4380f20d70SHarlanC let end = "\r\n";
4480f20d70SHarlanC
4580f20d70SHarlanC let start_index = request_data.find(start)? + start.len();
4680f20d70SHarlanC let end_index = request_data[start_index..].find(end)? + start_index;
4780f20d70SHarlanC let length_str = &request_data[start_index..end_index];
4880f20d70SHarlanC
4980f20d70SHarlanC length_str.trim().parse().ok()
5080f20d70SHarlanC }
5180f20d70SHarlanC
5280f20d70SHarlanC impl Unmarshal for HttpRequest {
unmarshal(request_data: &str) -> Option<Self>5380f20d70SHarlanC fn unmarshal(request_data: &str) -> Option<Self> {
5480f20d70SHarlanC log::trace!("len: {} content: {}", request_data.len(), request_data);
5580f20d70SHarlanC
5680f20d70SHarlanC let mut http_request = HttpRequest::default();
5780f20d70SHarlanC let header_end_idx = if let Some(idx) = request_data.find("\r\n\r\n") {
5880f20d70SHarlanC let data_except_body = &request_data[..idx];
5980f20d70SHarlanC let mut lines = data_except_body.lines();
6080f20d70SHarlanC //parse the first line
6180f20d70SHarlanC //POST /whip?app=live&stream=test HTTP/1.1
6280f20d70SHarlanC if let Some(request_first_line) = lines.next() {
6380f20d70SHarlanC let mut fields = request_first_line.split_ascii_whitespace();
6480f20d70SHarlanC if let Some(method) = fields.next() {
6580f20d70SHarlanC http_request.method = method.to_string();
6680f20d70SHarlanC }
6780f20d70SHarlanC if let Some(path) = fields.next() {
6880f20d70SHarlanC let path_data: Vec<&str> = path.splitn(2, '?').collect();
6980f20d70SHarlanC http_request.path = path_data[0].to_string();
7080f20d70SHarlanC
7180f20d70SHarlanC if path_data.len() > 1 {
7280f20d70SHarlanC let pars = path_data[1].to_string();
7380f20d70SHarlanC let pars_array: Vec<&str> = pars.split('&').collect();
7480f20d70SHarlanC
7580f20d70SHarlanC for ele in pars_array {
7680f20d70SHarlanC let (k, v) = scanf!(ele, '=', String, String);
7780f20d70SHarlanC if k.is_none() || v.is_none() {
7880f20d70SHarlanC continue;
7980f20d70SHarlanC }
8080f20d70SHarlanC http_request
8180f20d70SHarlanC .path_parameters_map
8280f20d70SHarlanC .insert(k.unwrap(), v.unwrap());
8380f20d70SHarlanC }
8480f20d70SHarlanC http_request.path_parameters = Some(pars);
8580f20d70SHarlanC }
8680f20d70SHarlanC }
8780f20d70SHarlanC if let Some(version) = fields.next() {
8880f20d70SHarlanC http_request.version = version.to_string();
8980f20d70SHarlanC }
9080f20d70SHarlanC }
9180f20d70SHarlanC //parse headers
9280f20d70SHarlanC for line in lines {
9380f20d70SHarlanC if let Some(index) = line.find(": ") {
9480f20d70SHarlanC let name = line[..index].to_string();
9580f20d70SHarlanC let value = line[index + 2..].to_string();
9680f20d70SHarlanC if name == "Host" {
9780f20d70SHarlanC let (address_val, port_val) = scanf!(value, ':', String, u16);
9880f20d70SHarlanC if let Some(address) = address_val {
9980f20d70SHarlanC http_request.address = address;
10080f20d70SHarlanC }
10180f20d70SHarlanC if let Some(port) = port_val {
10280f20d70SHarlanC http_request.port = port;
10380f20d70SHarlanC }
10480f20d70SHarlanC }
10580f20d70SHarlanC http_request.headers.insert(name, value);
10680f20d70SHarlanC }
10780f20d70SHarlanC }
10880f20d70SHarlanC idx + 4
10980f20d70SHarlanC } else {
11080f20d70SHarlanC return None;
11180f20d70SHarlanC };
11280f20d70SHarlanC log::trace!(
11380f20d70SHarlanC "header_end_idx is: {} {}",
11480f20d70SHarlanC header_end_idx,
11580f20d70SHarlanC request_data.len()
11680f20d70SHarlanC );
11780f20d70SHarlanC
11880f20d70SHarlanC if request_data.len() > header_end_idx {
11980f20d70SHarlanC //parse body
12080f20d70SHarlanC http_request.body = Some(request_data[header_end_idx..].to_string());
12180f20d70SHarlanC }
12280f20d70SHarlanC
12380f20d70SHarlanC Some(http_request)
12480f20d70SHarlanC }
12580f20d70SHarlanC }
12680f20d70SHarlanC
12780f20d70SHarlanC impl Marshal for HttpRequest {
marshal(&self) -> String12880f20d70SHarlanC fn marshal(&self) -> String {
12980f20d70SHarlanC let full_path = if let Some(parameters) = &self.path_parameters {
13080f20d70SHarlanC format!("{}?{}", self.path, parameters)
13180f20d70SHarlanC } else {
13280f20d70SHarlanC self.path.clone()
13380f20d70SHarlanC };
13480f20d70SHarlanC let mut request_str = format!("{} {} {}\r\n", self.method, full_path, self.version);
13580f20d70SHarlanC for (header_name, header_value) in &self.headers {
13680f20d70SHarlanC if header_name == &"Content-Length".to_string() {
13780f20d70SHarlanC if let Some(body) = &self.body {
13880f20d70SHarlanC request_str += &format!("Content-Length: {}\r\n", body.len());
13980f20d70SHarlanC }
14080f20d70SHarlanC } else {
14180f20d70SHarlanC request_str += &format!("{header_name}: {header_value}\r\n");
14280f20d70SHarlanC }
14380f20d70SHarlanC }
14480f20d70SHarlanC
14580f20d70SHarlanC request_str += "\r\n";
14680f20d70SHarlanC if let Some(body) = &self.body {
14780f20d70SHarlanC request_str += body;
14880f20d70SHarlanC }
14980f20d70SHarlanC request_str
15080f20d70SHarlanC }
15180f20d70SHarlanC }
15280f20d70SHarlanC
15380f20d70SHarlanC #[derive(Debug, Clone, Default)]
15480f20d70SHarlanC pub struct HttpResponse {
15580f20d70SHarlanC pub version: String,
15680f20d70SHarlanC pub status_code: u16,
15780f20d70SHarlanC pub reason_phrase: String,
15880f20d70SHarlanC pub headers: IndexMap<String, String>,
15980f20d70SHarlanC pub body: Option<String>,
16080f20d70SHarlanC }
16180f20d70SHarlanC
16280f20d70SHarlanC impl Unmarshal for HttpResponse {
unmarshal(request_data: &str) -> Option<Self>16380f20d70SHarlanC fn unmarshal(request_data: &str) -> Option<Self> {
16480f20d70SHarlanC let mut http_response = HttpResponse::default();
16580f20d70SHarlanC let header_end_idx = if let Some(idx) = request_data.find("\r\n\r\n") {
16680f20d70SHarlanC let data_except_body = &request_data[..idx];
16780f20d70SHarlanC let mut lines = data_except_body.lines();
16880f20d70SHarlanC //parse the first line
16980f20d70SHarlanC if let Some(request_first_line) = lines.next() {
17080f20d70SHarlanC let mut fields = request_first_line.split_ascii_whitespace();
17180f20d70SHarlanC
17280f20d70SHarlanC if let Some(version) = fields.next() {
17380f20d70SHarlanC http_response.version = version.to_string();
17480f20d70SHarlanC }
17580f20d70SHarlanC if let Some(status) = fields.next() {
17680f20d70SHarlanC if let Ok(status) = status.parse::<u16>() {
17780f20d70SHarlanC http_response.status_code = status;
17880f20d70SHarlanC }
17980f20d70SHarlanC }
18080f20d70SHarlanC if let Some(reason_phrase) = fields.next() {
18180f20d70SHarlanC http_response.reason_phrase = reason_phrase.to_string();
18280f20d70SHarlanC }
18380f20d70SHarlanC }
18480f20d70SHarlanC //parse headers
18580f20d70SHarlanC for line in lines {
18680f20d70SHarlanC if let Some(index) = line.find(": ") {
18780f20d70SHarlanC let name = line[..index].to_string();
18880f20d70SHarlanC let value = line[index + 2..].to_string();
18980f20d70SHarlanC http_response.headers.insert(name, value);
19080f20d70SHarlanC }
19180f20d70SHarlanC }
19280f20d70SHarlanC idx + 4
19380f20d70SHarlanC } else {
19480f20d70SHarlanC return None;
19580f20d70SHarlanC };
19680f20d70SHarlanC
19780f20d70SHarlanC if request_data.len() > header_end_idx {
19880f20d70SHarlanC //parse body
19980f20d70SHarlanC http_response.body = Some(request_data[header_end_idx..].to_string());
20080f20d70SHarlanC }
20180f20d70SHarlanC
20280f20d70SHarlanC Some(http_response)
20380f20d70SHarlanC }
20480f20d70SHarlanC }
20580f20d70SHarlanC
20680f20d70SHarlanC impl Marshal for HttpResponse {
marshal(&self) -> String20780f20d70SHarlanC fn marshal(&self) -> String {
20880f20d70SHarlanC let mut response_str = format!(
20980f20d70SHarlanC "{} {} {}\r\n",
21080f20d70SHarlanC self.version, self.status_code, self.reason_phrase
21180f20d70SHarlanC );
21280f20d70SHarlanC
21380f20d70SHarlanC for (header_name, header_value) in &self.headers {
214*f3f517c7SHarlan if header_name != &"Content-Length".to_string() {
21580f20d70SHarlanC response_str += &format!("{header_name}: {header_value}\r\n");
21680f20d70SHarlanC }
21780f20d70SHarlanC }
21880f20d70SHarlanC
219*f3f517c7SHarlan if let Some(body) = &self.body {
220*f3f517c7SHarlan response_str += &format!("Content-Length: {}\r\n", body.len());
221*f3f517c7SHarlan }
222*f3f517c7SHarlan
22380f20d70SHarlanC response_str += "\r\n";
22480f20d70SHarlanC if let Some(body) = &self.body {
22580f20d70SHarlanC response_str += body;
22680f20d70SHarlanC }
22780f20d70SHarlanC response_str
22880f20d70SHarlanC }
22980f20d70SHarlanC }
23080f20d70SHarlanC
23180f20d70SHarlanC #[cfg(test)]
23280f20d70SHarlanC mod tests {
23380f20d70SHarlanC
23480f20d70SHarlanC use super::Marshal;
23580f20d70SHarlanC use super::Unmarshal;
23680f20d70SHarlanC
23780f20d70SHarlanC use super::HttpRequest;
23880f20d70SHarlanC use super::HttpResponse;
23980f20d70SHarlanC
24080f20d70SHarlanC use indexmap::IndexMap;
24180f20d70SHarlanC use std::io::BufRead;
24280f20d70SHarlanC #[allow(dead_code)]
read_headers(reader: &mut dyn BufRead) -> Option<IndexMap<String, String>>24380f20d70SHarlanC fn read_headers(reader: &mut dyn BufRead) -> Option<IndexMap<String, String>> {
24480f20d70SHarlanC let mut headers = IndexMap::new();
24580f20d70SHarlanC loop {
24680f20d70SHarlanC let mut line = String::new();
24780f20d70SHarlanC match reader.read_line(&mut line) {
24880f20d70SHarlanC Ok(0) => break,
24980f20d70SHarlanC Ok(_) => {
25080f20d70SHarlanC if let Some(index) = line.find(": ") {
25180f20d70SHarlanC let name = line[..index].to_string();
25280f20d70SHarlanC let value = line[index + 2..].trim().to_string();
25380f20d70SHarlanC headers.insert(name, value);
25480f20d70SHarlanC }
25580f20d70SHarlanC }
25680f20d70SHarlanC Err(_) => return None,
25780f20d70SHarlanC }
25880f20d70SHarlanC }
25980f20d70SHarlanC Some(headers)
26080f20d70SHarlanC }
26180f20d70SHarlanC
26280f20d70SHarlanC #[test]
test_parse_http_request()26380f20d70SHarlanC fn test_parse_http_request() {
26480f20d70SHarlanC let request = "POST /whip/endpoint?app=live&stream=test HTTP/1.1\r\n\
26580f20d70SHarlanC Host: whip.example.com\r\n\
26680f20d70SHarlanC Content-Type: application/sdp\r\n\
26780f20d70SHarlanC Content-Length: 1326\r\n\
26880f20d70SHarlanC \r\n\
26980f20d70SHarlanC v=0\r\n\
27080f20d70SHarlanC o=- 5228595038118931041 2 IN IP4 127.0.0.1\r\n\
27180f20d70SHarlanC s=-\r\n\
27280f20d70SHarlanC t=0 0\r\n\
27380f20d70SHarlanC a=group:BUNDLE 0 1\r\n\
27480f20d70SHarlanC a=extmap-allow-mixed\r\n\
27580f20d70SHarlanC a=msid-semantic: WMS\r\n\
27680f20d70SHarlanC m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\
27780f20d70SHarlanC c=IN IP4 0.0.0.0\r\n\
27880f20d70SHarlanC a=rtcp:9 IN IP4 0.0.0.0\r\n\
27980f20d70SHarlanC a=ice-ufrag:EsAw\r\n\
28080f20d70SHarlanC a=ice-pwd:bP+XJMM09aR8AiX1jdukzR6Y\r\n\
28180f20d70SHarlanC a=ice-options:trickle\r\n\
28280f20d70SHarlanC 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\
28380f20d70SHarlanC a=setup:actpass\r\n\
28480f20d70SHarlanC a=mid:0\r\n\
28580f20d70SHarlanC a=bundle-only\r\n\
28680f20d70SHarlanC a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
28780f20d70SHarlanC a=sendonly\r\n\
28880f20d70SHarlanC a=msid:- d46fb922-d52a-4e9c-aa87-444eadc1521b\r\n\
28980f20d70SHarlanC a=rtcp-mux\r\n\
29080f20d70SHarlanC a=rtpmap:111 opus/48000/2\r\n\
29180f20d70SHarlanC a=fmtp:111 minptime=10;useinbandfec=1\r\n\
29280f20d70SHarlanC m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n\
29380f20d70SHarlanC c=IN IP4 0.0.0.0\r\n\
29480f20d70SHarlanC a=rtcp:9 IN IP4 0.0.0.0\r\n\
29580f20d70SHarlanC a=ice-ufrag:EsAw\r\n\
29680f20d70SHarlanC a=ice-pwd:bP+XJMM09aR8AiX1jdukzR6Y\r\n\
29780f20d70SHarlanC a=ice-options:trickle\r\n\
29880f20d70SHarlanC 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\
29980f20d70SHarlanC a=setup:actpass\r\n\
30080f20d70SHarlanC a=mid:1\r\n\
30180f20d70SHarlanC a=bundle-only\r\n\
30280f20d70SHarlanC a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
30380f20d70SHarlanC a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
30480f20d70SHarlanC a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
30580f20d70SHarlanC a=sendonly\r\n\
30680f20d70SHarlanC a=msid:- d46fb922-d52a-4e9c-aa87-444eadc1521b\r\n\
30780f20d70SHarlanC a=rtcp-mux\r\n\
30880f20d70SHarlanC a=rtcp-rsize\r\n\
30980f20d70SHarlanC a=rtpmap:96 VP8/90000\r\n\
31080f20d70SHarlanC a=rtcp-fb:96 ccm fir\r\n\
31180f20d70SHarlanC a=rtcp-fb:96 nack\r\n\
31280f20d70SHarlanC a=rtcp-fb:96 nack pli\r\n\
31380f20d70SHarlanC a=rtpmap:97 rtx/90000\r\n\
31480f20d70SHarlanC a=fmtp:97 apt=96\r\n";
31580f20d70SHarlanC
31680f20d70SHarlanC if let Some(parser) = HttpRequest::unmarshal(request) {
31780f20d70SHarlanC println!(" parser: {parser:?}");
31880f20d70SHarlanC let marshal_result = parser.marshal();
31980f20d70SHarlanC print!("marshal result: =={marshal_result}==");
32080f20d70SHarlanC assert_eq!(request, marshal_result);
32180f20d70SHarlanC }
32280f20d70SHarlanC }
32380f20d70SHarlanC
32480f20d70SHarlanC #[test]
test_whep_request()32580f20d70SHarlanC fn test_whep_request() {
32680f20d70SHarlanC let request = "POST /whep?app=live&stream=test HTTP/1.1\r\n\
32780f20d70SHarlanC Host: localhost:3000\r\n\
32880f20d70SHarlanC Accept: */*\r\n\
32980f20d70SHarlanC Sec-Fetch-Site: same-origin\r\n\
33080f20d70SHarlanC Accept-Language: zh-cn\r\n\
33180f20d70SHarlanC Accept-Encoding: gzip, deflate\r\n\
33280f20d70SHarlanC Sec-Fetch-Mode: cors\r\n\
33380f20d70SHarlanC Content-Type: application/sdp\r\n\
33480f20d70SHarlanC Origin: http://localhost:3000\r\n\
33580f20d70SHarlanC 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\
33680f20d70SHarlanC Referer: http://localhost:3000/\r\n\
33780f20d70SHarlanC Content-Length: 3895\r\n\
33880f20d70SHarlanC Connection: keep-alive\r\n\
33980f20d70SHarlanC Sec-Fetch-Dest: empty\r\n\
34080f20d70SHarlanC \r\n\
34180f20d70SHarlanC v=0\r\n\
34280f20d70SHarlanC o=- 6550659986740559335 2 IN IP4 127.0.0.1\r\n\
34380f20d70SHarlanC s=-\r\n\
34480f20d70SHarlanC t=0 0\r\n\
34580f20d70SHarlanC a=group:BUNDLE 0 1\r\n\
34680f20d70SHarlanC a=extmap-allow-mixed\r\n\
34780f20d70SHarlanC a=msid-semantic: WMS\r\n\
34880f20d70SHarlanC 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\
34980f20d70SHarlanC c=IN IP4 0.0.0.0\r\n\
35080f20d70SHarlanC a=rtcp:9 IN IP4 0.0.0.0\r\n\
35180f20d70SHarlanC a=ice-ufrag:0mum\r\n\
35280f20d70SHarlanC a=ice-pwd:DD4LnAhZLQNLSzRZWZRh9Jm4\r\n\
35380f20d70SHarlanC a=ice-options:trickle\r\n\
35480f20d70SHarlanC 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\
35580f20d70SHarlanC a=setup:actpass\r\n\
35680f20d70SHarlanC a=mid:0\r\n\
35780f20d70SHarlanC a=extmap:1 urn:ietf:params:rtp-hdrext:toffset\r\n\
35880f20d70SHarlanC a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n\
35980f20d70SHarlanC a=extmap:3 urn:3gpp:video-orientation\r\n\
36080f20d70SHarlanC a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n\
36180f20d70SHarlanC a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\r\n\
36280f20d70SHarlanC a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\r\n\
36380f20d70SHarlanC a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\r\n\
36480f20d70SHarlanC a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space\r\n\
36580f20d70SHarlanC a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
36680f20d70SHarlanC a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
36780f20d70SHarlanC a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
36880f20d70SHarlanC a=recvonly\r\n\
36980f20d70SHarlanC a=rtcp-mux\r\n\
37080f20d70SHarlanC a=rtcp-rsize\r\n\
37180f20d70SHarlanC a=rtpmap:96 H264/90000\r\n\
37280f20d70SHarlanC a=rtcp-fb:96 goog-remb\r\n\
37380f20d70SHarlanC a=rtcp-fb:96 transport-cc\r\n\
37480f20d70SHarlanC a=rtcp-fb:96 ccm fir\r\n\
37580f20d70SHarlanC a=rtcp-fb:96 nack\r\n\
37680f20d70SHarlanC a=rtcp-fb:96 nack pli\r\n\
37780f20d70SHarlanC a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f\r\n\
37880f20d70SHarlanC a=rtpmap:97 rtx/90000\r\n\
37980f20d70SHarlanC a=fmtp:97 apt=96\r\n\
38080f20d70SHarlanC a=rtpmap:98 H264/90000\r\n\
38180f20d70SHarlanC a=rtcp-fb:98 goog-remb\r\n\
38280f20d70SHarlanC a=rtcp-fb:98 transport-cc\r\n\
38380f20d70SHarlanC a=rtcp-fb:98 ccm fir\r\n\
38480f20d70SHarlanC a=rtcp-fb:98 nack\r\n\
38580f20d70SHarlanC a=rtcp-fb:98 nack pli\r\n\
38680f20d70SHarlanC a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n\
38780f20d70SHarlanC a=rtpmap:99 rtx/90000\r\n\
38880f20d70SHarlanC a=fmtp:99 apt=98\r\n\
38980f20d70SHarlanC a=rtpmap:100 H264/90000\r\n\
39080f20d70SHarlanC a=rtcp-fb:100 goog-remb\r\n\
39180f20d70SHarlanC a=rtcp-fb:100 transport-cc\r\n\
39280f20d70SHarlanC a=rtcp-fb:100 ccm fir\r\n\
39380f20d70SHarlanC a=rtcp-fb:100 nack\r\n\
39480f20d70SHarlanC a=rtcp-fb:100 nack pli\r\n\
39580f20d70SHarlanC a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=640c1f\r\n\
39680f20d70SHarlanC a=rtpmap:101 rtx/90000\r\n\
39780f20d70SHarlanC a=fmtp:101 apt=100\r\n\
39880f20d70SHarlanC a=rtpmap:102 H264/90000\r\n\
39980f20d70SHarlanC a=rtcp-fb:102 goog-remb\r\n\
40080f20d70SHarlanC a=rtcp-fb:102 transport-cc\r\n\
40180f20d70SHarlanC a=rtcp-fb:102 ccm fir\r\n\
40280f20d70SHarlanC a=rtcp-fb:102 nack\r\n\
40380f20d70SHarlanC a=rtcp-fb:102 nack pli\r\n\
40480f20d70SHarlanC a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\r\n\
40580f20d70SHarlanC a=rtpmap:125 rtx/90000\r\n\
40680f20d70SHarlanC a=fmtp:125 apt=102\r\n\
40780f20d70SHarlanC a=rtpmap:104 VP8/90000\r\n\
40880f20d70SHarlanC a=rtcp-fb:104 goog-remb\r\n\
40980f20d70SHarlanC a=rtcp-fb:104 transport-cc\r\n\
41080f20d70SHarlanC a=rtcp-fb:104 ccm fir\r\n\
41180f20d70SHarlanC a=rtcp-fb:104 nack\r\n\
41280f20d70SHarlanC a=rtcp-fb:104 nack pli\r\n\
41380f20d70SHarlanC a=rtpmap:124 rtx/90000\r\n\
41480f20d70SHarlanC a=fmtp:124 apt=104\r\n\
41580f20d70SHarlanC a=rtpmap:106 VP9/90000\r\n\
41680f20d70SHarlanC a=rtcp-fb:106 goog-remb\r\n\
41780f20d70SHarlanC a=rtcp-fb:106 transport-cc\r\n\
41880f20d70SHarlanC a=rtcp-fb:106 ccm fir\r\n\
41980f20d70SHarlanC a=rtcp-fb:106 nack\r\n\
42080f20d70SHarlanC a=rtcp-fb:106 nack pli\r\n\
42180f20d70SHarlanC a=fmtp:106 profile-id=0\r\n\
42280f20d70SHarlanC a=rtpmap:107 rtx/90000\r\n\
42380f20d70SHarlanC a=fmtp:107 apt=106\r\n\
42480f20d70SHarlanC a=rtpmap:108 red/90000\r\n\
42580f20d70SHarlanC a=rtpmap:109 rtx/90000\r\n\
42680f20d70SHarlanC a=fmtp:109 apt=108\r\n\
42780f20d70SHarlanC a=rtpmap:127 ulpfec/90000\r\n\
42880f20d70SHarlanC a=rtpmap:35 flexfec-03/90000\r\n\
42980f20d70SHarlanC a=rtcp-fb:35 goog-remb\r\n\
43080f20d70SHarlanC a=rtcp-fb:35 transport-cc\r\n\
43180f20d70SHarlanC a=fmtp:35 repair-window=10000000\r\n\
43280f20d70SHarlanC m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 9 0 8 105 13 110 113 126\r\n\
43380f20d70SHarlanC c=IN IP4 0.0.0.0\r\n\
43480f20d70SHarlanC a=rtcp:9 IN IP4 0.0.0.0\r\n\
43580f20d70SHarlanC a=ice-ufrag:0mum\r\n\
43680f20d70SHarlanC a=ice-pwd:DD4LnAhZLQNLSzRZWZRh9Jm4\r\n\
43780f20d70SHarlanC a=ice-options:trickle\r\n\
43880f20d70SHarlanC 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\
43980f20d70SHarlanC a=setup:actpass\r\n\
44080f20d70SHarlanC a=mid:1\r\n\
44180f20d70SHarlanC a=extmap:14 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n\
44280f20d70SHarlanC a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n\
44380f20d70SHarlanC a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n\
44480f20d70SHarlanC a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
44580f20d70SHarlanC a=recvonly\r\n\
44680f20d70SHarlanC a=rtcp-mux\r\n\
44780f20d70SHarlanC a=rtpmap:111 opus/48000/2\r\n\
44880f20d70SHarlanC a=rtcp-fb:111 transport-cc\r\n\
44980f20d70SHarlanC a=fmtp:111 minptime=10;useinbandfec=1\r\n\
45080f20d70SHarlanC a=rtpmap:63 red/48000/2\r\n\
45180f20d70SHarlanC a=fmtp:63 111/111\r\n\
45280f20d70SHarlanC a=rtpmap:103 ISAC/16000\r\n\
45380f20d70SHarlanC a=rtpmap:9 G722/8000\r\n\
45480f20d70SHarlanC a=rtpmap:0 PCMU/8000\r\n\
45580f20d70SHarlanC a=rtpmap:8 PCMA/8000\r\n\
45680f20d70SHarlanC a=rtpmap:105 CN/16000\r\n\
45780f20d70SHarlanC a=rtpmap:13 CN/8000\r\n\
45880f20d70SHarlanC a=rtpmap:110 telephone-event/48000\r\n\
45980f20d70SHarlanC a=rtpmap:113 telephone-event/16000\r\n\
46080f20d70SHarlanC a=rtpmap:126 telephone-event/8000\r\n";
46180f20d70SHarlanC
46280f20d70SHarlanC if let Some(l) = super::parse_content_length(request) {
46380f20d70SHarlanC println!("content length is : {l}");
46480f20d70SHarlanC }
46580f20d70SHarlanC
46680f20d70SHarlanC if let Some(parser) = HttpRequest::unmarshal(request) {
46780f20d70SHarlanC println!(" parser: {parser:?}");
46880f20d70SHarlanC let marshal_result = parser.marshal();
46980f20d70SHarlanC print!("marshal result: =={marshal_result}==");
47080f20d70SHarlanC assert_eq!(request, marshal_result);
47180f20d70SHarlanC }
47280f20d70SHarlanC }
47380f20d70SHarlanC
47480f20d70SHarlanC #[test]
test_parse_http_response()47580f20d70SHarlanC fn test_parse_http_response() {
47680f20d70SHarlanC let response = "HTTP/1.1 201 Created\r\n\
47780f20d70SHarlanC Content-Type: application/sdp\r\n\
47880f20d70SHarlanC Location: https://whip.example.com/resource/id\r\n\
47980f20d70SHarlanC Content-Length: 1392\r\n\
48080f20d70SHarlanC \r\n\
48180f20d70SHarlanC v=0\r\n\
48280f20d70SHarlanC o=- 1657793490019 1 IN IP4 127.0.0.1\r\n\
48380f20d70SHarlanC s=-\r\n\
48480f20d70SHarlanC t=0 0\r\n\
48580f20d70SHarlanC a=group:BUNDLE 0 1\r\n\
48680f20d70SHarlanC a=extmap-allow-mixed\r\n\
48780f20d70SHarlanC a=ice-lite\r\n\
48880f20d70SHarlanC a=msid-semantic: WMS *\r\n\
48980f20d70SHarlanC m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n\
49080f20d70SHarlanC c=IN IP4 0.0.0.0\r\n\
49180f20d70SHarlanC a=rtcp:9 IN IP4 0.0.0.0\r\n\
49280f20d70SHarlanC a=ice-ufrag:38sdf4fdsf54\r\n\
49380f20d70SHarlanC a=ice-pwd:2e13dde17c1cb009202f627fab90cbec358d766d049c9697\r\n\
49480f20d70SHarlanC 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\
49580f20d70SHarlanC a=candidate:1 1 UDP 2130706431 198.51.100.1 39132 typ host\r\n\
49680f20d70SHarlanC a=setup:passive\r\n\
49780f20d70SHarlanC a=mid:0\r\n\
49880f20d70SHarlanC a=bundle-only\r\n\
49980f20d70SHarlanC a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
50080f20d70SHarlanC a=recvonly\r\n\
50180f20d70SHarlanC a=rtcp-mux\r\n\
50280f20d70SHarlanC a=rtcp-rsize\r\n\
50380f20d70SHarlanC a=rtpmap:111 opus/48000/2\r\n\
50480f20d70SHarlanC a=fmtp:111 minptime=10;useinbandfec=1\r\n\
50580f20d70SHarlanC m=video 9 UDP/TLS/RTP/SAVPF 96 97\r\n\
50680f20d70SHarlanC c=IN IP4 0.0.0.0\r\n\
50780f20d70SHarlanC a=rtcp:9 IN IP4 0.0.0.0\r\n\
50880f20d70SHarlanC a=ice-ufrag:38sdf4fdsf54\r\n\
50980f20d70SHarlanC a=ice-pwd:2e13dde17c1cb009202f627fab90cbec358d766d049c9697\r\n\
51080f20d70SHarlanC 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\
51180f20d70SHarlanC a=candidate:1 1 UDP 2130706431 198.51.100.1 39132 typ host\r\n\
51280f20d70SHarlanC a=setup:passive\r\n\
51380f20d70SHarlanC a=mid:1\r\n\
51480f20d70SHarlanC a=bundle-only\r\n\
51580f20d70SHarlanC a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n\
51680f20d70SHarlanC a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n\
51780f20d70SHarlanC a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n\
51880f20d70SHarlanC a=recvonly\r\n\
51980f20d70SHarlanC a=rtcp-mux\r\n\
52080f20d70SHarlanC a=rtcp-rsize\r\n\
52180f20d70SHarlanC a=rtpmap:96 VP8/90000\r\n\
52280f20d70SHarlanC a=rtcp-fb:96 ccm fir\r\n\
52380f20d70SHarlanC a=rtcp-fb:96 nack\r\n\
52480f20d70SHarlanC a=rtcp-fb:96 nack pli\r\n\
52580f20d70SHarlanC a=rtpmap:97 rtx/90000\r\n\
52680f20d70SHarlanC a=fmtp:97 apt=96\r\n";
52780f20d70SHarlanC
52880f20d70SHarlanC if let Some(parser) = HttpResponse::unmarshal(response) {
52980f20d70SHarlanC println!(" parser: {parser:?}");
53080f20d70SHarlanC let marshal_result = parser.marshal();
53180f20d70SHarlanC print!("marshal result: =={marshal_result}==");
53280f20d70SHarlanC assert_eq!(response, marshal_result);
53380f20d70SHarlanC }
53480f20d70SHarlanC }
53580f20d70SHarlanC }
536