h2::Error now knows whether protocol errors happened because the user sent them, because it was received from the remote peer, or because the library itself emitted an error because it detected a protocol violation. It also keeps track of whether it came from a RST_STREAM or GO_AWAY frame, and in the case of the latter, it includes the additional debug data if any. Fixes #530
		
			
				
	
	
		
			468 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			468 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use futures::future::join;
 | |
| use futures::{StreamExt, TryStreamExt};
 | |
| use h2_support::prelude::*;
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn recv_push_works() {
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let mock = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_default_settings!(settings);
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::headers(1).response(404)).await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::data(1, "").eos()).await;
 | |
|         srv.send_frame(frames::headers(2).response(200)).await;
 | |
|         srv.send_frame(frames::data(2, "promised_data").eos()).await;
 | |
|     };
 | |
|     let h2 = async move {
 | |
|         let (mut client, mut h2) = client::handshake(io).await.unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
|         let (mut resp, _) = client.send_request(request, true).unwrap();
 | |
|         let pushed = resp.push_promises();
 | |
|         let check_resp_status = async move {
 | |
|             let resp = resp.await.unwrap();
 | |
|             assert_eq!(resp.status(), StatusCode::NOT_FOUND);
 | |
|         };
 | |
|         let check_pushed_response = async move {
 | |
|             let p = pushed.and_then(|headers| async move {
 | |
|                 let (request, response) = headers.into_parts();
 | |
|                 assert_eq!(request.into_parts().0.method, Method::GET);
 | |
|                 let resp = response.await.unwrap();
 | |
|                 assert_eq!(resp.status(), StatusCode::OK);
 | |
|                 let b = util::concat(resp.into_body()).await.unwrap();
 | |
|                 assert_eq!(b, "promised_data");
 | |
|                 Ok(())
 | |
|             });
 | |
|             let ps: Vec<_> = p.collect().await;
 | |
|             assert_eq!(1, ps.len())
 | |
|         };
 | |
| 
 | |
|         h2.drive(join(check_resp_status, check_pushed_response))
 | |
|             .await;
 | |
|     };
 | |
| 
 | |
|     join(mock, h2).await;
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn pushed_streams_arent_dropped_too_early() {
 | |
|     // tests that by default, received push promises work
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let mock = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_default_settings!(settings);
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::headers(1).response(404)).await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 4).request("GET", "https://http2.akamai.com/style2.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::data(1, "").eos()).await;
 | |
|         idle_ms(10).await;
 | |
|         srv.send_frame(frames::headers(2).response(200)).await;
 | |
|         srv.send_frame(frames::headers(4).response(200).eos()).await;
 | |
|         srv.send_frame(frames::data(2, "").eos()).await;
 | |
|         srv.recv_frame(frames::go_away(4)).await;
 | |
|     };
 | |
| 
 | |
|     let h2 = async move {
 | |
|         let (mut client, mut h2) = client::handshake(io).await.unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
|         let (mut resp, _) = client.send_request(request, true).unwrap();
 | |
|         let mut pushed = resp.push_promises();
 | |
|         let check_status = async move {
 | |
|             let resp = resp.await.unwrap();
 | |
|             assert_eq!(resp.status(), StatusCode::NOT_FOUND);
 | |
|         };
 | |
| 
 | |
|         let check_pushed = async move {
 | |
|             let mut count = 0;
 | |
|             while let Some(headers) = pushed.next().await {
 | |
|                 let (request, response) = headers.unwrap().into_parts();
 | |
|                 assert_eq!(request.into_parts().0.method, Method::GET);
 | |
|                 let resp = response.await.unwrap();
 | |
|                 assert_eq!(resp.status(), StatusCode::OK);
 | |
|                 count += 1;
 | |
|             }
 | |
|             assert_eq!(2, count);
 | |
|         };
 | |
| 
 | |
|         drop(client);
 | |
| 
 | |
|         h2.drive(join(check_pushed, check_status)).await;
 | |
|         h2.await.expect("client");
 | |
|     };
 | |
| 
 | |
|     join(mock, h2).await;
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn recv_push_when_push_disabled_is_conn_error() {
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let mock = async move {
 | |
|         let _ = srv.assert_client_handshake().await;
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 3).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::headers(1).response(200).eos()).await;
 | |
|         srv.recv_frame(frames::go_away(0).protocol_error()).await;
 | |
|     };
 | |
| 
 | |
|     let h2 = async move {
 | |
|         let (mut client, h2) = client::Builder::new()
 | |
|             .enable_push(false)
 | |
|             .handshake::<_, Bytes>(io)
 | |
|             .await
 | |
|             .unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
| 
 | |
|         let req = async move {
 | |
|             let res = client.send_request(request, true).unwrap().0.await;
 | |
|             let err = res.unwrap_err();
 | |
|             assert_eq!(
 | |
|                 err.to_string(),
 | |
|                 "connection error detected: unspecific protocol error detected"
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         // client should see a protocol error
 | |
|         let conn = async move {
 | |
|             let res = h2.await;
 | |
|             let err = res.unwrap_err();
 | |
|             assert_eq!(
 | |
|                 err.to_string(),
 | |
|                 "connection error detected: unspecific protocol error detected"
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         join(conn, req).await;
 | |
|     };
 | |
| 
 | |
|     join(mock, h2).await;
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn pending_push_promises_reset_when_dropped() {
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let srv = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_default_settings!(settings);
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::headers(1).response(200).eos()).await;
 | |
|         srv.recv_frame(frames::reset(2).cancel()).await;
 | |
|     };
 | |
| 
 | |
|     let client = async move {
 | |
|         let (mut client, mut conn) = client::handshake(io).await.unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
|         let req = async {
 | |
|             let resp = client
 | |
|                 .send_request(request, true)
 | |
|                 .unwrap()
 | |
|                 .0
 | |
|                 .await
 | |
|                 .expect("response");
 | |
|             assert_eq!(resp.status(), StatusCode::OK);
 | |
|         };
 | |
| 
 | |
|         let _ = conn.drive(req).await;
 | |
|         conn.await.expect("client");
 | |
|         drop(client);
 | |
|     };
 | |
| 
 | |
|     join(srv, client).await;
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn recv_push_promise_over_max_header_list_size() {
 | |
|     h2_support::trace_init!();
 | |
|     let (io, mut srv) = mock::new();
 | |
| 
 | |
|     let srv = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_frame_eq(settings, frames::settings().max_header_list_size(10));
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.recv_frame(frames::reset(2).refused()).await;
 | |
|         srv.send_frame(frames::headers(1).response(200).eos()).await;
 | |
|         idle_ms(10).await;
 | |
|     };
 | |
| 
 | |
|     let client = async move {
 | |
|         let (mut client, mut conn) = client::Builder::new()
 | |
|             .max_header_list_size(10)
 | |
|             .handshake::<_, Bytes>(io)
 | |
|             .await
 | |
|             .expect("handshake");
 | |
|         let request = Request::builder()
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
| 
 | |
|         let req = async move {
 | |
|             let err = client
 | |
|                 .send_request(request, true)
 | |
|                 .expect("send_request")
 | |
|                 .0
 | |
|                 .await
 | |
|                 .expect_err("response");
 | |
|             assert_eq!(err.reason(), Some(Reason::REFUSED_STREAM));
 | |
|         };
 | |
| 
 | |
|         conn.drive(req).await;
 | |
|         conn.await.expect("client");
 | |
|     };
 | |
|     join(srv, client).await;
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn recv_invalid_push_promise_headers_is_stream_protocol_error() {
 | |
|     // Unsafe method or content length is stream protocol error
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let mock = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_default_settings!(settings);
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::headers(1).response(404)).await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("POST", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 4)
 | |
|                 .request("GET", "https://http2.akamai.com/style.css")
 | |
|                 .field(http::header::CONTENT_LENGTH, 1),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 6)
 | |
|                 .request("GET", "https://http2.akamai.com/style.css")
 | |
|                 .field(http::header::CONTENT_LENGTH, 0),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(frames::headers(1).response(404).eos()).await;
 | |
|         srv.recv_frame(frames::reset(2).protocol_error()).await;
 | |
|         srv.recv_frame(frames::reset(4).protocol_error()).await;
 | |
|         srv.send_frame(frames::headers(6).response(200).eos()).await;
 | |
|     };
 | |
| 
 | |
|     let h2 = async move {
 | |
|         let (mut client, mut h2) = client::handshake(io).await.unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
|         let (mut resp, _) = client.send_request(request, true).unwrap();
 | |
|         let check_pushed_response = async move {
 | |
|             let pushed = resp.push_promises();
 | |
|             let p = pushed.and_then(|headers| headers.into_parts().1);
 | |
|             let ps: Vec<_> = p.collect().await;
 | |
|             // CONTENT_LENGTH = 0 is ok
 | |
|             assert_eq!(1, ps.len());
 | |
|         };
 | |
|         h2.drive(check_pushed_response).await;
 | |
|     };
 | |
| 
 | |
|     join(mock, h2).await;
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| #[ignore]
 | |
| fn recv_push_promise_with_wrong_authority_is_stream_error() {
 | |
|     // if server is foo.com, :authority = bar.com is stream error
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn recv_push_promise_skipped_stream_id() {
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let mock = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_default_settings!(settings);
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 4).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.recv_frame(frames::go_away(0).protocol_error()).await;
 | |
|     };
 | |
| 
 | |
|     let h2 = async move {
 | |
|         let (mut client, h2) = client::handshake(io).await.unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
| 
 | |
|         let req = async move {
 | |
|             let err = client
 | |
|                 .send_request(request, true)
 | |
|                 .unwrap()
 | |
|                 .0
 | |
|                 .await
 | |
|                 .unwrap_err();
 | |
|             assert_eq!(
 | |
|                 err.to_string(),
 | |
|                 "connection error detected: unspecific protocol error detected"
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         // client should see a protocol error
 | |
|         let conn = async move {
 | |
|             let res = h2.await;
 | |
|             let err = res.unwrap_err();
 | |
|             assert_eq!(
 | |
|                 err.to_string(),
 | |
|                 "connection error detected: unspecific protocol error detected"
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         join(conn, req).await;
 | |
|     };
 | |
| 
 | |
|     join(mock, h2).await;
 | |
| }
 | |
| 
 | |
| #[tokio::test]
 | |
| async fn recv_push_promise_dup_stream_id() {
 | |
|     h2_support::trace_init!();
 | |
| 
 | |
|     let (io, mut srv) = mock::new();
 | |
|     let mock = async move {
 | |
|         let settings = srv.assert_client_handshake().await;
 | |
|         assert_default_settings!(settings);
 | |
|         srv.recv_frame(
 | |
|             frames::headers(1)
 | |
|                 .request("GET", "https://http2.akamai.com/")
 | |
|                 .eos(),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.send_frame(
 | |
|             frames::push_promise(1, 2).request("GET", "https://http2.akamai.com/style.css"),
 | |
|         )
 | |
|         .await;
 | |
|         srv.recv_frame(frames::go_away(0).protocol_error()).await;
 | |
|     };
 | |
| 
 | |
|     let h2 = async move {
 | |
|         let (mut client, h2) = client::handshake(io).await.unwrap();
 | |
|         let request = Request::builder()
 | |
|             .method(Method::GET)
 | |
|             .uri("https://http2.akamai.com/")
 | |
|             .body(())
 | |
|             .unwrap();
 | |
| 
 | |
|         let req = async move {
 | |
|             let res = client.send_request(request, true).unwrap().0.await;
 | |
|             let err = res.unwrap_err();
 | |
|             assert_eq!(
 | |
|                 err.to_string(),
 | |
|                 "connection error detected: unspecific protocol error detected"
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         // client should see a protocol error
 | |
|         let conn = async move {
 | |
|             let res = h2.await;
 | |
|             let err = res.unwrap_err();
 | |
|             assert_eq!(
 | |
|                 err.to_string(),
 | |
|                 "connection error detected: unspecific protocol error detected"
 | |
|             );
 | |
|         };
 | |
| 
 | |
|         join(conn, req).await;
 | |
|     };
 | |
| 
 | |
|     join(mock, h2).await;
 | |
| }
 |