net-wire 0.1.0

Allocation-free no_std wire views and codecs for network protocols.
Documentation
use net_wire::http2::hpack::{
    HpackIntegerParseError, HpackLiteralMode, HpackRepresentation, HpackRepresentationParseError,
    HpackStringLiteralParseError,
};

#[test]
fn hpack_representations_parse_all_wire_forms_and_exact_boundaries() {
    let indexed = HpackRepresentation::parse(&[0x82, 0xaa]).unwrap();
    let HpackRepresentation::Indexed(indexed) = indexed else {
        panic!("expected indexed field");
    };
    assert_eq!(indexed.as_bytes(), [0x82]);
    assert_eq!(indexed.index().as_bytes(), [0x82]);
    assert_eq!(indexed.index_value(), 2);

    let wire = [0x42, 0x81, 0xfe, 0xaa];
    let literal = HpackRepresentation::parse(&wire).unwrap();
    let HpackRepresentation::Literal(literal) = literal else {
        panic!("expected literal field");
    };
    assert_eq!(literal.as_bytes(), &wire[..3]);
    assert_eq!(literal.mode(), HpackLiteralMode::IncrementalIndexing);
    assert_eq!(literal.name_index_value(), 2);
    assert_eq!(literal.name(), None);
    assert!(literal.value().is_huffman());
    assert_eq!(literal.value().encoded_bytes(), [0xfe]);

    let wire = [0x3f, 0x80, 0x00, 0xaa];
    let update = HpackRepresentation::parse(&wire).unwrap();
    let HpackRepresentation::DynamicTableSizeUpdate(update) = update else {
        panic!("expected table size update");
    };
    assert_eq!(update.as_bytes(), &wire[..3]);
    assert_eq!(update.size().as_bytes(), [0x3f, 0x80, 0x00]);
    assert_eq!(update.size_value(), 31);

    for (wire, mode) in [
        (
            &[0x10, 0x01, b'n', 0x81, 0xff, 0xaa][..],
            HpackLiteralMode::NeverIndexed,
        ),
        (
            &[0x00, 0x81, 0x00, 0x81, 0xfe, 0xaa][..],
            HpackLiteralMode::WithoutIndexing,
        ),
    ] {
        let literal = HpackRepresentation::parse(wire).unwrap();
        let HpackRepresentation::Literal(literal) = literal else {
            panic!("expected literal field");
        };
        assert_eq!(literal.as_bytes(), &wire[..wire.len() - 1]);
        assert_eq!(literal.mode(), mode);
        assert_eq!(literal.name_index_value(), 0);
        let name = literal.name().unwrap();
        assert!(literal.value().is_huffman());
        if mode == HpackLiteralMode::NeverIndexed {
            assert_eq!(name.as_bytes(), [0x01, b'n']);
            assert!(!name.is_huffman());
        } else {
            assert_eq!(name.as_bytes(), [0x81, 0x00]);
            assert!(name.is_huffman());
        }
    }

    for (wire, mode, has_name) in [
        (
            &[0x41, 0x00][..],
            HpackLiteralMode::IncrementalIndexing,
            false,
        ),
        (
            &[0x40, 0x00, 0x00][..],
            HpackLiteralMode::IncrementalIndexing,
            true,
        ),
        (&[0x11, 0x00][..], HpackLiteralMode::NeverIndexed, false),
        (
            &[0x10, 0x00, 0x00][..],
            HpackLiteralMode::NeverIndexed,
            true,
        ),
        (&[0x01, 0x00][..], HpackLiteralMode::WithoutIndexing, false),
        (
            &[0x00, 0x00, 0x00][..],
            HpackLiteralMode::WithoutIndexing,
            true,
        ),
    ] {
        let HpackRepresentation::Literal(literal) = HpackRepresentation::parse(wire).unwrap()
        else {
            panic!("expected literal field");
        };
        assert_eq!(literal.mode(), mode);
        assert_eq!(literal.name().is_some(), has_name);
        assert_eq!(literal.name_index_value() == 0, has_name);
    }
}

#[test]
fn hpack_representations_preserve_overlong_encodings_and_dispatch_boundaries() {
    for (wire, kind) in [
        (&[0x81][..], 0),
        (&[0x40, 0x00, 0x00][..], 1),
        (&[0x20][..], 2),
        (&[0x10, 0x00, 0x00][..], 3),
        (&[0x00, 0x00, 0x00][..], 4),
    ] {
        match (kind, HpackRepresentation::parse(wire).unwrap()) {
            (0, HpackRepresentation::Indexed(_))
            | (1 | 3 | 4, HpackRepresentation::Literal(_))
            | (2, HpackRepresentation::DynamicTableSizeUpdate(_)) => {}
            _ => panic!("wrong representation dispatch"),
        }
    }

    let wire = [0x7f, 0x80, 0x00, 0x00, 0xaa];
    let literal = HpackRepresentation::parse(&wire).unwrap();
    let HpackRepresentation::Literal(literal) = literal else {
        panic!("expected literal field");
    };
    assert_eq!(literal.as_bytes(), &wire[..4]);
    assert_eq!(literal.name_index().as_bytes(), [0x7f, 0x80, 0x00]);
    assert_eq!(literal.name_index_value(), 63);
    assert_eq!(literal.value().length().as_bytes(), [0x00]);
    assert_eq!(literal.value().encoded_bytes(), []);
}

#[test]
fn hpack_representations_distinguish_malformed_components() {
    assert_eq!(
        HpackRepresentation::parse(&[0xff]),
        Err(HpackRepresentationParseError::IndexedIndex(
            HpackIntegerParseError::Incomplete {
                required: 2,
                available: 1,
            }
        ))
    );
    assert_eq!(
        HpackRepresentation::parse(&[0x80, 0x00]),
        Err(HpackRepresentationParseError::IndexedFieldZero)
    );
    assert_eq!(
        HpackRepresentation::parse(&[0x7f]),
        Err(HpackRepresentationParseError::LiteralNameIndex(
            HpackIntegerParseError::Incomplete {
                required: 2,
                available: 1,
            }
        ))
    );
    assert!(matches!(
        HpackRepresentation::parse(&[0x00, 0x02, b'n']),
        Err(HpackRepresentationParseError::LiteralName(
            HpackStringLiteralParseError::IncompleteEncodedPayload { .. }
        ))
    ));
    assert!(matches!(
        HpackRepresentation::parse(&[0x42, 0x02, b'v']),
        Err(HpackRepresentationParseError::LiteralValue(
            HpackStringLiteralParseError::IncompleteEncodedPayload { .. }
        ))
    ));
}