sark-h2 0.29.5

Fiber-free HTTP/2 protocol primitives and manifold server for sark
use common::{
    hpack::Fields,
    protocol::{
        Session,
        rules::{Request, Settings, Wire},
        session::{self, Frame},
    },
};
use sark_h2::{
    ServerRole,
    conn::{config, event::Event, machine::Connection},
    frame::payload::Continuation,
    stream::Id,
};
pub mod common;

use sark_core::manifold::stream::{Boundary, Section};
use sark_h2::{frame, frame::numbers::ErrorCode};
use sark_http::field::metadata::{map::record::Entry, section::rules};

#[test]
fn accepts_trailers() {
    for len in [991, 992] {
        let plan = config::Validated::new(config::Config {
            local_settings: config::Settings {
                max_header_list_size: Some(1024),
                ..config::Settings::DEFAULT
            },
            ..config::Config::default()
        })
        .expect("header limits");
        let mut conn = Connection::<ServerRole, rules::RetainRaw<26>>::from_plan(plan)
            .expect("prefixed header storage");
        Session::new(&mut conn).try_prime().expect("preface");
        let initial = Request::new(&[]).try_encode().expect("request head");
        conn.ingress()
            .ingest(
                &Frame::new(1, &initial)
                    .try_headers(Boundary::More, frame::Block::End)
                    .expect("request frame"),
            )
            .expect("request admission");
        assert!(matches!(
            conn.ingress().poll_event(),
            Some(Event::Headers { .. })
        ));
        conn.ingress()
            .ingest(
                &Frame::new(1, b"x")
                    .try_data(Boundary::More)
                    .expect("request DATA frame"),
            )
            .expect("request DATA admission");
        assert!(matches!(
            conn.ingress().poll_event(),
            Some(Event::Data { .. })
        ));
        conn.transfer().drain(usize::MAX);
        let value = vec![b'a'; len];
        let fields = Fields::new(&[Entry::new(b"x", &value)]).encode_never();
        conn.ingress()
            .ingest(
                &Frame::new(1, &fields)
                    .try_headers(Boundary::End, frame::Block::End)
                    .expect("trailer frame"),
            )
            .expect("trailer admission");
        let event = conn.ingress().poll_event();
        if len == 991 {
            assert!(matches!(
                event,
                Some(Event::Headers {
                    head,
                    section: Section::Trailing,
                    boundary: Boundary::End,
                    ..
                }) if head.fields().get(b"x") == Some(value.as_slice())
            ));
            assert!(
                Wire::new(conn.output().first())
                    .try_reset()
                    .expect("protocol wire fixture")
                    .is_none()
            );
        } else {
            assert!(matches!(
                event,
                Some(Event::StreamReset {
                    error: ErrorCode::ProtocolError,
                    validation: Some(rejected),
                    ..
                }) if rejected.admit == sark_h2::hpack::decode::limit::Admission::Exceeded
            ));
        }
    }
}

#[test]
fn rejects_pseudo_trailer() {
    let mut conn = Connection::<ServerRole>::try_new().expect("server fixture");
    Session::new(&mut conn)
        .try_prime()
        .expect("protocol fixture");
    let initial = Frame::new(
        1,
        &Request::new(&[]).try_encode().expect("protocol fixture"),
    )
    .try_headers(Boundary::More, frame::Block::End)
    .expect("protocol wire fixture");
    conn.ingress()
        .ingest(&initial)
        .expect("initial request HEADERS frame fits ingress");
    assert!(matches!(
        conn.ingress().poll_event(),
        Some(Event::Headers { .. })
    ));
    conn.ingress()
        .ingest(
            &session::Frame::new(1, b"x")
                .try_data(Boundary::More)
                .expect("protocol wire fixture"),
        )
        .expect("request DATA frame fits ingress");
    assert!(matches!(
        conn.ingress().poll_event(),
        Some(Event::Data { .. })
    ));
    conn.transfer().drain(usize::MAX);

    let block = Fields::new(&[Entry::new(b":method", b"GET")])
        .try_encode()
        .expect("protocol fixture");
    let trailing = Frame::new(1, &block)
        .try_headers(Boundary::End, frame::Block::End)
        .expect("protocol wire fixture");
    conn.ingress()
        .ingest(&trailing)
        .expect("pseudo trailer HEADERS frame fits ingress");
    assert!(matches!(
        conn.ingress().poll_event(),
        Some(Event::StreamReset {
            stream_id, error: ErrorCode::ProtocolError, ..
        }) if stream_id == Id::new(1).expect("fixture domain")
    ));
    let rst = Wire::new(conn.output().first())
        .try_reset()
        .expect("protocol wire fixture")
        .expect("RST_STREAM emitted");
    assert!(
        (rst)
            == (
                Id::new(1).expect("fixture domain"),
                ErrorCode::ProtocolError
            )
    );
}

#[test]
fn validates_continuation() {
    let mut conn = Connection::<ServerRole>::try_new().expect("server fixture");
    Session::new(&mut conn)
        .try_prime()
        .expect("protocol fixture");
    let block = Fields::new(&[
        Entry::new(b":scheme", b"http"),
        Entry::new(b":path", b"/"),
        Entry::new(b":authority", b"x"),
    ])
    .try_encode()
    .expect("protocol fixture");
    let split = block.len() / 2;
    conn.ingress()
        .ingest(
            &Frame::new(1, &block[..split])
                .try_headers(Boundary::End, frame::Block::More)
                .expect("protocol wire fixture"),
        )
        .expect("partial request HEADERS frame fits ingress");
    assert!(conn.ingress().poll_event().is_none());
    conn.ingress()
        .ingest(&{
            let mut out = Vec::new();
            Continuation::new(
                Id::new(1).expect("fixture domain"),
                frame::Block::End,
                &block[split..],
            )
            .expect("remaining request bytes form a valid CONTINUATION")
            .encode(&mut out)
            .expect("frame allocation");
            out
        })
        .expect("request CONTINUATION frame fits ingress");
    assert!(conn.ingress().poll_event().is_none());
    let rst = Wire::new(conn.output().first())
        .try_reset()
        .expect("protocol wire fixture")
        .expect("RST_STREAM emitted");
    assert!(
        (rst)
            == (
                Id::new(1).expect("fixture domain"),
                ErrorCode::ProtocolError
            )
    );
}

#[test]
fn rejects_split_limit() {
    let max = Request::new(&[]).size() - 1;
    let mut conn = Settings::limited(max)
        .try_connect::<ServerRole>()
        .expect("header limit fixture");
    Session::new(&mut conn)
        .try_prime()
        .expect("protocol fixture");
    let block = Request::new(&[]).try_encode().expect("protocol fixture");
    let split = block.len() / 2;
    conn.ingress()
        .ingest(
            &Frame::new(1, &block[..split])
                .try_headers(Boundary::End, frame::Block::More)
                .expect("protocol wire fixture"),
        )
        .expect("partial oversized HEADERS frame fits ingress");
    assert!(conn.ingress().poll_event().is_none());
    conn.ingress()
        .ingest(&{
            let mut out = Vec::new();
            Continuation::new(
                Id::new(1).expect("fixture domain"),
                frame::Block::End,
                &block[split..],
            )
            .expect("remaining oversized bytes form a valid CONTINUATION")
            .encode(&mut out)
            .expect("frame allocation");
            out
        })
        .expect("oversized CONTINUATION frame fits ingress");
    assert!(conn.ingress().poll_event().is_none());
    let rst = Wire::new(conn.output().first())
        .try_reset()
        .expect("protocol wire fixture")
        .expect("RST_STREAM emitted");
    assert!(
        (rst)
            == (
                Id::new(1).expect("fixture domain"),
                ErrorCode::ProtocolError
            )
    );
    assert!(
        !conn
            .status()
            .has_stream(Id::new(1).expect("fixture domain"))
    );
}