The following errata report has been verified for RFC9204, "QPACK: Field Compression for HTTP/3".
-------------------------------------- You may review the report below and at: https://www.rfc-editor.org/errata/eid8410 -------------------------------------- Status: Verified Type: Technical Reported by: Kazu Yamamoto <k...@iij.ad.jp> Date Reported: 2025-05-08 Verified by: Mike Bishop (IESG) Section: Appendix C Original Text ------------- base = dynamicTable.getInsertCount() requiredInsertCount = 0 for line in fieldLines: staticIndex = staticTable.findIndex(line) if staticIndex is not None: encodeStaticIndexReference(streamBuffer, staticIndex) continue dynamicIndex = dynamicTable.findIndex(line) if dynamicIndex is None: # No matching entry. Either insert+index or encode literal staticNameIndex = staticTable.findName(line.name) if staticNameIndex is None: dynamicNameIndex = dynamicTable.findName(line.name) if shouldIndex(line) and dynamicTable.canIndex(line): encodeInsert(encoderBuffer, staticNameIndex, dynamicNameIndex, line) dynamicIndex = dynamicTable.add(line) if dynamicIndex is None: # Could not index it, literal if dynamicNameIndex is not None: # Encode literal with dynamic name, possibly above Base encodeDynamicLiteral(streamBuffer, dynamicNameIndex, base, line) requiredInsertCount = max(requiredInsertCount, dynamicNameIndex) else: # Encodes a literal with a static name or literal name encodeLiteral(streamBuffer, staticNameIndex, line) else: # Dynamic index reference assert(dynamicIndex is not None) requiredInsertCount = max(requiredInsertCount, dynamicIndex) # Encode dynamicIndex, possibly above Base encodeDynamicIndexReference(streamBuffer, dynamicIndex, base) # encode the prefix if requiredInsertCount == 0: encodeInteger(prefixBuffer, 0x00, 0, 8) encodeInteger(prefixBuffer, 0x00, 0, 7) else: wireRIC = ( requiredInsertCount % (2 * getMaxEntries(maxTableCapacity)) ) + 1; encodeInteger(prefixBuffer, 0x00, wireRIC, 8) if base >= requiredInsertCount: encodeInteger(prefixBuffer, 0x00, base - requiredInsertCount, 7) else: encodeInteger(prefixBuffer, 0x80, requiredInsertCount - base - 1, 7) return encoderBuffer, prefixBuffer + streamBuffer Corrected Text -------------- base = dynamicTable.getInsertCount() requiredInsertCount = 0 for line in fieldLines: staticIndex = staticTable.findIndex(line) if staticIndex is not None: encodeStaticIndexReference(streamBuffer, staticIndex) continue dynamicIndex = dynamicTable.findIndex(line) if dynamicIndex is None: # No matching entry. Either insert+index or encode literal staticNameIndex = staticTable.findName(line.name) if staticNameIndex is None: dynamicNameIndex = dynamicTable.findName(line.name) if shouldIndex(line) and dynamicTable.canIndex(line): encodeInsert(encoderBuffer, staticNameIndex, dynamicNameIndex, line) dynamicIndex = dynamicTable.add(line) if dynamicIndex is None: # Could not index it, literal if dynamicNameIndex is not None: # Encode literal with dynamic name, possibly above Base encodeDynamicLiteral(streamBuffer, dynamicNameIndex, base, line) requiredInsertCount = max(requiredInsertCount, dynamicNameIndex + 1) else: # Encodes a literal with a static name or literal name encodeLiteral(streamBuffer, staticNameIndex, line) else: # Dynamic index reference assert(dynamicIndex is not None) requiredInsertCount = max(requiredInsertCount, dynamicIndex + 1) # Encode dynamicIndex, possibly above Base encodeDynamicIndexReference(streamBuffer, dynamicIndex, base) # encode the prefix if requiredInsertCount == 0: encodeInteger(prefixBuffer, 0x00, 0, 8) encodeInteger(prefixBuffer, 0x00, 0, 7) else: wireRIC = ( requiredInsertCount % (2 * getMaxEntries(maxTableCapacity)) ) + 1; encodeInteger(prefixBuffer, 0x00, wireRIC, 8) if base >= requiredInsertCount: encodeInteger(prefixBuffer, 0x00, base - requiredInsertCount, 7) else: encodeInteger(prefixBuffer, 0x80, requiredInsertCount - base - 1, 7) return encoderBuffer, prefixBuffer + streamBuffer Notes ----- "Sample Single-Pass Encoding Algorithm" in Appendix C has a bug that Count is equel to the index. Count must be one higher than index. Reasoning the code: requiredInsertCount is initialized with 0: requiredInsertCount = 0 dynamicIndex can be 0 if it is the first entry of the dynamic table: dynamicIndex = dynamicTable.findIndex(line) In this case, requiredInsertCount stay with 0: requiredInsertCount = max(requiredInsertCount, dynamicIndex) This results in a wrong prefix: if requiredInsertCount == 0: encodeInteger(prefixBuffer, 0x00, 0, 8) encodeInteger(prefixBuffer, 0x00, 0, 7) The following code is correct: requiredInsertCount = max(requiredInsertCount, dynamicIndex + 1) -------------------------------------- RFC9204 (draft-ietf-quic-qpack-21) -------------------------------------- Title : QPACK: Field Compression for HTTP/3 Publication Date : June 2022 Author(s) : C. Krasic, M. Bishop, A. Frindell, Ed. Category : PROPOSED STANDARD Source : QUIC Stream : IETF Verifying Party : IESG