From 0da922505612cde07c3c9583c7b5ae8d2cbff299 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 19 Jul 2018 13:49:40 +0200 Subject: [PATCH] dynamic table resize Signed-off-by: Greg Wilkins --- .../jetty/http2/hpack/HpackDecoder.java | 7 + .../jetty/http2/hpack/HpackDecoderTest.java | 152 +++++------------- .../jetty/http2/hpack/HpackEncoderTest.java | 24 +++ .../test/resources/jetty-logging.properties | 2 +- 4 files changed, 71 insertions(+), 114 deletions(-) diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java index 74b8b87deb9..083f10b8bc5 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackDecoder.java @@ -74,6 +74,8 @@ public class HpackDecoder // If the buffer is big, don't even think about decoding it if (buffer.remaining()>_builder.getMaxSize()) throw new HpackException.SessionException("431 Request Header Fields too large"); + + boolean emitted = false; while(buffer.hasRemaining()) { @@ -99,6 +101,7 @@ public class HpackDecoder if (LOG.isDebugEnabled()) LOG.debug("decode IdxStatic {}",entry); // emit field + emitted = true; _builder.emit(entry.getHttpField()); // TODO copy and add to reference set if there is room @@ -109,6 +112,7 @@ public class HpackDecoder if (LOG.isDebugEnabled()) LOG.debug("decode Idx {}",entry); // emit + emitted = true; _builder.emit(entry.getHttpField()); } } @@ -133,6 +137,8 @@ public class HpackDecoder LOG.debug("decode resize="+size); if (size>_localMaxDynamicTableSize) throw new IllegalArgumentException(); + if (emitted) + throw new HpackException.CompressionException("Dynamic table resize after fields"); _context.resize(size); continue; @@ -240,6 +246,7 @@ public class HpackDecoder } // emit the field + emitted = true; _builder.emit(field); // if indexed add to dynamic table diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java index e6241d288d4..05b21814c39 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackDecoderTest.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http2.hpack.HpackException.CompressionException; import org.eclipse.jetty.http2.hpack.HpackException.StreamException; import org.eclipse.jetty.util.TypeUtil; import org.hamcrest.Matchers; @@ -188,26 +189,55 @@ public class HpackDecoderTest @Test public void testResize() throws Exception { - String encoded = "3f6166871e33A13a47497f205f8841E92b043d492d49"; + String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(4096, 8192); MetaData metaData = decoder.decode(buffer); - assertThat(metaData.getFields().get(HttpHeader.HOST),is("aHostName")); - assertThat(metaData.getFields().get(HttpHeader.CONTENT_TYPE),is("some/content")); - assertThat(decoder.getHpackContext().getDynamicTableSize(),is(0)); + assertThat(metaData.getFields().get(HttpHeader.HOST),is( "localhost0")); + assertThat(metaData.getFields().get(HttpHeader.COOKIE),is("abcdefghij")); + assertThat(decoder.getHpackContext().getMaxDynamicTableSize(),is(50)); + assertThat(decoder.getHpackContext().size(),is(1)); + + + } + + @Test + public void testBadResize() throws Exception + { + /* + 4. Dynamic Table Management + 4.2. Maximum Table Size + × 1: Sends a dynamic table size update at the end of header block + -> The endpoint MUST treat this as a decoding error. + Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) + Connection closed + */ + + String encoded = "203f136687A0E41d139d090760881c6490B2Cd39Ba7f20"; + ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); + HpackDecoder decoder = new HpackDecoder(4096, 8192); + try + { + decoder.decode(buffer); + Assert.fail(); + } + catch(CompressionException e) + { + Assert.assertThat(e.getMessage(),Matchers.containsString("Dynamic table resize after fields")); + } } @Test public void testTooBigToIndex() throws Exception { - String encoded = "44FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; + String encoded = "3f610f17FfEc02Df3990A190A0D4Ee5b3d2940Ec98Aa4a62D127D29e273a0aA20dEcAa190a503b262d8a2671D4A2672a927aA874988a2471D05510750c951139EdA2452a3a548cAa1aA90bE4B228342864A9E0D450A5474a92992a1aA513395448E3A0Aa17B96cFe3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f14E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F3E7Cf9f3e7cF9F353F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F7F54f"; ByteBuffer buffer = ByteBuffer.wrap(TypeUtil.fromHexString(encoded)); HpackDecoder decoder = new HpackDecoder(128,8192); MetaData metaData = decoder.decode(buffer); assertThat(decoder.getHpackContext().getDynamicTableSize(),is(0)); - assertThat(((MetaData.Request)metaData).getURI().toString(),Matchers.startsWith("This is a very large field")); + assertThat(metaData.getFields().get("host"),Matchers.startsWith("This is a very large field")); } @Test @@ -430,131 +460,27 @@ public class HpackDecoderTest { Assert.assertThat(ex.getMessage(),Matchers.containsString("Duplicate")); } - - - } /* - 8.1.2.3. Request Pseudo-Header Fields - - 8.1.2.6. Malformed Requests and Responses - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:18, flags:0x04, stream_id:1) - [send] DATA Frame (length:4, flags:0x01, stream_id:1) - [recv] HEADERS Frame (length:100, flags:0x04, stream_id:1) - [recv] DATA Frame (length:687, flags:0x01, stream_id:1) - [recv] Timeout - × 1: Sends a HEADERS frame with the "content-length" header field which does not equal the DATA frame payload length - -> The endpoint MUST treat this as a stream error of type PROTOCOL_ERROR. - Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR) - RST_STREAM Frame (Error Code: PROTOCOL_ERROR) - Connection closed - Actual: DATA Frame (length:687, flags:0x01, stream_id:1) - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:18, flags:0x04, stream_id:1) - [send] DATA Frame (length:4, flags:0x00, stream_id:1) - [send] DATA Frame (length:4, flags:0x01, stream_id:1) - [recv] HEADERS Frame (length:100, flags:0x04, stream_id:1) - [recv] DATA Frame (length:687, flags:0x01, stream_id:1) - [recv] Timeout - × 2: Sends a HEADERS frame with the "content-length" header field which does not equal the sum of the multiple DATA frames payload length - -> The endpoint MUST treat this as a stream error of type PROTOCOL_ERROR. - Expected: GOAWAY Frame (Error Code: PROTOCOL_ERROR) - RST_STREAM Frame (Error Code: PROTOCOL_ERROR) - Connection closed - Actual: DATA Frame (length:687, flags:0x01, stream_id:1) - - 8.2. Server Push - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] PUSH_PROMISE Frame (length:19, flags:0x04, stream_id:1) - [recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0) - ✔ 1: Sends a PUSH_PROMISE frame - - HPACK: Header Compression for HTTP/2 - 2. Compression Process Overview - 2.3. Indexing Tables - 2.3.3. Index Address Space - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:16, flags:0x05, stream_id:1) - [recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0) - [recv] Connection closed - ✔ 1: Sends a header field representation with invalid index - - 4. Dynamic Table Management - 4.2. Maximum Table Size - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:16, flags:0x05, stream_id:1) - [recv] HEADERS Frame (length:101, flags:0x04, stream_id:1) - [recv] DATA Frame (length:687, flags:0x01, stream_id:1) - [recv] Timeout - × 1: Sends a dynamic table size update at the end of header block - -> The endpoint MUST treat this as a decoding error. - Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) - Connection closed - Actual: DATA Frame (length:687, flags:0x01, stream_id:1) + 8.1.2.6. Malformed Requests and Responses + × 1: Sends a HEADERS frame with the "content-length" header field which does not equal the DATA frame payload length + × 2: Sends a HEADERS frame with the "content-length" header field which does not equal the sum of the multiple DATA frames payload length 5. Primitive Type Representations 5.2. String Literal Representation - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:27, flags:0x05, stream_id:1) - [recv] HEADERS Frame (length:101, flags:0x04, stream_id:1) - [recv] DATA Frame (length:687, flags:0x01, stream_id:1) - [recv] Timeout × 1: Sends a Huffman-encoded string literal representation with padding longer than 7 bits -> The endpoint MUST treat this as a decoding error. Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) Connection closed Actual: DATA Frame (length:687, flags:0x01, stream_id:1) - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:26, flags:0x05, stream_id:1) - [recv] HEADERS Frame (length:101, flags:0x04, stream_id:1) - [recv] DATA Frame (length:687, flags:0x01, stream_id:1) - [recv] Timeout × 2: Sends a Huffman-encoded string literal representation padded by zero -> The endpoint MUST treat this as a decoding error. Expected: GOAWAY Frame (Error Code: COMPRESSION_ERROR) Connection closed Actual: DATA Frame (length:687, flags:0x01, stream_id:1) - [send] SETTINGS Frame (length:6, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:24, flags:0x00, stream_id:0) - [send] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [recv] WINDOW_UPDATE Frame (length:4, flags:0x00, stream_id:0) - [recv] SETTINGS Frame (length:0, flags:0x01, stream_id:0) - [send] HEADERS Frame (length:28, flags:0x05, stream_id:1) - [recv] GOAWAY Frame (length:20, flags:0x00, stream_id:0) - [recv] Connection closed ✔ 3: Sends a Huffman-encoded string literal representation containing the EOS symbol - */ } diff --git a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java index 00349c46017..66381b69c35 100644 --- a/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java +++ b/jetty-http2/http2-hpack/src/test/java/org/eclipse/jetty/http2/hpack/HpackEncoderTest.java @@ -249,4 +249,28 @@ public class HpackEncoderTest context.get(HpackContext.STATIC_SIZE+1).getSize()+context.get(HpackContext.STATIC_SIZE+2).getSize())); } + + @Test + public void testResize() + { + HttpFields fields = new HttpFields(); + fields.add("host", "localhost0"); + fields.add("cookie","abcdefghij"); + + HpackEncoder encoder = new HpackEncoder(4096); + + ByteBuffer buffer = BufferUtil.allocate(4096); + int pos = BufferUtil.flipToFill(buffer); + encoder.encodeMaxDynamicTableSize(buffer,0); + encoder.setRemoteMaxDynamicTableSize(50); + encoder.encode(buffer,new MetaData(HttpVersion.HTTP_2,fields)); + BufferUtil.flipToFlush(buffer,pos); + + HpackContext context = encoder.getHpackContext(); + + Assert.assertThat(context.getMaxDynamicTableSize(),Matchers.is(50)); + Assert.assertThat(context.size(),Matchers.is(1)); + + + } } diff --git a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties b/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties index d33a7c32778..e40e8e43ce1 100644 --- a/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties +++ b/jetty-http2/http2-hpack/src/test/resources/jetty-logging.properties @@ -1,3 +1,3 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.http2.LEVEL=INFO -org.eclipse.jetty.http2.hpack.LEVEL=DEBUG +org.eclipse.jetty.http2.hpack.LEVEL=INFO