mod fixtures;
use std::collections::HashMap;
use fixtures::*;
use h2session::{H2SessionCache, ParseError, ParseErrorKind, ParsedH2Message, StreamId};
use rstest::rstest;
fn parse_buffer(buffer: &[u8]) -> Result<HashMap<StreamId, ParsedH2Message>, ParseError> {
let cache: H2SessionCache<&str> = H2SessionCache::new();
cache.parse("test", buffer)
}
fn unique_body_pattern(stream_id: u32, chunk_num: u32) -> Vec<u8> {
format!("STREAM{}:CHUNK{}", stream_id, chunk_num)
.as_bytes()
.to_vec()
}
#[test]
fn test_interleaved_data_body_integrity() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/resource", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_headers_frame(3, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_headers_frame(5, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_data_frame(1, &unique_body_pattern(1, 1), false));
buffer.extend(build_data_frame(3, &unique_body_pattern(3, 1), false));
buffer.extend(build_data_frame(1, &unique_body_pattern(1, 2), false));
buffer.extend(build_data_frame(5, &unique_body_pattern(5, 1), false));
buffer.extend(build_data_frame(3, &unique_body_pattern(3, 2), false));
buffer.extend(build_data_frame(1, &unique_body_pattern(1, 3), true)); buffer.extend(build_data_frame(5, &unique_body_pattern(5, 2), false));
buffer.extend(build_data_frame(3, &unique_body_pattern(3, 3), true)); buffer.extend(build_data_frame(5, &unique_body_pattern(5, 3), true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 3, "should have 3 completed streams");
let expected_body_1 = [
unique_body_pattern(1, 1),
unique_body_pattern(1, 2),
unique_body_pattern(1, 3),
]
.concat();
let expected_body_3 = [
unique_body_pattern(3, 1),
unique_body_pattern(3, 2),
unique_body_pattern(3, 3),
]
.concat();
let expected_body_5 = [
unique_body_pattern(5, 1),
unique_body_pattern(5, 2),
unique_body_pattern(5, 3),
]
.concat();
assert_eq!(
messages.get(&StreamId(1)).unwrap().body,
expected_body_1,
"Stream 1 body integrity"
);
assert_eq!(
messages.get(&StreamId(3)).unwrap().body,
expected_body_3,
"Stream 3 body integrity"
);
assert_eq!(
messages.get(&StreamId(5)).unwrap().body,
expected_body_5,
"Stream 5 body integrity"
);
}
#[test]
fn test_interleaved_data_single_byte_chunks() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "test.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_headers_frame(3, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_data_frame(1, b"A", false));
buffer.extend(build_data_frame(3, b"1", false));
buffer.extend(build_data_frame(1, b"B", false));
buffer.extend(build_data_frame(3, b"2", false));
buffer.extend(build_data_frame(1, b"C", false));
buffer.extend(build_data_frame(3, b"3", false));
buffer.extend(build_data_frame(1, b"D", true));
buffer.extend(build_data_frame(3, b"4", true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 2);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"ABCD");
assert_eq!(messages.get(&StreamId(3)).unwrap().body, b"1234");
}
#[test]
fn test_hpack_dynamic_table_cross_stream() {
let mut buffer = connection_start();
let mut hpack_block_1 = hpack_get_request("/", "example.com");
hpack_block_1.extend(hpack_literal_with_indexing("x-custom", "value1"));
buffer.extend(build_complete_headers_frame(1, &hpack_block_1));
let mut hpack_block_3 = hpack_get_request("/other", "example.com");
hpack_block_3.extend(hpack_indexed(62));
buffer.extend(build_complete_headers_frame(3, &hpack_block_3));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 2);
let stream_1_headers = &messages.get(&StreamId(1)).unwrap().headers;
let stream_3_headers = &messages.get(&StreamId(3)).unwrap().headers;
assert!(
stream_1_headers
.iter()
.any(|(k, v)| k == "x-custom" && v == "value1"),
"Stream 1 should have x-custom: value1"
);
assert!(
stream_3_headers
.iter()
.any(|(k, v)| k == "x-custom" && v == "value1"),
"Stream 3 should reference x-custom: value1 from dynamic table"
);
}
#[test]
fn test_hpack_dynamic_table_multiple_entries() {
let mut buffer = connection_start();
let mut hpack_1 = hpack_get_request("/", "example.com");
hpack_1.extend(hpack_literal_with_indexing("header-a", "value-a"));
buffer.extend(build_complete_headers_frame(1, &hpack_1));
let mut hpack_3 = hpack_get_request("/", "example.com");
hpack_3.extend(hpack_literal_with_indexing("header-b", "value-b"));
hpack_3.extend(hpack_indexed(62)); buffer.extend(build_complete_headers_frame(3, &hpack_3));
let mut hpack_5 = hpack_get_request("/", "example.com");
hpack_5.extend(hpack_indexed(62)); hpack_5.extend(hpack_indexed(63)); buffer.extend(build_complete_headers_frame(5, &hpack_5));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 3);
let s5 = messages.get(&StreamId(5)).unwrap();
assert!(
s5.headers
.iter()
.any(|(k, v)| k == "header-a" && v == "value-a")
);
assert!(
s5.headers
.iter()
.any(|(k, v)| k == "header-b" && v == "value-b")
);
}
#[test]
fn test_flow_control_initial_window_size_parsing() {
let cache: H2SessionCache<&str> = H2SessionCache::new();
let mut buffer = connection_start();
buffer.extend(build_settings_frame(&[
(0x04, 32768), ]));
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = cache.parse("test", &buffer).expect("should parse");
assert_eq!(messages.len(), 1);
}
#[test]
fn test_window_update_frame_parsing() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_window_update_frame(0, 65535));
buffer.extend(build_window_update_frame(1, 32768));
buffer.extend(build_data_frame(1, b"body", true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"body");
}
#[test]
fn test_settings_all_parameters() {
let mut buffer = connection_start();
buffer.extend(build_settings_frame(&[
(0x01, 8192), (0x02, 0), (0x03, 100), (0x04, 32768), (0x05, 32768), (0x06, 16384), ]));
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
}
#[test]
fn test_settings_ack() {
let mut buffer = connection_start();
buffer.extend(build_settings_ack_frame());
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
}
#[test]
fn test_settings_mid_connection() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
buffer.extend(build_settings_frame(&[(0x04, 16384)]));
buffer.extend(build_complete_headers_frame(3, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 2);
}
#[test]
fn test_incremental_parsing_consistency() {
let mut full_buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
full_buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
full_buffer.extend(build_data_frame(1, b"body1", true));
full_buffer.extend(build_headers_frame(3, &hpack_block, FLAG_END_HEADERS));
full_buffer.extend(build_data_frame(3, b"body3", true));
let messages_full = parse_buffer(&full_buffer).expect("full parse should work");
assert_eq!(messages_full.len(), 2);
let cache: H2SessionCache<&str> = H2SessionCache::new();
let mut all_messages = HashMap::new();
let chunk1_end = CONNECTION_PREFACE.len() + 9 + 9 + 5;
let _ = cache.parse("test", &full_buffer[..chunk1_end]);
let chunk2_end = chunk1_end + full_buffer.len() - chunk1_end;
if let Ok(msgs) = cache.parse("test", &full_buffer[chunk1_end..chunk2_end]) {
all_messages.extend(msgs);
}
}
#[test]
fn test_incremental_single_frame_at_a_time() {
let cache: H2SessionCache<&str> = H2SessionCache::new();
let hpack_block = hpack_get_request("/", "example.com");
let _ = cache.parse("test", CONNECTION_PREFACE);
let _ = cache.parse("test", &build_empty_settings_frame());
let _ = cache.parse(
"test",
&build_headers_frame(1, &hpack_block, FLAG_END_HEADERS),
);
let result = cache.parse("test", &build_data_frame(1, b"data", true));
match result {
Ok(messages) => {
if !messages.is_empty() {
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"data");
}
},
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn test_large_body_many_chunks() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/large", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
let mut expected_body = Vec::new();
for i in 0..15 {
let chunk = format!("CHUNK-{:02}-", i);
expected_body.extend_from_slice(chunk.as_bytes());
let is_last = i == 14;
buffer.extend(build_data_frame(1, chunk.as_bytes(), is_last));
}
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, expected_body);
}
#[test]
fn test_large_body_interleaved_with_other_streams() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_complete_headers_frame(3, &hpack_block));
buffer.extend(build_data_frame(1, b"part1", false));
buffer.extend(build_headers_frame(5, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_data_frame(1, b"part2", false));
buffer.extend(build_data_frame(5, b"five", true)); buffer.extend(build_data_frame(1, b"part3", true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 3);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"part1part2part3");
assert!(messages.get(&StreamId(3)).unwrap().body.is_empty());
assert_eq!(messages.get(&StreamId(5)).unwrap().body, b"five");
}
#[test]
fn test_continuation_single() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/continuation", "example.com");
let mid = hpack_block.len() / 2;
buffer.extend(build_headers_frame(1, &hpack_block[..mid], FLAG_END_STREAM));
buffer.extend(build_continuation_frame(1, &hpack_block[mid..], true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(1)).unwrap().path,
Some("/continuation".to_string())
);
}
#[test]
fn test_continuation_multiple() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/multi-cont", "example.com");
let part_size = hpack_block.len() / 4 + 1;
let parts: Vec<&[u8]> = hpack_block.chunks(part_size).collect();
buffer.extend(build_headers_frame(1, parts[0], FLAG_END_STREAM));
for (i, part) in parts.iter().enumerate().skip(1) {
let is_last = i == parts.len() - 1;
buffer.extend(build_continuation_frame(1, part, is_last));
}
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(1)).unwrap().path,
Some("/multi-cont".to_string())
);
}
#[test]
fn test_continuation_without_headers() {
let mut buffer = connection_start();
buffer.extend(build_continuation_frame(1, b"garbage", true));
let result = parse_buffer(&buffer);
assert!(
matches!(
result,
Err(ParseError {
kind: ParseErrorKind::Http2HeadersIncomplete,
..
})
),
"Expected Http2HeadersIncomplete, got {:?}",
result
);
}
#[test]
fn test_data_frame_unknown_stream() {
let mut buffer = connection_start();
buffer.extend(build_data_frame(1, b"data", true));
let result = parse_buffer(&buffer);
assert!(
matches!(
result,
Err(ParseError {
kind: ParseErrorKind::Http2StreamNotFound,
..
})
),
"Expected Http2StreamNotFound for unknown stream, got {result:?}"
);
}
#[test]
fn test_malformed_hpack() {
let mut buffer = connection_start();
let invalid_hpack = vec![0xFF, 0xFF, 0xFF, 0xFF];
buffer.extend(build_complete_headers_frame(1, &invalid_hpack));
let result = parse_buffer(&buffer);
assert!(
matches!(
result,
Err(ParseError {
kind: ParseErrorKind::Http2HpackError(_),
..
})
),
"Expected Http2HpackError, got {:?}",
result
);
}
#[test]
fn test_empty_buffer() {
let result = parse_buffer(&[]);
assert!(
matches!(result, Ok(ref m) if m.is_empty()),
"Expected Ok(empty map) for empty buffer, got {result:?}"
);
}
#[test]
fn test_incomplete_frame() {
let mut buffer = connection_start();
buffer.extend(&[0x00, 0x00, 0x10, 0x01, 0x05]);
let result = parse_buffer(&buffer);
assert!(
matches!(result, Ok(ref m) if m.is_empty()),
"Expected Ok(empty map) for incomplete frame, got {result:?}"
);
}
#[test]
fn test_data_frame_with_padding() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_data_frame_padded(1, b"actual-data", 10, true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"actual-data");
}
#[test]
fn test_response_parsing() {
let mut buffer = connection_start();
let mut hpack_response = hpack_static::status_200();
hpack_response.extend(hpack_literal_without_indexing("content-type", "text/plain"));
buffer.extend(build_headers_frame(1, &hpack_response, FLAG_END_HEADERS));
buffer.extend(build_data_frame(1, b"OK", true));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().status, Some(200));
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"OK");
}
#[test]
fn test_ping_frame_ignored() {
let mut buffer = connection_start();
buffer.extend(build_ping_frame(&[1, 2, 3, 4, 5, 6, 7, 8], false));
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
}
#[test]
fn test_goaway_frame_ignored() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
buffer.extend(build_goaway_frame(1, 0));
let messages = parse_buffer(&buffer).expect("should parse successfully");
assert_eq!(messages.len(), 1);
}
#[test]
fn test_multiple_connections_isolated() {
let cache: H2SessionCache<&str> = H2SessionCache::new();
let mut buffer1 = connection_start();
let hpack_1 = hpack_get_request("/conn1", "example.com");
buffer1.extend(build_complete_headers_frame(1, &hpack_1));
let mut buffer2 = connection_start();
let hpack_2 = hpack_get_request("/conn2", "other.com");
buffer2.extend(build_complete_headers_frame(1, &hpack_2));
let msgs1 = cache.parse("conn1", &buffer1).expect("conn1 should parse");
let msgs2 = cache.parse("conn2", &buffer2).expect("conn2 should parse");
assert_eq!(msgs1.len(), 1);
assert_eq!(msgs2.len(), 1);
assert_eq!(
msgs1.get(&StreamId(1)).unwrap().path,
Some("/conn1".to_string())
);
assert_eq!(
msgs2.get(&StreamId(1)).unwrap().path,
Some("/conn2".to_string())
);
assert_eq!(
msgs1.get(&StreamId(1)).unwrap().authority,
Some("example.com".to_string())
);
assert_eq!(
msgs2.get(&StreamId(1)).unwrap().authority,
Some("other.com".to_string())
);
}
#[test]
fn test_real_traffic_parsing() {
let fixture_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/real_traffic.bin"
);
if std::path::Path::new(fixture_path).exists() {
let buffer = std::fs::read(fixture_path).expect("should read fixture");
let expected_path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/real_traffic_expected.json"
);
let messages = parse_buffer(&buffer).expect("should parse real traffic");
if std::path::Path::new(expected_path).exists() {
let expected_json =
std::fs::read_to_string(expected_path).expect("should read expected.json");
let expected: serde_json::Value =
serde_json::from_str(&expected_json).expect("should parse JSON");
let min_expected_count = expected["message_count"].as_u64().unwrap() as usize;
assert!(
messages.len() >= min_expected_count,
"Should parse at least {} messages, got {}",
min_expected_count,
messages.len()
);
let streams = expected["streams"].as_array().unwrap();
let found_count = streams
.iter()
.filter(|stream| {
let expected_path = stream["path"].as_str().unwrap();
messages
.values()
.any(|m| m.path.as_ref().map(|p| p == expected_path).unwrap_or(false))
})
.count();
assert!(
found_count > 0 || messages.values().any(|m| m.is_response()),
"Should find at least some request paths or have responses"
);
}
for msg in messages.values() {
assert!(
msg.method.is_some() || msg.status.is_some(),
"Each message should have either method (request) or status (response)"
);
}
} else {
eprintln!(
"Skipping real traffic test - fixture not found. Run generate_real_traffic to create \
it."
);
}
}
#[test]
fn test_headers_padded_and_priority_flags() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/padded-priority", "example.com");
buffer.extend(build_headers_frame_padded_priority(
1,
&hpack_block,
5, 0, false, 16, true, true, ));
let messages = parse_buffer(&buffer).expect("should parse HEADERS with PADDED+PRIORITY");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(1)).unwrap().path,
Some("/padded-priority".to_string())
);
assert_eq!(
messages.get(&StreamId(1)).unwrap().method,
Some("GET".to_string())
);
}
#[test]
fn test_headers_priority_only() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/priority-only", "example.com");
buffer.extend(build_headers_frame_priority(
1,
&hpack_block,
0, true, 255, true, true, ));
let messages = parse_buffer(&buffer).expect("should parse HEADERS with PRIORITY");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(1)).unwrap().path,
Some("/priority-only".to_string())
);
}
#[test]
fn test_headers_padded_only() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/padded-only", "example.com");
buffer.extend(build_headers_frame_padded(
1,
&hpack_block,
10, true, true, ));
let messages = parse_buffer(&buffer).expect("should parse HEADERS with PADDED");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(1)).unwrap().path,
Some("/padded-only".to_string())
);
}
#[test]
fn test_dynamic_table_eviction() {
let mut buffer = connection_start();
let fill_block = hpack_fill_dynamic_table(4096);
buffer.extend(build_headers_frame(
1,
&fill_block,
FLAG_END_HEADERS | FLAG_END_STREAM,
));
let result = parse_buffer(&buffer);
assert!(
result.is_ok() || result.is_err(),
"Should not panic on table eviction"
);
}
#[test]
fn test_dynamic_table_size_zero() {
let mut buffer = connection_start();
buffer.extend(build_settings_frame(&[(0x01, 0)]));
let mut hpack_1 = hpack_get_request("/", "example.com");
hpack_1.extend(hpack_literal_with_indexing("x-custom", "value"));
buffer.extend(build_complete_headers_frame(1, &hpack_1));
let hpack_3 = hpack_get_request("/other", "example.com");
buffer.extend(build_complete_headers_frame(3, &hpack_3));
let result = parse_buffer(&buffer);
assert!(
result.is_ok(),
"Should handle table size 0: {:?}",
result.err()
);
}
#[rstest]
#[case::at_max(16384)]
#[case::just_under_max(16383)]
fn test_frame_size_boundary(#[case] body_size: usize) {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
let data = vec![b'X'; body_size];
buffer.extend(build_data_frame(1, &data, true));
let messages = parse_buffer(&buffer).expect("should parse frame at boundary");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body.len(), body_size);
}
#[test]
fn test_many_small_frames() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
for i in 0..999 {
buffer.extend(build_data_frame(1, &[i as u8], false));
}
buffer.extend(build_data_frame(1, &[255], true));
let messages = parse_buffer(&buffer).expect("should parse many small frames");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body.len(), 1000);
}
#[test]
fn test_stream_id_max_client() {
let max_stream_id: u32 = 0x7FFFFFFF;
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/max-stream", "example.com");
buffer.extend(build_complete_headers_frame(max_stream_id, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse max stream ID");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(max_stream_id)).unwrap().stream_id,
StreamId(max_stream_id)
);
}
#[test]
fn test_stream_id_large_odd() {
let large_stream_id: u32 = 0x7FFFFFFE - 1; let mut buffer = connection_start();
let hpack_block = hpack_get_request("/large-stream", "example.com");
buffer.extend(build_complete_headers_frame(large_stream_id, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse large stream ID");
assert_eq!(
messages.get(&StreamId(large_stream_id)).unwrap().stream_id,
StreamId(large_stream_id)
);
}
#[test]
fn test_stream_id_server_initiated() {
let mut buffer = connection_start();
let mut hpack_response = hpack_static::status_200();
hpack_response.extend(hpack_literal_without_indexing("content-type", "text/html"));
buffer.extend(build_complete_headers_frame(2, &hpack_response));
let messages = parse_buffer(&buffer).expect("should parse server-initiated stream");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(2)).unwrap().stream_id, StreamId(2));
assert_eq!(messages.get(&StreamId(2)).unwrap().status, Some(200));
}
#[test]
fn test_multiple_high_stream_ids() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
let ids = [1001, 2003, 3005, 4007, 5009];
for &id in &ids {
buffer.extend(build_complete_headers_frame(id, &hpack_block));
}
let messages = parse_buffer(&buffer).expect("should parse multiple high stream IDs");
assert_eq!(messages.len(), 5);
let parsed_ids: Vec<StreamId> = messages.keys().copied().collect();
for &expected_id in &ids {
assert!(
parsed_ids.contains(&StreamId(expected_id)),
"Should contain stream {}",
expected_id
);
}
}
#[test]
fn test_huffman_encoded_header_value() {
let mut buffer = connection_start();
let mut hpack_block = hpack_static::method_get();
hpack_block.extend(hpack_static::scheme_https());
hpack_block.extend(hpack_static::path_root());
hpack_block.extend(hpack_huffman::literal_indexed_name_huffman_value(
1, &hpack_huffman::www_example_com(),
));
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse Huffman-encoded headers");
assert_eq!(messages.len(), 1);
assert_eq!(
messages.get(&StreamId(1)).unwrap().authority,
Some("www.example.com".to_string())
);
}
#[test]
fn test_huffman_encoded_custom_header() {
let mut buffer = connection_start();
let mut hpack_block = hpack_get_request("/", "example.com");
hpack_block.extend(hpack_huffman::literal_huffman(
&hpack_huffman::custom_key(),
&hpack_huffman::custom_value(),
));
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse Huffman custom header");
assert_eq!(messages.len(), 1);
let has_custom = messages
.get(&StreamId(1))
.unwrap()
.headers
.iter()
.any(|(k, v)| k == "custom-key" && v == "custom-value");
assert!(has_custom, "Should have decoded Huffman custom header");
}
#[test]
fn test_mixed_huffman_and_literal() {
let mut buffer = connection_start();
let mut hpack_block = hpack_static::method_get();
hpack_block.extend(hpack_static::scheme_https());
hpack_block.extend(hpack_static::path_root());
hpack_block.extend(hpack_literal_without_indexing(":authority", "example.com"));
hpack_block.extend(hpack_huffman::literal_huffman(
&hpack_huffman::custom_key(),
&hpack_huffman::custom_value(),
));
hpack_block.extend(hpack_literal_without_indexing("x-plain", "plainvalue"));
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse mixed encoding");
assert_eq!(messages.len(), 1);
let msg = messages.get(&StreamId(1)).unwrap();
assert_eq!(msg.authority, Some("example.com".to_string()));
let has_custom = msg
.headers
.iter()
.any(|(k, v)| k == "custom-key" && v == "custom-value");
let has_plain = msg
.headers
.iter()
.any(|(k, v)| k == "x-plain" && v == "plainvalue");
assert!(has_custom, "Should have Huffman header");
assert!(has_plain, "Should have literal header");
}
#[test]
fn test_zero_length_body() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/empty", "example.com");
buffer.extend(build_complete_headers_frame(1, &hpack_block));
let messages = parse_buffer(&buffer).expect("should parse zero-length body");
assert_eq!(messages.len(), 1);
assert!(messages.get(&StreamId(1)).unwrap().body.is_empty());
}
#[test]
fn test_zero_length_data_frame() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_data_frame(1, &[], true));
let messages = parse_buffer(&buffer).expect("should parse zero-length DATA");
assert_eq!(messages.len(), 1);
assert!(messages.get(&StreamId(1)).unwrap().body.is_empty());
}
#[test]
fn test_max_padding() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_data_frame_padded(1, b"tiny", 255, true));
let messages = parse_buffer(&buffer).expect("should parse max padding");
assert_eq!(messages.len(), 1);
assert_eq!(messages.get(&StreamId(1)).unwrap().body, b"tiny");
}
#[test]
fn test_window_update_max_increment() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
buffer.extend(build_headers_frame(1, &hpack_block, FLAG_END_HEADERS));
buffer.extend(build_window_update_frame(0, 0x7FFFFFFF));
buffer.extend(build_window_update_frame(1, 0x7FFFFFFF));
buffer.extend(build_data_frame(1, b"data", true));
let messages = parse_buffer(&buffer).expect("should handle max window update");
assert_eq!(messages.len(), 1);
}
#[test]
fn test_many_concurrent_streams() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
for i in 0..100u32 {
let stream_id = i * 2 + 1; buffer.extend(build_complete_headers_frame(stream_id, &hpack_block));
}
let messages = parse_buffer(&buffer).expect("should parse 100 streams");
assert_eq!(messages.len(), 100);
}
#[test]
fn test_deeply_interleaved_streams() {
let mut buffer = connection_start();
let hpack_block = hpack_get_request("/", "example.com");
for i in 0..10u32 {
let stream_id = i * 2 + 1;
buffer.extend(build_headers_frame(
stream_id,
&hpack_block,
FLAG_END_HEADERS,
));
}
for chunk in 0..10 {
for i in 0..10u32 {
let stream_id = i * 2 + 1;
let is_last = chunk == 9;
let data = format!("S{}C{}", stream_id, chunk);
buffer.extend(build_data_frame(stream_id, data.as_bytes(), is_last));
}
}
let messages = parse_buffer(&buffer).expect("should parse deeply interleaved");
assert_eq!(messages.len(), 10);
for (stream_id, msg) in &messages {
let expected_body: String = (0..10).map(|c| format!("S{}C{}", stream_id, c)).collect();
assert_eq!(
String::from_utf8_lossy(&msg.body),
expected_body,
"Stream {} body mismatch",
stream_id
);
}
}