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.
1. Core · Mechanism
Read every scenario without motion
Stream interleave
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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?
- All stream 3 frames wait because stream 1 started first.
- 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}]
- New x-mode becomes 62 and the older x-course moves back one position.
- 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