diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000000..d29f0b121805
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "okhttp-hpacktests/src/test/resources/hpack-test-case"]
+ path = okhttp-hpacktests/src/test/resources/hpack-test-case
+ url = git://github.com/http2jp/hpack-test-case.git
diff --git a/okhttp-hpacktests/README.md b/okhttp-hpacktests/README.md
new file mode 100644
index 000000000000..f6b904996144
--- /dev/null
+++ b/okhttp-hpacktests/README.md
@@ -0,0 +1,22 @@
+OkHttp HPACK tests
+==================
+
+These tests use the [hpack-test-case][1] project to validate OkHttp's HPACK
+implementation. The HPACK test cases are in a separate git submodule, so to
+initialize them, you must run:
+
+ git submodule init
+ git submodule update
+
+When new interop tests are available, you should update
+HpackDecodeInteropGoodTest#GOOD_INTEROP_TESTS with the directory name.
+
+TODO
+----
+
+ * Add maven goal to avoid manual call to git submodule init.
+ * Make hpack-test-case update itself from git, and run new tests.
+ * Add maven goal to generate stories and a pull request to hpack-test-case
+ to have others validate our output.
+
+[1]: https://github.com/http2jp/hpack-test-case
diff --git a/okhttp-hpacktests/pom.xml b/okhttp-hpacktests/pom.xml
new file mode 100644
index 000000000000..0e8849076c26
--- /dev/null
+++ b/okhttp-hpacktests/pom.xml
@@ -0,0 +1,57 @@
+
+
+
+ 4.0.0
+
+
+ com.squareup.okhttp
+ parent
+ 2.0.1-SNAPSHOT
+
+
+ okhttp-hpacktests
+ OkHttp HPACK Tests
+
+
+
+ com.squareup.okio
+ okio
+
+
+ com.squareup.okhttp
+ okhttp
+ ${project.version}
+
+
+ junit
+ junit
+ test
+
+
+ com.squareup.okhttp
+ mockwebserver
+ ${project.version}
+ test
+
+
+
+ com.google.code.gson
+ gson
+ 2.2.4
+ compile
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+
+
diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropBadTest.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropBadTest.java
new file mode 100644
index 000000000000..8d611ac91b09
--- /dev/null
+++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropBadTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.spdy.hpackjson.Story;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+
+/**
+ * Known bad tests for HPACK interop.
+ */
+// TODO: fix these tests (see if the input/test is legit, fix the implementation.)
+@Ignore
+@RunWith(Parameterized.class)
+public class HpackDecodeInteropBadTest extends HpackDecodeTestBase {
+
+ private static final String[] BAD_INTEROP_TESTS = { "go-hpack", "haskell-http2-diff-huffman",
+ "haskell-http2-linear-huffman", "haskell-http2-naive-huffman",
+ "haskell-http2-static-huffman", "node-http2-protocol", "twitter-hpack" };
+
+ public HpackDecodeInteropBadTest(Story story) {
+ super(story);
+ }
+
+ @Parameterized.Parameters(name="{0}")
+ public static Collection createStories() throws Exception {
+ return createStories(BAD_INTEROP_TESTS);
+ }
+
+ @Test
+ public void testGoodDecoderInterop() throws Exception {
+ testDecoder();
+ }
+}
diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropGoodTest.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropGoodTest.java
new file mode 100644
index 000000000000..bf29e9e5c006
--- /dev/null
+++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropGoodTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.spdy.hpackjson.Story;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+
+/**
+ * Known good tests for HPACK interop.
+ */
+@RunWith(Parameterized.class)
+public class HpackDecodeInteropGoodTest extends HpackDecodeTestBase {
+
+
+ private static final String[] GOOD_INTEROP_TESTS = { "haskell-http2-diff",
+ "haskell-http2-linear", "haskell-http2-naive", "haskell-http2-static",
+ "hyper-hpack", "nghttp2", "nghttp2-16384-4096",
+ "nghttp2-change-table-size", "node-http2-hpack" };
+
+ public HpackDecodeInteropGoodTest(Story story) {
+ super(story);
+ }
+
+ @Parameterized.Parameters(name="{0}")
+ public static Collection createStories() throws Exception {
+ return createStories(GOOD_INTEROP_TESTS);
+ }
+
+ @Test
+ public void testGoodDecoderInterop() throws Exception {
+ testDecoder();
+ }
+}
diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeTestBase.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeTestBase.java
new file mode 100644
index 000000000000..f1d68a3f3875
--- /dev/null
+++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeTestBase.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.spdy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.squareup.okhttp.internal.spdy.hpackjson.Case;
+import com.squareup.okhttp.internal.spdy.hpackjson.HpackJsonUtil;
+import com.squareup.okhttp.internal.spdy.hpackjson.Story;
+import okio.Buffer;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * Tests Hpack implementation using https://github.com/http2jp/hpack-test-case/
+ */
+public class HpackDecodeTestBase {
+
+ /**
+ * Reads all stories in the folders provided, asserts if no story found.
+ */
+ protected static Collection createStories(String[] interopTests)
+ throws Exception {
+ List result = new ArrayList<>();
+ for (String interopTestName : interopTests) {
+ List stories = HpackJsonUtil.readStories(interopTestName);
+ if (stories.isEmpty()) {
+ fail("No stories for: " + interopTestName);
+ }
+ for (Story story : stories) {
+ result.add(new Story[] { story });
+ }
+ }
+ return result;
+ }
+
+ private final Buffer bytesIn = new Buffer();
+ private final HpackDraft08.Reader hpackReader = new HpackDraft08.Reader(4096, bytesIn);
+
+ private final Story story;
+
+ public HpackDecodeTestBase(Story story) {
+ this.story = story;
+ }
+
+ /**
+ * Expects wire to be set for all cases, and compares the decoder's output to
+ * expected headers.
+ */
+ protected void testDecoder() throws Exception {
+ testDecoder(story);
+ }
+
+ protected void testDecoder(Story story) throws Exception {
+ for (Case caze : story.getCases()) {
+ bytesIn.write(caze.getWire());
+ hpackReader.readHeaders();
+ hpackReader.emitReferenceSet();
+ assertSetEquals(String.format("seqno=%d", caze.getSeqno()), caze.getHeaders(),
+ hpackReader.getAndReset());
+ }
+ }
+ /**
+ * Checks if {@code expected} and {@code observed} are equal when viewed as a
+ * set and headers are deduped.
+ *
+ * TODO: See if duped headers should be preserved on decode and verify.
+ */
+ private static void assertSetEquals(
+ String message, List expected, List observed) {
+ assertEquals(message, new LinkedHashSet<>(expected), new LinkedHashSet<>(observed));
+ }
+
+ protected Story getStory() {
+ return story;
+ }
+}
diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackRoundTripTest.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackRoundTripTest.java
new file mode 100644
index 000000000000..c55bb8b27895
--- /dev/null
+++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackRoundTripTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.spdy;
+
+import com.squareup.okhttp.internal.spdy.hpackjson.Case;
+import com.squareup.okhttp.internal.spdy.hpackjson.Story;
+import okio.Buffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+
+/**
+ * Tests for round-tripping headers through hpack..
+ */
+// TODO: update hpack-test-case with the output of our encoder.
+// This test will hide complementary bugs in the encoder and decoder,
+// We should test that the encoder is producing responses that are
+// d]
+@RunWith(Parameterized.class)
+public class HpackRoundTripTest extends HpackDecodeTestBase {
+
+ private static final String[] RAW_DATA = { "raw-data" };
+
+ @Parameterized.Parameters(name="{0}")
+ public static Collection getStories() throws Exception {
+ return createStories(RAW_DATA);
+ }
+
+ private Buffer bytesOut = new Buffer();
+ private HpackDraft08.Writer hpackWriter = new HpackDraft08.Writer(bytesOut);
+
+ public HpackRoundTripTest(Story story) {
+ super(story);
+ }
+
+ @Test
+ public void testRoundTrip() throws Exception {
+ Story story = getStory().clone();
+ // Mutate cases in base class.
+ for (Case caze : story.getCases()) {
+ hpackWriter.writeHeaders(caze.getHeaders());
+ caze.setWire(bytesOut.readByteString());
+ }
+
+ testDecoder(story);
+ }
+
+}
diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Case.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Case.java
new file mode 100644
index 000000000000..d5d272872cca
--- /dev/null
+++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Case.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.squareup.okhttp.internal.spdy.hpackjson;
+
+import com.squareup.okhttp.internal.spdy.Header;
+import okio.ByteString;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Representation of an individual case (set of headers and wire format).
+ * There are many cases for a single story. This class is used reflectively
+ * with Gson to parse stories.
+ */
+public class Case implements Cloneable {
+
+ private int seqno;
+ private String wire;
+ private List