Hacker DirectoryHacker Directory

WEB / WEB-08.02

HTTP/2 streams, frames, and HPACK

Follow independent streams within one connection; frame payloads, field compression, and TCP ordering operate at different layers.

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

Stream interleave

  1. Two request streams

    GET streams 1/3 are received on an established connection; response fields/content have not arrived.

    Client / Duru
    1/3 awaiting fields
    Atlas server
    GET 1/3 received
    Local connection state
    Context established
  2. Responses ready

    The server prepares two representations associated with stream IDs rather than one combined body.

    Client / Duru
    1/3 awaiting fields
    Atlas server
    1:10 bytes; 3:4 bytes ready
    Local connection state
    Context established
  3. HEADERS arrived

    Two complete response field sections arrive with END_HEADERS while both response bodies remain incomplete.

    Client / Duru
    1/3 status 200; body 0
    Atlas server
    Fields delivered
    Local connection state
    HPACK contexts synchronized
  4. DATA 1 fragment

    Six content bytes arrive for stream 1 without END_STREAM, so that response remains incomplete.

    Client / Duru
    1:6/10; 3:0/4
    Atlas server
    DATA 1 first delivered
    Local connection state
    TCP ordered delivery
  5. DATA 3 complete

    Stream 3 receives four bytes for Çay with END_STREAM while stream 1 remains at six content bytes.

    Client / Duru
    1:6/10; 3 complete: Çay
    Atlas server
    DATA 3 delivered
    Local connection state
    Completion order: 3
  6. Final DATA 1

    Stream 1 receives its final four bytes with END_STREAM, yielding Atlas note and ten accumulated bytes.

    Client / Duru
    1 complete: Atlas note; 3 complete
    Atlas server
    DATA 1 final delivered
    Local connection state
    Completion order: 3,1
  7. Correlation result

    The client associates each stream with its own request instead of concatenating all arrivals into one body.

    Client / Duru
    Two separate responses complete
    Atlas server
    No pending DATA
    Local connection state
    Connection remains shared

HPACK context

  1. Encoder prepared the first literal

    With supplied capacity 128 and initially empty tables, the client encoder prepares the incremental literal x-course: atlas and inserts it at 62 in its own table. The server decoder has not received the fragment and remains empty.

    Client / Duru
    Literal x-course ready; encoder62=x-course
    Atlas server
    No fragment received
    Local connection state
    Decoder dynamic empty
  2. First literal arrived

    Benign field representation bytes reach the decoder; local decoding will next update its table.

    Client / Duru
    Literal x-course ready; encoder62=x-course
    Atlas server
    Literal bytes received
    Local connection state
    Decoder dynamic empty
  3. Entry 62 inserted

    The decoder processes x-course: atlas as an incremental literal, accounting 45 bytes at combined index 62.

    Client / Duru
    Literal x-course ready; encoder62=x-course
    Atlas server
    x-course decoded
    Local connection state
    62=x-course; size45
  4. Encoder prepared the next fields

    The client encoder inserts x-mode: demo, making x-mode 62 and x-course 63 in its own table. The second literal and following bf indexed fragment are ready. The server decoder still has only its first insertion, 62=x-course.

    Client / Duru
    x-mode literal + bf ready; encoder62/63
    Atlas server
    x-course decoded
    Local connection state
    62=x-course; size45
  5. Second literal arrived

    The second field representation arrives while the previous table remains unchanged until local processing.

    Client / Duru
    x-mode literal + bf ready; encoder62/63
    Atlas server
    Second literal bytes received
    Local connection state
    62=x-course; size45
  6. Indices shifted

    x-mode: demo becomes newest entry 62 and shifts x-course to 63; total accounting 87 stays within capacity.

    Client / Duru
    x-mode literal + bf ready; encoder62/63
    Atlas server
    x-mode decoded
    Local connection state
    62=x-mode; 63=x-course; size87
  7. Index 63 arrived

    The bf indexed representation arrives for the current decoder context; it is neither a literal nor static index.

    Client / Duru
    x-mode literal + bf ready; encoder62/63
    Atlas server
    bf bytes received
    Local connection state
    62=x-mode; 63=x-course; size87
  8. Resolved in context

    The decoder resolves 63 as x-course: atlas without changing the table; fragment meaning depends on context.

    Client / Duru
    Fragment sequence complete
    Atlas server
    Decoded x-course: atlas
    Local connection state
    Table size87 unchanged

DATA credit

  1. Credit boundary

    Four DATA bytes are pending for stream 3 with remaining stream credit two and connection credit ten.

    Client / Duru
    Peer credit2
    Atlas server
    DATA4 pending
    Local connection state
    stream2 / connection10
  2. Sending waits

    This example chooses one four-byte DATA frame and waits with stream credit 2. The protocol permits partial progress using a smaller DATA frame; that choice is not shown. Connection credit 10 does not remove the stream limit.

    Client / Duru
    Peer credit2
    Atlas server
    DATA4 blocked by stream
    Local connection state
    stream2 / connection10
  3. Peer update prepared

    The client prepares WINDOW_UPDATE +2 for stream 3. It has not reached the server, whose remaining credit stays 2. The selected single four-byte DATA frame cannot be sent yet.

    Client / Duru
    WINDOW_UPDATE 3 +2 ready
    Atlas server
    DATA4 blocked by stream
    Local connection state
    stream2 / connection10
  4. WINDOW_UPDATE arrived

    The client’s +2 update for stream 3 arrives, raising the sender’s current remaining stream credit to four.

    Client / Duru
    Update +2 sent
    Atlas server
    DATA4 now allowed
    Local connection state
    stream4 / connection10
  5. DATA prepared

    The sender selects a valid four-byte DATA frame and consumes four bytes of both windows for sending.

    Client / Duru
    Awaiting DATA
    Atlas server
    DATA4 ready; END_STREAM
    Local connection state
    stream0 / connection6
  6. DATA arrived

    The client receives Çay with END_STREAM; this branch did not send before the peer update arrived.

    Client / Duru
    3 complete: Çay
    Atlas server
    DATA4 delivered
    Local connection state
    stream0 / connection6

TCP gap

  1. Fields already known

    Response fields were decoded earlier; new content frames occupy later bytes of the shared TCP stream.

    Client / Duru
    Bodies pending
    Atlas server
    Later DATA prepared
    Local connection state
    TCP earlier bytes missing
  2. Waiting for TCP gap

    A supplied TCP gap prevents later stream 1/3 frames from reaching the parser, without inventing DATA arrival.

    Client / Duru
    Bodies pending
    Atlas server
    Later DATA prepared
    Local connection state
    Later parser delivery blocked
  3. Gap resolved

    A supplied transport event restores ordered delivery; HTTP frame arrival occurs in the following step.

    Client / Duru
    Bodies pending
    Atlas server
    Later DATA prepared
    Local connection state
    TCP parser delivery available
  4. Stream 3 DATA arrived

    Once ordered bytes reach the parser, stream 3’s DATA frame can update client state.

    Client / Duru
    3 complete: Çay; 1 pending
    Atlas server
    DATA3 delivered
    Local connection state
    TCP gap resolved
  5. Wait reason identified

    The client identifies the prior wait as TCP delivery rather than HTTP response order, without timing measurements.

    Client / Duru
    3 complete; 1 pending
    Atlas server
    No new DATA event
    Local connection state
    Reason: TCP ordered delivery

ACTORS · STATE · DATA FLOW

HTTP semantics remain while message transport changes

Duru requests /atlas-notes and /tea. HTTP/2 retains GET and 200 semantics but uses pseudo-header control data and binary frames rather than textual request/status-lines. Readable :method, :scheme, :authority, :path, and :status are not literal HTTP/1.1 start-lines.

One request/response exchange consumes an HTTP/2 stream. Client-initiated streams use odd IDs such as 1, 3, and 5; stream 0 is reserved for connection control. IDs correlate activity within a connection without identifying a user, global request, or tenant permission. Here 1 and 3 carry two safe GET exchanges.

The connection preface and SETTINGS are part of ordinary protocol operation. Scenes begin with an established connection and already opened request streams; omitted setup does not mean setup is optional. Shared HTTP semantics retain the method, media, and permission boundaries developed in WEB-07.

Multiplexing ties response order to each stream

The server splits stream 1’s Atlas note into DATA carrying Atlas plus a space, then note: 6 and 4 bytes. Stream 3’s four-byte Çay frame interleaves between them. Stream 3 completes first using END_STREAM; stream 1’s final bytes arrive later. HTTP/1.1’s response-order queue no longer imposes that same completion order.

Order within each stream still matters. HEADERS carries fields, DATA carries content, and END_STREAM or a terminating trailer section marks completion. END_HEADERS finishes a field block rather than a stream. A section split across HEADERS/CONTINUATION must remain contiguous; arbitrary frame interleaving is not allowed inside that block.

Multiplexing does not change TCP’s single ordered byte stream. Missing earlier TCP bytes can prevent later HTTP/2 frames from progressing to the parser, even when a later frame belongs to another stream. This transport head-of-line is different from response-order blocking. The tcp-gap scene uses a supplied condition rather than inducing packet loss.

Frame headers and content lengths are measured separately

An HTTP/2 frame has a fixed nine-octet header: a 24-bit payload Length, Type, Flags, and a reserved bit plus 31-bit Stream Identifier. Length excludes those nine bytes. The artifact uses DATA=0, END_STREAM=1, payloadLength=4, and stream 3; adding UTF-8 Çay produces a 13-byte fragment.

This is one valid DATA frame within an established response context rather than a complete connection capture. HEADERS semantic projections are separate, and complete HPACK bytes are not inferred from that ledger. HTTP content length can span multiple DATA payloads; a frame Length counts only its own payload.

DATA respects both stream and connection flow-control windows. With stream credit 2, a four-byte DATA frame waits even if connection credit is 10. An arriving WINDOW_UPDATE +2 makes stream credit 4; sending consumes four bytes from both windows. These are remaining credits, not initial protocol defaults or production tuning. Waiting here follows the choice to send all four bytes in one frame; an implementation can make partial progress using smaller DATA frames that fit the available credit.

HPACK maintains shared field context between peers

HPACK compresses field sections rather than bodies, using static and dynamic tables plus literals. The static table has 61 entries: :method GET at 2, :path / at 4, :scheme https at 7, and :status 200 at 8. Indexed encoding reuses a name/value pair; string literals can also use static Huffman coding.

Without Huffman, the fixture inserts x-course: atlas using incremental indexing. Its initially empty dynamic table places the new entry at combined index 62. Adding x-mode: demo makes that newest entry 62 and shifts x-course to 63. Index 62 is not a permanent code for x-course; current context determines its meaning.

Entry accounting is decoded name bytes plus value bytes plus 32: 45 for the first entry, 42 for the second, and 87 total. Supplied capacity 128 needs no eviction. Encoder/decoder contexts are directional and shared across streams, not separate tables per stream. The artifact decodes bounded benign fragments, not a full codec, TLS secrecy, or every HTTP/2 validation rule.

2. Red · Trust & Impact

Read every scenario without motion

Index context

  1. The first index was tied to its snapshot

    The static table has 61 entries. x-course: atlas becomes the first dynamic entry at combined index 62. Its meaning depends on this directional context’s state.

    Frame/field record
    First: 62=x-course:atlas
    Context/credit gate
    Check pending
    Acceptance boundary
    No verdict
  2. A new insertion shifted the old index

    Inserting x-mode: demo gives it index 62 and shifts x-course: atlas to 63. Carrying the old meaning of 62 into a new field block would produce the wrong field.

    Frame/field record
    First: 62=x-course:atlas
    Context/credit gate
    Now: 62=x-mode:demo / 63=x-course:atlas
    Acceptance boundary
    No verdict
  3. Field meaning resolved in the current context

    The fragment bf selects index 63, meaning x-course: atlas in this snapshot. Hex alone supplies no table history; a wrong context can mislead routing or audit interpretation.

    Frame/field record
    First: 62=x-course:atlas
    Context/credit gate
    Now: 62=x-mode:demo / 63=x-course:atlas
    Acceptance boundary
    bf→63→x-course:atlas
  4. Entry accounting was separated from DATA credit

    Entry sizes 45 and 42 total 87, below the supplied capacity 128. This HPACK accounting is neither DATA flow credit nor a bound on all application memory.

    Frame/field record
    45+42=87 / capacity 128
    Context/credit gate
    Now: 62=x-mode:demo / 63=x-course:atlas
    Acceptance boundary
    bf→63→x-course:atlas
  5. Decoder ownership retained direction and order

    Decoder direction and field-block order are retained together. These isolated fragments are not complete HEADERS; synchronized capacity is supplied. Field decoding grants no user permission.

    Frame/field record
    45+42=87 / capacity 128
    Context/credit gate
    Directional context required
    Acceptance boundary
    bf→63→x-course:atlas
  6. Compression results did not become permission

    Index 63 resolves correctly in this context. Decoded-size limits, sensitive-field policy and exchange authorization remain separate gates. Compression proves neither encryption nor tenant isolation.

    Frame/field record
    45+42=87 / capacity 128
    Context/credit gate
    Directional context required
    Acceptance boundary
    Field resolved / permission separate

DATA credit

  1. Pending DATA was related to two credits

    Stream 3 has four pending DATA bytes, remaining stream credit 2 and connection credit 10. These are supplied after prior activity, not protocol initial-window defaults.

    Frame/field record
    Pending 4 / stream 2 / connection 10
    Context/credit gate
    Check pending
    Acceptance boundary
    No verdict
  2. Readiness was separated from send eligibility

    The selected single four-byte frame does not fit stream credit 2; connection credit 10 cannot compensate. Partial progress using a smaller DATA frame is possible. This example shows waiting for a peer update rather than taking that option.

    Frame/field record
    Pending 4 / stream 2 / connection 10
    Context/credit gate
    Four-byte send blocked by stream credit
    Acceptance boundary
    No verdict
  3. Waiting was not treated as application denial

    blockedInitially true describes peer credit, not authorization denial. DATA flow control is not a sufficient resource limit for decoded headers or all business work.

    Frame/field record
    Pending 4 / stream 2 / connection 10
    Context/credit gate
    Four-byte send blocked by stream credit
    Acceptance boundary
    Credit wait / permission unknown
  4. The window update retained its supplied scope

    A supplied windowUpdateIncrement of 2 makes updatedStreamCredit 4. This local review does not measure packet arrival. Sender credit must not increase before receipt of a peer update.

    Frame/field record
    Supplied update 2 / stream credit 4
    Context/credit gate
    Stream credit 4; four-byte send eligible
    Acceptance boundary
    Credit wait / permission unknown
  5. Send accounting deducted from both windows

    After four DATA bytes, stream credit is 0 and connection credit 6. The separate TCP-gap case concerns unavailable earlier bytes. Credit waiting and ordered-delivery waiting are different reasons.

    Frame/field record
    Supplied update 2 / stream credit 4
    Context/credit gate
    After send: stream 0 / connection 6
    Acceptance boundary
    Credit wait / permission unknown
  6. Arithmetic was not generalized to unmeasured runtime

    The calculation is consistent for these supplied credits. No real throughput or TCP-recovery result follows. Trusted request context and resource authorization per stream remain independent of credit.

    Frame/field record
    Supplied update 2 / stream credit 4
    Context/credit gate
    After send: stream 0 / connection 6
    Acceptance boundary
    Credit known / application separate

ASSUMPTION · BOUNDARY · CONTROL

Transport state is not subject state

Streams 1 and 3 distinguish exchanges; their shared connection does not mean authorization ran once for everything. The failure condition is substituting stream IDs or reuse for trusted subject/resource policy. Impact depends on which protected response is associated with an incorrect subject.

Connection-specific HTTP/1.1 fields are not generated in HTTP/2 sections: Connection, Keep-Alive, and Transfer-Encoding are prohibited, with request TE: trailers a separate narrow exception. Maintained stacks perform version conversion, and a textual display does not prove identical wire syntax.

Correlate each exchange with canonical routing, trusted authentication, and resource permissions. A TLS connection peer and application subject remain distinct. This fixture contains benign metadata/content; stream separation alone does not test tenant isolation.

Compression and multiplexing retain resource limits

Resolving a dynamic index without current context produces a different field, potentially misleading routing, caching, or policy analysis. Decoder state, direction, and field-block order must be known together; the last hex byte alone is insufficient.

Stream count, buffered DATA, decoded field size, and compression context are different resource dimensions. Flow control provides DATA credit rather than unlimited protection for decoded headers or all application work. Protocol defaults should not be copied as universal production limits.

Use maintained codecs, bounded decoded fields/work, and privacy-aware compression policies. Never-indexed literal semantics can help sensitive fields, but are not encryption and require intermediaries to preserve that representation. This lesson supplies no secret fields or compression probing.

3. Blue · Secure development & defense

Read every scenario without motion

Per-stream body

  1. Interleaved DATA was marked by stream

    DATA order is Atlas on stream 1, Çay on stream 3, then note on stream 1. Global concatenation produces the wrong body; each payload is related to its streamId.

    Frame ledger
    DATA1(6), DATA3(4), DATA1(4)
    Stream correlation
    Check pending
    Evidence verdict
    No verdict
  2. Content was accumulated separately

    Stream 1 accumulates 6+4 bytes as Atlas note; stream 3 has four bytes as Çay. DATA payloadLength counts content; the nine-byte frame header is not added to content-length.

    Frame ledger
    DATA1(6), DATA3(4), DATA1(4)
    Stream correlation
    Stream1=10 / stream3=4
    Evidence verdict
    No verdict
  3. END_HEADERS was not body completion

    At the initial HEADERS observation, endHeaders=true and endStream=false; both bodies have zero bytes then. DATA1 next accumulates six bytes. When DATA3 completes at 4/4 with END_STREAM, stream 1 has only 6/10. The previous totals describe final output, not this intermediate instant.

    Frame ledger
    Header complete / body state per stream
    Stream correlation
    At DATA3 completion: stream1 6/10; stream3 4/4
    Evidence verdict
    No verdict
  4. Completion order was calculated per stream

    Stream 3 receives its final DATA first, giving completionOrder 3,1. The HTTP/1.1 pipeline-order rule does not apply here. With stream correlation retained, that different order is ordinary.

    Frame ledger
    Header complete / body state per stream
    Stream correlation
    Completion 3,1 / lengths match
    Evidence verdict
    No verdict
  5. The length oracle was bounded by controls

    Normal XML reports 4/4 and 10/10 bytes with endStream true, yielding zero mismatches. The positive control reports 2/4 and yields one match. These authored observations are not real capture fields.

    Frame ledger
    Header complete / body state per stream
    Stream correlation
    Completion 3,1 / lengths match
    Evidence verdict
    Normal 0 / positive 1
  6. Correlation did not become a tenant-isolation claim

    The receiver HTTP/2 parser must observe streamId, DATA payload counts and END_STREAM together on the same connection in the response direction. These fields support the two bodies and completion order 3,1; user/tenant permission needs the application’s separate record.

    Frame ledger
    Header complete / body state per stream
    Stream correlation
    Completion 3,1 / lengths match
    Evidence verdict
    Bodies correlated / tenant policy unverified

Observer gap

  1. The TCP gap was separated from decoder waiting

    tcpGap supplies headersPreviouslyDecoded true, with earlier TCP bytes unavailable to the parser. Later streamIds are 1 and 3. This ordered-byte dependency is not HPACK-field-decoding wait.

    Frame ledger
    Fields decoded / earlier bytes unavailable
    Stream correlation
    Check pending
    Evidence verdict
    No verdict
  2. The TCP dependency affected multiple streams

    allLaterFramesParserBlocked is true. Multiplexing does not remove TCP ordered delivery. Stream 3’s different ID does not bypass unavailable earlier bytes on the same connection.

    Frame ledger
    Fields decoded / earlier bytes unavailable
    Stream correlation
    Later frames 1 and 3 blocked by TCP
    Evidence verdict
    No verdict
  3. The gap cause was not presented as measured

    The parser can progress after the supplied gap resolution. No real missing packet or timing was measured. A dependency can be known while its cause is unknown; no automatic network-loss diagnosis is added.

    Frame ledger
    Dependency known / loss cause unmeasured
    Stream correlation
    Later frames 1 and 3 blocked by TCP
    Evidence verdict
    No verdict
  4. Receiver coverage was queried separately

    The alternative review lacks a complete receiver ledger. Copying expectedBodies cannot turn a missing frame into an observation. Completion requires endStream and accumulated content to be observed together.

    Frame ledger
    Dependency known / loss cause unmeasured
    Stream correlation
    Receiver completion coverage missing
    Evidence verdict
    No verdict
  5. Zero mismatches did not close the unknown result

    A missing stream observation may produce no selector mismatch. Completion remains UNKNOWN. Without stream ID, direction, frame type and receiver coverage, complete validation cannot be established.

    Frame ledger
    Dependency known / loss cause unmeasured
    Stream correlation
    Receiver completion coverage missing
    Evidence verdict
    UNKNOWN: receiver completion
  6. Minimal evidence and wait reasons were retained

    Further review needs scoped completion flags and content counts, not sensitive bodies. Credit wait and TCP-byte wait retain different reasons. Normal-fixture results are not transferred without new evidence.

    Frame ledger
    Dependency known / loss cause unmeasured
    Stream correlation
    Receiver completion coverage missing
    Evidence verdict
    Needed: scoped frame/flag/count coverage

IMPLEMENTATION · SECURITY CONTROL · VERIFICATION

Reassemble a frame ledger by stream

Separate the header projections for streams 1 and 3, then assemble DATA by stream ID. Even with arrival order DATA1(6), DATA3(4), DATA1(4), outputs are Atlas note and Çay. Concatenating every arrival into one body would correlate them incorrectly.

Track END_HEADERS, END_STREAM, and Content-Length separately: the first two concern flags/context, while the last is an HTTP field. The selector finds a supplied END_STREAM observation whose accumulated content differs from expected length; normal and inconsistent fixtures yield 0 and 1.

Missing frames or the wrong capture direction can also produce 0 without complete validation. Diagnostics can record protocol, connection alias, stream ID, frame type, and accumulated count without logging entire sensitive bodies.

Expose wait reasons and compression context

The window branch has stream credit 2, connection credit 10, and four pending content bytes. The smaller credit bounds DATA progress; a peer update changes sender credit only after arrival. Sending then subtracts the used amount from both windows, and ready does not mean sent.

The TCP-gap branch already has decoded fields but later bytes have not progressed to the parser; this differs from a pending field decode. In the HPACK branch, resolve 62/63 against the current table snapshot instead of guessing a fragment’s meaning.

A native local HTTP/2 check can observe completion order for two safe GET streams over plaintext loopback. Binary fragments and the HPACK subset are separate bounded byte exercises. TLS, browser negotiation, captured frames, proxies, and resource-limit stress require distinct verification.

NOW APPLY IT TO THE CASE

Which boundaries remain despite multiplexing, HPACK and flow control?

Atlas note travels on stream 1 and Çay on stream 3, which may complete first. HPACK index 63 selects x-course: atlas in the current table. DATA credit and ordered TCP bytes are distinct waiting conditions.

Which application conditions would remain if the case used another HTTP transport? Do not equate stream or packet completion with successful note parsing.

Open the related case file ↗

READ THE EXAMPLE TOGETHER

Stream/frame and HPACK context ledger

Select a field and examine what it supports and where the evidence ends.

Read the raw artifact and field explanations
{
  "connectionEstablishedSupplied": true,
  "requestStreams": [
    {
      "id": 1,
      "method": "GET",
      "path": "/atlas-notes"
    },
    {
      "id": 3,
      "method": "GET",
      "path": "/tea"
    }
  ],
  "responseHeadersProjection": [
    {
      "streamId": 1,
      "fields": {
        ":status": "200",
        "content-type": "text/plain; charset=utf-8",
        "content-length": "10"
      },
      "endHeaders": true,
      "endStream": false
    },
    {
      "streamId": 3,
      "fields": {
        ":status": "200",
        "content-type": "text/plain; charset=utf-8",
        "content-length": "4"
      },
      "endHeaders": true,
      "endStream": false
    }
  ],
  "dataFrames": [
    {
      "streamId": 1,
      "type": "DATA",
      "typeValue": 0,
      "flags": 0,
      "endStream": false,
      "payloadLength": 6,
      "content": "Atlas ",
      "payloadHex": "41746c617320",
      "frameHex": "00000600000000000141746c617320",
      "frameOctets": 15
    },
    {
      "streamId": 3,
      "type": "DATA",
      "typeValue": 0,
      "flags": 1,
      "endStream": true,
      "payloadLength": 4,
      "content": "Çay",
      "payloadHex": "c3876179",
      "frameHex": "000004000100000003c3876179",
      "frameOctets": 13
    },
    {
      "streamId": 1,
      "type": "DATA",
      "typeValue": 0,
      "flags": 1,
      "endStream": true,
      "payloadLength": 4,
      "content": "note",
      "payloadHex": "6e6f7465",
      "frameHex": "0000040001000000016e6f7465",
      "frameOctets": 13
    }
  ],
  "expectedBodies": {
    "1": "Atlas note",
    "3": "Çay"
  },
  "completionOrder": [
    3,
    1
  ],
  "hpack": {
    "staticTableLength": 61,
    "staticEntries": [
      {
        "index": 2,
        "name": ":method",
        "value": "GET"
      },
      {
        "index": 4,
        "name": ":path",
        "value": "/"
      },
      {
        "index": 7,
        "name": ":scheme",
        "value": "https"
      },
      {
        "index": 8,
        "name": ":status",
        "value": "200"
      }
    ],
    "capacitySupplied": 128,
    "initialDynamicEntries": [],
    "huffman": false,
    "fragmentScope": "Isolated benign HPACK field representations, not complete request HEADERS or a connection capture. Capacity already synchronized and supplied; no table-size update fragment is shown.",
    "operations": [
      {
        "kind": "literal-incremental",
        "name": "x-course",
        "value": "atlas",
        "hex": "4008782d636f757273650561746c6173",
        "entrySize": 45,
        "entriesNewestFirst": [
          {
            "name": "x-course",
            "value": "atlas",
            "combinedIndex": 62
          }
        ]
      },
      {
        "kind": "literal-incremental",
        "name": "x-mode",
        "value": "demo",
        "hex": "4006782d6d6f64650464656d6f",
        "entrySize": 42,
        "entriesNewestFirst": [
          {
            "name": "x-mode",
            "value": "demo",
            "combinedIndex": 62
          },
          {
            "name": "x-course",
            "value": "atlas",
            "combinedIndex": 63
          }
        ]
      },
      {
        "kind": "indexed",
        "index": 63,
        "hex": "bf",
        "resolvedName": "x-course",
        "resolvedValue": "atlas",
        "tableSize": 87
      }
    ]
  },
  "flow": {
    "streamId": 3,
    "pendingDataOctets": 4,
    "initialRemainingStreamCredit": 2,
    "initialRemainingConnectionCredit": 10,
    "blockedInitially": true,
    "windowUpdateIncrement": 2,
    "updatedStreamCredit": 4,
    "afterSendStreamCredit": 0,
    "afterSendConnectionCredit": 6,
    "scope": "Remaining credits supplied after previous activity, not protocol initial-window defaults."
  },
  "tcpGap": {
    "condition": "Earlier TCP bytes unavailable to parser",
    "headersPreviouslyDecoded": true,
    "laterFrameStreamIds": [
      1,
      3
    ],
    "allLaterFramesParserBlocked": true,
    "afterSuppliedGapResolutionParserCanProgress": true,
    "scope": "Logical ordered-delivery dependency; no missing packet, congestion algorithm, or timing measurement."
  },
  "selector": {
    "language": "XPath",
    "xml": "<observations><stream id=\"3\" endStream=\"true\" contentOctets=\"4\" expectedOctets=\"4\"/><stream id=\"1\" endStream=\"true\" contentOctets=\"10\" expectedOctets=\"10\"/></observations>",
    "xpath": "count(/observations/stream[@endStream=\"true\" and @contentOctets!=@expectedOctets])",
    "expected": 0,
    "positiveFixtureXml": "<observations><stream id=\"3\" endStream=\"true\" contentOctets=\"2\" expectedOctets=\"4\"/></observations>",
    "positiveExpected": 1,
    "scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
    "missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
  }
}
dataFrames
Valid DATA fragments: Length counts payload; the header adds 9 bytes.
hpack.operations
Newest-first indices in the current shared directional context.
flow / tcpGap
Peer-credit wait differs from ordered TCP-delivery wait.

Semantic HEADERS projections, valid DATA fragments, and bounded HPACK subset are distinct evidence. Established contexts are supplied; no TLS/browser/packet-loss reproduction. Native local HTTP2 evidence has a separate scope.

Two reality checks

HTTP/2 eliminates every form of head-of-line blocking.

Responses can interleave by stream, while TCP ordered delivery and flow-control boundaries can still block progress. Identify the wait reason.

HPACK index 62 always denotes the same header.

After 61 static entries, 62 is the current newest dynamic entry. The second insertion shifts x-course to 63; direction and context matter.

YOUR TURN

Work through this story.

Predict the outcome, find the evidence boundary, and explain it in your own words.

DATA frames are stream 1 Atlas , stream 3 Çay, stream 1 note. Which response completes first?

  1. All stream 3 frames wait because stream 1 started first.
  2. Stream 3 with body Çay; then stream 1 with Atlas note.
Read the explained answer

Stream 3 with body Çay; then stream 1 with Atlas note. — Interleaved frames are reconstructed by stream ID; completionOrder=[3, 1].

Why does the x-course index move from 62 to 63 after inserting a new dynamic entry?

Selected lesson artifact fields; teaching example

hpack.operations[0].entriesNewestFirst
[{"name":"x-course","value":"atlas","combinedIndex":62}]
hpack.operations[1].entriesNewestFirst
[{"name":"x-mode","value":"demo","combinedIndex":62},{"name":"x-course","value":"atlas","combinedIndex":63}]
  1. New x-mode becomes 62 and the older x-course moves back one position.
  2. The index is immutable; 62 means x-course on every connection.
Read the explained answer

New x-mode becomes 62 and the older x-course moves back one position. — After 61 static entries, dynamic combined indices depend on context. An index is not a global header ID.

Why can a TCP gap stop parsing subsequent frames of both streams even though HTTP/2 streams are separate?

Read the explained answer

Frame IDs differ at HTTP level; the TCP connection supplies one ordered byte stream to the parser. Until an earlier byte gap closes, later frame bytes may be unavailable. This logical fixture measures neither a real loss cause nor timers; stream flow credit is another limit.

□ I separated a transport gap from HTTP-stream ordering.

□ I did not treat logical blocking as real loss diagnosis.

Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples

Sources and scope

  • RFC 9113 — HTTP/21 Introduction · 4.1 Frame Format · 4.3 Field Section Compression and Decompression · 4.3.1 Compression State · 5 Streams and Multiplexing · 5.1.1 Stream Identifiers · 5.2.1 Flow-Control Principles · 6.1 DATA · 6.2 HEADERS · 6.9 WINDOW_UPDATE · 8.1 HTTP Message Framing · 8.2.2 Connection-Specific Header Fields
  • RFC 7541 — HPACK2.3 Indexing Tables · 2.3.3 Index Address Space · 4.1 Calculating Table Size · 5.2 String Literal Representation · 6.1 Indexed Header Field Representation · 6.2.1 Literal Header Field with Incremental Indexing · 6.2.3 Literal Header Field Never Indexed · Appendix A Static Table Definition