net-wire 0.1.0

Allocation-free no_std wire views and codecs for network protocols.
Documentation
use net_wire::http1::{Http1ChunkedBody, Http1Field, Http1ParseError};

#[test]
fn chunked_body_preserves_chunks_extensions_trailers_and_suffix() {
    let wire = b"A;foo=bar\r\n0123456789\r\n3\r\nabc\r\n000; end=\"yes\"\r\nX-T: one\r\nX-T:\ttwo \t\r\n\r\nNEXT";
    let body = Http1ChunkedBody::parse(wire).unwrap();
    assert_eq!(body.as_bytes(), &wire[..71]);
    assert_eq!(body.decoded_len(), 13);
    assert_eq!(body.last_chunk_extensions(), b"; end=\"yes\"");

    let mut chunks = body.chunks();
    let first = chunks.next().unwrap();
    assert_eq!(first.size(), 10);
    assert_eq!(first.data(), b"0123456789");
    assert_eq!(first.extensions(), b";foo=bar");
    let second = chunks.next().unwrap();
    assert_eq!(second.size(), 3);
    assert_eq!(second.data(), b"abc");
    assert_eq!(second.extensions(), b"");
    assert!(chunks.next().is_none());

    let trailers: [Http1Field<'_>; 2] = body
        .trailers()
        .iter()
        .collect::<Vec<_>>()
        .try_into()
        .unwrap();
    assert_eq!(trailers[0].value(), b"one");
    assert_eq!(trailers[1].raw_value(), b"\ttwo \t");
    assert_eq!(trailers[1].value(), b"two");

    let empty = Http1ChunkedBody::parse(b"0000\r\n\r\nrest").unwrap();
    assert_eq!(empty.as_bytes(), b"0000\r\n\r\n");
    assert_eq!(empty.decoded_len(), 0);
    assert!(empty.chunks().next().is_none());

    let escaped = Http1ChunkedBody::parse(b"1; q=\"a\\\"b\"\r\nx\r\n0\r\n\r\n").unwrap();
    assert_eq!(
        escaped.chunks().next().unwrap().extensions(),
        b"; q=\"a\\\"b\""
    );
}

#[test]
fn chunked_body_distinguishes_malformed_overflow_and_incomplete() {
    assert_eq!(
        Http1ChunkedBody::parse(b"g\r\n"),
        Err(Http1ParseError::InvalidChunkSize)
    );
    assert_eq!(
        Http1ChunkedBody::parse(b"1   \r\na\r\n0\r\n\r\n"),
        Err(Http1ParseError::InvalidChunkSize)
    );
    assert_eq!(
        Http1ChunkedBody::parse(b"1; q=\"bad\\\r\nx\r\n0\r\n\r\n"),
        Err(Http1ParseError::InvalidChunkSize)
    );
    if usize::BITS == 64 {
        assert_eq!(
            Http1ChunkedBody::parse(b"10000000000000000\r\n"),
            Err(Http1ParseError::ChunkSizeOverflow)
        );
    }
    assert!(matches!(
        Http1ChunkedBody::parse(b"3\r\nab"),
        Err(Http1ParseError::Incomplete {
            required: 8,
            available: 5
        })
    ));
    assert_eq!(
        Http1ChunkedBody::parse(b"1\r\naX\r\n0\r\n\r\n"),
        Err(Http1ParseError::InvalidChunkTerminator)
    );
    assert_eq!(
        Http1ChunkedBody::parse(b"0\r\nBad Field: x\r\n\r\n"),
        Err(Http1ParseError::InvalidTrailer)
    );
    assert_eq!(
        Http1ChunkedBody::parse(b"0\r\nX: y\n\n"),
        Err(Http1ParseError::InvalidTrailer)
    );
    assert!(matches!(
        Http1ChunkedBody::parse(b"0\r\nX: y\r\n"),
        Err(Http1ParseError::Incomplete { .. })
    ));

    for name in [
        b"Content-Length" as &[u8],
        b"Transfer-Encoding",
        b"Host",
        b"Trailer",
        b"TE",
    ] {
        let mut wire = [0u8; 64];
        wire[..3].copy_from_slice(b"0\r\n");
        wire[3..3 + name.len()].copy_from_slice(name);
        let end = 3 + name.len();
        wire[end..end + 9].copy_from_slice(b": value\r\n");
        wire[end + 9..end + 11].copy_from_slice(b"\r\n");
        assert_eq!(
            Http1ChunkedBody::parse(&wire[..end + 11]),
            Err(Http1ParseError::InvalidTrailer)
        );
    }
}