diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java index 1a9e2d7..6ad6919 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Headers.java @@ -247,10 +247,16 @@ private static String readData(InputStream in) throws IOException { private static void writeData(Buffers out, String value) throws InterruptedException { byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - // TODO huffman - writeInt(out, 0, 7, bytes.length); - out.write(bytes); + Buffers b = new Buffers(); + Http2Huffman.encode(b, bytes); + if (b.length() < bytes.length) { + writeInt(out, 0x80, 7, b.length()); + b.read(out, -1, false); + } else { + writeInt(out, 0, 7, bytes.length); + out.write(bytes); + } } protected Entry get(int i) { diff --git a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java index 7c6e144..fd8441e 100644 --- a/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java +++ b/unknow-server-servlet/src/main/java/unknow/server/servlet/http2/Http2Huffman.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.io.InputStream; +import unknow.server.util.io.Buffers; + /** * HPACK static huffman table * https://httpwg.org/specs/rfc7541.html#huffman.code @@ -37,6 +39,29 @@ public class Http2Huffman { 10, 13, 22, 256 // 29, 30 }; + private static final int[] codes = { 0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, 0xffffea, 0x3ffffffc, 0xfffffe9, + 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, 0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, 0xffffff5, + 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, + 0x17, 0x18, 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb, 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, 0x1ffc, 0x3ffc, 0x22, 0x7ffd, + 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, 0x7fc, + 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, + 0xffffeb, 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3, 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, + 0x7fffe7, 0xffffef, 0x3fffda, 0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, 0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, + 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, 0x7ffff0, 0x3fffe5, + 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7, 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf, + 0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, 0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, + 0xffffffd, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, + 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0xffffffe, 0x7ffffec, + 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee }; + + private static final int[] sizes = { 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, + 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, + 22, 23, 22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, + 23, 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, + 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26 }; + private Http2Huffman() { } @@ -71,6 +96,33 @@ public static String decode(InputStream b, int max, StringBuilder sb) throws IOE return sb.toString(); } + public static void encode(Buffers b, byte[] data) throws InterruptedException { + C c = new C(); + for (int i = 0; i < data.length; i++) + encode(c, b, data[i]); + if (c.cnt != 0) + b.write(c.bit | (0xff >> c.cnt)); + } + + private static void encode(C c, Buffers buf, byte b) throws InterruptedException { + int code = codes[b]; + int size = sizes[b]; + + while (size > 0) { + int r = 8 - c.cnt; + if (size <= r) { + c.cnt += size; + c.bit |= (code << (r - size)) & 0xFF; + return; + } + + size -= r; + buf.write(c.bit | (code >> size) & 0xFF); + c.bit = 0; + c.cnt = 0; + } + } + private static final char read(S s) throws IOException { int first = 0; /* first code of length len */ int index = 0; /* index of first code of length len in symbol table */ @@ -97,12 +149,14 @@ private static final char read(S s) throws IOException { } } - static final class S { - final InputStream b; - int max; - + static class C { int bit; int cnt; + } + + static class S extends C { + final InputStream b; + int max; public S(InputStream b, int max) { this.b = b; diff --git a/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java new file mode 100644 index 0000000..060f942 --- /dev/null +++ b/unknow-server-servlet/src/test/java/unknow/server/servlet/http2/Http2HuffmanTest.java @@ -0,0 +1,53 @@ +package unknow.server.servlet.http2; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import unknow.server.util.io.Buffers; +import unknow.server.util.io.BuffersUtils; + +public class Http2HuffmanTest { + public static Stream input() { + //@formatter:off + return Stream.of( + Arguments.of("www.example.com", new byte[] { b(0xf1), b(0xe3), b(0xc2), b(0xe5), b(0xf2), b(0x3a), b(0x6b), b(0xa0), b(0xab), b(0x90), b(0xf4), b(0xff) }), + Arguments.of("no-cache", new byte[]{b(0xa8),b(0xeb),b(0x10),b(0x64),b(0x9c),b(0xbf)}), + Arguments.of("custom-key", new byte[]{b(0x25),b(0xa8),b(0x49),b(0xe9),b(0x5b),b(0xa9),b(0x7d),b(0x7f)}), + Arguments.of("custom-value", new byte[]{b(0x25),b(0xa8),b(0x49),b(0xe9),b(0x5b),b(0xb8),b(0xe8),b(0xb4),b(0xbf)}) + ); //@formatter:on + } + + @ParameterizedTest + @MethodSource("input") + public void decode(String decoded, byte[] encoded) throws IOException { + StringBuilder sb = new StringBuilder(); + try (InputStream b = new ByteArrayInputStream(encoded)) { + Http2Huffman.decode(b, encoded.length, sb); + } + assertEquals(decoded, sb.toString()); + } + + @ParameterizedTest + @MethodSource("input") + public void encode(String decoded, byte[] encoded) throws InterruptedException { + byte[] bytes = decoded.getBytes(StandardCharsets.US_ASCII); + Buffers b = new Buffers(); + Http2Huffman.encode(b, bytes); + byte[] array = BuffersUtils.toArray(b, 0, -1); + assertArrayEquals(encoded, array); + } + + public static final byte b(int i) { + return (byte) (i & 0xFF); + } +}