ntex-h2 3.9.1

An HTTP/2 client and server
Documentation
mod support;

use ntex_bytes::BytesMut;
use ntex_codec::Decoder;
use ntex_h2::{Codec, frame, frame::FrameError};
use ntex_http::{HeaderMap, HeaderName, Method, StatusCode};
use ntex_io::testing::IoTest;
use ntex_util::future::join;

use support::{build_large_headers, frames};

// ===== DATA =====

#[macro_export]
macro_rules! decode_frame {
    ($type: ident, $bytes: ident) => {{
        use ntex_h2::frame::Frame;

        match Codec::default().decode(&mut $bytes) {
            Ok(Some(Frame::$type(frame))) => frame,
            frame => panic!("unexpected frame; actual={:?}", frame),
        }
    }};
}

#[macro_export]
macro_rules! decode_err {
    ($bytes: ident, $type: expr) => {{
        match Codec::default().decode(&mut $bytes) {
            Err(e) => assert_eq!(e, $type),
            frame => panic!("expected error; actual={:?}", frame),
        }
    }};
}

#[test]
fn read_data_no_padding() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[0, 0, 5, 0, 0, 0, 0, 0, 1]);
    buf.extend_from_slice(b"hello");

    let data = decode_frame!(Data, buf);
    assert_eq!(data.stream_id(), 1);
    assert_eq!(data.payload(), &b"hello"[..]);
    assert!(!data.is_end_stream());
}

#[test]
fn read_data_empty_payload() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 1]);

    let data = decode_frame!(Data, buf);
    assert_eq!(data.stream_id(), 1);
    assert_eq!(data.payload(), &b""[..]);
    assert!(!data.is_end_stream());
}

#[test]
fn read_data_end_stream() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[0, 0, 5, 0, 1, 0, 0, 0, 1]);
    buf.extend_from_slice(b"hello");

    let data = decode_frame!(Data, buf);
    assert_eq!(data.stream_id(), 1);
    assert_eq!(data.payload(), &b"hello"[..]);
    assert!(data.is_end_stream());
}

#[test]
fn read_data_padding() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[0, 0, 16, 0, 0x8, 0, 0, 0, 1]);
    buf.extend_from_slice(&[5]); // Pad length
    buf.extend_from_slice(b"helloworld"); // Data
    buf.extend_from_slice(b"\0\0\0\0\0"); // Padding

    let data = decode_frame!(Data, buf);
    assert_eq!(data.stream_id(), 1);
    assert_eq!(data.payload(), &b"helloworld"[..]);
    assert!(!data.is_end_stream());
}

#[test]
fn read_push_promise() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[
        0, 0, 0x5, 0x5, 0x4, 0, 0, 0, 0x1, // stream id
        0, 0, 0, 0x2,  // promised id
        0x82, // HPACK :method="GET"
    ]);

    decode_err!(buf, FrameError::UnexpectedPushPromise);
}

#[test]
fn read_data_stream_id_zero() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[0, 0, 5, 0, 0, 0, 0, 0, 0]);
    buf.extend_from_slice(b"hello"); // Data

    decode_err!(buf, FrameError::InvalidStreamId);
}

// ===== HEADERS =====

#[ntex::test]
async fn read_continuation_frames() {
    let (cli, srv) = IoTest::create();

    let large = build_large_headers();
    let frame = large
        .iter()
        .fold(
            frames::headers(1).response(200),
            |frame, &(name, ref value)| frame.field(name, &value[..]),
        )
        .eos();

    let srv_rx = support::start_server(srv);
    let client = support::start_client(cli);

    let srv_fut = async move {
        let msg = srv_rx.recv().await.unwrap();

        let hdrs = frame.into_fields();
        msg.stream()
            .send_response(StatusCode::OK, hdrs, true)
            .unwrap();

        let (pseudo, _hdrs, eof) = get_headers!(msg);
        assert_eq!(pseudo.path, Some("/index.html".into()));
        assert!(eof);
    };

    let client_fut = async move {
        let (_snd, rcv) = client
            .send(Method::GET, "/index.html".into(), HeaderMap::new(), true)
            .await
            .expect("response");

        let msg = rcv.recv().await.unwrap();
        let (pseudo, hdrs, _eof) = get_headers!(msg);

        assert_eq!(pseudo.status, Some(StatusCode::OK));
        let expected = large
            .iter()
            .fold(HeaderMap::new(), |mut map, &(name, ref value)| {
                map.append(HeaderName::try_from(name).unwrap(), value.parse().unwrap());
                map
            });
        assert_eq!(hdrs, expected);
    };

    join(srv_fut, client_fut).await;
}

#[test]
fn update_max_frame_len_at_rest() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[0, 0, 5, 0, 0, 0, 0, 0, 1]);
    buf.extend_from_slice(b"hello");
    buf.extend_from_slice(&[0, 64, 1, 0, 0, 0, 0, 0, 1]);
    buf.extend_from_slice(&vec![0; 16_385]);

    assert_eq!(decode_frame!(Data, buf).payload(), &b"hello"[..]);

    let codec = Codec::default();
    codec.set_recv_frame_size(16_384);
    assert_eq!(codec.recv_frame_size(), 16_384);
    assert_eq!(
        codec.decode(&mut buf).unwrap_err().to_string(),
        "Frame size exceeded"
    );
}

#[test]
fn read_goaway_with_debug_data() {
    let mut buf = BytesMut::new();
    buf.extend_from_slice(&[
        // head
        0, 0, 22, 7, 0, 0, 0, 0, 0, // last_stream_id
        0, 0, 0, 1, // error_code
        0, 0, 0, 11,
    ]);
    // debug_data
    buf.extend_from_slice(b"too_many_pings");

    let data = decode_frame!(GoAway, buf);
    assert_eq!(data.reason(), frame::Reason::ENHANCE_YOUR_CALM);
    assert_eq!(data.last_stream_id(), 1);
    assert_eq!(&**data.data(), b"too_many_pings");
}

// #[tokio::test]
// async fn write_continuation_frames() {
//     // An invalid dependency ID results in a stream level error. The hpack
//     // payload should still be decoded.
//     h2_support::trace_init!();
//     let (io, mut srv) = mock::new();

//     let large = build_large_headers();

//     // Build the large request frame
//     let frame = large.iter().fold(
//         frames::headers(1).request("GET", "https://http2.akamai.com/"),
//         |frame, &(name, ref value)| frame.field(name, &value[..]),
//     );

//     let srv = async move {
//         let settings = srv.assert_client_handshake().await;
//         assert_default_settings!(settings);
//         srv.recv_frame(frame.eos()).await;
//         srv.send_frame(frames::headers(1).response(204).eos()).await;
//     };

//     let client = async move {
//         let (mut client, mut conn) = client::handshake(io).await.expect("handshake");

//         let mut request = Request::builder();
//         request = request.uri("https://http2.akamai.com/");

//         for &(name, ref value) in &large {
//             request = request.header(name, &value[..]);
//         }

//         let request = request.body(()).unwrap();

//         let req = async {
//             let res = client
//                 .send_request(request, true)
//                 .expect("send_request1")
//                 .0
//                 .await;
//             let response = res.unwrap();
//             assert_eq!(response.status(), StatusCode::NO_CONTENT);
//         };

//         conn.drive(req).await;
//         conn.await.unwrap();
//     };

//     join(srv, client).await;
// }

// #[tokio::test]
// async fn client_settings_header_table_size() {
//     // A server sets the SETTINGS_HEADER_TABLE_SIZE to 0, test that the
//     // client doesn't send indexed headers.
//     h2_support::trace_init!();

//     let io = mock_io::Builder::new()
//         // Read SETTINGS_HEADER_TABLE_SIZE = 0
//         .handshake_read_settings(&[
//             0, 0, 6, // len
//             4, // type
//             0, // flags
//             0, 0, 0, 0, // stream id
//             0, 0x1, // id = SETTINGS_HEADER_TABLE_SIZE
//             0, 0, 0, 0, // value = 0
//         ])
//         // Write GET / (1st)
//         .write(&[
//             0, 0, 0x10, 1, 5, 0, 0, 0, 1, 0x82, 0x87, 0x41, 0x8B, 0x9D, 0x29, 0xAC, 0x4B, 0x8F,
//             0xA8, 0xE9, 0x19, 0x97, 0x21, 0xE9, 0x84,
//         ])
//         .write(frames::SETTINGS_ACK)
//         // Read response
//         .read(&[0, 0, 1, 1, 5, 0, 0, 0, 1, 137])
//         // Write GET / (2nd, doesn't use indexed headers)
//         // - Sends 0x20 about size change
//         // - Sends :authority as literal instead of indexed
//         .write(&[
//             0, 0, 0x11, 1, 5, 0, 0, 0, 3, 0x20, 0x82, 0x87, 0x1, 0x8B, 0x9D, 0x29, 0xAC, 0x4B,
//             0x8F, 0xA8, 0xE9, 0x19, 0x97, 0x21, 0xE9, 0x84,
//         ])
//         .read(&[0, 0, 1, 1, 5, 0, 0, 0, 3, 137])
//         .build();

//     let (mut client, mut conn) = client::handshake(io).await.expect("handshake");

//     let req1 = client.get("https://http2.akamai.com");
//     conn.drive(req1).await.expect("req1");

//     let req2 = client.get("https://http2.akamai.com");
//     conn.drive(req2).await.expect("req1");
// }

// #[tokio::test]
// async fn server_settings_header_table_size() {
//     // A client sets the SETTINGS_HEADER_TABLE_SIZE to 0, test that the
//     // server doesn't send indexed headers.
//     h2_support::trace_init!();

//     let io = mock_io::Builder::new()
//         .read(MAGIC_PREFACE)
//         // Read SETTINGS_HEADER_TABLE_SIZE = 0
//         .read(&[
//             0, 0, 6, // len
//             4, // type
//             0, // flags
//             0, 0, 0, 0, // stream id
//             0, 0x1, // id = SETTINGS_HEADER_TABLE_SIZE
//             0, 0, 0, 0, // value = 0
//         ])
//         .write(frames::SETTINGS)
//         .write(frames::SETTINGS_ACK)
//         .read(frames::SETTINGS_ACK)
//         // Write GET /
//         .read(&[
//             0, 0, 0x10, 1, 5, 0, 0, 0, 1, 0x82, 0x87, 0x41, 0x8B, 0x9D, 0x29, 0xAC, 0x4B, 0x8F,
//             0xA8, 0xE9, 0x19, 0x97, 0x21, 0xE9, 0x84,
//         ])
//         // Read response
//         //.write(&[0, 0, 6, 1, 5, 0, 0, 0, 1, 136, 64, 129, 31, 129, 143])
//         .write(&[0, 0, 7, 1, 5, 0, 0, 0, 1, 32, 136, 0, 129, 31, 129, 143])
//         .build();

//     let mut srv = server::handshake(io).await.expect("handshake");

//     let (_req, mut stream) = srv.accept().await.unwrap().unwrap();

//     let rsp = http::Response::builder()
//         .status(200)
//         .header("a", "b")
//         .body(())
//         .unwrap();
//     stream.send_response(rsp, true).unwrap();

//     assert!(srv.accept().await.is_none());
// }