brbzull0 commented on code in PR #13924:
URL: https://github.com/apache/trafficserver/pull/13924#discussion_r4399701918


##########
src/proxy/http3/QPACK.cc:
##########
@@ -265,7 +298,9 @@ QPACK::encode(uint64_t stream_id, HTTPHdr &header_set, 
MIOBuffer *header_block,
   header_block_len += header_data_prefix->size();
 
   header_block->append_block(compressed_headers);
-  header_block_len += compressed_headers->size();
+  for (IOBufferBlock *block = compressed_headers; block != nullptr; block = 
block->next.get()) {
+    header_block_len += block->size();
+  }

Review Comment:
   question: `header_block_len` can now be larger than the slice that 
`Http3HeaderFramer::generate_frame()` puts in one frame:
   
   
https://github.com/apache/trafficserver/blob/a09837e5a0871542f5ef077907f9e9e235d3b44d/src/proxy/http3/Http3HeaderFramer.cc#L68
   
   A larger header block then goes out as several HEADERS frames. RFC 9114, 
Section 4.1 sends the header section as a single HEADERS frame, and HTTP/3 has 
no CONTINUATION frame, so the peer cannot decode a block split across frames. 
The slicing predates this PR, but it could not trigger while the block was 
capped at one 2K block. Should it be handled here (one HEADERS frame for the 
whole block, or a clean failure past a limit), or in a follow-up issue?



##########
src/proxy/http3/test/test_QPACK.cc:
##########
@@ -479,6 +479,64 @@ TEST_CASE("decode() rejects oversized Required Insert 
Count at entry", "[qpack-d
   hdr.destroy();
 }
 
+// QPACK::encode() compresses a header set into IOBufferBlocks that are then
+// framed as a HEADERS frame. A response whose compressed fields do not fit in 
a
+// single 2 KB block must still be encoded in full; a field that is silently
+// dropped never reaches the client. Encode a large header set, decode the
+// resulting header block with a second QPACK instance and check that every
+// field survived the round trip.
+TEST_CASE("encode() keeps every field of a header set larger than one buffer 
block", "[qpack-encode-large]")
+{
+  QUICApplicationDriver driver;
+  auto                  encoder = 
std::make_unique<QPACK>(driver.get_connection(), UINT32_MAX, 0, 0, 
MAX_FIELD_SIZE);

Review Comment:
   suggestion: The new test does not reach two of the new branches. Every 
field's `max_encoded_field_size()` fits in the initial block, so the branch 
that replaces the empty first block (QPACK.cc L270-L274) never runs. With a 
table size of 0 the dynamic table is unused, so the `ret == 0` checks around 
`ref_entry()` never run either. Adding a first field that is too large for the 
initial block on its own would cover the replace branch with the same round 
trip.



##########
src/proxy/http3/QPACK.cc:
##########
@@ -242,10 +253,32 @@ QPACK::encode(uint64_t stream_id, HTTPHdr &header_set, 
MIOBuffer *header_block,
   uint16_t       largest_reference  = 0;
   uint16_t       smallest_reference = 0;
   IOBufferBlock *compressed_headers = new_IOBufferBlock();
+  IOBufferBlock *current_block      = compressed_headers;
   compressed_headers->alloc(BUFFER_SIZE_INDEX_2K);
 
   for (auto &field : header_set) {
-    int ret            = this->_encode_header(field, base_index, 
compressed_headers, referred_index);
+    // The field encoders fail when the field does not fit in the space left 
in the block. Chain a block that is large
+    // enough for the whole field before encoding it, so that no field is left 
out of the header block.
+    int64_t required = max_encoded_field_size(field);
+    if (required > current_block->write_avail()) {
+      if (required > DEFAULT_MAX_BUFFER_SIZE) {
+        compressed_headers->free();
+        return -1;
+      }

Review Comment:
   issue: This is the first path on which `encode()` can fail for a real 
response, but the only production caller drops the result. 
`Http3HeaderFramer::_generate_header_block()` ignores the return value of 
`encode()`:
   
   
https://github.com/apache/trafficserver/blob/a09837e5a0871542f5ef077907f9e9e235d3b44d/src/proxy/http3/Http3HeaderFramer.cc#L139-L142
   
   When `encode()` returns here, `_header_block` is already allocated and 
`_header_block_len` is still 0. So `generate_frame()` builds a HEADERS frame 
with an empty payload and marks the header as sent:
   
   
https://github.com/apache/trafficserver/blob/a09837e5a0871542f5ef077907f9e9e235d3b44d/src/proxy/http3/Http3HeaderFramer.cc#L67-L79
   
   An empty payload has no Required Insert Count and Base prefix. It is not a 
valid encoded field section (RFC 9204, Section 4.5.1), so the peer cannot 
decode it. The failure is still swallowed, just one level up. Could 
`_generate_header_block()` check the result and fail the stream instead of 
producing a frame?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to