From 18950deede61f95b6c0a3e7ae45d34ce05de2bc9 Mon Sep 17 00:00:00 2001 From: Suraj Rajan <5915832+skrcode@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:55:55 +0530 Subject: [PATCH 1/2] okhttp: avoid unnecessary Header allocation for lowercase names Reuse the immutable input Header when its name is already canonical, while preserving allocation and case folding for mixed-case names. Add a regression test for repeated mixed-case indexing. Generated-by: JAIPilot Cloud Signed-off-by: Suraj Rajan <5915832+skrcode@users.noreply.github.com> --- .../io/grpc/okhttp/internal/framed/Hpack.java | 18 ++++++++++++++++-- .../grpc/okhttp/internal/framed/HpackTest.java | 13 +++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java index 437a8e093b4..028cd355cab 100644 --- a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java +++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java @@ -490,7 +490,7 @@ void writeHeaders(List headerBlock) throw out.writeByte(0x40); writeByteString(name); writeByteString(value); - insertIntoDynamicTable(new io.grpc.okhttp.internal.framed.Header(name, value)); + insertIntoDynamicTable(canonicalHeader(header, name, value)); } else if (name.startsWith(PSEUDO_PREFIX) && !io.grpc.okhttp.internal.framed.Header.TARGET_AUTHORITY.equals(name) && !io.grpc.okhttp.internal.framed.Header.TARGET_PATH.equals(name)) { @@ -504,11 +504,25 @@ void writeHeaders(List headerBlock) throw // Literal Header Field with Incremental Indexing - Indexed Name. writeInt(headerNameIndex, PREFIX_6_BITS, 0x40); writeByteString(value); - insertIntoDynamicTable(new io.grpc.okhttp.internal.framed.Header(name, value)); + insertIntoDynamicTable(canonicalHeader(header, name, value)); } } } + /** + * Returns {@code header} as-is when its name is already the lowercase {@code name} used for + * indexing, avoiding an extra Header allocation on the common path where header names are + * already lowercase (e.g. gRPC's generated headers and the {@code :path}/{@code :authority} + * pseudo headers). A value comparison detects that case while also avoiding an allocation if + * an equivalent lowercase name is represented by a different {@link ByteString} instance. + */ + private static io.grpc.okhttp.internal.framed.Header canonicalHeader( + io.grpc.okhttp.internal.framed.Header header, ByteString name, ByteString value) { + return name.equals(header.name) + ? header + : new io.grpc.okhttp.internal.framed.Header(name, value); + } + // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1 void writeInt(int value, int prefixMask, int bits) throws IOException { // Write the raw value for a single byte value. diff --git a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java index 2a023a098a4..15c583ca7ef 100644 --- a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java +++ b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java @@ -1140,6 +1140,19 @@ public void pseudoHeaderIndexingForPathAndAuthority() throws IOException { assertEquals(3, hpackWriter.dynamicTableHeaderCount); } + @Test + public void mixedCaseHeaderNameIndexedOnRepeat() throws IOException { + hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR"))); + assertBytes(0x40, 3, 'f', 'o', 'o', 3, 'B', 'a', 'R'); + assertEquals(1, hpackWriter.dynamicTableHeaderCount); + + // Even though the header name is mixed case again, the dynamic table entry was stored + // with the lowercase name, so the repeat is emitted as an indexed reference. + hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR"))); + assertBytes(0xbe); + assertEquals(1, hpackWriter.dynamicTableHeaderCount); + } + @Test public void evictToRecoverBytesDoesNotNpeWhenBytesToRecoverExceedsTable() throws IOException { hpackWriter.writeHeaders(headerEntries("custom-key", "custom-value")); From 7fd6615047eab00569534a06da0673187dd02b2e Mon Sep 17 00:00:00 2001 From: Suraj Rajan <5915832+skrcode@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:57:45 +0530 Subject: [PATCH 2/2] okhttp: lock lowercase Header reuse Make the allocation-saving branch a durable, deterministic regression property by asserting that the dynamic table retains the immutable input Header. Generated-by: JAIPilot Cloud Signed-off-by: Suraj Rajan <5915832+skrcode@users.noreply.github.com> --- .../java/io/grpc/okhttp/internal/framed/HpackTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java index 15c583ca7ef..60f25af5648 100644 --- a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java +++ b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java @@ -18,6 +18,7 @@ import static okio.ByteString.decodeHex; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; import java.io.IOException; @@ -1153,6 +1154,15 @@ public void mixedCaseHeaderNameIndexedOnRepeat() throws IOException { assertEquals(1, hpackWriter.dynamicTableHeaderCount); } + @Test + public void lowercaseHeaderIsReusedInDynamicTable() throws IOException { + Header header = new Header("custom-key", "custom-value"); + + hpackWriter.writeHeaders(Arrays.asList(header)); + + assertSame(header, hpackWriter.dynamicTable[hpackWriter.dynamicTable.length - 1]); + } + @Test public void evictToRecoverBytesDoesNotNpeWhenBytesToRecoverExceedsTable() throws IOException { hpackWriter.writeHeaders(headerEntries("custom-key", "custom-value"));