Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/protocols/ioxide.http2/Http2Connection.Frames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ private void HandleHeaders(in FrameHeader header, ReadOnlySpan<byte> payload)

// A header block can span HEADERS + CONTINUATION frames, and HPACK cannot be decoded
// piecewise - the whole block has to be in hand first.
pending.HeadersDone = false; // trailers open a block of their own, and the request waits for its END_HEADERS
pending.AppendHeaderBlock(block);

if (pending.HeaderBlock.Length > _options.MaxHeaderListSize)
Expand Down
31 changes: 31 additions & 0 deletions tests/Ioxide.Tests.Unit/Http2TrailerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,33 @@ public static void Register(Runner runner)
peer.Close(run);
});

runner.Test("h2 request: a buffered request's trailers split over HEADERS and CONTINUATION merge into it, and the connection serves on", () =>
{
var served = new List<string>();

using var peer = new Peer(new Http2Options());
Task run = peer.Connection.RunAsync(request =>
{
request.TryGetHeader("x-checksum"u8, out ReadOnlyMemory<byte> checksum);
request.TryGetHeader("x-digest"u8, out ReadOnlyMemory<byte> digest);
served.Add($"{request.StreamId} '{Ascii(request.Path)}' {request.Body.Length} x-checksum '{Ascii(checksum)}' x-digest '{Ascii(digest)}'");
return Http2Response.Text("done");
});

peer.Open();
peer.Headers(1, Request("/upload"), endStream: false);
peer.Data(1, 400, endStream: false);
peer.HeadersOpen(1, Field("x-checksum", "abc"), endStream: true); // END_STREAM, with the block still open
peer.Continuation(1, Field("x-digest", "def")); // END_HEADERS
peer.Headers(3, Request("/next"), endStream: true);

Assert.True(served.SequenceEqual(["1 '/upload' 400 x-checksum 'abc' x-digest 'def'", "3 '/next' 0 x-checksum '' x-digest ''"]),
$"the handler was handed: {string.Join(", ", served)}");
Assert.Equal("", GoAways(peer));

peer.Close(run);
});

runner.Test("h2 request: a stream refused past MaxConcurrentStreams still gets REFUSED_STREAM, and its id opens nothing later", () =>
{
var served = new List<string>();
Expand Down Expand Up @@ -343,6 +370,10 @@ public void Headers(int streamId, byte[] block, bool endStream)

public void Continuation(int streamId, byte[] block) => Feed(FrameBytes(0x9, 0x4, streamId, block));

/// <summary>A HEADERS frame without END_HEADERS: a CONTINUATION ends its block.</summary>
public void HeadersOpen(int streamId, byte[] block, bool endStream)
=> Feed(FrameBytes(0x1, (byte)(endStream ? 0x1 : 0), streamId, block));

public void Data(int streamId, int bytes, bool endStream)
=> Feed(FrameBytes(0x0, (byte)(endStream ? 0x1 : 0), streamId, Enumerable.Repeat((byte)'z', bytes).ToArray()));

Expand Down
Loading