[
  {
    "path": ".gitignore",
    "content": "target\n.idea\n*.iml\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\nsudo: false\njdk:\n  - openjdk7\nafter_success:\n  - mvn clean cobertura:cobertura coveralls:cobertura\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "README.md",
    "content": "# netty-http2 [![Build Status](https://travis-ci.org/twitter/netty-http2.svg)](https://travis-ci.org/twitter/netty-http2)\n\nLegacy HTTP/2 codec for Netty. Use the codec shipped with Netty 4.1 instead https://github.com/netty/netty/tree/4.1/codec-http2\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"https://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"https://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.twitter</groupId>\n    <artifactId>netty-http2</artifactId>\n    <version>0.17.12-SNAPSHOT</version>\n    <name>HTTP/2</name>\n    <url>http://github.com/twitter/netty-http2</url>\n    <description>HTTP/2 for Netty</description>\n\n    <scm>\n        <connection>scm:git:git@github.com:twitter/netty-http2.git</connection>\n        <url>scm:git:git@github.com:twitter/netty-http2.git</url>\n        <developerConnection>scm:git:git@github.com:twitter/netty-http2.git</developerConnection>\n    </scm>\n\n    <licenses>\n        <license>\n            <name>The Apache Software License, Version 2.0</name>\n            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>\n        </license>\n    </licenses>\n\n    <developers>\n        <developer>\n            <name>Jeff Pinner</name>\n            <email>jpinner@twitter.com</email>\n        </developer>\n    </developers>\n\n    <properties>\n        <maven.compiler.source>1.6</maven.compiler.source>\n        <maven.compiler.target>1.6</maven.compiler.target>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <encoding>UTF-8</encoding>\n    </properties>\n\n    <distributionManagement>\n        <snapshotRepository>\n            <id>sonatype-nexus-snapshots</id>\n            <name>Sonatype OSS</name>\n            <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n        </snapshotRepository>\n        <repository>\n            <id>sonatype-nexus-staging</id>\n            <name>Nexus Release Repository</name>\n            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>\n        </repository>\n    </distributionManagement>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.twitter</groupId>\n            <artifactId>hpack</artifactId>\n            <version>1.0.2</version>\n        </dependency>\n        <dependency>\n            <groupId>io.netty</groupId>\n            <artifactId>netty-codec-http</artifactId>\n            <version>4.1.0.CR4</version>\n        </dependency>\n        <!-- Test Deps -->\n        <dependency>\n            <groupId>junit</groupId>\n            <artifactId>junit</artifactId>\n            <version>4.12</version>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-core</artifactId>\n            <version>1.9.5</version>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n    <repositories>\n        <repository>\n            <id>sonatype-nexus-snapshots</id>\n            <url>https://oss.sonatype.org/content/repositories/snapshots</url>\n            <releases>\n                <enabled>false</enabled>\n            </releases>\n            <snapshots>\n                <enabled>true</enabled>\n            </snapshots>\n        </repository>\n    </repositories>\n\n    <build>\n        <pluginManagement>\n            <plugins>\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-release-plugin</artifactId>\n                    <version>2.1</version>\n                    <configuration>\n                        <mavenExecutorId>forked-path</mavenExecutorId>\n                        <useReleaseProfile>false</useReleaseProfile>\n                        <arguments>-Psonatype-oss-release</arguments>\n                    </configuration>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <version>2.5.1</version>\n                <configuration>\n                    <source>1.6</source>\n                    <target>1.6</target>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-surefire-plugin</artifactId>\n                <version>2.12</version>\n                <configuration>\n                    <argLine>-Xmx1024m</argLine>\n                    <redirectTestOutputToFile>false</redirectTestOutputToFile>\n                    <includes>\n                        <include>**/Test*.java</include>\n                        <include>**/*Test.java</include>\n                        <include>**/*Spec.java</include>\n                    </includes>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>cobertura-maven-plugin</artifactId>\n                <version>2.5.2</version>\n                <configuration>\n                    <format>xml</format>\n                    <maxmem>256m</maxmem>\n                    <aggregate>true</aggregate>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.eluder.coveralls</groupId>\n                <artifactId>coveralls-maven-plugin</artifactId>\n                <version>2.2.0</version>\n            </plugin>\n        </plugins>\n    </build>\n    <profiles>\n        <profile>\n            <id>sonatype-oss-release</id>\n            <build>\n                <plugins>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-source-plugin</artifactId>\n                        <version>2.1.2</version>\n                        <executions>\n                            <execution>\n                                <id>attach-sources</id>\n                                <goals>\n                                    <goal>jar-no-fork</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-javadoc-plugin</artifactId>\n                        <version>2.7</version>\n                        <executions>\n                            <execution>\n                                <id>attach-javadocs</id>\n                                <goals>\n                                    <goal>jar</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                    <plugin>\n                        <groupId>org.apache.maven.plugins</groupId>\n                        <artifactId>maven-gpg-plugin</artifactId>\n                        <version>1.1</version>\n                        <executions>\n                            <execution>\n                                <id>sign-artifacts</id>\n                                <phase>verify</phase>\n                                <goals>\n                                    <goal>sign</goal>\n                                </goals>\n                            </execution>\n                        </executions>\n                    </plugin>\n                </plugins>\n            </build>\n        </profile>\n    </profiles>\n</project>\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttp2Headers.java",
    "content": "package com.twitter.http2;\n\nimport io.netty.handler.codec.DefaultHeaders.NameValidator;\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\nimport io.netty.util.AsciiString;\nimport io.netty.util.ByteProcessor;\nimport io.netty.util.internal.PlatformDependent;\n\nimport static io.netty.util.internal.ObjectUtil.checkNotNull;\n\npublic class DefaultHttp2Headers extends DefaultHttpHeaders {\n  private static final ByteProcessor HEADER_NAME_VALIDATOR = new ByteProcessor() {\n    @Override\n    public boolean process(byte value) throws Exception {\n      validateChar((char) (value & 0xFF));\n      return true;\n    }\n  };\n\n  private static void validateChar(char character) {\n    switch (character) {\n      case '\\t':\n      case '\\n':\n      case 0x0b:\n      case '\\f':\n      case '\\r':\n      case ' ':\n      case ',':\n      case ';':\n      case '=':\n        throw new IllegalArgumentException(\n                \"a header name cannot contain the following prohibited characters: =,; \\\\t\\\\r\\\\n\\\\v\\\\f: \" +\n                        character);\n      default:\n        // Check to see if the character is not an ASCII character, or invalid\n        if (character > 127) {\n          throw new IllegalArgumentException(\"a header name cannot contain non-ASCII character: \" +\n                  character);\n        }\n    }\n  }\n\n  static final NameValidator<CharSequence> Http2NameValidator = new NameValidator<CharSequence>() {\n    @Override\n    public void validateName(CharSequence name) {\n      if (name instanceof AsciiString) {\n        try {\n          ((AsciiString) name).forEachByte(HEADER_NAME_VALIDATOR);\n        } catch (Exception e) {\n          PlatformDependent.throwException(e);\n        }\n      } else {\n        checkNotNull(name, \"name\");\n        // Go through each character in the name\n        for (int index = 0; index < name.length(); ++index) {\n          validateChar(name.charAt(index));\n        }\n      }\n    }\n  };\n\n  public DefaultHttp2Headers() {\n    super(true, Http2NameValidator);\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpDataFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufHolder;\nimport io.netty.buffer.Unpooled;\nimport io.netty.util.IllegalReferenceCountException;\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpDataFrame} implementation.\n */\npublic class DefaultHttpDataFrame extends DefaultHttpStreamFrame implements HttpDataFrame {\n\n    private final ByteBuf data;\n    private boolean last;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     */\n    public DefaultHttpDataFrame(int streamId) {\n        this(streamId, Unpooled.buffer(0));\n    }\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     * @param data     the payload of the frame. Can not exceed {@link HttpCodecUtil#HTTP_MAX_LENGTH}\n     */\n    public DefaultHttpDataFrame(int streamId, ByteBuf data) {\n        super(streamId);\n        if (data == null) {\n            throw new NullPointerException(\"data\");\n        }\n        this.data = validate(data);\n    }\n\n    private static ByteBuf validate(ByteBuf data) {\n        if (data.readableBytes() > HttpCodecUtil.HTTP_MAX_LENGTH) {\n            throw new IllegalArgumentException(\"data payload cannot exceed \"\n                    + HttpCodecUtil.HTTP_MAX_LENGTH + \" bytes\");\n        }\n        return data;\n    }\n\n    @Override\n    public boolean isLast() {\n        return last;\n    }\n\n    @Override\n    public HttpDataFrame setLast(boolean last) {\n        this.last = last;\n        return this;\n    }\n\n    @Override\n    public HttpDataFrame setStreamId(int streamId) {\n        super.setStreamId(streamId);\n        return this;\n    }\n\n    @Override\n    public ByteBuf content() {\n        if (data.refCnt() <= 0) {\n            throw new IllegalReferenceCountException(data.refCnt());\n        }\n        return data;\n    }\n\n    @Override\n    public HttpDataFrame copy() {\n        HttpDataFrame frame = new DefaultHttpDataFrame(getStreamId(), content().copy());\n        frame.setLast(isLast());\n        return frame;\n    }\n\n    @Override\n    public HttpDataFrame duplicate() {\n        HttpDataFrame frame = new DefaultHttpDataFrame(getStreamId(), content().duplicate());\n        frame.setLast(isLast());\n        return frame;\n    }\n\n    @Override\n    public int refCnt() {\n        return data.refCnt();\n    }\n\n    @Override\n    public HttpDataFrame retain() {\n        data.retain();\n        return this;\n    }\n\n    @Override\n    public HttpDataFrame retain(int increment) {\n        data.retain(increment);\n        return this;\n    }\n\n    @Override\n    public boolean release() {\n        return data.release();\n    }\n\n    @Override\n    public boolean release(int decrement) {\n        return data.release(decrement);\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(\"(last: \");\n        buf.append(isLast());\n        buf.append(')');\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Stream-ID = \");\n        buf.append(getStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Size = \");\n        if (refCnt() == 0) {\n            buf.append(\"(freed)\");\n        } else {\n            buf.append(content().readableBytes());\n        }\n        return buf.toString();\n    }\n\n    @Override\n    public ByteBufHolder touch() {\n        data.touch();\n        return this;\n    }\n\n    @Override\n    public ByteBufHolder touch(Object o) {\n        data.touch(o);\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpGoAwayFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpGoAwayFrame} implementation.\n */\npublic class DefaultHttpGoAwayFrame implements HttpGoAwayFrame {\n\n    private int lastStreamId;\n    private HttpErrorCode errorCode;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param lastStreamId the Last-Stream-ID of this frame\n     * @param code         the error code of this frame\n     */\n    public DefaultHttpGoAwayFrame(int lastStreamId, int code) {\n        this(lastStreamId, HttpErrorCode.valueOf(code));\n    }\n\n    /**\n     * Creates a new instance.\n     *\n     * @param lastStreamId the Last-Stream-ID of this frame\n     * @param errorCode    the error code of this frame\n     */\n    public DefaultHttpGoAwayFrame(int lastStreamId, HttpErrorCode errorCode) {\n        setLastStreamId(lastStreamId);\n        setErrorCode(errorCode);\n    }\n\n    @Override\n    public int getLastStreamId() {\n        return lastStreamId;\n    }\n\n    @Override\n    public HttpGoAwayFrame setLastStreamId(int lastStreamId) {\n        if (lastStreamId < 0) {\n            throw new IllegalArgumentException(\n                    \"Last-Stream-ID cannot be negative: \" + lastStreamId);\n        }\n        this.lastStreamId = lastStreamId;\n        return this;\n    }\n\n    @Override\n    public HttpErrorCode getErrorCode() {\n        return errorCode;\n    }\n\n    @Override\n    public HttpGoAwayFrame setErrorCode(HttpErrorCode errorCode) {\n        this.errorCode = errorCode;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Last-Stream-ID = \");\n        buf.append(getLastStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Error Code: \");\n        buf.append(getErrorCode().toString());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpHeaderBlockFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.util.internal.StringUtil;\n\nimport java.util.Map;\n\n/**\n * The default {@link HttpHeaderBlockFrame} implementation.\n */\npublic abstract class DefaultHttpHeaderBlockFrame extends DefaultHttpStreamFrame\n        implements HttpHeaderBlockFrame {\n\n    private boolean invalid;\n    private boolean truncated;\n    private final HttpHeaders headers = new DefaultHttp2Headers();\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     */\n    protected DefaultHttpHeaderBlockFrame(int streamId) {\n        super(streamId);\n    }\n\n    @Override\n    public boolean isInvalid() {\n        return invalid;\n    }\n\n    @Override\n    public HttpHeaderBlockFrame setInvalid() {\n        invalid = true;\n        return this;\n    }\n\n    @Override\n    public boolean isTruncated() {\n        return truncated;\n    }\n\n    @Override\n    public HttpHeaderBlockFrame setTruncated() {\n        truncated = true;\n        return this;\n    }\n\n    @Override\n    public HttpHeaders headers() {\n        return headers;\n    }\n\n    protected void appendHeaders(StringBuilder buf) {\n        for (Map.Entry<String, String> e : headers()) {\n            buf.append(\"    \");\n            buf.append(e.getKey());\n            buf.append(\": \");\n            buf.append(e.getValue());\n            buf.append(StringUtil.NEWLINE);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpHeadersFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_DEPENDENCY;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;\n\n/**\n * The default {@link HttpHeadersFrame} implementation.\n */\npublic class DefaultHttpHeadersFrame extends DefaultHttpHeaderBlockFrame\n        implements HttpHeadersFrame {\n\n    private boolean last;\n    private boolean exclusive = false;\n    private int dependency = HTTP_DEFAULT_DEPENDENCY;\n    private int weight = HTTP_DEFAULT_WEIGHT;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     */\n    public DefaultHttpHeadersFrame(int streamId) {\n        super(streamId);\n    }\n\n    @Override\n    public boolean isLast() {\n        return last;\n    }\n\n    @Override\n    public HttpHeadersFrame setLast(boolean last) {\n        this.last = last;\n        return this;\n    }\n\n    @Override\n    public boolean isExclusive() {\n        return exclusive;\n    }\n\n    @Override\n    public HttpHeadersFrame setExclusive(boolean exclusive) {\n        this.exclusive = exclusive;\n        return this;\n    }\n\n    @Override\n    public int getDependency() {\n        return dependency;\n    }\n\n    @Override\n    public HttpHeadersFrame setDependency(int dependency) {\n        if (dependency < 0) {\n            throw new IllegalArgumentException(\n                    \"Dependency cannot be negative: \" + dependency);\n        }\n        this.dependency = dependency;\n        return this;\n    }\n\n    @Override\n    public int getWeight() {\n        return weight;\n    }\n\n    @Override\n    public HttpHeadersFrame setWeight(int weight) {\n        if (weight <= 0 || weight > 256) {\n            throw new IllegalArgumentException(\n                    \"Illegal weight: \" + weight);\n        }\n        this.weight = weight;\n        return this;\n    }\n\n    @Override\n    public HttpHeadersFrame setStreamId(int streamId) {\n        super.setStreamId(streamId);\n        return this;\n    }\n\n    @Override\n    public HttpHeadersFrame setInvalid() {\n        super.setInvalid();\n        return this;\n    }\n\n    @Override\n    public HttpHeadersFrame setTruncated() {\n        super.setTruncated();\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(\"(last: \");\n        buf.append(isLast());\n        buf.append(')');\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Stream-ID = \");\n        buf.append(getStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Dependency = \");\n        buf.append(getDependency());\n        buf.append(\" (exclusive: \");\n        buf.append(isExclusive());\n        buf.append(\", weight: \");\n        buf.append(getWeight());\n        buf.append(')');\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Headers:\");\n        buf.append(StringUtil.NEWLINE);\n        appendHeaders(buf);\n\n        // Remove the last newline.\n        buf.setLength(buf.length() - StringUtil.NEWLINE.length());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpPingFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpPingFrame} implementation.\n */\npublic class DefaultHttpPingFrame implements HttpPingFrame {\n\n    private long data;\n    private boolean pong;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param data the data payload of this frame\n     */\n    public DefaultHttpPingFrame(long data) {\n        setData(data);\n    }\n\n    @Override\n    public long getData() {\n        return data;\n    }\n\n    @Override\n    public HttpPingFrame setData(long data) {\n        this.data = data;\n        return this;\n    }\n\n    @Override\n    public boolean isPong() {\n        return pong;\n    }\n\n    @Override\n    public HttpPingFrame setPong(boolean pong) {\n        this.pong = pong;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(\"(pong: \");\n        buf.append(isPong());\n        buf.append(')');\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Data = \");\n        buf.append(getData());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpPriorityFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpPriorityFrame} implementation.\n */\npublic class DefaultHttpPriorityFrame implements HttpPriorityFrame {\n\n    private int streamId;\n    private boolean exclusive;\n    private int dependency;\n    private int weight;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId   the stream identifier of this frame\n     * @param exclusive  if the dependency of the stream is exclusive\n     * @param dependency the dependency of the stream\n     * @param weight     the weight of the dependency of the stream\n     */\n    public DefaultHttpPriorityFrame(int streamId, boolean exclusive, int dependency, int weight) {\n        setStreamId(streamId);\n        setExclusive(exclusive);\n        setDependency(dependency);\n        setWeight(weight);\n    }\n\n    @Override\n    public int getStreamId() {\n        return streamId;\n    }\n\n    @Override\n    public HttpPriorityFrame setStreamId(int streamId) {\n        if (streamId <= 0) {\n            throw new IllegalArgumentException(\n                    \"Stream identifier must be positive: \" + streamId);\n        }\n        this.streamId = streamId;\n        return this;\n    }\n\n    @Override\n    public boolean isExclusive() {\n        return exclusive;\n    }\n\n    @Override\n    public HttpPriorityFrame setExclusive(boolean exclusive) {\n        this.exclusive = exclusive;\n        return this;\n    }\n\n    @Override\n    public int getDependency() {\n        return dependency;\n    }\n\n    @Override\n    public HttpPriorityFrame setDependency(int dependency) {\n        if (dependency < 0) {\n            throw new IllegalArgumentException(\n                    \"Dependency cannot be negative: \" + dependency);\n        }\n        this.dependency = dependency;\n        return this;\n    }\n\n    @Override\n    public int getWeight() {\n        return weight;\n    }\n\n    @Override\n    public HttpPriorityFrame setWeight(int weight) {\n        if (weight <= 0 || weight > 256) {\n            throw new IllegalArgumentException(\n                    \"Illegal weight: \" + weight);\n        }\n        this.weight = weight;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Stream-ID = \");\n        buf.append(getStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Dependency = \");\n        buf.append(getDependency());\n        buf.append(\" (exclusive: \");\n        buf.append(isExclusive());\n        buf.append(\", weight: \");\n        buf.append(getWeight());\n        buf.append(')');\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpPushPromiseFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpPushPromiseFrame} implementation.\n */\npublic class DefaultHttpPushPromiseFrame extends DefaultHttpHeaderBlockFrame\n        implements HttpPushPromiseFrame {\n\n    private int promisedStreamId;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     */\n    public DefaultHttpPushPromiseFrame(int streamId, int promisedStreamId) {\n        super(streamId);\n        setPromisedStreamId(promisedStreamId);\n    }\n\n    @Override\n    public int getPromisedStreamId() {\n        return promisedStreamId;\n    }\n\n    @Override\n    public HttpPushPromiseFrame setPromisedStreamId(int promisedStreamId) {\n        if (promisedStreamId <= 0) {\n            throw new IllegalArgumentException(\n                    \"Promised-Stream-ID must be positive: \" + promisedStreamId);\n        }\n        this.promisedStreamId = promisedStreamId;\n        return this;\n    }\n\n    @Override\n    public HttpPushPromiseFrame setStreamId(int streamId) {\n        super.setStreamId(streamId);\n        return this;\n    }\n\n    @Override\n    public HttpPushPromiseFrame setInvalid() {\n        super.setInvalid();\n        return this;\n    }\n\n    @Override\n    public HttpPushPromiseFrame setTruncated() {\n        super.setTruncated();\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Stream-ID = \");\n        buf.append(getStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Promised-Stream-ID = \");\n        buf.append(getPromisedStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Headers:\");\n        buf.append(StringUtil.NEWLINE);\n        appendHeaders(buf);\n\n        // Remove the last newline.\n        buf.setLength(buf.length() - StringUtil.NEWLINE.length());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpRstStreamFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpRstStreamFrame} implementation.\n */\npublic class DefaultHttpRstStreamFrame implements HttpRstStreamFrame {\n\n    private int streamId;\n    private HttpErrorCode errorCode;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     * @param code     the error code of this frame\n     */\n    public DefaultHttpRstStreamFrame(int streamId, int code) {\n        this(streamId, HttpErrorCode.valueOf(code));\n    }\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId  the stream identifier of this frame\n     * @param errorCode the error code of this frame\n     */\n    public DefaultHttpRstStreamFrame(int streamId, HttpErrorCode errorCode) {\n        setStreamId(streamId);\n        setErrorCode(errorCode);\n    }\n\n    @Override\n    public int getStreamId() {\n        return streamId;\n    }\n\n    @Override\n    public HttpRstStreamFrame setStreamId(int streamId) {\n        if (streamId <= 0) {\n            throw new IllegalArgumentException(\n                    \"Stream identifier must be positive: \" + streamId);\n        }\n        this.streamId = streamId;\n        return this;\n    }\n\n    @Override\n    public HttpErrorCode getErrorCode() {\n        return errorCode;\n    }\n\n    @Override\n    public HttpRstStreamFrame setErrorCode(HttpErrorCode errorCode) {\n        this.errorCode = errorCode;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Stream-ID = \");\n        buf.append(getStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Error Code: \");\n        buf.append(getErrorCode().toString());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpSettingsFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeMap;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpSettingsFrame} implementation.\n */\npublic class DefaultHttpSettingsFrame implements HttpSettingsFrame {\n\n    private final Map<Integer, Integer> settingsMap = new TreeMap<Integer, Integer>();\n    private boolean ack;\n\n    @Override\n    public Set<Integer> getIds() {\n        return settingsMap.keySet();\n    }\n\n    @Override\n    public boolean isSet(int id) {\n        return settingsMap.containsKey(id);\n    }\n\n    @Override\n    public int getValue(int id) {\n        if (settingsMap.containsKey(id)) {\n            return settingsMap.get(id);\n        } else {\n            return -1;\n        }\n    }\n\n    @Override\n    public HttpSettingsFrame setValue(int id, int value) {\n        if (id < 0 || id > HttpCodecUtil.HTTP_SETTINGS_MAX_ID) {\n            throw new IllegalArgumentException(\"Setting ID is not valid: \" + id);\n        }\n        settingsMap.put(id, value);\n        return this;\n    }\n\n    @Override\n    public HttpSettingsFrame removeValue(int id) {\n        settingsMap.remove(id);\n        return this;\n    }\n\n    @Override\n    public boolean isAck() {\n        return ack;\n    }\n\n    @Override\n    public HttpSettingsFrame setAck(boolean ack) {\n        this.ack = ack;\n        return this;\n    }\n\n    private Set<Map.Entry<Integer, Integer>> getSettings() {\n        return settingsMap.entrySet();\n    }\n\n    private void appendSettings(StringBuilder buf) {\n        for (Map.Entry<Integer, Integer> e : getSettings()) {\n            buf.append(\"--> \");\n            buf.append(e.getKey().toString());\n            buf.append(':');\n            buf.append(e.getValue().toString());\n            buf.append(StringUtil.NEWLINE);\n        }\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(\"(ack: \");\n        buf.append(isAck());\n        buf.append(')');\n        buf.append(StringUtil.NEWLINE);\n        appendSettings(buf);\n        buf.setLength(buf.length() - StringUtil.NEWLINE.length());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpStreamFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * The default {@link HttpStreamFrame} implementation.\n */\npublic abstract class DefaultHttpStreamFrame implements HttpStreamFrame {\n\n    private int streamId;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId the stream identifier of this frame\n     */\n    protected DefaultHttpStreamFrame(int streamId) {\n        setStreamId(streamId);\n    }\n\n    @Override\n    public int getStreamId() {\n        return streamId;\n    }\n\n    @Override\n    public HttpStreamFrame setStreamId(int streamId) {\n        if (streamId <= 0) {\n            throw new IllegalArgumentException(\n                    \"Stream identifier must be positive: \" + streamId);\n        }\n        this.streamId = streamId;\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/DefaultHttpWindowUpdateFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.util.internal.StringUtil;\n\n/**\n * The default {@link HttpWindowUpdateFrame} implementation.\n */\npublic class DefaultHttpWindowUpdateFrame implements HttpWindowUpdateFrame {\n\n    private int streamId;\n    private int windowSizeIncrement;\n\n    /**\n     * Creates a new instance.\n     *\n     * @param streamId            the stream identifier of this frame\n     * @param windowSizeIncrement the Window-Size-Increment of this frame\n     */\n    public DefaultHttpWindowUpdateFrame(int streamId, int windowSizeIncrement) {\n        setStreamId(streamId);\n        setWindowSizeIncrement(windowSizeIncrement);\n    }\n\n    @Override\n    public int getStreamId() {\n        return streamId;\n    }\n\n    @Override\n    public DefaultHttpWindowUpdateFrame setStreamId(int streamId) {\n        if (streamId < 0) {\n            throw new IllegalArgumentException(\n                    \"Stream identifier cannot be negative: \" + streamId);\n        }\n        this.streamId = streamId;\n        return this;\n    }\n\n    @Override\n    public int getWindowSizeIncrement() {\n        return windowSizeIncrement;\n    }\n\n    @Override\n    public DefaultHttpWindowUpdateFrame setWindowSizeIncrement(int windowSizeIncrement) {\n        if (windowSizeIncrement <= 0) {\n            throw new IllegalArgumentException(\n                    \"Window-Size-Increment must be positive: \" + windowSizeIncrement);\n        }\n        this.windowSizeIncrement = windowSizeIncrement;\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder buf = new StringBuilder();\n        buf.append(StringUtil.simpleClassName(this));\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Stream-ID = \");\n        buf.append(getStreamId());\n        buf.append(StringUtil.NEWLINE);\n        buf.append(\"--> Window-Size-Increment = \");\n        buf.append(getWindowSizeIncrement());\n        return buf.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpCodecUtil.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.buffer.ByteBuf;\n\nfinal class HttpCodecUtil {\n\n    static final int HTTP_FRAME_HEADER_SIZE = 9;\n    static final int HTTP_MAX_LENGTH = 0x4000; // Initial MAX_FRAME_SIZE value is 2^14\n\n    static final int HTTP_DATA_FRAME = 0x00;\n    static final int HTTP_HEADERS_FRAME = 0x01;\n    static final int HTTP_PRIORITY_FRAME = 0x02;\n    static final int HTTP_RST_STREAM_FRAME = 0x03;\n    static final int HTTP_SETTINGS_FRAME = 0x04;\n    static final int HTTP_PUSH_PROMISE_FRAME = 0x05;\n    static final int HTTP_PING_FRAME = 0x06;\n    static final int HTTP_GOAWAY_FRAME = 0x07;\n    static final int HTTP_WINDOW_UPDATE_FRAME = 0x08;\n    static final int HTTP_CONTINUATION_FRAME = 0x09;\n\n    static final byte HTTP_FLAG_ACK = 0x01;\n    static final byte HTTP_FLAG_END_STREAM = 0x01;\n    static final byte HTTP_FLAG_END_SEGMENT = 0x02;\n    static final byte HTTP_FLAG_END_HEADERS = 0x04;\n    static final byte HTTP_FLAG_PADDED = 0x08;\n    static final byte HTTP_FLAG_PRIORITY = 0x20;\n\n    static final int HTTP_DEFAULT_WEIGHT = 16;\n    static final int HTTP_DEFAULT_DEPENDENCY = 0;\n\n    static final int HTTP_SETTINGS_MAX_ID = 0xFFFF; // Identifier is a 16-bit field\n\n    static final int HTTP_CONNECTION_STREAM_ID = 0;\n\n    /**\n     * Reads a big-endian unsigned short integer from the buffer.\n     */\n    static int getUnsignedShort(ByteBuf buf, int offset) {\n        return (buf.getByte(offset) & 0xFF) << 8\n                | buf.getByte(offset + 1) & 0xFF;\n    }\n\n    /**\n     * Reads a big-endian unsigned medium integer from the buffer.\n     */\n    static int getUnsignedMedium(ByteBuf buf, int offset) {\n        return (buf.getByte(offset) & 0xFF) << 16\n                | (buf.getByte(offset + 1) & 0xFF) << 8\n                | buf.getByte(offset + 2) & 0xFF;\n    }\n\n    /**\n     * Reads a big-endian (31-bit) integer from the buffer.\n     */\n    static int getUnsignedInt(ByteBuf buf, int offset) {\n        return (buf.getByte(offset) & 0x7F) << 24\n                | (buf.getByte(offset + 1) & 0xFF) << 16\n                | (buf.getByte(offset + 2) & 0xFF) << 8\n                | buf.getByte(offset + 3) & 0xFF;\n    }\n\n    /**\n     * Reads a big-endian signed integer from the buffer.\n     */\n    static int getSignedInt(ByteBuf buf, int offset) {\n        return (buf.getByte(offset) & 0xFF) << 24\n                | (buf.getByte(offset + 1) & 0xFF) << 16\n                | (buf.getByte(offset + 2) & 0xFF) << 8\n                | buf.getByte(offset + 3) & 0xFF;\n    }\n\n    /**\n     * Reads a big-endian signed long from the buffer.\n     */\n    static long getSignedLong(ByteBuf buf, int offset) {\n        return ((long) buf.getByte(offset) & 0xFF) << 56\n                | ((long) buf.getByte(offset + 1) & 0xFF) << 48\n                | ((long) buf.getByte(offset + 2) & 0xFF) << 40\n                | ((long) buf.getByte(offset + 3) & 0xFF) << 32\n                | ((long) buf.getByte(offset + 4) & 0xFF) << 24\n                | ((long) buf.getByte(offset + 5) & 0xFF) << 16\n                | ((long) buf.getByte(offset + 6) & 0xFF) << 8\n                | (long) buf.getByte(offset + 7) & 0xFF;\n    }\n\n    /**\n     * Returns {@code true} if the stream identifier is for a server initiated stream.\n     */\n    static boolean isServerId(int streamId) {\n        // Server initiated streams have even stream identifiers\n        return streamId % 2 == 0;\n    }\n\n    private HttpCodecUtil() {\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpConnection.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Comparator;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentLinkedQueue;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport io.netty.channel.ChannelPromise;\nimport io.netty.util.internal.EmptyArrays;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_CONNECTION_STREAM_ID;\n\nfinal class HttpConnection {\n\n    private static final HttpProtocolException STREAM_CLOSED =\n            new HttpProtocolException(\"Stream closed\");\n\n    static {\n        STREAM_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);\n    }\n\n    private final AtomicInteger activeLocalStreams = new AtomicInteger();\n    private final AtomicInteger activeRemoteStreams = new AtomicInteger();\n    private final Map<Integer, Node> streams = new ConcurrentHashMap<Integer, Node>();\n\n    private final AtomicInteger sendWindowSize;\n    private final AtomicInteger receiveWindowSize;\n\n    public HttpConnection(int sendWindowSize, int receiveWindowSize) {\n        streams.put(HTTP_CONNECTION_STREAM_ID, new Node(null));\n        this.sendWindowSize = new AtomicInteger(sendWindowSize);\n        this.receiveWindowSize = new AtomicInteger(receiveWindowSize);\n    }\n\n    int numActiveStreams(boolean remote) {\n        if (remote) {\n            return activeRemoteStreams.get();\n        } else {\n            return activeLocalStreams.get();\n        }\n    }\n\n    boolean noActiveStreams() {\n        return activeRemoteStreams.get() + activeLocalStreams.get() == 0;\n    }\n\n    void acceptStream(\n            int streamId, boolean remoteSideClosed, boolean localSideClosed,\n            int streamSendWindowSize, int streamReceiveWindowSize, boolean remote) {\n        StreamState state = null;\n        if (!remoteSideClosed || !localSideClosed) {\n            state = new StreamState(\n                    remoteSideClosed, localSideClosed, streamSendWindowSize, streamReceiveWindowSize);\n        }\n        Node node = new Node(state);\n        node.parent = streams.get(HTTP_CONNECTION_STREAM_ID);\n        streams.put(streamId, node);\n        if (state != null) {\n            if (remote) {\n                activeRemoteStreams.incrementAndGet();\n            } else {\n                activeLocalStreams.incrementAndGet();\n            }\n        }\n    }\n\n    private StreamState removeActiveStream(int streamId, boolean remote) {\n        Node stream = streams.remove(streamId);\n        if (stream != null && stream.state != null) {\n            StreamState state = stream.state;\n            stream.close();\n            if (remote) {\n                activeRemoteStreams.decrementAndGet();\n            } else {\n                activeLocalStreams.decrementAndGet();\n            }\n            return state;\n        }\n        return null;\n    }\n\n    void removeStream(int streamId, boolean remote) {\n        StreamState state = removeActiveStream(streamId, remote);\n        if (state != null) {\n            state.clearPendingWrites(STREAM_CLOSED);\n        }\n    }\n\n    boolean isRemoteSideClosed(int streamId) {\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state == null || state.isRemoteSideClosed();\n    }\n\n    void closeRemoteSide(int streamId, boolean remote) {\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        if (state != null) {\n            state.closeRemoteSide();\n            if (state.isLocalSideClosed()) {\n                removeActiveStream(streamId, remote);\n            }\n        }\n    }\n\n    boolean isLocalSideClosed(int streamId) {\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state == null || state.isLocalSideClosed();\n    }\n\n    void closeLocalSide(int streamId, boolean remote) {\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        if (state != null) {\n            state.closeLocalSide();\n            if (state.isRemoteSideClosed()) {\n                removeActiveStream(streamId, remote);\n            }\n        }\n    }\n\n    int getSendWindowSize(int streamId) {\n        if (streamId == HTTP_CONNECTION_STREAM_ID) {\n            return sendWindowSize.get();\n        }\n\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state != null ? state.getSendWindowSize() : -1;\n    }\n\n    int updateSendWindowSize(int streamId, int deltaWindowSize) {\n        if (streamId == HTTP_CONNECTION_STREAM_ID) {\n            return sendWindowSize.addAndGet(deltaWindowSize);\n        }\n\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state != null ? state.updateSendWindowSize(deltaWindowSize) : -1;\n    }\n\n    int updateReceiveWindowSize(int streamId, int deltaWindowSize) {\n        if (streamId == HTTP_CONNECTION_STREAM_ID) {\n            return receiveWindowSize.addAndGet(deltaWindowSize);\n        }\n\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        if (state == null) {\n            return -1;\n        }\n        if (deltaWindowSize > 0) {\n            state.setReceiveWindowSizeLowerBound(0);\n        }\n        return state.updateReceiveWindowSize(deltaWindowSize);\n    }\n\n    int getReceiveWindowSizeLowerBound(int streamId) {\n        if (streamId == HTTP_CONNECTION_STREAM_ID) {\n            return 0;\n        }\n\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state != null ? state.getReceiveWindowSizeLowerBound() : 0;\n    }\n\n    void updateAllSendWindowSizes(int deltaWindowSize) {\n        for (Node stream : streams.values()) {\n            StreamState state = stream.state;\n            if (state != null) {\n                state.updateSendWindowSize(deltaWindowSize);\n            }\n        }\n    }\n\n    void updateAllReceiveWindowSizes(int deltaWindowSize) {\n        for (Node stream : streams.values()) {\n            StreamState state = stream.state;\n            if (state != null) {\n                state.updateReceiveWindowSize(deltaWindowSize);\n                if (deltaWindowSize < 0) {\n                    state.setReceiveWindowSizeLowerBound(deltaWindowSize);\n                }\n            }\n        }\n    }\n\n    boolean putPendingWrite(int streamId, PendingWrite evt) {\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state != null && state.putPendingWrite(evt);\n    }\n\n    PendingWrite getPendingWrite(int streamId) {\n        if (streamId == HTTP_CONNECTION_STREAM_ID) {\n            Node connection = streams.get(HTTP_CONNECTION_STREAM_ID);\n            return getPendingWrite(connection);\n        }\n\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state != null ? state.getPendingWrite() : null;\n    }\n\n    PendingWrite getPendingWrite(Node node) {\n        PendingWrite e = null;\n        if (node.state != null && node.state.getSendWindowSize() > 0) {\n            e = node.state.getPendingWrite();\n        }\n        if (e == null) {\n            for (Node child : node.children) {\n                e = getPendingWrite(child);\n                if (e != null) {\n                    break;\n                }\n            }\n        }\n        return e;\n    }\n\n    PendingWrite removePendingWrite(int streamId) {\n        Node stream = streams.get(streamId);\n        StreamState state = stream == null ? null : stream.state;\n        return state != null ? state.removePendingWrite() : null;\n    }\n\n    /**\n     * Set the priority of the stream.\n     */\n    boolean setPriority(int streamId, boolean exclusive, int dependency, int weight) {\n        Node stream = streams.get(streamId);\n        if (stream == null) {\n            // stream closed?\n            return false;\n        }\n\n        Node parent = streams.get(dependency);\n        if (parent == null) {\n            // garbage collected?\n            stream.parent.removeDependent(stream);\n\n            // set to default priority\n            Node root = streams.get(HTTP_CONNECTION_STREAM_ID);\n            root.addDependent(false, stream);\n            stream.setWeight(HTTP_DEFAULT_WEIGHT);\n            return false;\n        }\n\n        // check if we need to restructure the tree\n        if (parent == stream.parent) {\n            if (exclusive) {\n                // move dependents to stream\n                parent.addDependent(true, stream);\n            }\n        } else {\n            stream.parent.removeDependent(stream);\n            parent.addDependent(exclusive, stream);\n        }\n\n        stream.setWeight(weight);\n        return true;\n    }\n\n    private static final class StreamState {\n\n        private boolean remoteSideClosed;\n        private boolean localSideClosed;\n        private final AtomicInteger sendWindowSize;\n        private final AtomicInteger receiveWindowSize;\n        private int receiveWindowSizeLowerBound;\n        private final ConcurrentLinkedQueue<PendingWrite> pendingWriteQueue =\n                new ConcurrentLinkedQueue<PendingWrite>();\n\n        StreamState(\n                boolean remoteSideClosed, boolean localSideClosed,\n                int sendWindowSize, int receiveWindowSize) {\n            this.remoteSideClosed = remoteSideClosed;\n            this.localSideClosed = localSideClosed;\n            this.sendWindowSize = new AtomicInteger(sendWindowSize);\n            this.receiveWindowSize = new AtomicInteger(receiveWindowSize);\n        }\n\n        boolean isRemoteSideClosed() {\n            return remoteSideClosed;\n        }\n\n        void closeRemoteSide() {\n            remoteSideClosed = true;\n        }\n\n        boolean isLocalSideClosed() {\n            return localSideClosed;\n        }\n\n        void closeLocalSide() {\n            localSideClosed = true;\n        }\n\n        int getSendWindowSize() {\n            return sendWindowSize.get();\n        }\n\n        int updateSendWindowSize(int deltaWindowSize) {\n            return sendWindowSize.addAndGet(deltaWindowSize);\n        }\n\n        int updateReceiveWindowSize(int deltaWindowSize) {\n            return receiveWindowSize.addAndGet(deltaWindowSize);\n        }\n\n        int getReceiveWindowSizeLowerBound() {\n            return receiveWindowSizeLowerBound;\n        }\n\n        void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {\n            this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound;\n        }\n\n        boolean putPendingWrite(PendingWrite msg) {\n            return pendingWriteQueue.offer(msg);\n        }\n\n        PendingWrite getPendingWrite() {\n            return pendingWriteQueue.peek();\n        }\n\n        PendingWrite removePendingWrite() {\n            return pendingWriteQueue.poll();\n        }\n\n        void clearPendingWrites(Throwable cause) {\n            for (; ; ) {\n                PendingWrite pendingWrite = pendingWriteQueue.poll();\n                if (pendingWrite == null) {\n                    break;\n                }\n                pendingWrite.fail(cause);\n            }\n        }\n    }\n\n    private static final class Node {\n\n        private static final Comparator<Node> COMPARATOR = new WeightComparator();\n\n        public Node parent; // the dependency of the stream\n        public int weight;  // the weight of the dependency\n\n        // Children should be iterator in weighted order\n        public Set<Node> children = new TreeSet<Node>(COMPARATOR); // the dependents of the stream\n        public int dependentWeights; // the total weight of all the dependents of the stream\n\n        public StreamState state;\n\n        public Node(StreamState state) {\n            this.state = state;\n        }\n\n        public void close() {\n            this.state = null;\n        }\n\n        public void setWeight(int weight) {\n            // Remove and re-add parent to maintain comparator order\n            parent.removeDependent(this);\n            this.weight = weight;\n            parent.addDependent(false, this);\n        }\n\n        public void addDependent(boolean exclusive, Node node) {\n            removeDependent(node);\n            if (exclusive) {\n                for (Node child : children) {\n                    node.addDependent(false, child);\n                }\n                children.clear();\n                dependentWeights = 0;\n            }\n            children.add(node);\n            dependentWeights += node.weight;\n        }\n\n        public void removeDependent(Node node) {\n            if (children.remove(node)) {\n                dependentWeights -= node.weight;\n            }\n        }\n    }\n\n    private static final class WeightComparator implements Comparator<Node> {\n        @Override\n        public int compare(Node n1, Node n2) {\n            return n2.weight - n1.weight;\n        }\n    }\n\n    public static final class PendingWrite {\n        public final HttpDataFrame httpDataFrame;\n        public final ChannelPromise promise;\n\n        PendingWrite(HttpDataFrame httpDataFrame, ChannelPromise promise) {\n            this.httpDataFrame = httpDataFrame;\n            this.promise = promise;\n        }\n\n        void fail(Throwable cause) {\n            // httpDataFrame.release();\n            promise.setFailure(cause);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpConnectionHandler.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.io.IOException;\nimport java.net.SocketAddress;\nimport java.nio.channels.ClosedChannelException;\nimport java.util.List;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandler;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.ByteToMessageDecoder;\nimport io.netty.util.internal.EmptyArrays;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_CONNECTION_STREAM_ID;\nimport static com.twitter.http2.HttpCodecUtil.isServerId;\n\n/**\n * Manages streams within an HTTP/2 connection.\n */\npublic class HttpConnectionHandler extends ByteToMessageDecoder\n        implements HttpFrameDecoderDelegate, ChannelOutboundHandler {\n\n    private static final HttpProtocolException PROTOCOL_EXCEPTION =\n            new HttpProtocolException();\n\n    static {\n        PROTOCOL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);\n    }\n\n    private static final HttpSettingsFrame SETTINGS_ACK_FRAME =\n            new DefaultHttpSettingsFrame().setAck(true);\n\n    private static final int DEFAULT_HEADER_TABLE_SIZE = 4096;\n\n    private static final int DEFAULT_WINDOW_SIZE = 65535;\n    private int initialSendWindowSize = DEFAULT_WINDOW_SIZE;\n    private int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;\n    private volatile int initialConnectionReceiveWindowSize = DEFAULT_WINDOW_SIZE;\n\n    private final HttpConnection httpConnection =\n            new HttpConnection(initialSendWindowSize, initialReceiveWindowSize);\n    private int lastStreamId;\n\n    private static final int DEFAULT_MAX_CONCURRENT_STREAMS = Integer.MAX_VALUE;\n    private int remoteConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS;\n    private int localConcurrentStreams = DEFAULT_MAX_CONCURRENT_STREAMS;\n\n    private boolean sentGoAwayFrame;\n    private boolean receivedGoAwayFrame;\n\n    private final ChannelFutureListener connectionErrorListener =\n            new ConnectionErrorFutureListener();\n    private ChannelFutureListener closingChannelFutureListener;\n\n    private final boolean server;\n    private final boolean handleStreamWindowUpdates;\n\n    private final HttpFrameDecoder httpFrameDecoder;\n    private final HttpFrameEncoder httpFrameEncoder;\n    private final HttpHeaderBlockDecoder httpHeaderBlockDecoder;\n    private final HttpHeaderBlockEncoder httpHeaderBlockEncoder;\n\n    private HttpHeaderBlockFrame httpHeaderBlockFrame;\n    private HttpSettingsFrame httpSettingsFrame;\n\n    private boolean needSettingsAck;\n\n    private boolean changeDecoderHeaderTableSize;\n    private int headerTableSize;\n\n    private boolean changeEncoderHeaderTableSize;\n    private int lastHeaderTableSize = Integer.MAX_VALUE;\n    private int minHeaderTableSize = Integer.MAX_VALUE;\n    private boolean pushEnabled = true;\n\n    private ChannelHandlerContext context;\n\n    /**\n     * Creates a new connection handler.\n     *\n     * @param server {@code true} if and only if this connection handler should\n     *               handle the server endpoint of the connection.\n     *               {@code false} if and only if this connection handler should\n     *               handle the client endpoint of the connection.\n     */\n    public HttpConnectionHandler(boolean server) {\n        this(server, true);\n    }\n\n    /**\n     * Creates a new connection handler with the specified options.\n     */\n    public HttpConnectionHandler(boolean server, boolean handleStreamWindowUpdates) {\n        this(server, handleStreamWindowUpdates, 8192, 16384);\n    }\n\n    /**\n     * Creates a new connection handler with the specified options.\n     */\n    public HttpConnectionHandler(boolean server, int maxChunkSize, int maxHeaderSize) {\n        this(server, true, maxChunkSize, maxHeaderSize);\n    }\n\n    /**\n     * Creates a new connection handler with the specified options.\n     */\n    public HttpConnectionHandler(\n            boolean server, boolean handleStreamWindowUpdates, int maxChunkSize, int maxHeaderSize) {\n        this.server = server;\n        this.handleStreamWindowUpdates = handleStreamWindowUpdates;\n        httpFrameDecoder = new HttpFrameDecoder(server, this, maxChunkSize);\n        httpFrameEncoder = new HttpFrameEncoder();\n        httpHeaderBlockDecoder = new HttpHeaderBlockDecoder(maxHeaderSize, DEFAULT_HEADER_TABLE_SIZE);\n        httpHeaderBlockEncoder = new HttpHeaderBlockEncoder(DEFAULT_HEADER_TABLE_SIZE);\n    }\n\n    public void setConnectionReceiveWindowSize(int connectionReceiveWindowSize) {\n      if (connectionReceiveWindowSize < 0) {\n          throw new IllegalArgumentException(\"connectionReceiveWindowSize\");\n      }\n      // This will not send a window update frame immediately.\n      // If this value increases the allowed receive window size,\n      // a WINDOW_UPDATE frame will be sent when only half of the\n      // session window size remains during data frame processing.\n      // If this value decreases the allowed receive window size,\n      // the window will be reduced as data frames are processed.\n      initialConnectionReceiveWindowSize = connectionReceiveWindowSize;\n    }\n\n    @Override\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\n        super.handlerAdded(ctx);\n        context = ctx;\n    }\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {\n        httpFrameDecoder.decode(in);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readDataFramePadding(int streamId, boolean endStream, int padding) {\n        int deltaWindowSize = -1 * padding;\n        int newConnectionWindowSize = httpConnection.updateReceiveWindowSize(\n                HTTP_CONNECTION_STREAM_ID, deltaWindowSize);\n\n        // Check if connection window size is reduced beyond allowable lower bound\n        if (newConnectionWindowSize < 0) {\n            issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n            return;\n        }\n\n        // Send a WINDOW_UPDATE frame if less than half the connection window size remains\n        if (newConnectionWindowSize <= initialConnectionReceiveWindowSize / 2) {\n            int windowSizeIncrement = initialConnectionReceiveWindowSize - newConnectionWindowSize;\n            httpConnection.updateReceiveWindowSize(HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);\n            ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(\n                    HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);\n            context.writeAndFlush(frame);\n        }\n\n        // Check if we received a DATA frame for a stream which is half-closed (remote) or closed\n        if (httpConnection.isRemoteSideClosed(streamId)) {\n            if (streamId <= lastStreamId) {\n                issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);\n            } else if (!sentGoAwayFrame) {\n                issueStreamError(streamId, HttpErrorCode.PROTOCOL_ERROR);\n            }\n            return;\n        }\n\n        // Update receive window size\n        int newWindowSize = httpConnection.updateReceiveWindowSize(streamId, deltaWindowSize);\n\n        // Window size can become negative if we sent a SETTINGS frame that reduces the\n        // size of the transfer window after the peer has written data frames.\n        // The value is bounded by the length that SETTINGS frame decrease the window.\n        // This difference is stored for the connection when writing the SETTINGS frame\n        // and is cleared once we send a WINDOW_UPDATE frame.\n        if (newWindowSize < httpConnection.getReceiveWindowSizeLowerBound(streamId)) {\n            issueStreamError(streamId, HttpErrorCode.FLOW_CONTROL_ERROR);\n            return;\n        }\n\n        // Send a WINDOW_UPDATE frame if less than half the stream window size remains\n        // Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.\n        if (handleStreamWindowUpdates && newWindowSize <= initialReceiveWindowSize / 2 && !endStream) {\n            int windowSizeIncrement = initialReceiveWindowSize - newWindowSize;\n            httpConnection.updateReceiveWindowSize(streamId, windowSizeIncrement);\n            ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(streamId, windowSizeIncrement);\n            context.writeAndFlush(frame);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readDataFrame(int streamId, boolean endStream, boolean endSegment, ByteBuf data) {\n        // HTTP/2 DATA frame processing requirements:\n        //\n        // If an endpoint receives a data frame for a Stream-ID which is not open\n        // and the endpoint has not sent a GOAWAY frame, it must issue a stream error\n        // with the error code INVALID_STREAM for the Stream-ID.\n        //\n        // If an endpoint receives multiple data frames for invalid Stream-IDs,\n        // it may close the connection.\n        //\n        // If an endpoint refuses a stream it must ignore any data frames for that stream.\n        //\n        // If an endpoint receives a data frame after the stream is half-closed (remote)\n        // or closed, it must respond with a stream error of type STREAM_CLOSED.\n\n        int deltaWindowSize = -1 * data.readableBytes();\n        int newConnectionWindowSize = httpConnection.updateReceiveWindowSize(\n                HTTP_CONNECTION_STREAM_ID, deltaWindowSize);\n\n        // Check if connection window size is reduced beyond allowable lower bound\n        if (newConnectionWindowSize < 0) {\n            issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n            return;\n        }\n\n        // Send a WINDOW_UPDATE frame if less than half the connection window size remains\n        if (newConnectionWindowSize <= initialConnectionReceiveWindowSize / 2) {\n            int windowSizeIncrement = initialConnectionReceiveWindowSize - newConnectionWindowSize;\n            httpConnection.updateReceiveWindowSize(HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);\n            ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(\n                    HTTP_CONNECTION_STREAM_ID, windowSizeIncrement);\n            context.writeAndFlush(frame);\n        }\n\n        // Check if we received a DATA frame for a stream which is half-closed (remote) or closed\n        if (httpConnection.isRemoteSideClosed(streamId)) {\n            if (streamId <= lastStreamId) {\n                issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);\n            } else if (!sentGoAwayFrame) {\n                issueStreamError(streamId, HttpErrorCode.PROTOCOL_ERROR);\n            }\n            return;\n        }\n\n        // Update receive window size\n        int newWindowSize = httpConnection.updateReceiveWindowSize(streamId, deltaWindowSize);\n\n        // Window size can become negative if we sent a SETTINGS frame that reduces the\n        // size of the transfer window after the peer has written data frames.\n        // The value is bounded by the length that SETTINGS frame decrease the window.\n        // This difference is stored for the connection when writing the SETTINGS frame\n        // and is cleared once we send a WINDOW_UPDATE frame.\n        if (newWindowSize < httpConnection.getReceiveWindowSizeLowerBound(streamId)) {\n            issueStreamError(streamId, HttpErrorCode.FLOW_CONTROL_ERROR);\n            return;\n        }\n\n        // Window size became negative due to sender writing frame before receiving SETTINGS\n        // Send data frames upstream in initialReceiveWindowSize chunks\n        if (newWindowSize < 0) {\n            while (data.readableBytes() > initialReceiveWindowSize) {\n                ByteBuf partialData = data.readBytes(initialReceiveWindowSize);\n                HttpDataFrame partialDataFrame = new DefaultHttpDataFrame(streamId, partialData);\n                context.fireChannelRead(partialDataFrame);\n            }\n        }\n\n        // Send a WINDOW_UPDATE frame if less than half the stream window size remains\n        // Recipient should not send a WINDOW_UPDATE frame as it consumes the last data frame.\n        if (handleStreamWindowUpdates && newWindowSize <= initialReceiveWindowSize / 2 && !endStream) {\n            int windowSizeIncrement = initialReceiveWindowSize - newWindowSize;\n            httpConnection.updateReceiveWindowSize(streamId, windowSizeIncrement);\n            ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(streamId, windowSizeIncrement);\n            context.writeAndFlush(frame);\n        }\n\n        // Close the remote side of the stream if this is the last frame\n        if (endStream) {\n            halfCloseStream(streamId, true, context.channel().newSucceededFuture());\n        }\n\n        HttpDataFrame httpDataFrame = new DefaultHttpDataFrame(streamId, data);\n        httpDataFrame.setLast(endStream);\n        context.fireChannelRead(httpDataFrame);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readHeadersFrame(\n            int streamId,\n            boolean endStream,\n            boolean endSegment,\n            boolean exclusive,\n            int dependency,\n            int weight\n    ) {\n        // HTTP/2 HEADERS frame processing requirements:\n        //\n        // If an endpoint receives a HEADERS frame with a Stream-ID that is less than\n        // any previously received HEADERS, it must issue a connection error of type\n        // PROTOCOL_ERROR.\n        //\n        // If an endpoint receives multiple SYN_STREAM frames with the same active\n        // Stream-ID, it must issue a stream error with the status code PROTOCOL_ERROR.\n        //\n        // The recipient can reject a stream by sending a stream error with the\n        // status code REFUSED_STREAM.\n\n        if (isRemoteInitiatedId(streamId)) {\n            if (streamId <= lastStreamId) {\n                // Check if we received a HEADERS frame for a stream which is half-closed (remote) or closed\n                if (httpConnection.isRemoteSideClosed(streamId)) {\n                    issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);\n                    return;\n                }\n            } else {\n                // Try to accept the stream\n                if (!acceptStream(streamId, exclusive, dependency, weight)) {\n                    issueStreamError(streamId, HttpErrorCode.REFUSED_STREAM);\n                    return;\n                }\n            }\n        } else {\n            // Check if we received a HEADERS frame for a stream which is half-closed (remote) or closed\n            if (httpConnection.isRemoteSideClosed(streamId)) {\n                issueStreamError(streamId, HttpErrorCode.STREAM_CLOSED);\n                return;\n            }\n        }\n\n        // Close the remote side of the stream if this is the last frame\n        if (endStream) {\n            halfCloseStream(streamId, true, context.channel().newSucceededFuture());\n        }\n\n        HttpHeadersFrame httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);\n        httpHeadersFrame.setLast(endStream);\n        httpHeadersFrame.setExclusive(exclusive);\n        httpHeadersFrame.setDependency(dependency);\n        httpHeadersFrame.setWeight(weight);\n        httpHeaderBlockFrame = httpHeadersFrame;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readPriorityFrame(int streamId, boolean exclusive, int dependency, int weight) {\n        if (streamId == dependency) {\n            issueStreamError(streamId, HttpErrorCode.PROTOCOL_ERROR);\n        } else {\n            setPriority(streamId, exclusive, dependency, weight);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readRstStreamFrame(int streamId, int errorCode) {\n        // If a RST_STREAM frame identifying an idle stream is received,\n        // the recipient MUST treat this as a connection error of type\n        // PROTOCOL_ERROR.\n        removeStream(streamId, context.channel().newSucceededFuture());\n        HttpRstStreamFrame httpRstStreamFrame = new DefaultHttpRstStreamFrame(streamId, errorCode);\n        context.fireChannelRead(httpRstStreamFrame);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readSettingsFrame(boolean ack) {\n        needSettingsAck = !ack;\n        httpSettingsFrame = new DefaultHttpSettingsFrame();\n        httpSettingsFrame.setAck(ack);\n        if (ack && changeDecoderHeaderTableSize) {\n            httpHeaderBlockDecoder.setMaxHeaderTableSize(headerTableSize);\n            changeDecoderHeaderTableSize = false;\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readSetting(int id, int value) {\n        httpSettingsFrame.setValue(id, value);\n        switch (id) {\n        case HttpSettingsFrame.SETTINGS_HEADER_TABLE_SIZE:\n            // Ignore 'negative' values -- they are too large for java\n            if (value >= 0) {\n                changeEncoderHeaderTableSize = true;\n                lastHeaderTableSize = value;\n                if (lastHeaderTableSize < minHeaderTableSize) {\n                    minHeaderTableSize = lastHeaderTableSize;\n                }\n            }\n            break;\n        case HttpSettingsFrame.SETTINGS_ENABLE_PUSH:\n            if (value == 0) {\n                pushEnabled = false;\n            } else if (value == 1) {\n                pushEnabled = true;\n            } else {\n                issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n            }\n            break;\n        case HttpSettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS:\n            if (value >= 0) {\n                remoteConcurrentStreams = value;\n            }\n            break;\n        case HttpSettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE:\n            if (value >= 0) {\n                updateInitialSendWindowSize(value);\n            } else {\n                issueConnectionError(HttpErrorCode.FLOW_CONTROL_ERROR);\n            }\n            break;\n        case HttpSettingsFrame.SETTINGS_MAX_FRAME_SIZE:\n            if (value != HttpCodecUtil.HTTP_MAX_LENGTH) {\n                issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n            }\n            break;\n        default:\n            // Ignore Unknown Settings\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readSettingsEnd() {\n        if (changeEncoderHeaderTableSize) {\n            synchronized (httpHeaderBlockEncoder) {\n                httpHeaderBlockEncoder.setDecoderMaxHeaderTableSize(minHeaderTableSize);\n                httpHeaderBlockEncoder.setDecoderMaxHeaderTableSize(lastHeaderTableSize);\n\n                // Writes of settings ack must occur in order\n                ByteBuf frame = httpFrameEncoder.encodeSettingsFrame(SETTINGS_ACK_FRAME);\n                context.writeAndFlush(frame);\n            }\n            changeEncoderHeaderTableSize = false;\n            lastHeaderTableSize = Integer.MAX_VALUE;\n            minHeaderTableSize = Integer.MAX_VALUE;\n        } else if (needSettingsAck) {\n            ByteBuf frame = httpFrameEncoder.encodeSettingsFrame(SETTINGS_ACK_FRAME);\n            context.writeAndFlush(frame);\n        }\n        Object frame = httpSettingsFrame;\n        httpSettingsFrame = null;\n        context.fireChannelRead(frame);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readPushPromiseFrame(int streamId, int promisedStreamId) {\n        // TODO(jpinner) handle push promise frames\n        // Any we receive must be associated with a \"peer-initiated\" stream.\n        // Since we don't have a way currently to initiate streams, any\n        // frame that we receive must be treated as a protocol error.\n        issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readPingFrame(long data, boolean ack) {\n        // HTTP/2 PING frame processing requirements:\n        //\n        // Receivers of a PING frame should send an identical frame to the sender\n        // as soon as possible.\n        //\n        // Receivers of a PING frame must ignore frames that it did not initiate\n        HttpPingFrame httpPingFrame = new DefaultHttpPingFrame(data);\n        httpPingFrame.setPong(true);\n        if (ack) {\n            context.fireChannelRead(httpPingFrame);\n        } else {\n            ByteBuf frame = httpFrameEncoder.encodePingFrame(data, false);\n            context.writeAndFlush(frame);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readGoAwayFrame(int lastStreamId, int errorCode) {\n        receivedGoAwayFrame = true;\n        HttpGoAwayFrame httpGoAwayFrame = new DefaultHttpGoAwayFrame(lastStreamId, errorCode);\n        context.fireChannelRead(httpGoAwayFrame);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readWindowUpdateFrame(int streamId, int windowSizeIncrement) {\n        // HTTP/2 WINDOW_UPDATE frame processing requirements:\n        //\n        // Receivers of a WINDOW_UPDATE that cause the window size to exceed 2^31\n        // must send a RST_STREAM with the status code FLOW_CONTROL_ERROR.\n        //\n        // Sender should ignore all WINDOW_UPDATE frames associated with a stream\n        // after sending the last frame for the stream.\n\n        // Ignore frames for half-closed streams\n        if (streamId != HTTP_CONNECTION_STREAM_ID && httpConnection.isLocalSideClosed(streamId)) {\n            return;\n        }\n\n        // Check for numerical overflow\n        if (httpConnection.getSendWindowSize(streamId) > Integer.MAX_VALUE - windowSizeIncrement) {\n            if (streamId == HTTP_CONNECTION_STREAM_ID) {\n                issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n            } else {\n                issueStreamError(streamId, HttpErrorCode.FLOW_CONTROL_ERROR);\n            }\n            return;\n        }\n\n        updateSendWindowSize(context, streamId, windowSizeIncrement);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readHeaderBlock(ByteBuf headerBlockFragment) {\n        try {\n            httpHeaderBlockDecoder.decode(headerBlockFragment, httpHeaderBlockFrame);\n        } catch (IOException e) {\n            httpHeaderBlockFrame = null;\n            issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readHeaderBlockEnd() {\n        httpHeaderBlockDecoder.endHeaderBlock(httpHeaderBlockFrame);\n\n        if (httpHeaderBlockFrame == null) {\n            return;\n        }\n\n        // Check if we received a valid Header Block\n        if (httpHeaderBlockFrame.isInvalid()) {\n            issueStreamError(httpHeaderBlockFrame.getStreamId(), HttpErrorCode.PROTOCOL_ERROR);\n            return;\n        }\n\n        Object frame = httpHeaderBlockFrame;\n        httpHeaderBlockFrame = null;\n        context.fireChannelRead(frame);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void readFrameError(String message) {\n        issueConnectionError(HttpErrorCode.PROTOCOL_ERROR);\n    }\n\n    @Override\n    public void bind(\n            ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)\n            throws Exception {\n        ctx.bind(localAddress, promise);\n    }\n\n    @Override\n    public void connect(\n            ChannelHandlerContext ctx,\n            SocketAddress remoteAddress,\n            SocketAddress localAddress,\n            ChannelPromise promise\n    ) throws Exception {\n        ctx.connect(remoteAddress, localAddress, promise);\n    }\n\n    @Override\n    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        // HTTP/2 connection requirements:\n        //\n        // When either endpoint closes the transport-level connection,\n        // it must first send a GOAWAY frame.\n        //\n        // Avoid NotYetConnectedException\n        if (!ctx.channel().isActive()) {\n            ctx.disconnect(promise);\n        } else {\n            sendGoAwayFrame(ctx, promise);\n        }\n    }\n\n    @Override\n    public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        // HTTP/2 connection requirements:\n        //\n        // When either endpoint closes the transport-level connection,\n        // it must first send a GOAWAY frame.\n        //\n        // Avoid NotYetConnectedException\n        if (!ctx.channel().isActive()) {\n            ctx.close(promise);\n        } else {\n            sendGoAwayFrame(ctx, promise);\n        }\n    }\n\n    @Override\n    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n        ctx.deregister(promise);\n    }\n\n    @Override\n    public void read(ChannelHandlerContext ctx) throws Exception {\n        ctx.read();\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)\n            throws Exception {\n        if (msg instanceof HttpDataFrame) {\n\n            HttpDataFrame httpDataFrame = (HttpDataFrame) msg;\n            int streamId = httpDataFrame.getStreamId();\n\n            // Frames must not be sent on half-closed streams\n            if (httpConnection.isLocalSideClosed(streamId)) {\n                promise.setFailure(PROTOCOL_EXCEPTION);\n                return;\n            }\n\n            // HTTP/2 DATA frame flow control processing requirements:\n            //\n            // Sender must not send a data frame with data length greater\n            // than the transfer window size.\n            //\n            // After sending each data frame, the sender decrements its\n            // transfer window size by the amount of data transmitted.\n            //\n            // When the window size becomes less than or equal to 0, the\n            // sender must pause transmitting data frames.\n\n            int dataLength = httpDataFrame.content().readableBytes();\n            int sendWindowSize = httpConnection.getSendWindowSize(streamId);\n            int connectionSendWindowSize = httpConnection.getSendWindowSize(\n                    HTTP_CONNECTION_STREAM_ID);\n            sendWindowSize = Math.min(sendWindowSize, connectionSendWindowSize);\n\n            if (sendWindowSize <= 0) {\n                // Stream is stalled -- enqueue Data frame and return\n                httpConnection.putPendingWrite(\n                        streamId, new HttpConnection.PendingWrite(httpDataFrame, promise));\n                return;\n            } else if (sendWindowSize < dataLength) {\n                // Stream is not stalled but we cannot send the entire frame\n                httpConnection.updateSendWindowSize(streamId, -1 * sendWindowSize);\n                httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * sendWindowSize);\n\n                // Create a partial data frame whose length is the current window size\n                ByteBuf data = httpDataFrame.content().readSlice(sendWindowSize).retain();\n                ByteBuf partialDataFrame = httpFrameEncoder.encodeDataFrame(streamId, false, data);\n\n                // Enqueue the remaining data (will be the first frame queued)\n                httpConnection.putPendingWrite(\n                        streamId, new HttpConnection.PendingWrite(httpDataFrame, promise));\n\n                ChannelPromise writeFuture = ctx.channel().newPromise();\n\n                // The transfer window size is pre-decremented when sending a data frame downstream.\n                // Close the connection on write failures that leaves the transfer window in a corrupt state.\n                writeFuture.addListener(connectionErrorListener);\n\n                ctx.write(partialDataFrame, writeFuture);\n                return;\n            } else {\n                // Window size is large enough to send entire data frame\n                httpConnection.updateSendWindowSize(streamId, -1 * dataLength);\n                httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * dataLength);\n\n                // The transfer window size is pre-decremented when sending a data frame downstream.\n                // Close the connection on write failures that leaves the transfer window in a corrupt state.\n                promise.addListener(connectionErrorListener);\n            }\n\n            // Close the local side of the stream if this is the last frame\n            if (httpDataFrame.isLast()) {\n                halfCloseStream(streamId, false, promise);\n            }\n\n            ByteBuf frame = httpFrameEncoder.encodeDataFrame(\n                    streamId,\n                    httpDataFrame.isLast(),\n                    httpDataFrame.content()\n            );\n            ctx.write(frame, promise);\n\n        } else if (msg instanceof HttpHeadersFrame) {\n\n            HttpHeadersFrame httpHeadersFrame = (HttpHeadersFrame) msg;\n            int streamId = httpHeadersFrame.getStreamId();\n\n            if (isRemoteInitiatedId(streamId)) {\n                if (streamId <= lastStreamId) {\n                    // Attempting to send headers for an older stream\n                    // (older than the latest accepted remote initiated stream)\n                    // Ensure that the frames are not sent on a half-closed (local) or closed streams\n                    if (httpConnection.isLocalSideClosed(streamId)) {\n                        promise.setFailure(PROTOCOL_EXCEPTION);\n                        return;\n                    }\n                } else {\n                    // If we are attempting to write to a remote initiated stream id which is greater than the latest\n                    // accepted stream Id then we must throw a protocol exception! i.e we cannot write on a remote\n                    // initiated stream which we have not accepted before\n                    promise.setFailure(PROTOCOL_EXCEPTION);\n                    return;\n                }\n            } else {\n                // This is a locally initiated stream (Push)\n                boolean exclusive = httpHeadersFrame.isExclusive();\n                int dependency = httpHeadersFrame.getDependency();\n                int weight = httpHeadersFrame.getWeight();\n                if (!acceptStream(streamId, exclusive, dependency, weight)) {\n                    promise.setFailure(PROTOCOL_EXCEPTION);\n                    return;\n                }\n            }\n\n            // Close the local side of the stream if this is the last frame\n            if (httpHeadersFrame.isLast()) {\n                halfCloseStream(streamId, false, promise);\n            }\n\n            synchronized (httpHeaderBlockEncoder) {\n                ByteBuf frame = httpFrameEncoder.encodeHeadersFrame(\n                        httpHeadersFrame.getStreamId(),\n                        httpHeadersFrame.isLast(),\n                        httpHeadersFrame.isExclusive(),\n                        httpHeadersFrame.getDependency(),\n                        httpHeadersFrame.getWeight(),\n                        httpHeaderBlockEncoder.encode(ctx, httpHeadersFrame)\n                );\n                // Writes of compressed data must occur in order\n                ctx.write(frame, promise);\n            }\n\n        } else if (msg instanceof HttpPriorityFrame) {\n\n            HttpPriorityFrame httpPriorityFrame = (HttpPriorityFrame) msg;\n            int streamId = httpPriorityFrame.getStreamId();\n            boolean exclusive = httpPriorityFrame.isExclusive();\n            int dependency = httpPriorityFrame.getDependency();\n            int weight = httpPriorityFrame.getWeight();\n            setPriority(streamId, exclusive, dependency, weight);\n            ByteBuf frame = httpFrameEncoder.encodePriorityFrame(\n                    streamId,\n                    exclusive,\n                    dependency,\n                    weight\n            );\n            ctx.write(frame, promise);\n\n        } else if (msg instanceof HttpRstStreamFrame) {\n\n            HttpRstStreamFrame httpRstStreamFrame = (HttpRstStreamFrame) msg;\n            removeStream(httpRstStreamFrame.getStreamId(), promise);\n            ByteBuf frame = httpFrameEncoder.encodeRstStreamFrame(\n                    httpRstStreamFrame.getStreamId(),\n                    httpRstStreamFrame.getErrorCode().getCode());\n            ctx.write(frame, promise);\n\n        } else if (msg instanceof HttpSettingsFrame) {\n\n            // TODO(jpinner) currently cannot have more than one settings frame outstanding at a time\n\n            HttpSettingsFrame httpSettingsFrame = (HttpSettingsFrame) msg;\n            if (httpSettingsFrame.isAck()) {\n                // Cannot send an acknowledgement frame\n                promise.setFailure(PROTOCOL_EXCEPTION);\n                return;\n            }\n\n            int newHeaderTableSize =\n                    httpSettingsFrame.getValue(HttpSettingsFrame.SETTINGS_HEADER_TABLE_SIZE);\n            if (newHeaderTableSize >= 0) {\n                headerTableSize = newHeaderTableSize;\n                changeDecoderHeaderTableSize = true;\n            }\n\n            int newConcurrentStreams =\n                    httpSettingsFrame.getValue(HttpSettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS);\n            if (newConcurrentStreams >= 0) {\n                localConcurrentStreams = newConcurrentStreams;\n            }\n\n            int newInitialWindowSize =\n                    httpSettingsFrame.getValue(HttpSettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE);\n            if (newInitialWindowSize >= 0) {\n                updateInitialReceiveWindowSize(newInitialWindowSize);\n            }\n\n            ByteBuf frame = httpFrameEncoder.encodeSettingsFrame(httpSettingsFrame);\n            ctx.write(frame, promise);\n\n        } else if (msg instanceof HttpPushPromiseFrame) {\n\n            if (!pushEnabled) {\n                promise.setFailure(PROTOCOL_EXCEPTION);\n                return;\n            }\n\n            synchronized (httpHeaderBlockEncoder) {\n              HttpPushPromiseFrame httpPushPromiseFrame = (HttpPushPromiseFrame) msg;\n              ByteBuf frame = httpFrameEncoder.encodePushPromiseFrame(\n                  httpPushPromiseFrame.getStreamId(),\n                  httpPushPromiseFrame.getPromisedStreamId(),\n                  httpHeaderBlockEncoder.encode(ctx, httpPushPromiseFrame)\n                  );\n              // Writes of compressed data must occur in order\n              ctx.write(frame, promise);\n            }\n\n        } else if (msg instanceof HttpPingFrame) {\n\n            HttpPingFrame httpPingFrame = (HttpPingFrame) msg;\n            if (httpPingFrame.isPong()) {\n                // Cannot send a PONG frame\n                promise.setFailure(PROTOCOL_EXCEPTION);\n            } else {\n                ByteBuf frame = httpFrameEncoder.encodePingFrame(httpPingFrame.getData(), false);\n                ctx.write(frame, promise);\n            }\n\n        } else if (msg instanceof HttpGoAwayFrame) {\n\n            // Why is this being sent? Intercept it and fail the write.\n            // Should have sent a CLOSE ChannelStateEvent\n            promise.setFailure(PROTOCOL_EXCEPTION);\n\n        } else if (msg instanceof HttpWindowUpdateFrame) {\n\n            HttpWindowUpdateFrame httpWindowUpdateFrame = (HttpWindowUpdateFrame) msg;\n            int streamId = httpWindowUpdateFrame.getStreamId();\n\n            if (handleStreamWindowUpdates || streamId == HTTP_CONNECTION_STREAM_ID) {\n                // Why is this being sent? Intercept it and fail the write.\n                promise.setFailure(PROTOCOL_EXCEPTION);\n            } else {\n                int windowSizeIncrement = httpWindowUpdateFrame.getWindowSizeIncrement();\n                httpConnection.updateReceiveWindowSize(streamId, windowSizeIncrement);\n                ByteBuf frame = httpFrameEncoder.encodeWindowUpdateFrame(streamId, windowSizeIncrement);\n                ctx.write(frame, promise);\n            }\n\n        } else {\n\n            ctx.write(msg, promise);\n        }\n    }\n\n    @Override\n    public void flush(ChannelHandlerContext ctx) throws Exception {\n        ctx.flush();\n    }\n\n    // HTTP/2 Connection Error Handling:\n    //\n    // When a connection error occurs, the endpoint encountering the error must first\n    // send a GOAWAY frame with the stream identifier of the most recently received stream\n    // from the remote endpoint, and the error code for why the connection is terminating.\n    //\n    // After sending the GOAWAY frame, the endpoint must close the TCP connection.\n    private void issueConnectionError(HttpErrorCode status) {\n        ChannelFuture future = sendGoAwayFrame(status);\n        future.addListener(ChannelFutureListener.CLOSE);\n    }\n\n    // Http/2 Stream Error Handling:\n    //\n    // Upon a stream error, the endpoint must send a RST_STREAM frame which contains\n    // the Stream-ID for the stream where the error occurred and the error status which\n    // caused the error.\n    //\n    // After sending the RST_STREAM, the stream is closed to the sending endpoint.\n    //\n    // Note: this is only called by the worker thread\n    private void issueStreamError(int streamId, HttpErrorCode errorCode) {\n\n        boolean fireMessageReceived = !httpConnection.isRemoteSideClosed(streamId);\n        ChannelPromise promise = context.channel().newPromise();\n        removeStream(streamId, promise);\n\n        ByteBuf frame = httpFrameEncoder.encodeRstStreamFrame(streamId, errorCode.getCode());\n        context.writeAndFlush(frame, promise);\n        if (fireMessageReceived) {\n            HttpRstStreamFrame httpRstStreamFrame = new DefaultHttpRstStreamFrame(streamId, errorCode);\n            context.fireChannelRead(httpRstStreamFrame);\n        }\n    }\n\n    // Helper functions\n\n    private boolean isRemoteInitiatedId(int id) {\n        boolean serverId = isServerId(id);\n        return server && !serverId || !server && serverId;\n    }\n\n    // need to synchronize to prevent new streams from being created while updating active streams\n    private synchronized void updateInitialSendWindowSize(int newInitialWindowSize) {\n        int deltaWindowSize = newInitialWindowSize - initialSendWindowSize;\n        initialSendWindowSize = newInitialWindowSize;\n        httpConnection.updateAllSendWindowSizes(deltaWindowSize);\n    }\n\n    // need to synchronize to prevent new streams from being created while updating active streams\n    private synchronized void updateInitialReceiveWindowSize(int newInitialWindowSize) {\n        int deltaWindowSize = newInitialWindowSize - initialReceiveWindowSize;\n        initialReceiveWindowSize = newInitialWindowSize;\n        httpConnection.updateAllReceiveWindowSizes(deltaWindowSize);\n    }\n\n    // need to synchronize accesses to sentGoAwayFrame, lastStreamId, and initial window sizes\n    private synchronized boolean acceptStream(\n            int streamId, boolean exclusive, int dependency, int weight) {\n        // Cannot initiate any new streams after receiving or sending GOAWAY\n        if (receivedGoAwayFrame || sentGoAwayFrame) {\n            return false;\n        }\n\n        boolean remote = isRemoteInitiatedId(streamId);\n        int maxConcurrentStreams = remote ? localConcurrentStreams : remoteConcurrentStreams;\n        if (httpConnection.numActiveStreams(remote) >= maxConcurrentStreams) {\n            return false;\n        }\n        httpConnection.acceptStream(\n                streamId, false, false, initialSendWindowSize, initialReceiveWindowSize, remote);\n        if (remote) {\n            lastStreamId = streamId;\n        }\n        setPriority(streamId, exclusive, dependency, weight);\n        return true;\n    }\n\n    private synchronized boolean setPriority(\n            int streamId, boolean exclusive, int dependency, int weight) {\n        return httpConnection.setPriority(streamId, exclusive, dependency, weight);\n    }\n\n    private void halfCloseStream(int streamId, boolean remote, ChannelFuture future) {\n        if (remote) {\n            httpConnection.closeRemoteSide(streamId, isRemoteInitiatedId(streamId));\n        } else {\n            httpConnection.closeLocalSide(streamId, isRemoteInitiatedId(streamId));\n        }\n        if (closingChannelFutureListener != null && httpConnection.noActiveStreams()) {\n            future.addListener(closingChannelFutureListener);\n        }\n    }\n\n    private void removeStream(int streamId, ChannelFuture future) {\n        httpConnection.removeStream(streamId, isRemoteInitiatedId(streamId));\n        if (closingChannelFutureListener != null && httpConnection.noActiveStreams()) {\n            future.addListener(closingChannelFutureListener);\n        }\n    }\n\n    private void updateSendWindowSize(\n            ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {\n        httpConnection.updateSendWindowSize(streamId, windowSizeIncrement);\n\n        while (true) {\n            // Check if we have unblocked a stalled stream\n            HttpConnection.PendingWrite e = httpConnection.getPendingWrite(streamId);\n            if (e == null) {\n                break;\n            }\n\n            HttpDataFrame httpDataFrame = e.httpDataFrame;\n            int dataFrameSize = httpDataFrame.content().readableBytes();\n            int writeStreamId = httpDataFrame.getStreamId();\n            int sendWindowSize = httpConnection.getSendWindowSize(writeStreamId);\n            int connectionSendWindowSize = httpConnection.getSendWindowSize(\n                    HTTP_CONNECTION_STREAM_ID);\n            sendWindowSize = Math.min(sendWindowSize, connectionSendWindowSize);\n\n            if (sendWindowSize <= 0) {\n                return;\n            } else if (sendWindowSize < dataFrameSize) {\n                // We can send a partial frame\n                httpConnection.updateSendWindowSize(writeStreamId, -1 * sendWindowSize);\n                httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * sendWindowSize);\n\n                // Create a partial data frame whose length is the current window size\n                ByteBuf data = httpDataFrame.content().readSlice(sendWindowSize).retain();\n                ByteBuf partialDataFrame = httpFrameEncoder.encodeDataFrame(writeStreamId, false, data);\n\n                ChannelPromise writeFuture = ctx.channel().newPromise();\n\n                // The transfer window size is pre-decremented when sending a data frame downstream.\n                // Close the connection on write failures that leaves the transfer window in a corrupt state.\n                writeFuture.addListener(connectionErrorListener);\n\n                ctx.writeAndFlush(partialDataFrame, writeFuture);\n            } else {\n                // Window size is large enough to send entire data frame\n                httpConnection.removePendingWrite(writeStreamId);\n                httpConnection.updateSendWindowSize(writeStreamId, -1 * dataFrameSize);\n                httpConnection.updateSendWindowSize(HTTP_CONNECTION_STREAM_ID, -1 * dataFrameSize);\n\n                // The transfer window size is pre-decremented when sending a data frame downstream.\n                // Close the connection on write failures that leaves the transfer window in a corrupt state.\n                e.promise.addListener(connectionErrorListener);\n\n                // Close the local side of the stream if this is the last frame\n                if (httpDataFrame.isLast()) {\n                    halfCloseStream(writeStreamId, false, e.promise);\n                }\n\n                ByteBuf frame = httpFrameEncoder.encodeDataFrame(\n                        writeStreamId,\n                        httpDataFrame.isLast(),\n                        httpDataFrame.content()\n                );\n\n                ctx.writeAndFlush(frame, e.promise);\n            }\n        }\n    }\n\n    private void sendGoAwayFrame(ChannelHandlerContext ctx, ChannelPromise promise) {\n        ChannelFuture future = sendGoAwayFrame(HttpErrorCode.NO_ERROR);\n        if (httpConnection.noActiveStreams()) {\n            future.addListener(new ClosingChannelFutureListener(ctx, promise));\n        } else {\n            closingChannelFutureListener = new ClosingChannelFutureListener(ctx, promise);\n        }\n    }\n\n    private synchronized ChannelFuture sendGoAwayFrame(HttpErrorCode httpErrorCode) {\n        if (!sentGoAwayFrame) {\n            sentGoAwayFrame = true;\n            ChannelPromise promise = context.channel().newPromise();\n            ByteBuf frame = httpFrameEncoder.encodeGoAwayFrame(lastStreamId, httpErrorCode.getCode());\n            context.writeAndFlush(frame, promise);\n            return promise;\n        }\n        return context.channel().newSucceededFuture();\n    }\n\n    private final class ConnectionErrorFutureListener implements ChannelFutureListener {\n        @Override\n        public void operationComplete(ChannelFuture future) throws Exception {\n            if (!future.isSuccess()) {\n                issueConnectionError(HttpErrorCode.INTERNAL_ERROR);\n            }\n        }\n    }\n\n    private static final class ClosingChannelFutureListener implements ChannelFutureListener {\n        private final ChannelHandlerContext ctx;\n        private final ChannelPromise promise;\n\n        ClosingChannelFutureListener(ChannelHandlerContext ctx, ChannelPromise promise) {\n            this.ctx = ctx;\n            this.promise = promise;\n        }\n\n        public void operationComplete(ChannelFuture sentGoAwayFuture) throws Exception {\n            if (!(sentGoAwayFuture.cause() instanceof ClosedChannelException)) {\n                ctx.close(promise);\n            } else {\n                promise.setSuccess();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpDataFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufHolder;\n\n/**\n * An HTTP/2 DATA Frame\n */\npublic interface HttpDataFrame extends ByteBufHolder, HttpStreamFrame {\n\n    /**\n     * Returns {@code true} if this frame is the last frame to be transmitted on the stream.\n     */\n    boolean isLast();\n\n    /**\n     * Sets if this frame is the last frame to be transmitted on the stream.\n     */\n    HttpDataFrame setLast(boolean last);\n\n    @Override\n    HttpDataFrame setStreamId(int streamId);\n\n    /**\n     * Returns the data payload of this frame.  If there is no data payload\n     * {@link io.netty.buffer.Unpooled#EMPTY_BUFFER} is returned.\n     * <p/>\n     * The data payload cannot exceed 16384 bytes.\n     */\n    @Override\n    ByteBuf content();\n\n    @Override\n    HttpDataFrame copy();\n\n    @Override\n    HttpDataFrame duplicate();\n\n    @Override\n    HttpDataFrame retain();\n\n    @Override\n    HttpDataFrame retain(int increment);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpErrorCode.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\npublic class HttpErrorCode implements Comparable<HttpErrorCode> {\n\n    /**\n     * 0 No Error\n     */\n    public static final HttpErrorCode NO_ERROR =\n            new HttpErrorCode(0, \"NO_ERROR\");\n\n    /**\n     * 1 Protocol Error\n     */\n    public static final HttpErrorCode PROTOCOL_ERROR =\n            new HttpErrorCode(1, \"PROTOCOL_ERROR\");\n\n    /**\n     * 2 Internal Error\n     */\n    public static final HttpErrorCode INTERNAL_ERROR =\n            new HttpErrorCode(2, \"INTERNAL_ERROR\");\n\n    /**\n     * 3 Flow Control Error\n     */\n    public static final HttpErrorCode FLOW_CONTROL_ERROR =\n            new HttpErrorCode(3, \"FLOW_CONTROL_ERROR\");\n\n    /**\n     * 4 Settings Timeout\n     */\n    public static final HttpErrorCode SETTINGS_TIMEOUT =\n            new HttpErrorCode(4, \"SETTINGS_TIMEOUT\");\n\n    /**\n     * 5 Stream Closed\n     */\n    public static final HttpErrorCode STREAM_CLOSED =\n            new HttpErrorCode(5, \"STREAM_CLOSED\");\n\n    /**\n     * 6 Frame Size Error\n     */\n    public static final HttpErrorCode FRAME_SIZE_ERROR =\n            new HttpErrorCode(6, \"FRAME_SIZE_ERROR\");\n\n    /**\n     * 7 Refused Stream\n     */\n    public static final HttpErrorCode REFUSED_STREAM =\n            new HttpErrorCode(7, \"REFUSED_STREAM\");\n\n    /**\n     * 8 Cancel\n     */\n    public static final HttpErrorCode CANCEL =\n            new HttpErrorCode(8, \"CANCEL\");\n\n    /**\n     * 9 Compression Error\n     */\n    public static final HttpErrorCode COMPRESSION_ERROR =\n            new HttpErrorCode(9, \"COMPRESSION_ERROR\");\n\n    /**\n     * 10 Connect Error\n     */\n    public static final HttpErrorCode CONNECT_ERROR =\n            new HttpErrorCode(10, \"CONNECT_ERROR\");\n\n    /**\n     * 11 Enhance Your Calm (420)\n     */\n    public static final HttpErrorCode ENHANCE_YOUR_CALM =\n            new HttpErrorCode(420, \"ENHANCE_YOUR_CALM\");\n\n    /**\n     * 12 Inadequate Security\n     */\n    public static final HttpErrorCode INADEQUATE_SECURITY =\n            new HttpErrorCode(12, \"INADEQUATE_SECURITY\");\n\n    /**\n     * Returns the {@link HttpErrorCode} represented by the specified code.\n     * If the specified code is a defined HTTP error code, a cached instance\n     * will be returned.  Otherwise, a new instance will be returned.\n     */\n    public static HttpErrorCode valueOf(int code) {\n        switch (code) {\n        case 0:\n            return NO_ERROR;\n        case 1:\n            return PROTOCOL_ERROR;\n        case 2:\n            return INTERNAL_ERROR;\n        case 3:\n            return FLOW_CONTROL_ERROR;\n        case 4:\n            return SETTINGS_TIMEOUT;\n        case 5:\n            return STREAM_CLOSED;\n        case 6:\n            return FRAME_SIZE_ERROR;\n        case 7:\n            return REFUSED_STREAM;\n        case 8:\n            return CANCEL;\n        case 9:\n            return COMPRESSION_ERROR;\n        case 10:\n            return CONNECT_ERROR;\n        case 11:\n            return ENHANCE_YOUR_CALM;\n        case 12:\n            return INADEQUATE_SECURITY;\n        case 420:\n            return ENHANCE_YOUR_CALM;\n        default:\n            return new HttpErrorCode(code, \"UNKNOWN (\" + code + ')');\n        }\n    }\n\n    private final int code;\n\n    private final String statusPhrase;\n\n    /**\n     * Creates a new instance with the specified {@code code} and its\n     * {@code statusPhrase}.\n     */\n    public HttpErrorCode(int code, String statusPhrase) {\n        if (statusPhrase == null) {\n            throw new NullPointerException(\"statusPhrase\");\n        }\n\n        this.code = code;\n        this.statusPhrase = statusPhrase;\n    }\n\n    /**\n     * Returns the code of this status.\n     */\n    public int getCode() {\n        return code;\n    }\n\n    /**\n     * Returns the status phrase of this status.\n     */\n    public String getStatusPhrase() {\n        return statusPhrase;\n    }\n\n    @Override\n    public int hashCode() {\n        return getCode();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof HttpErrorCode)) {\n            return false;\n        }\n\n        return getCode() == ((HttpErrorCode) o).getCode();\n    }\n\n    @Override\n    public String toString() {\n        return getStatusPhrase();\n    }\n\n    @Override\n    public int compareTo(HttpErrorCode o) {\n        return getCode() - o.getCode();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 Frame\n */\npublic interface HttpFrame {\n    // Tag interface\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpFrameDecoder.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.nio.charset.StandardCharsets;\n\nimport io.netty.buffer.ByteBuf;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_CONTINUATION_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DATA_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_ACK;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_HEADERS;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_SEGMENT;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_STREAM;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_PADDED;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_PRIORITY;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FRAME_HEADER_SIZE;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_GOAWAY_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_HEADERS_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_MAX_LENGTH;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_PING_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_PRIORITY_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_PUSH_PROMISE_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_RST_STREAM_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_SETTINGS_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_WINDOW_UPDATE_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.getSignedInt;\nimport static com.twitter.http2.HttpCodecUtil.getSignedLong;\nimport static com.twitter.http2.HttpCodecUtil.getUnsignedInt;\nimport static com.twitter.http2.HttpCodecUtil.getUnsignedMedium;\nimport static com.twitter.http2.HttpCodecUtil.getUnsignedShort;\n\n/**\n * Decodes {@link ByteBuf}s into HTTP/2 Frames.\n */\npublic class HttpFrameDecoder {\n\n    private static final byte[] CLIENT_CONNECTION_PREFACE =\n            \"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\".getBytes(StandardCharsets.US_ASCII);\n\n    private final int maxChunkSize;\n\n    private final HttpFrameDecoderDelegate delegate;\n\n    private State state;\n\n    // HTTP/2 frame header fields\n    private int length;\n    private short type;\n    private byte flags;\n    private int streamId;\n\n    // HTTP/2 frame padding length\n    private int paddingLength;\n\n    private enum State {\n        READ_CONNECTION_HEADER,\n        READ_FRAME_HEADER,\n        READ_PADDING_LENGTH,\n        READ_DATA_FRAME,\n        READ_DATA,\n        READ_HEADERS_FRAME,\n        READ_PRIORITY_FRAME,\n        READ_RST_STREAM_FRAME,\n        READ_SETTINGS_FRAME,\n        READ_SETTING,\n        READ_PUSH_PROMISE_FRAME,\n        READ_PING_FRAME,\n        READ_GOAWAY_FRAME,\n        READ_WINDOW_UPDATE_FRAME,\n        READ_CONTINUATION_FRAME_HEADER,\n        READ_HEADER_BLOCK,\n        SKIP_FRAME_PADDING,\n        SKIP_FRAME_PADDING_CONTINUATION,\n        FRAME_ERROR\n    }\n\n    /**\n     * Creates a new instance with the specified @{code HttpFrameDecoderDelegate}\n     * and the default {@code maxChunkSize (8192)}.\n     */\n    public HttpFrameDecoder(boolean server, HttpFrameDecoderDelegate delegate) {\n        this(server, delegate, 8192);\n    }\n\n    /**\n     * Creates a new instance with the specified parameters.\n     */\n    public HttpFrameDecoder(boolean server, HttpFrameDecoderDelegate delegate, int maxChunkSize) {\n        if (delegate == null) {\n            throw new NullPointerException(\"delegate\");\n        }\n        if (maxChunkSize <= 0) {\n            throw new IllegalArgumentException(\n                    \"maxChunkSize must be a positive integer: \" + maxChunkSize);\n        }\n        this.delegate = delegate;\n        this.maxChunkSize = maxChunkSize;\n        if (server) {\n            state = State.READ_CONNECTION_HEADER;\n        } else {\n            state = State.READ_FRAME_HEADER;\n        }\n    }\n\n    /**\n     * Decode the byte buffer.\n     */\n    public void decode(ByteBuf buffer) {\n        boolean endStream;\n        boolean endSegment;\n        int minLength;\n        int dependency;\n        int weight;\n        boolean exclusive;\n        int errorCode;\n\n        while (true) {\n            switch (state) {\n            case READ_CONNECTION_HEADER:\n                while (buffer.isReadable()) {\n                    byte b = buffer.readByte();\n                    if (b != CLIENT_CONNECTION_PREFACE[length++]) {\n                        state = State.FRAME_ERROR;\n                        delegate.readFrameError(\"Invalid Connection Header\");\n                        return;\n                    }\n\n                    if (length == CLIENT_CONNECTION_PREFACE.length) {\n                        state = State.READ_FRAME_HEADER;\n                        break;\n                    }\n                }\n                if (buffer.isReadable()) {\n                    break;\n                } else {\n                    return;\n                }\n\n            case READ_FRAME_HEADER:\n                // Wait until entire header is readable\n                if (buffer.readableBytes() < HTTP_FRAME_HEADER_SIZE) {\n                    return;\n                }\n\n                // Read frame header fields\n                readFrameHeader(buffer);\n\n                // TODO(jpinner) Sec 4.2 FRAME_SIZE_ERROR\n\n                if (!isValidFrameHeader(length, type, flags, streamId)) {\n                    state = State.FRAME_ERROR;\n                    delegate.readFrameError(\"Invalid Frame Header\");\n                } else if (frameHasPadding(type, flags)) {\n                    state = State.READ_PADDING_LENGTH;\n                } else {\n                    paddingLength = 0;\n                    state = getNextState(length, type);\n                }\n                break;\n\n            case READ_PADDING_LENGTH:\n                if (buffer.readableBytes() < 1) {\n                    return;\n                }\n\n                paddingLength = buffer.readUnsignedByte();\n                --length;\n\n                if (!isValidPaddingLength(length, type, flags, paddingLength)) {\n                    state = State.FRAME_ERROR;\n                    delegate.readFrameError(\"Invalid Frame Padding Length\");\n                } else {\n                    state = getNextState(length, type);\n                }\n                break;\n\n            case READ_DATA_FRAME:\n                endStream = hasFlag(flags, HTTP_FLAG_END_STREAM);\n                state = State.READ_DATA;\n                if (hasFlag(flags, HTTP_FLAG_PADDED)) {\n                    delegate.readDataFramePadding(streamId, endStream, paddingLength + 1);\n                }\n                break;\n\n            case READ_DATA:\n                // Generate data frames that do not exceed maxChunkSize\n                // maxChunkSize must be > 0 so we cannot infinitely loop\n                int dataLength = Math.min(maxChunkSize, length - paddingLength);\n\n                // Wait until entire frame is readable\n                if (buffer.readableBytes() < dataLength) {\n                    return;\n                }\n\n                ByteBuf data = buffer.readBytes(dataLength);\n                length -= dataLength;\n\n                if (length == paddingLength) {\n                    if (paddingLength == 0) {\n                        state = State.READ_FRAME_HEADER;\n                    } else {\n                        state = State.SKIP_FRAME_PADDING;\n                    }\n                }\n\n                endStream = length == paddingLength && hasFlag(flags, HTTP_FLAG_END_STREAM);\n                endSegment = length == paddingLength && hasFlag(flags, HTTP_FLAG_END_SEGMENT);\n\n                delegate.readDataFrame(streamId, endStream, endSegment, data);\n                break;\n\n            case READ_HEADERS_FRAME:\n                minLength = 0;\n                if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {\n                    minLength = 5;\n                }\n                if (buffer.readableBytes() < minLength) {\n                    return;\n                }\n\n                endStream = hasFlag(flags, HTTP_FLAG_END_STREAM);\n                endSegment = hasFlag(flags, HTTP_FLAG_END_SEGMENT);\n\n                exclusive = false;\n                dependency = 0;\n                weight = 16;\n                if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {\n                    dependency = getSignedInt(buffer, buffer.readerIndex());\n                    buffer.skipBytes(4);\n                    weight = buffer.readUnsignedByte() + 1;\n                    if (dependency < 0) {\n                        dependency = dependency & 0x7FFFFFFF;\n                        exclusive = true;\n                    }\n                    length -= 5;\n                }\n\n                state = State.READ_HEADER_BLOCK;\n                delegate.readHeadersFrame(streamId, endStream, endSegment, exclusive, dependency, weight);\n                break;\n\n            case READ_PRIORITY_FRAME:\n                if (buffer.readableBytes() < length) {\n                    return;\n                }\n\n                exclusive = false;\n                dependency = getSignedInt(buffer, buffer.readerIndex());\n                buffer.skipBytes(4);\n                weight = buffer.readUnsignedByte() + 1;\n                if (dependency < 0) {\n                    dependency = dependency & 0x7FFFFFFF;\n                    exclusive = true;\n                }\n\n                state = State.READ_FRAME_HEADER;\n                delegate.readPriorityFrame(streamId, exclusive, dependency, weight);\n                break;\n\n            case READ_RST_STREAM_FRAME:\n                if (buffer.readableBytes() < length) {\n                    return;\n                }\n\n                errorCode = getSignedInt(buffer, buffer.readerIndex());\n                buffer.skipBytes(length);\n\n                state = State.READ_FRAME_HEADER;\n                delegate.readRstStreamFrame(streamId, errorCode);\n                break;\n\n            case READ_SETTINGS_FRAME:\n                boolean ack = hasFlag(flags, HTTP_FLAG_ACK);\n\n                state = State.READ_SETTING;\n                delegate.readSettingsFrame(ack);\n                break;\n\n            case READ_SETTING:\n                if (length == 0) {\n                    state = State.READ_FRAME_HEADER;\n                    delegate.readSettingsEnd();\n                    break;\n                }\n\n                if (buffer.readableBytes() < 6) {\n                    return;\n                }\n\n                int id = getUnsignedShort(buffer, buffer.readerIndex());\n                int value = getSignedInt(buffer, buffer.readerIndex() + 2);\n                buffer.skipBytes(6);\n                length -= 6;\n\n                delegate.readSetting(id, value);\n                break;\n\n            case READ_PUSH_PROMISE_FRAME:\n                if (buffer.readableBytes() < 4) {\n                    return;\n                }\n\n                int promisedStreamId = getUnsignedInt(buffer, buffer.readerIndex());\n                buffer.skipBytes(4);\n                length -= 4;\n\n                if (promisedStreamId == 0) {\n                    state = State.FRAME_ERROR;\n                    delegate.readFrameError(\"Invalid Promised-Stream-ID\");\n                } else {\n                    state = State.READ_HEADER_BLOCK;\n                    delegate.readPushPromiseFrame(streamId, promisedStreamId);\n                }\n                break;\n\n            case READ_PING_FRAME:\n                if (buffer.readableBytes() < length) {\n                    return;\n                }\n\n                long ping = getSignedLong(buffer, buffer.readerIndex());\n                buffer.skipBytes(length);\n\n                boolean pong = hasFlag(flags, HTTP_FLAG_ACK);\n\n                state = State.READ_FRAME_HEADER;\n                delegate.readPingFrame(ping, pong);\n                break;\n\n            case READ_GOAWAY_FRAME:\n                if (buffer.readableBytes() < 8) {\n                    return;\n                }\n\n                int lastStreamId = getUnsignedInt(buffer, buffer.readerIndex());\n                errorCode = getSignedInt(buffer, buffer.readerIndex() + 4);\n                buffer.skipBytes(8);\n                length -= 8;\n\n                if (length == 0) {\n                    state = State.READ_FRAME_HEADER;\n                } else {\n                    paddingLength = length;\n                    state = State.SKIP_FRAME_PADDING;\n                }\n                delegate.readGoAwayFrame(lastStreamId, errorCode);\n                break;\n\n            case READ_WINDOW_UPDATE_FRAME:\n                // Wait until entire frame is readable\n                if (buffer.readableBytes() < length) {\n                    return;\n                }\n\n                int windowSizeIncrement = getUnsignedInt(buffer, buffer.readerIndex());\n                buffer.skipBytes(length);\n\n                if (windowSizeIncrement == 0) {\n                    state = State.FRAME_ERROR;\n                    delegate.readFrameError(\"Invalid Window Size Increment\");\n                } else {\n                    state = State.READ_FRAME_HEADER;\n                    delegate.readWindowUpdateFrame(streamId, windowSizeIncrement);\n                }\n                break;\n\n            case READ_CONTINUATION_FRAME_HEADER:\n                // Wait until entire frame header is readable\n                if (buffer.readableBytes() < HTTP_FRAME_HEADER_SIZE) {\n                    return;\n                }\n\n                // Read and validate continuation frame header fields\n                int prevStreamId = streamId;\n                readFrameHeader(buffer);\n\n                // TODO(jpinner) Sec 4.2 FRAME_SIZE_ERROR\n                // TODO(jpinner) invalid flags\n\n                if (type != HTTP_CONTINUATION_FRAME || streamId != prevStreamId) {\n                    state = State.FRAME_ERROR;\n                    delegate.readFrameError(\"Invalid Continuation Frame\");\n                } else {\n                    paddingLength = 0;\n                    state = State.READ_HEADER_BLOCK;\n                }\n                break;\n\n            case READ_HEADER_BLOCK:\n                if (length == paddingLength) {\n                    boolean endHeaders = hasFlag(flags, HTTP_FLAG_END_HEADERS);\n                    if (endHeaders) {\n                        state = State.SKIP_FRAME_PADDING;\n                        delegate.readHeaderBlockEnd();\n                    } else {\n                        state = State.SKIP_FRAME_PADDING_CONTINUATION;\n                    }\n                    break;\n                }\n\n                if (!buffer.isReadable()) {\n                    return;\n                }\n\n                int readableBytes = Math.min(buffer.readableBytes(), length - paddingLength);\n                ByteBuf headerBlockFragment = buffer.readBytes(readableBytes);\n                length -= readableBytes;\n\n                delegate.readHeaderBlock(headerBlockFragment);\n                break;\n\n            case SKIP_FRAME_PADDING:\n                int numBytes = Math.min(buffer.readableBytes(), length);\n                buffer.skipBytes(numBytes);\n                length -= numBytes;\n                if (length == 0) {\n                    state = State.READ_FRAME_HEADER;\n                    break;\n                }\n                return;\n\n            case SKIP_FRAME_PADDING_CONTINUATION:\n                int numPaddingBytes = Math.min(buffer.readableBytes(), length);\n                buffer.skipBytes(numPaddingBytes);\n                length -= numPaddingBytes;\n                if (length == 0) {\n                    state = State.READ_CONTINUATION_FRAME_HEADER;\n                    break;\n                }\n                return;\n\n            case FRAME_ERROR:\n                buffer.skipBytes(buffer.readableBytes());\n                return;\n\n            default:\n                throw new Error(\"Shouldn't reach here.\");\n            }\n        }\n    }\n\n    /**\n     * Reads the HTTP/2 Frame Header and sets the length, type, flags, and streamId member variables.\n     *\n     * @param buffer input buffer containing the entire 9-octet header\n     */\n    private void readFrameHeader(ByteBuf buffer) {\n        int frameOffset = buffer.readerIndex();\n        length = getUnsignedMedium(buffer, frameOffset);\n        type = buffer.getUnsignedByte(frameOffset + 3);\n        flags = buffer.getByte(frameOffset + 4);\n        streamId = getUnsignedInt(buffer, frameOffset + 5);\n        buffer.skipBytes(HTTP_FRAME_HEADER_SIZE);\n    }\n\n    private static boolean hasFlag(byte flags, byte flag) {\n        return (flags & flag) != 0;\n    }\n\n    private static boolean frameHasPadding(int type, byte flags) {\n        switch (type) {\n        case HTTP_DATA_FRAME:\n        case HTTP_HEADERS_FRAME:\n        case HTTP_PUSH_PROMISE_FRAME:\n            return hasFlag(flags, HTTP_FLAG_PADDED);\n        default:\n            return false;\n        }\n    }\n\n    private static State getNextState(int length, int type) {\n        switch (type) {\n        case HTTP_DATA_FRAME:\n            return State.READ_DATA_FRAME;\n\n        case HTTP_HEADERS_FRAME:\n            return State.READ_HEADERS_FRAME;\n\n        case HTTP_PRIORITY_FRAME:\n            return State.READ_PRIORITY_FRAME;\n\n        case HTTP_RST_STREAM_FRAME:\n            return State.READ_RST_STREAM_FRAME;\n\n        case HTTP_SETTINGS_FRAME:\n            return State.READ_SETTINGS_FRAME;\n\n        case HTTP_PUSH_PROMISE_FRAME:\n            return State.READ_PUSH_PROMISE_FRAME;\n\n        case HTTP_PING_FRAME:\n            return State.READ_PING_FRAME;\n\n        case HTTP_GOAWAY_FRAME:\n            return State.READ_GOAWAY_FRAME;\n\n        case HTTP_WINDOW_UPDATE_FRAME:\n            return State.READ_WINDOW_UPDATE_FRAME;\n\n        case HTTP_CONTINUATION_FRAME:\n            throw new Error(\"Shouldn't reach here.\");\n\n        default:\n            if (length != 0) {\n                return State.SKIP_FRAME_PADDING;\n            } else {\n                return State.READ_FRAME_HEADER;\n            }\n        }\n    }\n\n    private static boolean isValidFrameHeader(int length, short type, byte flags, int streamId) {\n        if (length > HTTP_MAX_LENGTH) {\n            return false;\n        }\n        int minLength;\n        switch (type) {\n        case HTTP_DATA_FRAME:\n            if (hasFlag(flags, HTTP_FLAG_PADDED)) {\n                minLength = 1;\n            } else {\n                minLength = 0;\n            }\n            return length >= minLength && streamId != 0;\n\n        case HTTP_HEADERS_FRAME:\n            if (hasFlag(flags, HTTP_FLAG_PADDED)) {\n                minLength = 1;\n            } else {\n                minLength = 0;\n            }\n            if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {\n                minLength += 5;\n            }\n            return length >= minLength && streamId != 0;\n\n        case HTTP_PRIORITY_FRAME:\n            return length == 5 && streamId != 0;\n\n        case HTTP_RST_STREAM_FRAME:\n            return length == 4 && streamId != 0;\n\n        case HTTP_SETTINGS_FRAME:\n            boolean lengthValid = hasFlag(flags, HTTP_FLAG_ACK) ? length == 0 : (length % 6) == 0;\n            return lengthValid && streamId == 0;\n\n        case HTTP_PUSH_PROMISE_FRAME:\n            if (hasFlag(flags, HTTP_FLAG_PADDED)) {\n                minLength = 5;\n            } else {\n                minLength = 4;\n            }\n            return length >= minLength && streamId != 0;\n\n        case HTTP_PING_FRAME:\n            return length == 8 && streamId == 0;\n\n        case HTTP_GOAWAY_FRAME:\n            return length >= 8 && streamId == 0;\n\n        case HTTP_WINDOW_UPDATE_FRAME:\n            return length == 4;\n\n        case HTTP_CONTINUATION_FRAME:\n            return false;\n\n        default:\n            return true;\n        }\n    }\n\n    private static boolean isValidPaddingLength(\n            int length, short type, byte flags, int paddingLength) {\n        switch (type) {\n        case HTTP_DATA_FRAME:\n            return length >= paddingLength;\n        case HTTP_HEADERS_FRAME:\n            if (hasFlag(flags, HTTP_FLAG_PRIORITY)) {\n                return length >= paddingLength + 5;\n            } else {\n                return length >= paddingLength;\n            }\n        case HTTP_PUSH_PROMISE_FRAME:\n            return length >= paddingLength + 4;\n        default:\n            throw new Error(\"Shouldn't reach here.\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpFrameDecoderDelegate.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.buffer.ByteBuf;\n\n/**\n * Callback interface for {@link HttpFrameDecoder}.\n */\npublic interface HttpFrameDecoderDelegate {\n\n    /**\n     * Called when a DATA frame is received.\n     */\n    void readDataFramePadding(int streamId, boolean endStream, int padding);\n\n    /**\n     * Called when a DATA frame is received.\n     */\n    void readDataFrame(int streamId, boolean endStream, boolean endSegment, ByteBuf data);\n\n    /**\n     * Called when a HEADERS frame is received.\n     * The Header Block Fragment is not included. See readHeaderBlock().\n     */\n    void readHeadersFrame(\n            int streamId,\n            boolean endStream,\n            boolean endSegment,\n            boolean exclusive,\n            int dependency,\n            int weight\n    );\n\n    /**\n     * Called when a PRIORITY frame is received.\n     */\n    void readPriorityFrame(int streamId, boolean exclusive, int dependency, int weight);\n\n    /**\n     * Called when a RST_STREAM frame is received.\n     */\n    void readRstStreamFrame(int streamId, int errorCode);\n\n    /**\n     * Called when a SETTINGS frame is received.\n     * Settings are not included. See readSetting().\n     */\n    void readSettingsFrame(boolean ack);\n\n    /**\n     * Called when an individual setting within a SETTINGS frame is received.\n     */\n    void readSetting(int id, int value);\n\n    /**\n     * Called when the entire SETTINGS frame has been received.\n     */\n    void readSettingsEnd();\n\n    /**\n     * Called when a PUSH_PROMISE frame is received.\n     * The Header Block Fragment is not included. See readHeaderBlock().\n     */\n    void readPushPromiseFrame(int streamId, int promisedStreamId);\n\n    /**\n     * Called when a PING frame is received.\n     */\n    void readPingFrame(long data, boolean ack);\n\n    /**\n     * Called when a GOAWAY frame is received.\n     */\n    void readGoAwayFrame(int lastStreamId, int errorCode);\n\n    /**\n     * Called when a WINDOW_UPDATE frame is received.\n     */\n    void readWindowUpdateFrame(int streamId, int windowSizeIncrement);\n\n    /**\n     * Called when the header block fragment within a HEADERS,\n     * PUSH_PROMISE, or CONTINUATION frame is received.\n     */\n    void readHeaderBlock(ByteBuf headerBlockFragment);\n\n    /**\n     * Called when an entire header block has been received.\n     */\n    void readHeaderBlockEnd();\n\n    /**\n     * Called when an unrecoverable connection error has occurred.\n     */\n    void readFrameError(String message);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpFrameEncoder.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Set;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_CONTINUATION_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DATA_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_DEPENDENCY;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_ACK;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_HEADERS;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_END_STREAM;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FLAG_PRIORITY;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FRAME_HEADER_SIZE;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_GOAWAY_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_HEADERS_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_MAX_LENGTH;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_PING_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_PRIORITY_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_PUSH_PROMISE_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_RST_STREAM_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_SETTINGS_FRAME;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_WINDOW_UPDATE_FRAME;\n\n/**\n * Encodes an HTTP/2 Frame into a {@link ByteBuf}.\n */\npublic class HttpFrameEncoder {\n\n    /**\n     * Encode an HTTP/2 DATA Frame\n     */\n    public ByteBuf encodeDataFrame(int streamId, boolean endStream, ByteBuf data) {\n        byte flags = endStream ? HTTP_FLAG_END_STREAM : 0;\n        ByteBuf header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);\n        writeFrameHeader(header, data.readableBytes(), HTTP_DATA_FRAME, flags, streamId);\n        return Unpooled.wrappedBuffer(header, data);\n    }\n\n    /**\n     * Encode an HTTP/2 HEADERS Frame\n     */\n    public ByteBuf encodeHeadersFrame(\n            int streamId,\n            boolean endStream,\n            boolean exclusive,\n            int dependency,\n            int weight,\n            ByteBuf headerBlock\n    ) {\n        byte flags = endStream ? HTTP_FLAG_END_STREAM : 0;\n        boolean hasPriority = exclusive\n                || dependency != HTTP_DEFAULT_DEPENDENCY || weight != HTTP_DEFAULT_WEIGHT;\n        if (hasPriority) {\n            flags |= HTTP_FLAG_PRIORITY;\n        }\n        int maxLength = hasPriority ? HTTP_MAX_LENGTH - 5 : HTTP_MAX_LENGTH;\n        boolean needsContinuations = headerBlock.readableBytes() > maxLength;\n        if (!needsContinuations) {\n            flags |= HTTP_FLAG_END_HEADERS;\n        }\n        int length = needsContinuations ? maxLength : headerBlock.readableBytes();\n        if (hasPriority) {\n            length += 5;\n        }\n        int frameLength = hasPriority ? HTTP_FRAME_HEADER_SIZE + 5 : HTTP_FRAME_HEADER_SIZE;\n        ByteBuf header = Unpooled.buffer(frameLength);\n        writeFrameHeader(header, length, HTTP_HEADERS_FRAME, flags, streamId);\n        if (hasPriority) {\n            if (exclusive) {\n                header.writeInt(dependency | 0x80000000);\n            } else {\n                header.writeInt(dependency);\n            }\n            header.writeByte(weight - 1);\n            length -= 5;\n        }\n        ByteBuf frame = Unpooled.wrappedBuffer(header, headerBlock.readSlice(length));\n        if (needsContinuations) {\n            while (headerBlock.readableBytes() > HTTP_MAX_LENGTH) {\n                header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);\n                writeFrameHeader(header, HTTP_MAX_LENGTH, HTTP_CONTINUATION_FRAME, (byte) 0, streamId);\n                frame = Unpooled.wrappedBuffer(frame, header, headerBlock.readSlice(HTTP_MAX_LENGTH));\n            }\n            header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);\n            writeFrameHeader(\n                    header,\n                    headerBlock.readableBytes(),\n                    HTTP_CONTINUATION_FRAME,\n                    HTTP_FLAG_END_HEADERS,\n                    streamId\n            );\n            frame = Unpooled.wrappedBuffer(frame, header, headerBlock);\n        }\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 PRIORITY Frame\n     */\n    public ByteBuf encodePriorityFrame(int streamId, boolean exclusive, int dependency, int weight) {\n        int length = 5;\n        byte flags = 0;\n        ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);\n        writeFrameHeader(frame, length, HTTP_PRIORITY_FRAME, flags, streamId);\n        if (exclusive) {\n            frame.writeInt(dependency | 0x80000000);\n        } else {\n            frame.writeInt(dependency);\n        }\n        frame.writeByte(weight - 1);\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 RST_STREAM Frame\n     */\n    public ByteBuf encodeRstStreamFrame(int streamId, int errorCode) {\n        int length = 4;\n        byte flags = 0;\n        ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);\n        writeFrameHeader(frame, length, HTTP_RST_STREAM_FRAME, flags, streamId);\n        frame.writeInt(errorCode);\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 SETTINGS Frame\n     */\n    public ByteBuf encodeSettingsFrame(HttpSettingsFrame httpSettingsFrame) {\n        Set<Integer> ids = httpSettingsFrame.getIds();\n        int length = ids.size() * 6;\n        byte flags = httpSettingsFrame.isAck() ? HTTP_FLAG_ACK : 0;\n        int streamId = 0;\n        ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);\n        writeFrameHeader(frame, length, HTTP_SETTINGS_FRAME, flags, streamId);\n        for (int id : ids) {\n            frame.writeShort(id);\n            frame.writeInt(httpSettingsFrame.getValue(id));\n        }\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 PUSH_PROMISE Frame\n     */\n    public ByteBuf encodePushPromiseFrame(int streamId, int promisedStreamId, ByteBuf headerBlock) {\n        boolean needsContinuations = headerBlock.readableBytes() > HTTP_MAX_LENGTH - 4;\n        int length = needsContinuations ? HTTP_MAX_LENGTH - 4 : headerBlock.readableBytes();\n        byte flags = needsContinuations ? 0 : HTTP_FLAG_END_HEADERS;\n        ByteBuf header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + 4);\n        writeFrameHeader(header, length + 4, HTTP_PUSH_PROMISE_FRAME, flags, streamId);\n        header.writeInt(promisedStreamId);\n        ByteBuf frame = Unpooled.wrappedBuffer(header, headerBlock.readSlice(length));\n        if (needsContinuations) {\n            while (headerBlock.readableBytes() > HTTP_MAX_LENGTH) {\n                header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);\n                writeFrameHeader(header, HTTP_MAX_LENGTH, HTTP_CONTINUATION_FRAME, (byte) 0, streamId);\n                frame = Unpooled.wrappedBuffer(frame, header, headerBlock.readSlice(HTTP_MAX_LENGTH));\n            }\n            header = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE);\n            writeFrameHeader(\n                    header,\n                    headerBlock.readableBytes(),\n                    HTTP_CONTINUATION_FRAME,\n                    HTTP_FLAG_END_HEADERS,\n                    streamId\n            );\n            frame = Unpooled.wrappedBuffer(frame, header, headerBlock);\n        }\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 PING Frame\n     */\n    public ByteBuf encodePingFrame(long data, boolean ack) {\n        int length = 8;\n        byte flags = ack ? HTTP_FLAG_ACK : 0;\n        int streamId = 0;\n        ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);\n        writeFrameHeader(frame, length, HTTP_PING_FRAME, flags, streamId);\n        frame.writeLong(data);\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 GOAWAY Frame\n     */\n    public ByteBuf encodeGoAwayFrame(int lastStreamId, int errorCode) {\n        int length = 8;\n        byte flags = 0;\n        int streamId = 0;\n        ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);\n        writeFrameHeader(frame, length, HTTP_GOAWAY_FRAME, flags, streamId);\n        frame.writeInt(lastStreamId);\n        frame.writeInt(errorCode);\n        return frame;\n    }\n\n    /**\n     * Encode an HTTP/2 WINDOW_UPDATE Frame\n     */\n    public ByteBuf encodeWindowUpdateFrame(int streamId, int windowSizeIncrement) {\n        int length = 4;\n        byte flags = 0;\n        ByteBuf frame = Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length);\n        writeFrameHeader(frame, length, HTTP_WINDOW_UPDATE_FRAME, flags, streamId);\n        frame.writeInt(windowSizeIncrement);\n        return frame;\n    }\n\n    private void writeFrameHeader(ByteBuf buffer, int length, int type, byte flags, int streamId) {\n        buffer.writeMedium(length);\n        buffer.writeByte(type);\n        buffer.writeByte(flags);\n        buffer.writeInt(streamId);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpGoAwayFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 GOAWAY Frame\n */\npublic interface HttpGoAwayFrame extends HttpFrame {\n\n    /**\n     * Returns the Last-Stream-ID of this frame.\n     */\n    int getLastStreamId();\n\n    /**\n     * Sets the Last-Stream-ID of this frame.  The Last-Stream-ID cannot be negative.\n     */\n    HttpGoAwayFrame setLastStreamId(int lastStreamId);\n\n    /**\n     * Returns the error code of this frame.\n     */\n    HttpErrorCode getErrorCode();\n\n    /**\n     * Sets the error code of this frame.\n     */\n    HttpGoAwayFrame setErrorCode(HttpErrorCode errorCode);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpHeaderBlockDecoder.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufInputStream;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.HttpHeaders;\n\nimport com.twitter.hpack.Decoder;\nimport com.twitter.hpack.HeaderListener;\n\nfinal class HttpHeaderBlockDecoder {\n\n    private static final HeaderListener NULL_HEADER_LISTENER = new NullHeaderListener();\n\n    private Decoder decoder;\n    private ByteBuf cumulation;\n\n    public HttpHeaderBlockDecoder(int maxHeaderSize, int maxHeaderTableSize) {\n        decoder = new Decoder(maxHeaderSize, maxHeaderTableSize);\n    }\n\n    /**\n     * Set the maximum header table size allowed by the decoder.\n     * This is the value of SETTINGS_HEADER_TABLE_SIZE sent to the peer.\n     *\n     * @param maxHeaderTableSize the maximum header table size allowed by the decoder\n     */\n    public void setMaxHeaderTableSize(int maxHeaderTableSize) {\n        decoder.setMaxHeaderTableSize(maxHeaderTableSize);\n    }\n\n    public void decode(ByteBuf headerBlock, final HttpHeaderBlockFrame frame) throws IOException {\n        HeaderListener headerListener = NULL_HEADER_LISTENER;\n        if (frame != null) {\n            headerListener = new HeaderListenerImpl(frame.headers());\n        }\n\n        if (cumulation == null) {\n            decoder.decode(new ByteBufInputStream(headerBlock), headerListener);\n            if (headerBlock.isReadable()) {\n                cumulation = Unpooled.buffer(headerBlock.readableBytes());\n                cumulation.writeBytes(headerBlock);\n            }\n        } else {\n            cumulation.writeBytes(headerBlock);\n            decoder.decode(new ByteBufInputStream(cumulation), headerListener);\n            if (cumulation.isReadable()) {\n                cumulation.discardReadBytes();\n            } else {\n                cumulation.release();\n                cumulation = null;\n            }\n        }\n    }\n\n    public void endHeaderBlock(final HttpHeaderBlockFrame frame) {\n        if (cumulation != null) {\n            if (cumulation.isReadable() && frame != null) {\n                frame.setInvalid();\n            }\n            cumulation.release();\n            cumulation = null;\n        }\n\n        boolean truncated = decoder.endHeaderBlock();\n\n        if (truncated && frame != null) {\n            frame.setTruncated();\n        }\n    }\n\n    private static final class NullHeaderListener implements HeaderListener {\n        @Override\n        public void addHeader(byte[] name, byte[] value, boolean sensitive) {\n            // No Op\n        }\n    }\n\n    private static final class HeaderListenerImpl implements HeaderListener {\n\n        private final HttpHeaders headers;\n\n        HeaderListenerImpl(HttpHeaders headers) {\n            this.headers = headers;\n        }\n\n        @Override\n        public void addHeader(byte[] name, byte[] value, boolean sensitive) {\n            String nameStr = new String(name, StandardCharsets.UTF_8);\n\n            // check for empty value\n            if (value.length == 0) {\n                addHeader(nameStr, \"\");\n                return;\n            }\n\n            // Sec. 8.1.3.3. Header Field Ordering\n            int index = 0;\n            int offset = 0;\n            while (index < value.length) {\n                while (index < value.length && value[index] != (byte) 0) {\n                    index++;\n                }\n                if (index - offset == 0) {\n                    addHeader(nameStr, \"\");\n                } else {\n                    String valueStr = new String(value, offset, index - offset, StandardCharsets.UTF_8);\n                    addHeader(nameStr, valueStr);\n                }\n                index++;\n                offset = index;\n            }\n        }\n\n        private void addHeader(String name, String value) {\n            boolean crumb = \"cookie\".equalsIgnoreCase(name);\n            if (value.length() == 0) {\n                if (crumb || headers.contains(name)) {\n                    return;\n                }\n            }\n            if (crumb) {\n                // Sec. 8.1.3.4. Cookie Header Field\n                String cookie = headers.get(name);\n                if (cookie == null) {\n                    headers.set(name, value);\n                } else {\n                    headers.set(name, cookie + \"; \" + value);\n                }\n            } else {\n                headers.add(name, value);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpHeaderBlockEncoder.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.Locale;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufOutputStream;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerContext;\n\nimport com.twitter.hpack.Encoder;\n\npublic class HttpHeaderBlockEncoder {\n\n    private static final byte[] COOKIE = {'c', 'o', 'o', 'k', 'i', 'e'};\n    private static final byte[] EMPTY = {};\n\n    private int encoderMaxHeaderTableSize;\n    private int decoderMaxHeaderTableSize;\n    private int maxHeaderTableSize;\n    private Encoder encoder;\n\n    /**\n     * Create a new instance.\n     */\n    public HttpHeaderBlockEncoder(int maxHeaderTableSize) {\n        encoderMaxHeaderTableSize = maxHeaderTableSize;\n        decoderMaxHeaderTableSize = maxHeaderTableSize;\n        this.maxHeaderTableSize = maxHeaderTableSize;\n        encoder = new Encoder(maxHeaderTableSize);\n    }\n\n    /**\n     * Set the maximum header table size allowed by the encoder.\n     *\n     * @param encoderMaxHeaderTableSize the maximum header table size allowed by the encoder\n     */\n    public void setEncoderMaxHeaderTableSize(int encoderMaxHeaderTableSize) {\n        this.encoderMaxHeaderTableSize = encoderMaxHeaderTableSize;\n        if (encoderMaxHeaderTableSize < maxHeaderTableSize) {\n            maxHeaderTableSize = encoderMaxHeaderTableSize;\n        }\n    }\n\n    /**\n     * Set the maximum header table size allowed by the peer's encoder.\n     * This is the value of SETTINGS_HEADER_TABLE_SIZE received from the peer.\n     *\n     * @param decoderMaxHeaderTableSize the maximum header table size allowed by the decoder\n     */\n    public void setDecoderMaxHeaderTableSize(int decoderMaxHeaderTableSize) {\n        this.decoderMaxHeaderTableSize = decoderMaxHeaderTableSize;\n        if (decoderMaxHeaderTableSize < maxHeaderTableSize) {\n            maxHeaderTableSize = decoderMaxHeaderTableSize;\n        }\n    }\n\n    /**\n     * Encode the header block frame.\n     */\n    public ByteBuf encode(ChannelHandlerContext ctx, HttpHeaderBlockFrame frame) throws IOException {\n        ByteBuf buf = Unpooled.buffer();\n        ByteBufOutputStream out = new ByteBufOutputStream(buf);\n\n        // The current allowable max header table size is the\n        // minimum of the encoder and decoder allowable sizes\n        int allowableHeaderTableSize = Math.min(encoderMaxHeaderTableSize, decoderMaxHeaderTableSize);\n\n        // maxHeaderTableSize will hold the smallest size seen the\n        // last call to encode. This might be smaller than the\n        // current allowable max header table size\n        if (maxHeaderTableSize < allowableHeaderTableSize) {\n            encoder.setMaxHeaderTableSize(out, maxHeaderTableSize);\n        }\n\n        // Check if the current allowable size is equal to the encoder's\n        // capacity and set the new size if necessary\n        if (allowableHeaderTableSize != encoder.getMaxHeaderTableSize()) {\n            encoder.setMaxHeaderTableSize(out, allowableHeaderTableSize);\n        }\n\n        // Store the current allowable size for the next call\n        maxHeaderTableSize = allowableHeaderTableSize;\n\n        // Now we can encode headers\n        for (String name : frame.headers().names()) {\n            if (\"cookie\".equalsIgnoreCase(name)) {\n                // Sec. 8.1.3.4. Cookie Header Field\n                for (String value : frame.headers().getAll(name)) {\n                    for (String crumb : value.split(\";\")) {\n                        byte[] valueBytes = crumb.trim().getBytes(StandardCharsets.UTF_8);\n                        encoder.encodeHeader(out, COOKIE, valueBytes, true);\n                    }\n                }\n            } else {\n                byte[] nameBytes = name.toLowerCase(Locale.ENGLISH).getBytes(StandardCharsets.UTF_8);\n                // Sec. 8.1.3.3. Header Field Ordering\n                List<String> values = frame.headers().getAll(name);\n                if (values.size() == 0) {\n                    encoder.encodeHeader(out, nameBytes, EMPTY, false);\n                } else {\n                    for (String value : values) {\n                        byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);\n                        encoder.encodeHeader(out, nameBytes, valueBytes, false);\n                    }\n                }\n            }\n        }\n\n        return buf;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpHeaderBlockFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.HttpHeaders;\n\npublic interface HttpHeaderBlockFrame extends HttpStreamFrame {\n\n    /**\n     * Returns {@code true} if this header block is invalid.\n     * A RST_STREAM frame with code PROTOCOL_ERROR should be sent.\n     */\n    boolean isInvalid();\n\n    /**\n     * Marks this header block as invalid.\n     */\n    HttpHeaderBlockFrame setInvalid();\n\n    /**\n     * Returns {@code true} if this header block has been truncated due to length restrictions.\n     */\n    boolean isTruncated();\n\n    /**\n     * Mark this header block as truncated.\n     */\n    HttpHeaderBlockFrame setTruncated();\n\n    /**\n     * Returns the {@link HttpHeaders} of this frame.\n     */\n    HttpHeaders headers();\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpHeadersFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 HEADERS Frame\n */\npublic interface HttpHeadersFrame extends HttpHeaderBlockFrame {\n\n    /**\n     * Returns {@code true} if this frame is the last frame to be transmitted on the stream.\n     */\n    boolean isLast();\n\n    /**\n     * Sets if this frame is the last frame to be transmitted on the stream.\n     */\n    HttpHeadersFrame setLast(boolean last);\n\n    /**\n     * Returns {@code true} if the dependency of the stream is exclusive.\n     */\n    boolean isExclusive();\n\n    /**\n     * Sets if the dependency of the stream is exclusive.\n     */\n    HttpHeadersFrame setExclusive(boolean exclusive);\n\n    /**\n     * Returns the dependency of the stream.\n     */\n    int getDependency();\n\n    /**\n     * Sets the dependency of the stream.  The dependency cannot be negative.\n     */\n    HttpHeadersFrame setDependency(int dependency);\n\n    /**\n     * Returns the weight of the dependency of the stream.\n     */\n    int getWeight();\n\n    /**\n     * Sets the weight of the dependency of the stream.\n     * The weight must be positive and cannot exceed 256 bytes.\n     */\n    HttpHeadersFrame setWeight(int weight);\n\n    @Override\n    HttpHeadersFrame setStreamId(int streamId);\n\n    @Override\n    HttpHeadersFrame setInvalid();\n\n    @Override\n    HttpHeadersFrame setTruncated();\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpMessageProxy.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.DecoderResult;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpMessage;\nimport io.netty.handler.codec.http.HttpVersion;\n\n/**\n * An {@link HttpMessage} decorator.\n */\nclass HttpMessageProxy implements HttpMessage {\n\n    private final HttpMessage message;\n\n    protected HttpMessageProxy(HttpMessage message) {\n        this.message = message;\n    }\n\n    @Override\n    public HttpVersion getProtocolVersion() {\n        return message.getProtocolVersion();\n    }\n\n    @Override\n    public HttpMessage setProtocolVersion(HttpVersion version) {\n        message.setProtocolVersion(version);\n        return this;\n    }\n\n    @Override\n    public HttpHeaders headers() {\n        return message.headers();\n    }\n\n    @Override\n    @Deprecated\n    public DecoderResult getDecoderResult() {\n        return message.getDecoderResult();\n    }\n\n    @Override\n    public void setDecoderResult(DecoderResult result) {\n        message.setDecoderResult(result);\n    }\n\n    @Override\n    public HttpVersion protocolVersion() {\n        return message.protocolVersion();\n    }\n\n    @Override\n    public DecoderResult decoderResult() {\n        return message.decoderResult();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpPingFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 PING Frame\n */\npublic interface HttpPingFrame extends HttpFrame {\n\n    /**\n     * Returns the data payload of this frame.\n     */\n    long getData();\n\n    /**\n     * Sets the data payload of this frame.\n     */\n    HttpPingFrame setData(long data);\n\n    /**\n     * Returns {@code true} if this frame is in response to a PING.\n     */\n    boolean isPong();\n\n    /**\n     * Sets if this frame is in response to a PING.\n     */\n    HttpPingFrame setPong(boolean pong);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpPriorityFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 PRIORITY Frame\n */\npublic interface HttpPriorityFrame extends HttpFrame {\n\n    /**\n     * Returns the stream identifier of this frame.\n     */\n    int getStreamId();\n\n    /**\n     * Sets the stream identifier of this frame.  The stream identifier must be positive.\n     */\n    HttpPriorityFrame setStreamId(int streamId);\n\n    /**\n     * Returns {@code true} if the dependency of the stream is exclusive.\n     */\n    boolean isExclusive();\n\n    /**\n     * Sets if the dependency of the stream is exclusive.\n     */\n    HttpPriorityFrame setExclusive(boolean exclusive);\n\n    /**\n     * Returns the dependency of the stream.\n     */\n    int getDependency();\n\n    /**\n     * Sets the dependency of the stream.  The dependency cannot be negative.\n     */\n    HttpPriorityFrame setDependency(int dependency);\n\n    /**\n     * Returns the weight of the dependency of the stream.\n     */\n    int getWeight();\n\n    /**\n     * Sets the weight of the dependency of the stream.\n     * The weight must be positive and cannot exceed 256 bytes.\n     */\n    HttpPriorityFrame setWeight(int weight);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpProtocolException.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\npublic class HttpProtocolException extends Exception {\n\n    /**\n     * Creates a new instance.\n     */\n    public HttpProtocolException() {\n        super();\n    }\n\n    /**\n     * Creates a new instance.\n     */\n    public HttpProtocolException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    /**\n     * Creates a new instance.\n     */\n    public HttpProtocolException(String message) {\n        super(message);\n    }\n\n    /**\n     * Creates a new instance.\n     */\n    public HttpProtocolException(Throwable cause) {\n        super(cause);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpPushPromiseFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 PUSH_PROMISE Frame\n */\npublic interface HttpPushPromiseFrame extends HttpHeaderBlockFrame {\n\n    /**\n     * Returns the Promised-Stream-ID of this frame.\n     */\n    int getPromisedStreamId();\n\n    /**\n     * Sets the Promised-Stream-ID of this frame.  The Promised-Stream-ID must be positive.\n     */\n    HttpPushPromiseFrame setPromisedStreamId(int promisedStreamId);\n\n    @Override\n    HttpPushPromiseFrame setStreamId(int streamId);\n\n    @Override\n    HttpPushPromiseFrame setInvalid();\n\n    @Override\n    HttpPushPromiseFrame setTruncated();\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpRequestProxy.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpVersion;\n\n/**\n * An {@link HttpRequest} decorator.\n */\nclass HttpRequestProxy extends HttpMessageProxy implements HttpRequest {\n\n    private final HttpRequest request;\n\n    public HttpRequestProxy(HttpRequest request) {\n        super(request);\n        this.request = request;\n    }\n\n    public HttpRequest httpRequest() {\n        return request;\n    }\n\n    @Override\n    @Deprecated\n    public HttpMethod getMethod() {\n        return request.getMethod();\n    }\n\n    @Override\n    public HttpRequest setMethod(HttpMethod method) {\n        request.setMethod(method);\n        return this;\n    }\n\n    @Override\n    @Deprecated\n    public String getUri() {\n        return request.getUri();\n    }\n\n    @Override\n    public HttpRequest setUri(String uri) {\n        request.setUri(uri);\n        return this;\n    }\n\n    @Override\n    public HttpRequest setProtocolVersion(HttpVersion version) {\n        request.setProtocolVersion(version);\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return request.toString();\n    }\n\n    @Override\n    public HttpMethod method() {\n        return request.method();\n    }\n\n    @Override\n    public String uri() {\n        return request.uri();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpResponseProxy.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\n\n/**\n * An {@link HttpResponse} decorator.\n */\npublic class HttpResponseProxy extends HttpMessageProxy implements HttpResponse {\n\n    private final HttpResponse response;\n\n    public HttpResponseProxy(HttpResponse response) {\n        super(response);\n        this.response = response;\n    }\n\n    @Override\n    @Deprecated\n    public HttpResponseStatus getStatus() {\n        return response.getStatus();\n    }\n\n    @Override\n    public HttpResponse setStatus(HttpResponseStatus status) {\n        response.setStatus(status);\n        return this;\n    }\n\n    @Override\n    public HttpResponse setProtocolVersion(HttpVersion version) {\n        response.setProtocolVersion(version);\n        return this;\n    }\n\n    @Override\n    public String toString() {\n        return response.toString();\n    }\n\n    @Override\n    public HttpResponseStatus status() {\n        return response.status();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpRstStreamFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 RST_STREAM Frame\n */\npublic interface HttpRstStreamFrame extends HttpFrame {\n\n    /**\n     * Returns the stream identifier of this frame.\n     */\n    int getStreamId();\n\n    /**\n     * Sets the stream identifier of this frame.  The stream identifier must be positive.\n     */\n    HttpRstStreamFrame setStreamId(int streamId);\n\n    /**\n     * Returns the error code of this frame.\n     */\n    HttpErrorCode getErrorCode();\n\n    /**\n     * Sets the error code of this frame.\n     */\n    HttpRstStreamFrame setErrorCode(HttpErrorCode errorCode);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpSettingsFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Set;\n\n/**\n * An HTTP/2 SETTINGS Frame\n */\npublic interface HttpSettingsFrame extends HttpFrame {\n\n    int SETTINGS_HEADER_TABLE_SIZE = 1;\n    int SETTINGS_ENABLE_PUSH = 2;\n    int SETTINGS_MAX_CONCURRENT_STREAMS = 3;\n    int SETTINGS_INITIAL_WINDOW_SIZE = 4;\n    int SETTINGS_MAX_FRAME_SIZE = 5;\n    int SETTINGS_MAX_HEADER_LIST_SIZE = 6;\n\n    /**\n     * Returns a {@code Set} of the setting IDs.\n     * The set's iterator will return the IDs in ascending order.\n     */\n    Set<Integer> getIds();\n\n    /**\n     * Returns {@code true} if the setting ID has a value.\n     */\n    boolean isSet(int id);\n\n    /**\n     * Returns the value of the setting ID.\n     * Returns -1 if the setting ID is not set.\n     */\n    int getValue(int id);\n\n    /**\n     * Sets the value of the setting ID.\n     * The ID cannot be negative and cannot exceed 65535.\n     */\n    HttpSettingsFrame setValue(int id, int value);\n\n    /**\n     * Removes the value of the setting ID.\n     */\n    HttpSettingsFrame removeValue(int id);\n\n    /**\n     * Returns {@code true} if this frame is an acknowledgement frame.\n     */\n    boolean isAck();\n\n    /**\n     * Sets if this frame is acknowledgement frame.\n     */\n    HttpSettingsFrame setAck(boolean ack);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpStreamDecoder.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ConcurrentHashMap;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.DecoderResult;\nimport io.netty.handler.codec.MessageToMessageDecoder;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.LastHttpContent;\n\n/**\n * Decodes {@link HttpFrame}s into {@link StreamedHttpRequest}s.\n */\npublic class HttpStreamDecoder extends MessageToMessageDecoder<Object> {\n\n    private static final CancellationException CANCELLATION_EXCEPTION =\n            new CancellationException(\"HTTP/2 RST_STREAM Frame Received\");\n\n    private final Map<Integer, StreamedHttpMessage> messageMap =\n            new ConcurrentHashMap<Integer, StreamedHttpMessage>();\n    private final Map<Integer, LastHttpContent> trailerMap =\n            new ConcurrentHashMap<Integer, LastHttpContent>();\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {\n        if (msg instanceof HttpHeadersFrame) {\n\n            HttpHeadersFrame httpHeadersFrame = (HttpHeadersFrame) msg;\n            int streamId = httpHeadersFrame.getStreamId();\n            StreamedHttpMessage message = messageMap.get(streamId);\n\n            if (message == null) {\n                if (httpHeadersFrame.headers().contains(\":status\")) {\n\n                    // If a client receives a reply with a truncated header block,\n                    // reply with a RST_STREAM frame with error code INTERNAL_ERROR.\n                    if (httpHeadersFrame.isTruncated()) {\n                        HttpRstStreamFrame httpRstStreamFrame =\n                                new DefaultHttpRstStreamFrame(streamId, HttpErrorCode.INTERNAL_ERROR);\n                        out.add(httpRstStreamFrame);\n                        return;\n                    }\n\n                    try {\n                        StreamedHttpResponse response = createHttpResponse(httpHeadersFrame);\n\n                        if (httpHeadersFrame.isLast()) {\n                            HttpHeaders.setContentLength(response, 0);\n                            response.getContent().close();\n                        } else {\n                            // Response body will follow in a series of Data Frames\n                            if (!HttpHeaders.isContentLengthSet(response)) {\n                                HttpHeaders.setTransferEncodingChunked(response);\n                            }\n                            messageMap.put(streamId, response);\n                        }\n                        out.add(response);\n                    } catch (Exception e) {\n                        // If a client receives a SYN_REPLY without valid getStatus and version headers\n                        // the client must reply with a RST_STREAM frame indicating a PROTOCOL_ERROR\n                        HttpRstStreamFrame httpRstStreamFrame =\n                                new DefaultHttpRstStreamFrame(streamId, HttpErrorCode.PROTOCOL_ERROR);\n                        ctx.writeAndFlush(httpRstStreamFrame);\n                        out.add(httpRstStreamFrame);\n                    }\n\n                } else {\n\n                    // If a client sends a request with a truncated header block, the server must\n                    // reply with a HTTP 431 REQUEST HEADER FIELDS TOO LARGE reply.\n                    if (httpHeadersFrame.isTruncated()) {\n                        httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);\n                        httpHeadersFrame.setLast(true);\n                        httpHeadersFrame.headers().set(\n                                \":status\", HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.code());\n                        ctx.writeAndFlush(httpHeadersFrame);\n                        return;\n                    }\n\n                    try {\n                        message = createHttpRequest(httpHeadersFrame);\n\n                        if (httpHeadersFrame.isLast()) {\n                            message.setDecoderResult(DecoderResult.SUCCESS);\n                            message.getContent().close();\n                        } else {\n                            // Request body will follow in a series of Data Frames\n                            messageMap.put(streamId, message);\n                        }\n\n                        out.add(message);\n\n                    } catch (Exception e) {\n                        // If a client sends a SYN_STREAM without all of the method, url (host and path),\n                        // scheme, and version headers the server must reply with a HTTP 400 BAD REQUEST reply.\n                        // Also sends HTTP 400 BAD REQUEST reply if header name/value pairs are invalid\n                        httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);\n                        httpHeadersFrame.setLast(true);\n                        httpHeadersFrame.headers().set(\":status\", HttpResponseStatus.BAD_REQUEST.code());\n                        ctx.writeAndFlush(httpHeadersFrame);\n                    }\n                }\n            } else {\n                LastHttpContent trailer = trailerMap.remove(streamId);\n                if (trailer == null) {\n                    trailer = new DefaultLastHttpContent();\n                }\n\n                // Ignore trailers in a truncated HEADERS frame.\n                if (!httpHeadersFrame.isTruncated()) {\n                    for (Map.Entry<String, String> e: httpHeadersFrame.headers()) {\n                        trailer.trailingHeaders().add(e.getKey(), e.getValue());\n                    }\n                }\n\n                if (httpHeadersFrame.isLast()) {\n                    messageMap.remove(streamId);\n                    message.addContent(trailer);\n                } else {\n                    trailerMap.put(streamId, trailer);\n                }\n            }\n\n        } else if (msg instanceof HttpDataFrame) {\n\n            HttpDataFrame httpDataFrame = (HttpDataFrame) msg;\n            int streamId = httpDataFrame.getStreamId();\n            StreamedHttpMessage message = messageMap.get(streamId);\n\n            // If message is not in map discard Data Frame.\n            if (message == null) {\n                return;\n            }\n\n            ByteBuf content = httpDataFrame.content();\n            if (content.isReadable()) {\n                content.retain();\n                message.addContent(new DefaultHttpContent(content));\n            }\n\n            if (httpDataFrame.isLast()) {\n                messageMap.remove(streamId);\n                message.addContent(LastHttpContent.EMPTY_LAST_CONTENT);\n                message.setDecoderResult(DecoderResult.SUCCESS);\n            }\n\n        } else if (msg instanceof HttpRstStreamFrame) {\n\n            HttpRstStreamFrame httpRstStreamFrame = (HttpRstStreamFrame) msg;\n            int streamId = httpRstStreamFrame.getStreamId();\n            StreamedHttpMessage message = messageMap.remove(streamId);\n\n            if (message != null) {\n                message.getContent().close();\n                message.setDecoderResult(DecoderResult.failure(CANCELLATION_EXCEPTION));\n            }\n\n        } else {\n            // HttpGoAwayFrame\n            out.add(msg);\n        }\n    }\n\n    private StreamedHttpRequest createHttpRequest(HttpHeadersFrame httpHeadersFrame)\n            throws Exception {\n        // Create the first line of the request from the name/value pairs\n        HttpMethod method = HttpMethod.valueOf(httpHeadersFrame.headers().get(\":method\"));\n        String url = httpHeadersFrame.headers().get(\":path\");\n\n        httpHeadersFrame.headers().remove(\":method\");\n        httpHeadersFrame.headers().remove(\":path\");\n\n        StreamedHttpRequest request = new StreamedHttpRequest(HttpVersion.HTTP_1_1, method, url);\n\n        // Remove the scheme header\n        httpHeadersFrame.headers().remove(\":scheme\");\n\n        // Replace the SPDY host header with the HTTP host header\n        String host = httpHeadersFrame.headers().get(\":authority\");\n        httpHeadersFrame.headers().remove(\":authority\");\n        httpHeadersFrame.headers().set(\"host\", host);\n\n        for (Map.Entry<String, String> e : httpHeadersFrame.headers()) {\n            String name = e.getKey();\n            String value = e.getValue();\n            if (name.charAt(0) != ':') {\n                request.headers().add(name, value);\n            }\n        }\n\n        // Set the Stream-ID as a header\n        request.headers().set(\"X-SPDY-Stream-ID\", httpHeadersFrame.getStreamId());\n\n        // The Connection and Keep-Alive headers are no longer valid\n        HttpHeaders.setKeepAlive(request, true);\n\n        // Transfer-Encoding header is not valid\n        request.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);\n\n        if (httpHeadersFrame.isLast()) {\n            request.getContent().close();\n            request.setDecoderResult(DecoderResult.SUCCESS);\n        } else {\n            request.setDecoderResult(DecoderResult.UNFINISHED);\n        }\n\n        return request;\n    }\n\n    private StreamedHttpResponse createHttpResponse(HttpHeadersFrame httpHeadersFrame)\n            throws Exception {\n        // Create the first line of the request from the name/value pairs\n        HttpResponseStatus status = HttpResponseStatus.valueOf(Integer.parseInt(\n                httpHeadersFrame.headers().get(\":status\")));\n\n        httpHeadersFrame.headers().remove(\":status\");\n\n        StreamedHttpResponse response = new StreamedHttpResponse(HttpVersion.HTTP_1_1, status);\n        for (Map.Entry<String, String> e : httpHeadersFrame.headers()) {\n            String name = e.getKey();\n            String value = e.getValue();\n            if (name.charAt(0) != ':') {\n                response.headers().add(name, value);\n            }\n        }\n\n        // Set the Stream-ID as a header\n        response.headers().set(\"X-SPDY-Stream-ID\", httpHeadersFrame.getStreamId());\n\n        // The Connection and Keep-Alive headers are no longer valid\n        HttpHeaders.setKeepAlive(response, true);\n\n        // Transfer-Encoding header is not valid\n        response.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);\n        response.headers().remove(HttpHeaders.Names.TRAILER);\n\n        if (httpHeadersFrame.isLast()) {\n            response.getContent().close();\n            response.setDecoderResult(DecoderResult.SUCCESS);\n        } else {\n            response.setDecoderResult(DecoderResult.UNFINISHED);\n        }\n\n        return response;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpStreamEncoder.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Map;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpMessage;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport io.netty.util.concurrent.Future;\nimport io.netty.util.concurrent.FutureListener;\n\n/**\n * Encodes {@link StreamedHttpResponse}s into {@link HttpFrame}s.\n */\npublic class HttpStreamEncoder extends ChannelOutboundHandlerAdapter {\n\n    private static final int MAX_DATA_LENGTH = 0x2000; // Limit Data Frames to 8k\n\n    private int currentStreamId;\n\n    @Override\n    public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise)\n            throws Exception {\n        if (msg instanceof HttpRequest) {\n\n            HttpRequest httpRequest = (HttpRequest) msg;\n            HttpHeadersFrame httpHeadersFrame = createHttpHeadersFrame(httpRequest);\n            currentStreamId = httpHeadersFrame.getStreamId();\n\n            ChannelPromise writeFuture = getMessageFuture(ctx, promise, currentStreamId, httpRequest);\n            if (promise == writeFuture) {\n                httpHeadersFrame.setLast(true);\n            } else {\n                promise = writeFuture;\n            }\n\n            ctx.write(httpHeadersFrame, promise);\n\n        } else if (msg instanceof HttpResponse) {\n\n            HttpResponse httpResponse = (HttpResponse) msg;\n            HttpHeadersFrame httpHeadersFrame = createHttpHeadersFrame(httpResponse);\n            currentStreamId = httpHeadersFrame.getStreamId();\n\n            ChannelPromise writeFuture = getMessageFuture(ctx, promise, currentStreamId, httpResponse);\n            if (promise == writeFuture) {\n                httpHeadersFrame.setLast(true);\n            } else {\n                promise = writeFuture;\n            }\n\n            ctx.write(httpHeadersFrame, promise);\n\n        } else if (msg instanceof HttpContent) {\n\n            HttpContent chunk = (HttpContent) msg;\n            writeChunk(ctx, promise, currentStreamId, chunk);\n\n        } else {\n            // Unknown message type\n            ctx.write(msg, promise);\n        }\n    }\n\n    private ChannelPromise getMessageFuture(\n            final ChannelHandlerContext ctx,\n            final ChannelPromise promise,\n            final int streamId,\n            HttpMessage message\n    ) {\n        if (message instanceof StreamedHttpMessage\n                && !((StreamedHttpMessage) message).getContent().isClosed()) {\n            final Pipe<HttpContent> pipe = ((StreamedHttpMessage) message).getContent();\n\n            ChannelPromise writeFuture = ctx.channel().newPromise();\n            writeFuture.addListener(new ChannelFutureListener() {\n                @Override\n                public void operationComplete(ChannelFuture future) throws Exception {\n                    // Channel's thread\n                    // First frame has been written\n\n                    if (future.isSuccess()) {\n                        pipe.receive().addListener(\n                                new ChunkListener(ctx, streamId, pipe, promise));\n                    } else if (future.isCancelled()) {\n                        pipe.close();\n                        promise.cancel(true);\n                    } else {\n                        pipe.close();\n                        promise.setFailure(future.cause());\n                    }\n                }\n            });\n\n            return writeFuture;\n        } else {\n            return promise;\n        }\n    }\n\n\n    /**\n     * Listens to chunks being ready on a pipe.\n     */\n    private class ChunkListener implements FutureListener<HttpContent> {\n        private final ChannelHandlerContext ctx;\n        private final int streamId;\n        private final Pipe<HttpContent> pipe;\n        private final ChannelPromise completionFuture;\n\n        ChunkListener(\n                ChannelHandlerContext ctx,\n                int streamId,\n                Pipe<HttpContent> pipe,\n                ChannelPromise completionFuture\n        ) {\n            this.ctx = ctx;\n            this.streamId = streamId;\n            this.pipe = pipe;\n            this.completionFuture = completionFuture;\n        }\n\n        @Override\n        public void operationComplete(final Future<HttpContent> future) throws Exception {\n            final FutureListener<HttpContent> chunkListener = this;\n\n            ctx.executor().execute(new Runnable() {\n                @Override\n                public void run() {\n                    if (future.isSuccess()) {\n                        HttpContent content = future.getNow();\n                        ChannelPromise writeFuture;\n\n                        if (content instanceof LastHttpContent) {\n                            writeFuture = completionFuture;\n                        } else {\n                            writeFuture = ctx.channel().newPromise();\n                            writeFuture.addListener(new ChannelFutureListener() {\n                                @Override\n                                public void operationComplete(ChannelFuture future) throws Exception {\n                                    if (future.isSuccess()) {\n                                        pipe.receive().addListener(chunkListener);\n                                    } else if (future.isCancelled()) {\n                                        pipe.close();\n                                        completionFuture.cancel(true);\n                                    } else {\n                                        pipe.close();\n                                        completionFuture.setFailure(future.cause());\n                                    }\n                                }\n                            });\n                        }\n\n                        writeChunk(ctx, writeFuture, streamId, content);\n                    } else {\n                        // Somebody closed the pipe\n                        // Send a reset frame to the channel and complete the completion future\n\n                        ctx.writeAndFlush(\n                                new DefaultHttpRstStreamFrame(streamId, HttpErrorCode.INTERNAL_ERROR));\n\n                        if (future.isCancelled()) {\n                            completionFuture.cancel(true);\n                        } else {\n                            completionFuture.setFailure(future.cause());\n                        }\n                    }\n                }\n            });\n        }\n    }\n\n    /**\n     * Writes an HTTP chunk downstream as one or more HTTP/2 frames.\n     */\n    protected void writeChunk(\n            ChannelHandlerContext ctx, ChannelPromise future, int streamId, HttpContent content) {\n\n        HttpFrame[] httpFrames = createHttpDataFrames(streamId, content.content());\n\n        if (content instanceof LastHttpContent) {\n            LastHttpContent trailer = (LastHttpContent) content;\n            HttpHeaders trailers = trailer.trailingHeaders();\n            if (trailers.isEmpty()) {\n                if (httpFrames.length == 0) {\n                    HttpDataFrame httpDataFrame = new DefaultHttpDataFrame(streamId);\n                    httpDataFrame.setLast(true);\n                    httpFrames = new HttpFrame[1];\n                    httpFrames[0] = httpDataFrame;\n                } else {\n                    HttpDataFrame httpDataFrame = (HttpDataFrame) httpFrames[httpFrames.length - 1];\n                    httpDataFrame.setLast(true);\n                }\n            } else {\n                // Create HTTP HEADERS frame out of trailers\n                HttpHeadersFrame httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);\n                httpHeadersFrame.setLast(true);\n                for (Map.Entry<String, String> entry : trailer.trailingHeaders()) {\n                    httpHeadersFrame.headers().add(entry.getKey(), entry.getValue());\n                }\n                if (httpFrames.length == 0) {\n                    httpFrames = new HttpFrame[1];\n                    httpFrames[0] = httpHeadersFrame;\n                } else {\n                    HttpFrame[] copy = new HttpFrame[httpFrames.length + 1];\n                    for (int i = 0; i < httpFrames.length; i++) {\n                        copy[i] = httpFrames[i];\n                    }\n                    copy[httpFrames.length] = httpHeadersFrame;\n                    httpFrames = copy;\n                }\n            }\n        }\n\n        ChannelPromise frameFuture = getFrameFuture(ctx, future, httpFrames);\n\n        // Trigger a write\n        frameFuture.setSuccess();\n    }\n\n    private static ChannelPromise getFrameFuture(\n            ChannelHandlerContext ctx, ChannelPromise future, HttpFrame[] httpFrames) {\n        ChannelPromise frameFuture = future;\n        for (int i = httpFrames.length; --i >= 0; ) {\n            future = ctx.channel().newPromise();\n            future.addListener(new HttpFrameWriter(ctx, frameFuture, httpFrames[i]));\n            frameFuture = future;\n        }\n        return frameFuture;\n    }\n\n    private static class HttpFrameWriter implements ChannelFutureListener {\n\n        private final ChannelHandlerContext ctx;\n        private final ChannelPromise promise;\n        private final Object msg;\n\n        HttpFrameWriter(ChannelHandlerContext ctx, ChannelPromise promise, Object msg) {\n            this.ctx = ctx;\n            this.promise = promise;\n            this.msg = msg;\n        }\n\n        public void operationComplete(ChannelFuture future) throws Exception {\n            if (future.isSuccess()) {\n                ctx.writeAndFlush(msg, promise);\n            } else if (future.isCancelled()) {\n                ReferenceCountUtil.release(msg);\n                promise.cancel(true);\n            } else {\n                ReferenceCountUtil.release(msg);\n                promise.setFailure(future.cause());\n            }\n        }\n    }\n\n    private HttpHeadersFrame createHttpHeadersFrame(HttpRequest httpRequest)\n            throws Exception {\n        // Get the Stream-ID from the headers\n        int streamId = HttpHeaders.getIntHeader(httpRequest, \"X-SPDY-Stream-ID\");\n        httpRequest.headers().remove(\"X-SPDY-Stream-ID\");\n\n        // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding\n        // headers are not valid and MUST not be sent.\n        httpRequest.headers().remove(HttpHeaders.Names.CONNECTION);\n        httpRequest.headers().remove(\"Keep-Alive\");\n        httpRequest.headers().remove(\"Proxy-Connection\");\n        httpRequest.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);\n\n        HttpHeadersFrame httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);\n\n        // Unfold the first line of the request into name/value pairs\n        httpHeadersFrame.headers().add(\":method\", httpRequest.getMethod().name());\n        httpHeadersFrame.headers().set(\":scheme\", \"https\");\n        httpHeadersFrame.headers().add(\":path\", httpRequest.getUri());\n\n        // Replace the HTTP host header with the SPDY host header\n        String host = httpRequest.headers().get(HttpHeaders.Names.HOST);\n        httpRequest.headers().remove(HttpHeaders.Names.HOST);\n        httpHeadersFrame.headers().add(\":authority\", host);\n\n        // Transfer the remaining HTTP headers\n        for (Map.Entry<String, String> entry : httpRequest.headers()) {\n            httpHeadersFrame.headers().add(entry.getKey(), entry.getValue());\n        }\n\n        return httpHeadersFrame;\n    }\n\n    private HttpHeadersFrame createHttpHeadersFrame(HttpResponse httpResponse)\n            throws Exception {\n        // Get the Stream-ID from the headers\n        int streamId = HttpHeaders.getIntHeader(httpResponse, \"X-SPDY-Stream-ID\");\n        httpResponse.headers().remove(\"X-SPDY-Stream-ID\");\n\n        // The Connection, Keep-Alive, Proxy-Connection, and Transfer-Encoding\n        // headers are not valid and MUST not be sent.\n        httpResponse.headers().remove(HttpHeaders.Names.CONNECTION);\n        httpResponse.headers().remove(\"Keep-Alive\");\n        httpResponse.headers().remove(\"Proxy-Connection\");\n        httpResponse.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING);\n\n        HttpHeadersFrame httpHeadersFrame = new DefaultHttpHeadersFrame(streamId);\n\n        // Unfold the first line of the response into name/value pairs\n        httpHeadersFrame.headers().set(\":status\", httpResponse.getStatus().code());\n\n        // Transfer the remaining HTTP headers\n        for (Map.Entry<String, String> entry : httpResponse.headers()) {\n            httpHeadersFrame.headers().add(entry.getKey(), entry.getValue());\n        }\n\n        return httpHeadersFrame;\n    }\n\n    private HttpDataFrame[] createHttpDataFrames(int streamId, ByteBuf content) {\n        int readableBytes = content.readableBytes();\n        int count = readableBytes / MAX_DATA_LENGTH;\n        if (readableBytes % MAX_DATA_LENGTH > 0) {\n            count++;\n        }\n        HttpDataFrame[] httpDataFrames = new HttpDataFrame[count];\n        for (int i = 0; i < count; i++) {\n            int dataSize = Math.min(content.readableBytes(), MAX_DATA_LENGTH);\n            HttpDataFrame httpDataFrame = new DefaultHttpDataFrame(streamId, content.readSlice(dataSize));\n            httpDataFrames[i] = httpDataFrame;\n        }\n        return httpDataFrames;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpStreamFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 Frame that is associated with an individual stream\n */\npublic interface HttpStreamFrame extends HttpFrame {\n\n    /**\n     * Returns the stream identifier of this frame.\n     */\n    int getStreamId();\n\n    /**\n     * Sets the stream identifier of this frame.  The stream identifier must be positive.\n     */\n    HttpStreamFrame setStreamId(int streamId);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/HttpWindowUpdateFrame.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\n/**\n * An HTTP/2 WINDOW_UPDATE Frame\n */\npublic interface HttpWindowUpdateFrame extends HttpFrame {\n\n    /**\n     * Returns the stream identifier of this frame.\n     */\n    int getStreamId();\n\n    /**\n     * Sets the stream identifier of this frame.  The stream identifier cannot be negative.\n     */\n    HttpWindowUpdateFrame setStreamId(int streamId);\n\n    /**\n     * Returns the Window-Size-Increment of this frame.\n     */\n    int getWindowSizeIncrement();\n\n    /**\n     * Sets the Window-Size-Increment of this frame.\n     * The Window-Size-Increment must be positive.\n     */\n    HttpWindowUpdateFrame setWindowSizeIncrement(int deltaWindowSize);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/Pipe.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.LinkedList;\nimport java.util.Objects;\nimport java.util.Queue;\nimport java.util.concurrent.ConcurrentLinkedQueue;\n\nimport io.netty.channel.ChannelException;\nimport io.netty.util.concurrent.Future;\nimport io.netty.util.concurrent.ImmediateEventExecutor;\nimport io.netty.util.concurrent.Promise;\n\n/**\n * Implements a stream that pipes objects between its two ends.\n * Futures are used to communicate when messages are sent and received.\n *\n * @param <T> the type of objects to send along the pipe\n */\npublic class Pipe<T> {\n\n    private static final ChannelException PIPE_CLOSED = new ChannelException(\"pipe closed\");\n\n    private static final Future<Void> SENT_FUTURE =\n            ImmediateEventExecutor.INSTANCE.newSucceededFuture(null);\n    private static final Future<Void> CLOSED_FUTURE =\n            ImmediateEventExecutor.INSTANCE.newFailedFuture(PIPE_CLOSED);\n\n    private Queue<Node> sendQueue = new LinkedList<Node>();\n    private Queue<Promise<T>> receiveQueue = new ConcurrentLinkedQueue<Promise<T>>();\n\n    private boolean closed;\n\n    /**\n     * Holds a message and an associated future.\n     */\n    private final class Node {\n        public T message;\n        public Promise<Void> promise;\n\n        Node(T message, Promise<Void> promise) {\n            this.message = message;\n            this.promise = promise;\n        }\n    }\n\n    /**\n     * Creates a new pipe and uses instances of {@link ImmediateEventExecutor}\n     * for the send and receive executors.\n     *\n     * @see ImmediateEventExecutor#INSTANCE\n     */\n    public Pipe() {\n        super();\n    }\n\n    /**\n     * Sends a message to this pipe. Returns a {@link Future} that is completed\n     * when the message is received.\n     * <p>\n     * If the pipe is closed then this will return a failed future.</p>\n     *\n     * @param message the message to send to the pipe\n     * @return a {@link Future} that is satisfied when the message is received,\n     * or a failed future if the pipe is closed.\n     * @throws NullPointerException  if the message is {@code null}.\n     * @throws IllegalStateException if the message could not be added to the queue for some reason.\n     * @see #receive()\n     */\n    public Future<Void> send(T message) {\n        Objects.requireNonNull(message, \"msg\");\n\n        Promise<T> receivePromise;\n\n        synchronized (this) {\n            if (closed) {\n                return CLOSED_FUTURE;\n            }\n\n            receivePromise = receiveQueue.poll();\n            if (receivePromise == null) {\n                Promise<Void> sendPromise = ImmediateEventExecutor.INSTANCE.newPromise();\n                sendQueue.add(new Node(message, sendPromise));\n                return sendPromise;\n            }\n        }\n\n        receivePromise.setSuccess(message);\n        return SENT_FUTURE;\n    }\n\n    /**\n     * Receives a message from this pipe.\n     * <p>\n     * If the pipe is closed then this will return a failed future.</p>\n     */\n    public Future<T> receive() {\n        Node node;\n\n        synchronized (this) {\n            node = sendQueue.poll();\n            if (node == null) {\n                if (closed) {\n                    return ImmediateEventExecutor.INSTANCE.newFailedFuture(PIPE_CLOSED);\n                }\n\n                Promise<T> promise = ImmediateEventExecutor.INSTANCE.newPromise();\n                receiveQueue.add(promise);\n                return promise;\n            }\n        }\n\n        node.promise.setSuccess(null);\n        return ImmediateEventExecutor.INSTANCE.newSucceededFuture(node.message);\n    }\n\n    /**\n     * Closes this pipe. This fails all outstanding receive futures.\n     * This does nothing if the pipe is already closed.\n     */\n    public void close() {\n        synchronized (this) {\n            if (closed) {\n                return;\n            }\n            closed = true;\n        }\n\n        while (!receiveQueue.isEmpty()) {\n            receiveQueue.poll().setFailure(PIPE_CLOSED);\n        }\n    }\n\n    /**\n     * Checks if this pipe is closed.\n     *\n     * @return whether this pipe is closed.\n     */\n    public synchronized boolean isClosed() {\n        return closed;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/StreamedHttpMessage.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpMessage;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.concurrent.Future;\n\n/**\n * An {@link HttpMessage} that adds support for streaming content using {@linkplain Pipe pipes}.\n * @see Pipe\n */\npublic interface StreamedHttpMessage extends HttpMessage {\n\n    /**\n     * Gets the pipe for sending the message body as {@linkplain HttpContent} objects.\n     * A {@linkplain LastHttpContent last http content} will indicate the end of the body.\n     *\n     * @return the message body pipe.\n     * @see <a href=\"https://tools.ietf.org/html/rfc2616#section-3.6.1\">RFC 2616, 3.6.1 Chunked Transfer Coding</a>\n     * @see <a href=\"https://tools.ietf.org/html/rfc2616#section-4.3\">RFC 2616, 4.3 Message Body</a>\n     */\n    Pipe<HttpContent> getContent();\n\n    /**\n     * Adds content to the pipe. This returns a future for determining whether the message was received.\n     * If the chunk is the last chunk then the pipe will be closed.\n     * <p>\n     * An {@link LastHttpContent} can be used for the trailer part of a chunked-encoded request.</p>\n     * <p>\n     * This method is preferable to {@code getContent().send()} because it does additional checks.</p>\n     *\n     * @param content the content to send\n     * @return a future indicating when the message was received.\n     */\n    Future<Void> addContent(HttpContent content);\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/StreamedHttpRequest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.concurrent.Future;\n\n/**\n * An {@link HttpRequest} that adds content streaming.\n */\npublic class StreamedHttpRequest extends HttpRequestProxy implements StreamedHttpMessage {\n\n    private Pipe<HttpContent> pipe = new Pipe<HttpContent>();\n\n    public StreamedHttpRequest(HttpVersion version, HttpMethod method, String uri) {\n        this(new DefaultHttpRequest(version, method, uri));\n    }\n\n    public StreamedHttpRequest(HttpRequest request) {\n        super(request);\n    }\n\n    @Override\n    public Pipe<HttpContent> getContent() {\n        return pipe;\n    }\n\n    @Override\n    public Future<Void> addContent(HttpContent content) {\n        Future<Void> future = pipe.send(content);\n        if (content instanceof LastHttpContent) {\n            pipe.close();\n        }\n        return future;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/twitter/http2/StreamedHttpResponse.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.DefaultHttpResponse;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.concurrent.Future;\n\n/**\n * An {@link HttpResponse} that adds content streaming.\n */\npublic class StreamedHttpResponse extends HttpResponseProxy implements StreamedHttpMessage {\n\n    private Pipe<HttpContent> pipe = new Pipe<HttpContent>();\n\n    public StreamedHttpResponse(HttpVersion version, HttpResponseStatus status) {\n        this(new DefaultHttpResponse(version, status));\n    }\n\n    public StreamedHttpResponse(HttpResponse response) {\n        super(response);\n    }\n\n    @Override\n    public Pipe<HttpContent> getContent() {\n        return pipe;\n    }\n\n    @Override\n    public Future<Void> addContent(HttpContent content) {\n        Future<Void> future = pipe.send(content);\n        if (content instanceof LastHttpContent) {\n            pipe.close();\n        }\n        return future;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/twitter/http2/HttpFrameDecoderTest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Random;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\n\nimport static io.netty.util.ReferenceCountUtil.releaseLater;\nimport static org.mockito.Matchers.anyString;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\nimport static org.mockito.Mockito.verifyZeroInteractions;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_DEPENDENCY;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_FRAME_HEADER_SIZE;\n\npublic class HttpFrameDecoderTest {\n\n    private static final Random RANDOM = new Random();\n\n    private final HttpFrameDecoderDelegate delegate =\n            Mockito.mock(HttpFrameDecoderDelegate.class);\n    private HttpFrameDecoder decoder;\n\n    @Before\n    public void createHandler() {\n        // Set server to false to ignore the Connection Header\n        decoder = new HttpFrameDecoder(false, delegate);\n    }\n\n    @Test\n    public void testClientConnectionPreface() throws Exception {\n        decoder = new HttpFrameDecoder(true, delegate);\n        ByteBuf connectionPreface = releaseLater(Unpooled.wrappedBuffer(new byte[]{\n                0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32,\n                0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a\n        }));\n        int length = 0;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(connectionPreface, frame)));\n\n        verify(delegate).readSettingsFrame(false);\n        verify(delegate).readSettingsEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidClientConnectionPreface() throws Exception {\n        decoder = new HttpFrameDecoder(true, delegate);\n        // Only write SETTINGS frame\n        int length = 0;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpDataFrame() throws Exception {\n        int length = RANDOM.nextInt() & 0x3FFF;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        writeRandomData(frame, length);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        for (int i = 0; i < length; i += 8192) {\n            // data frames do not exceed maxChunkSize\n            int off = HTTP_FRAME_HEADER_SIZE + i;\n            int len = Math.min(length - i, 8192);\n            inOrder.verify(delegate).readDataFrame(streamId, false, false, frame.slice(off, len));\n        }\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testEmptyHttpDataFrame() throws Exception {\n        int length = 0;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readDataFrame(streamId, false, false, Unpooled.EMPTY_BUFFER);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testLastHttpDataFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x01; // END_STREAM\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readDataFrame(streamId, true, false, Unpooled.EMPTY_BUFFER);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testLastSegmentHttpDataFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x02; // END_SEGMENT\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readDataFrame(streamId, false, true, Unpooled.EMPTY_BUFFER);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testPaddedHttpDataFrame() throws Exception {\n        int length = RANDOM.nextInt() & 0x3FFF | 0x01;\n        byte flags = 0x08; // PADDED\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int padding = Math.min(RANDOM.nextInt() & 0xFF, length - 1);\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        frame.writeByte(padding);\n        writeRandomData(frame, length - 1);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readDataFramePadding(streamId, false, padding + 1);\n        int dataLength = length - 1 - padding;\n        if (dataLength == 0) {\n            inOrder.verify(delegate).readDataFrame(streamId, false, false, Unpooled.EMPTY_BUFFER);\n        } else {\n            for (int i = 0; i < dataLength; i += 8192) {\n                // data frames do not exceed maxChunkSize\n                int off = HTTP_FRAME_HEADER_SIZE + 1 + i;\n                int len = Math.min(dataLength - i, 8192);\n                inOrder.verify(delegate).readDataFrame(streamId, false, false, frame.slice(off, len));\n            }\n        }\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpDataFrameReservedBits() throws Exception {\n        int length = 0;\n        byte flags = (byte) 0xC4; // should ignore any unknown flags\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        setReservedBits(frame);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readDataFrame(streamId, false, false, Unpooled.EMPTY_BUFFER);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpDataFrameStreamId() throws Exception {\n        int length = 0;\n        byte flags = 0;\n        int streamId = 0; // illegal stream identifier\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpDataFrameLength() throws Exception {\n        int length = 0; // illegal length\n        byte flags = 0x08; // PADDED\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpDataFramePaddingLength() throws Exception {\n        int length = 1;\n        byte flags = 0x08; // PADDED\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = dataFrame(length, flags, streamId);\n        frame.writeByte(1);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrame() throws Exception {\n        int headerBlockLength = 16;\n        int length = 5 + headerBlockLength;\n        byte flags = 0x04 | 0x20; // END_HEADERS | PRIORITY\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf frame = headersFrame(length, flags, streamId);\n        writePriorityFields(frame, exclusive, dependency, weight);\n        writeRandomData(frame, headerBlockLength);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, exclusive, dependency, weight + 1);\n        inOrder.verify(delegate).readHeaderBlock(\n                frame.slice(HTTP_FRAME_HEADER_SIZE + 5, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testLastHttpHeadersFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x01 | 0x04; // END_STREAM | END_HEADERS\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = headersFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, true, false, false, HTTP_DEFAULT_DEPENDENCY, HTTP_DEFAULT_WEIGHT);\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testLastSegmentHttpHeadersFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x02 | 0x04; // END_SEGMENT | END_HEADERS\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = headersFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, true, false, HTTP_DEFAULT_DEPENDENCY, HTTP_DEFAULT_WEIGHT);\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrameReservedBits() throws Exception {\n        int length = 0;\n        byte flags = (byte) 0xC4; // END_HEADERS -- should ignore any unknown flags\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = headersFrame(length, flags, streamId);\n        setReservedBits(frame);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, false, HTTP_DEFAULT_DEPENDENCY, HTTP_DEFAULT_WEIGHT);\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpHeadersFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x04 | 0x20; // END_HEADERS | PRIORITY\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = headersFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpHeadersFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x04; // END_HEADERS\n        int streamId = 0; // illegal stream identifier\n\n        ByteBuf frame = headersFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testContinuedHttpHeadersFrame() throws Exception {\n        int headerBlockLength = 16;\n        int length = 5;\n        byte flags = 0x20; // PRIORITY\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf headersFrame = headersFrame(length, flags, streamId);\n        writePriorityFields(headersFrame, exclusive, dependency, weight);\n        ByteBuf continuationFrame =\n                continuationFrame(headerBlockLength, (byte) 0x04, streamId); // END_HEADERS\n        writeRandomData(continuationFrame, headerBlockLength);\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(headersFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, exclusive, dependency, weight + 1);\n        inOrder.verify(delegate).readHeaderBlock(\n                continuationFrame.slice(HTTP_FRAME_HEADER_SIZE, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrameEmptyContinuation() throws Exception {\n        int length = 16;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf headersFrame = headersFrame(length, flags, streamId);\n        writeRandomData(headersFrame, length);\n        ByteBuf continuationFrame = continuationFrame(0, (byte) 0x04, streamId); // END_HEADERS\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(headersFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, false, HTTP_DEFAULT_DEPENDENCY, HTTP_DEFAULT_WEIGHT);\n        inOrder.verify(delegate).readHeaderBlock(\n                headersFrame.slice(HTTP_FRAME_HEADER_SIZE, length));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrameMultipleContinuations() throws Exception {\n        int headerBlockLength = 16;\n        int length = 5 + headerBlockLength;\n        byte flags = 0x01 | 0x20; // END_STREAM | PRIORITY\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf headersFrame = headersFrame(length, flags, streamId);\n        writePriorityFields(headersFrame, exclusive, dependency, weight);\n        writeRandomData(headersFrame, headerBlockLength);\n        ByteBuf continuationFrame1 = continuationFrame(0, (byte) 0x00, streamId);\n        ByteBuf continuationFrame2 = continuationFrame(0, (byte) 0x04, streamId); // END_HEADERS\n        decoder.decode(releaseLater(\n                Unpooled.wrappedBuffer(headersFrame, continuationFrame1, continuationFrame2)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, true, false, exclusive, dependency, weight + 1);\n        inOrder.verify(delegate).readHeaderBlock(\n                headersFrame.slice(HTTP_FRAME_HEADER_SIZE + 5, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrameContinuationReservedFlags() throws Exception {\n        int length = 16;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf headersFrame = headersFrame(length, flags, streamId);\n        writeRandomData(headersFrame, length);\n        ByteBuf continuationFrame = continuationFrame(0, (byte) 0xE7, streamId); // END_HEADERS\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(headersFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, false, HTTP_DEFAULT_DEPENDENCY, HTTP_DEFAULT_WEIGHT);\n        inOrder.verify(delegate).readHeaderBlock(headersFrame.slice(HTTP_FRAME_HEADER_SIZE, 16));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrameContinuationIllegalStreamId() throws Exception {\n        int length = 16;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf headersFrame = headersFrame(length, flags, streamId);\n        writeRandomData(headersFrame, length);\n        // different stream identifier\n        ByteBuf continuationFrame = continuationFrame(0, (byte) 0x04, streamId + 1); // END_HEADERS\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(headersFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, false, HTTP_DEFAULT_DEPENDENCY, HTTP_DEFAULT_WEIGHT);\n        inOrder.verify(delegate).readHeaderBlock(\n                headersFrame.slice(HTTP_FRAME_HEADER_SIZE, 16));\n        inOrder.verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpHeadersFrameMissingContinuation() throws Exception {\n        int length = 5;\n        byte flags = 0x20; // PRIORITY\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf headersFrame = headersFrame(length, flags, streamId);\n        writePriorityFields(headersFrame, exclusive, dependency, weight);\n        ByteBuf dataFrame = dataFrame(0, (byte) 0, streamId);\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(headersFrame, dataFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readHeadersFrame(\n                streamId, false, false, exclusive, dependency, weight + 1);\n        inOrder.verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPriorityFrame() throws Exception {\n        int length = 5;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf frame = priorityFrame(length, flags, streamId);\n        writePriorityFields(frame, exclusive, dependency, weight);\n        decoder.decode(frame);\n\n        verify(delegate).readPriorityFrame(streamId, exclusive, dependency, weight + 1);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPriorityFrameReservedBits() throws Exception {\n        int length = 5;\n        byte flags = (byte) 0xFF; // should ignore any flags\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf frame = priorityFrame(length, flags, streamId);\n        setReservedBits(frame);\n        writePriorityFields(frame, exclusive, dependency, weight);\n        decoder.decode(frame);\n\n        verify(delegate).readPriorityFrame(streamId, exclusive, dependency, weight + 1);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpPriorityFrame() throws Exception {\n        int length = 8; // invalid length\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf frame = priorityFrame(length, flags, streamId);\n        writePriorityFields(frame, exclusive, dependency, weight);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpPriorityFrame() throws Exception {\n        int length = 5;\n        byte flags = 0;\n        int streamId = 0; // illegal stream identifier\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = RANDOM.nextInt() & 0xFF;\n\n        ByteBuf frame = priorityFrame(length, flags, streamId);\n        writePriorityFields(frame, exclusive, dependency, weight);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpRstStreamFrame() throws Exception {\n        int length = 4;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = rstStreamFrame(length, flags, streamId);\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readRstStreamFrame(streamId, errorCode);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpRstStreamFrameReservedBits() throws Exception {\n        int length = 4;\n        byte flags = (byte) 0xFF; // should ignore any flags\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = rstStreamFrame(length, flags, streamId);\n        setReservedBits(frame);\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readRstStreamFrame(streamId, errorCode);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpRstStreamFrame() throws Exception {\n        int length = 8; // invalid length\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = rstStreamFrame(length, flags, streamId);\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpRstStreamFrame() throws Exception {\n        int length = 4;\n        byte flags = 0;\n        int streamId = 0; // illegal stream identifier\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = rstStreamFrame(length, flags, streamId);\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpSettingsFrame() throws Exception {\n        int length = 6;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value = RANDOM.nextInt();\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        frame.writeShort(id);\n        frame.writeInt(value);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readSettingsFrame(false);\n        inOrder.verify(delegate).readSetting(id, value);\n        inOrder.verify(delegate).readSettingsEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpSettingsAckFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x01; // ACK\n        int streamId = 0; // connection identifier\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readSettingsFrame(true);\n        inOrder.verify(delegate).readSettingsEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpSettingsFrameWithMultiples() throws Exception {\n        int length = 12;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value1 = RANDOM.nextInt();\n        int value2 = RANDOM.nextInt();\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        frame.writeShort(id);\n        frame.writeInt(value1);\n        frame.writeShort(id);\n        frame.writeInt(value2);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readSettingsFrame(false);\n        inOrder.verify(delegate).readSetting(id, value1);\n        inOrder.verify(delegate).readSetting(id, value2);\n        inOrder.verify(delegate).readSettingsEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpSettingsFrameReservedBits() throws Exception {\n        int length = 6;\n        byte flags = (byte) 0xFE; // should ignore any unknown flags\n        int streamId = 0; // connection identifier\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value = RANDOM.nextInt();\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        setReservedBits(frame);\n        frame.writeShort(id);\n        frame.writeInt(value);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readSettingsFrame(false);\n        inOrder.verify(delegate).readSetting(id, value);\n        inOrder.verify(delegate).readSettingsEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpSettingsFrame() throws Exception {\n        int length = 8; // invalid length\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value = RANDOM.nextInt();\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        frame.writeShort(id);\n        frame.writeInt(value);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpSettingsAckFrame() throws Exception {\n        int length = 6; // invalid length\n        byte flags = 0x01; // ACK\n        int streamId = 0; // connection identifier\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value = RANDOM.nextInt();\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        frame.writeShort(id);\n        frame.writeInt(value);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpSettingsFrame() throws Exception {\n        int length = 6;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01; // illegal stream identifier\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value = RANDOM.nextInt();\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        frame.writeShort(id);\n        frame.writeInt(value);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpSettingsAckFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x01; // ACK\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01; // illegal stream identifier\n\n        ByteBuf frame = settingsFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrame() throws Exception {\n        int headerBlockLength = 16;\n        int length = 4 + headerBlockLength;\n        byte flags = 0x04; // END_HEADERS\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = pushPromiseFrame(length, flags, streamId);\n        frame.writeInt(promisedStreamId);\n        writeRandomData(frame, headerBlockLength);\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlock(\n                frame.slice(HTTP_FRAME_HEADER_SIZE + 4, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrameReservedBits() throws Exception {\n        int length = 4;\n        byte flags = (byte) 0xE7; // END_HEADERS -- should ignore any unknown flags\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = pushPromiseFrame(length, flags, streamId);\n        setReservedBits(frame);\n        frame.writeInt(promisedStreamId | 0x80000000); // should ignore reserved bit\n        decoder.decode(frame);\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpPushPromiseFrame() throws Exception {\n        int length = 4;\n        byte flags = 0x04; // END_HEADERS\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = 0; // illegal stream identifier\n\n        ByteBuf frame = pushPromiseFrame(length, flags, streamId);\n        frame.writeInt(promisedStreamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testContinuedHttpPushPromiseFrame() throws Exception {\n        int headerBlockLength = 16;\n        int length = 4;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf pushPromiseFrame = pushPromiseFrame(length, flags, streamId);\n        pushPromiseFrame.writeInt(promisedStreamId);\n        ByteBuf continuationFrame =\n                continuationFrame(headerBlockLength, (byte) 0x04, streamId); // END_HEADERS\n        writeRandomData(continuationFrame, headerBlockLength);\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(pushPromiseFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlock(\n                continuationFrame.slice(HTTP_FRAME_HEADER_SIZE, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrameEmptyContinuation() throws Exception {\n        int headerBlockLength = 16;\n        int length = 4 + headerBlockLength;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf pushPromiseFrame = pushPromiseFrame(length, flags, streamId);\n        pushPromiseFrame.writeInt(promisedStreamId);\n        writeRandomData(pushPromiseFrame, headerBlockLength);\n        ByteBuf continuationFrame = continuationFrame(0, (byte) 0x04, streamId); // END_HEADERS\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(pushPromiseFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlock(\n                pushPromiseFrame.slice(HTTP_FRAME_HEADER_SIZE + 4, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrameMultipleContinuations() throws Exception {\n        int headerBlockLength = 16;\n        int length = 4 + headerBlockLength;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf pushPromiseFrame = pushPromiseFrame(length, flags, streamId);\n        pushPromiseFrame.writeInt(promisedStreamId);\n        writeRandomData(pushPromiseFrame, headerBlockLength);\n        ByteBuf continuationFrame1 = continuationFrame(0, (byte) 0x00, streamId);\n        ByteBuf continuationFrame2 = continuationFrame(0, (byte) 0x04, streamId); // END_HEADERS\n        decoder.decode(releaseLater(\n                Unpooled.wrappedBuffer(pushPromiseFrame, continuationFrame1, continuationFrame2)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlock(\n                pushPromiseFrame.slice(HTTP_FRAME_HEADER_SIZE + 4, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrameContinuationReservedFlags() throws Exception {\n        int headerBlockLength = 16;\n        int length = 4 + headerBlockLength;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf pushPromiseFrame = pushPromiseFrame(length, flags, streamId);\n        pushPromiseFrame.writeInt(promisedStreamId);\n        writeRandomData(pushPromiseFrame, headerBlockLength);\n        ByteBuf continuationFrame = continuationFrame(0, (byte) 0xE7, streamId); // END_HEADERS\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(pushPromiseFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlock(\n                pushPromiseFrame.slice(HTTP_FRAME_HEADER_SIZE + 4, headerBlockLength));\n        inOrder.verify(delegate).readHeaderBlockEnd();\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrameContinuationIllegalStreamId() throws Exception {\n        int headerBlockLength = 16;\n        int length = 4 + headerBlockLength;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf pushPromiseFrame = pushPromiseFrame(length, flags, streamId);\n        pushPromiseFrame.writeInt(promisedStreamId);\n        writeRandomData(pushPromiseFrame, headerBlockLength);\n        // different stream identifier\n        ByteBuf continuationFrame = continuationFrame(0, (byte) 0x04, streamId + 1); // END_HEADERS\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(pushPromiseFrame, continuationFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readHeaderBlock(\n                pushPromiseFrame.slice(HTTP_FRAME_HEADER_SIZE + 4, headerBlockLength));\n        inOrder.verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrameMissingContinuation() throws Exception {\n        int length = 4;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf pushPromiseFrame = pushPromiseFrame(length, flags, streamId);\n        pushPromiseFrame.writeInt(promisedStreamId);\n        ByteBuf dataFrame = dataFrame(0, (byte) 0, streamId);\n        decoder.decode(releaseLater(Unpooled.wrappedBuffer(pushPromiseFrame, dataFrame)));\n\n        InOrder inOrder = inOrder(delegate);\n        inOrder.verify(delegate).readPushPromiseFrame(streamId, promisedStreamId);\n        inOrder.verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPingFrame() throws Exception {\n        int length = 8;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        long data = RANDOM.nextLong();\n\n        ByteBuf frame = pingFrame(length, flags, streamId);\n        frame.writeLong(data);\n        decoder.decode(frame);\n\n        verify(delegate).readPingFrame(data, false);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPongFrame() throws Exception {\n        int length = 8;\n        byte flags = 0x01; // PONG\n        int streamId = 0; // connection identifier\n        long data = RANDOM.nextLong();\n\n        ByteBuf frame = pingFrame(length, flags, streamId);\n        frame.writeLong(data);\n        decoder.decode(frame);\n\n        verify(delegate).readPingFrame(data, true);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpPingFrameReservedBits() throws Exception {\n        int length = 8;\n        byte flags = (byte) 0xFE; // should ignore any unknown flags\n        int streamId = 0; // connection identifier\n        long data = RANDOM.nextLong();\n\n        ByteBuf frame = pingFrame(length, flags, streamId);\n        frame.writeLong(data);\n        decoder.decode(frame);\n\n        verify(delegate).readPingFrame(data, false);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpPingFrame() throws Exception {\n        int length = 12; // invalid length\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        long data = RANDOM.nextLong();\n\n        ByteBuf frame = pingFrame(length, flags, streamId);\n        frame.writeLong(data);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpPingFrame() throws Exception {\n        int length = 8;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01; // illegal stream identifier\n        long data = RANDOM.nextLong();\n\n        ByteBuf frame = pingFrame(length, flags, streamId);\n        frame.writeLong(data);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpGoAwayFrame() throws Exception {\n        int length = 8;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        int lastStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = goAwayFrame(length, flags, streamId);\n        frame.writeInt(lastStreamId);\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readGoAwayFrame(lastStreamId, errorCode);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpGoAwayFrameReservedBits() throws Exception {\n        int length = 8;\n        byte flags = (byte) 0xFF; // should ignore any flags\n        int streamId = 0; // connection identifier\n        int lastStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = goAwayFrame(length, flags, streamId);\n        setReservedBits(frame);\n        frame.writeInt(lastStreamId | 0x80000000); // should ignore reserved bit\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readGoAwayFrame(lastStreamId, errorCode);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpGoAwayFrameWithDebugData() throws Exception {\n        int debugDataLength = 1024;\n        int length = 8 + debugDataLength;\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        int lastStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = goAwayFrame(length, flags, streamId);\n        frame.writeInt(lastStreamId);\n        frame.writeInt(errorCode);\n        writeRandomData(frame, debugDataLength);\n        decoder.decode(frame);\n\n        verify(delegate).readGoAwayFrame(lastStreamId, errorCode);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpGoAwayFrame() throws Exception {\n        int length = 4; // invalid length\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n        int lastStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = goAwayFrame(length, flags, streamId);\n        frame.writeInt(lastStreamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpGoAwayFrame() throws Exception {\n        int length = 8;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01; // illegal stream identifier\n        int lastStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n\n        ByteBuf frame = goAwayFrame(length, flags, streamId);\n        frame.writeInt(lastStreamId);\n        frame.writeInt(errorCode);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpWindowUpdateFrame() throws Exception {\n        int length = 4;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF; // connection identifier allowed\n        int windowSizeIncrement = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = windowUpdateFrame(length, flags, streamId);\n        frame.writeInt(windowSizeIncrement);\n        decoder.decode(frame);\n\n        verify(delegate).readWindowUpdateFrame(streamId, windowSizeIncrement);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testHttpWindowUpdateFrameReservedBits() throws Exception {\n        int length = 4;\n        byte flags = (byte) 0xFF; // should ignore any unknown flags\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF; // connection identifier allowed\n        int windowSizeIncrement = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = windowUpdateFrame(length, flags, streamId);\n        setReservedBits(frame);\n        frame.writeInt(windowSizeIncrement | 0x80000000); // should ignore reserved bit\n        decoder.decode(frame);\n\n        verify(delegate).readWindowUpdateFrame(streamId, windowSizeIncrement);\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testInvalidHttpWindowUpdateFrame() throws Exception {\n        int length = 8; // invalid length\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF; // connection identifier allowed\n        int windowSizeIncrement = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = windowUpdateFrame(length, flags, streamId);\n        frame.writeInt(windowSizeIncrement);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalHttpWindowUpdateFrame() throws Exception {\n        int length = 4;\n        byte flags = 0;\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF; // connection identifier allowed\n        int windowSizeIncrement = 0; // illegal delta window size\n\n        ByteBuf frame = windowUpdateFrame(length, flags, streamId);\n        frame.writeInt(windowSizeIncrement);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testIllegalContinuationFrame() throws Exception {\n        int length = 0;\n        byte flags = 0x01 | 0x04; // END_STREAM | END_HEADERS\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n\n        ByteBuf frame = continuationFrame(length, flags, streamId);\n        decoder.decode(frame);\n\n        verify(delegate).readFrameError(anyString());\n        verifyNoMoreInteractions(delegate);\n    }\n\n    @Test\n    public void testUnknownFrame() throws Exception {\n        int length = 0;\n        int type = 0xFF; // unknown frame\n        byte flags = 0;\n        int streamId = 0; // connection identifier\n\n        ByteBuf frame = frame(length, type, flags, streamId);\n        decoder.decode(frame);\n\n        verifyZeroInteractions(delegate);\n    }\n\n    private ByteBuf frame(int length, int type, byte flags, int streamId) {\n        ByteBuf buffer = releaseLater(\n                Unpooled.buffer(HTTP_FRAME_HEADER_SIZE + length)\n        );\n        buffer.writeMedium(length);\n        buffer.writeByte(type);\n        buffer.writeByte(flags);\n        buffer.writeInt(streamId);\n        return buffer;\n    }\n\n    private ByteBuf dataFrame(int length, byte flags, int streamId) {\n        int type = 0x0; // DATA frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf headersFrame(int length, byte flags, int streamId) {\n        int type = 0x1; // HEADERS frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf priorityFrame(int length, byte flags, int streamId) {\n        int type = 0x2; // PRIORITY frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf rstStreamFrame(int length, byte flags, int streamId) {\n        int type = 0x3; // RST_STREAM frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf settingsFrame(int length, byte flags, int streamId) {\n        int type = 0x4; // SETTINGS frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf pushPromiseFrame(int length, byte flags, int streamId) {\n        int type = 0x5; // PUSH_PROMISE frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf pingFrame(int length, byte flags, int streamId) {\n        int type = 0x6; // PUSH frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf goAwayFrame(int length, byte flags, int streamId) {\n        int type = 0x7; // GOAWAY frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf windowUpdateFrame(int length, byte flags, int streamId) {\n        int type = 0x8; // WINDOW_UPDATE frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private ByteBuf continuationFrame(int length, byte flags, int streamId) {\n        int type = 0x9; // CONTINUATION frame\n        return frame(length, type, flags, streamId);\n    }\n\n    private void setReservedBits(ByteBuf frame) {\n        frame.setInt(5, frame.getInt(5) | 0x80000000);\n    }\n\n    private void writeRandomData(ByteBuf frame, int length) {\n        for (int i = 0; i < length; i++) {\n            frame.writeByte(RANDOM.nextInt());\n        }\n    }\n\n    private void writePriorityFields(ByteBuf frame, boolean exclusive, int dependency, int weight) {\n        int dependencyWithFlag = exclusive ? dependency | 0x80000000 : dependency;\n        frame.writeInt(dependencyWithFlag);\n        frame.writeByte(weight);\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/twitter/http2/HttpFrameEncoderTest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.util.Random;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport org.junit.Test;\n\nimport static io.netty.util.ReferenceCountUtil.releaseLater;\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\n\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_DEPENDENCY;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;\nimport static com.twitter.http2.HttpCodecUtil.HTTP_MAX_LENGTH;\n\npublic class HttpFrameEncoderTest {\n\n    private static final Random RANDOM = new Random();\n\n    private static final HttpFrameEncoder ENCODER = new HttpFrameEncoder();\n\n    @Test\n    public void testHttpDataFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf data = Unpooled.buffer(1024);\n        for (int i = 0; i < 256; i++) {\n            data.writeInt(RANDOM.nextInt());\n        }\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeDataFrame(streamId, false, data.duplicate())\n        );\n        assertDataFrame(frame, streamId, false, data);\n    }\n\n    @Test\n    public void testEmptyHttpDataFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeDataFrame(streamId, false, Unpooled.EMPTY_BUFFER)\n        );\n        assertDataFrame(frame, streamId, false, Unpooled.EMPTY_BUFFER);\n    }\n\n    @Test\n    public void testLastHttpDataFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeDataFrame(streamId, true, Unpooled.EMPTY_BUFFER)\n        );\n        assertDataFrame(frame, streamId, true, Unpooled.EMPTY_BUFFER);\n    }\n\n    @Test\n    public void testHttpHeadersFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = (RANDOM.nextInt() & 0xFF) + 1;\n        ByteBuf headerBlock = Unpooled.buffer(1024);\n        for (int i = 0; i < 256; i++) {\n            headerBlock.writeInt(RANDOM.nextInt());\n        }\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeHeadersFrame(\n                        streamId, false, exclusive, dependency, weight, headerBlock.duplicate())\n        );\n        assertHeadersFrame(frame, streamId, exclusive, dependency, weight, false, headerBlock);\n    }\n\n    @Test\n    public void testEmptyHttpHeadersFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = (RANDOM.nextInt() & 0xFF) + 1;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeHeadersFrame(\n                        streamId, false, exclusive, dependency, weight, Unpooled.EMPTY_BUFFER)\n        );\n        assertHeadersFrame(\n                frame, streamId, exclusive, dependency, weight, false, Unpooled.EMPTY_BUFFER);\n    }\n\n    @Test\n    public void testLastHttpHeadersFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = false;\n        int dependency = HTTP_DEFAULT_DEPENDENCY;\n        int weight = HTTP_DEFAULT_WEIGHT;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeHeadersFrame(\n                        streamId, true, exclusive, dependency, weight, Unpooled.EMPTY_BUFFER)\n        );\n        assertHeadersFrame(\n                frame, streamId, exclusive, dependency, weight, true, Unpooled.EMPTY_BUFFER);\n    }\n\n    @Test\n    public void testContinuedHttpHeadersFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = (RANDOM.nextInt() & 0xFF) + 1;\n        ByteBuf headerBlock = Unpooled.buffer(2 * HTTP_MAX_LENGTH);\n        for (int i = 0; i < 2 * HTTP_MAX_LENGTH; i++) {\n            headerBlock.writeByte(RANDOM.nextInt());\n        }\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeHeadersFrame(\n                        streamId, false, exclusive, dependency, weight, headerBlock.duplicate())\n        );\n        assertHeadersFrame(\n                frame, streamId, exclusive, dependency, weight, false, headerBlock);\n    }\n\n    @Test\n    public void testHttpPriorityFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        boolean exclusive = RANDOM.nextBoolean();\n        int dependency = RANDOM.nextInt() & 0x7FFFFFFF;\n        int weight = (RANDOM.nextInt() & 0xFF) + 1;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodePriorityFrame(streamId, exclusive, dependency, weight)\n        );\n        assertPriorityFrame(frame, streamId, exclusive, dependency, weight);\n    }\n\n    @Test\n    public void testHttpRstStreamFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeRstStreamFrame(streamId, errorCode)\n        );\n        assertRstStreamFrame(frame, streamId, errorCode);\n    }\n\n    @Test\n    public void testHttpSettingsFrame() throws Exception {\n        int id = RANDOM.nextInt() & 0xFFFF;\n        int value = RANDOM.nextInt();\n        HttpSettingsFrame httpSettingsFrame = new DefaultHttpSettingsFrame();\n        httpSettingsFrame.setValue(id, value);\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeSettingsFrame(httpSettingsFrame)\n        );\n        assertSettingsFrame(frame, false, id, value);\n    }\n\n    @Test\n    public void testHttpSettingsAckFrame() throws Exception {\n        HttpSettingsFrame httpSettingsFrame = new DefaultHttpSettingsFrame();\n        httpSettingsFrame.setAck(true);\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeSettingsFrame(httpSettingsFrame)\n        );\n        assertSettingsFrame(frame, true, 0, 0);\n    }\n\n    @Test\n    public void testHttpPushPromiseFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf headerBlock = Unpooled.buffer(1024);\n        for (int i = 0; i < 256; i++) {\n            headerBlock.writeInt(RANDOM.nextInt());\n        }\n        ByteBuf frame = releaseLater(\n                ENCODER.encodePushPromiseFrame(streamId, promisedStreamId, headerBlock.duplicate())\n        );\n        assertPushPromiseFrame(frame, streamId, promisedStreamId, headerBlock);\n    }\n\n    @Test\n    public void testEmptyHttpPushPromiseFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodePushPromiseFrame(streamId, promisedStreamId, Unpooled.EMPTY_BUFFER)\n        );\n        assertPushPromiseFrame(frame, streamId, promisedStreamId, Unpooled.EMPTY_BUFFER);\n    }\n\n    @Test\n    public void testContinuedHttpPushPromiseFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int promisedStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf headerBlock = Unpooled.buffer(2 * HTTP_MAX_LENGTH);\n        for (int i = 0; i < 2 * HTTP_MAX_LENGTH; i++) {\n            headerBlock.writeByte(RANDOM.nextInt());\n        }\n        ByteBuf frame = releaseLater(\n                ENCODER.encodePushPromiseFrame(streamId, promisedStreamId, headerBlock.duplicate())\n        );\n        assertPushPromiseFrame(frame, streamId, promisedStreamId, headerBlock);\n    }\n\n    @Test\n    public void testHttpPingFrame() throws Exception {\n        long data = RANDOM.nextLong();\n        ByteBuf frame = releaseLater(\n                ENCODER.encodePingFrame(data, false)\n        );\n        assertPingFrame(frame, false, data);\n    }\n\n    @Test\n    public void testHttpPongFrame() throws Exception {\n        long data = RANDOM.nextLong();\n        ByteBuf frame = releaseLater(\n                ENCODER.encodePingFrame(data, true)\n        );\n        assertPingFrame(frame, true, data);\n    }\n\n    @Test\n    public void testHttpGoAwayFrame() throws Exception {\n        int lastStreamId = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        int errorCode = RANDOM.nextInt();\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeGoAwayFrame(lastStreamId, errorCode)\n        );\n        assertGoAwayFrame(frame, lastStreamId, errorCode);\n    }\n\n    @Test\n    public void testHttpWindowUpdateFrame() throws Exception {\n        int streamId = RANDOM.nextInt() & 0x7FFFFFFF; // connection identifier allowed\n        int windowSizeIncrement = RANDOM.nextInt() & 0x7FFFFFFF | 0x01;\n        ByteBuf frame = releaseLater(\n                ENCODER.encodeWindowUpdateFrame(streamId, windowSizeIncrement)\n        );\n        assertWindowUpdateFrame(frame, streamId, windowSizeIncrement);\n    }\n\n    private static void assertDataFrame(ByteBuf frame, int streamId, boolean last, ByteBuf data) {\n        byte type = 0x00;\n        byte flags = 0x00;\n        if (last) {\n            flags |= 0x01;\n        }\n        int length = assertFrameHeader(frame, type, flags, streamId);\n        assertEquals(data.readableBytes(), length);\n        for (int i = 0; i < length; i++) {\n            assertEquals(data.getByte(i), frame.readByte());\n        }\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertHeadersFrame(\n            ByteBuf frame,\n            int streamId,\n            boolean exclusive,\n            int dependency,\n            int weight,\n            boolean last,\n            ByteBuf headerBlock\n    ) {\n        boolean hasPriority =\n                exclusive || dependency != HTTP_DEFAULT_DEPENDENCY || weight != HTTP_DEFAULT_WEIGHT;\n        int maxLength = hasPriority ? HTTP_MAX_LENGTH - 5 : HTTP_MAX_LENGTH;\n        byte type = 0x01;\n        byte flags = 0x00;\n        if (last) {\n            flags |= 0x01;\n        }\n        if (headerBlock.readableBytes() <= maxLength) {\n            flags |= 0x04;\n        }\n        if (hasPriority) {\n            flags |= 0x20;\n        }\n        int length = assertFrameHeader(frame, type, flags, streamId);\n        if (hasPriority) {\n            assertTrue(length >= 5);\n            if (exclusive) {\n                assertEquals(dependency | 0x80000000, frame.readInt());\n            } else {\n                assertEquals(dependency, frame.readInt());\n            }\n            assertEquals(weight - 1, frame.readUnsignedByte());\n            length -= 5;\n        }\n        assertTrue(length <= headerBlock.readableBytes());\n        for (int i = 0; i < length; i++) {\n            assertEquals(headerBlock.readByte(), frame.readByte());\n        }\n        while (headerBlock.isReadable()) {\n            type = 0x09;\n            flags = 0x00;\n            if (headerBlock.readableBytes() <= HTTP_MAX_LENGTH) {\n                flags |= 0x04;\n            }\n            length = assertFrameHeader(frame, type, flags, streamId);\n            assertTrue(length <= headerBlock.readableBytes());\n            for (int i = 0; i < length; i++) {\n                assertEquals(headerBlock.readByte(), frame.readByte());\n            }\n        }\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertPriorityFrame(\n            ByteBuf frame, int streamId, boolean exclusive, int dependency, int weight\n    ) {\n        byte type = 0x02;\n        byte flags = 0x00;\n        assertEquals(5, assertFrameHeader(frame, type, flags, streamId));\n        if (exclusive) {\n            assertEquals(dependency | 0x80000000, frame.readInt());\n        } else {\n            assertEquals(dependency, frame.readInt());\n        }\n        assertEquals(weight - 1, frame.readUnsignedByte());\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertRstStreamFrame(ByteBuf frame, int streamId, int errorCode) {\n        byte type = 0x03;\n        byte flags = 0x00;\n        assertEquals(4, assertFrameHeader(frame, type, flags, streamId));\n        assertEquals(errorCode, frame.readInt());\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertSettingsFrame(ByteBuf frame, boolean ack, int id, int value) {\n        byte type = 0x04;\n        byte flags = ack ? (byte) 0x01 : 0x00;\n        int length = assertFrameHeader(frame, type, flags, 0);\n        if (ack) {\n            assertEquals(0, length);\n        } else {\n            assertEquals(6, length);\n            assertEquals(id, frame.readUnsignedShort());\n            assertEquals(value, frame.readInt());\n        }\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertPushPromiseFrame(\n            ByteBuf frame, int streamId, int promisedStreamId, ByteBuf headerBlock) {\n        int maxLength = HTTP_MAX_LENGTH - 4;\n        byte type = 0x05;\n        byte flags = 0x00;\n        if (headerBlock.readableBytes() <= maxLength) {\n            flags |= 0x04;\n        }\n        int length = assertFrameHeader(frame, type, flags, streamId);\n        assertTrue(length >= 4);\n        assertEquals(promisedStreamId, frame.readInt());\n        length -= 4;\n        assertTrue(length <= headerBlock.readableBytes());\n        for (int i = 0; i < length; i++) {\n            assertEquals(headerBlock.readByte(), frame.readByte());\n        }\n        while (headerBlock.isReadable()) {\n            type = 0x09;\n            flags = 0x00;\n            if (headerBlock.readableBytes() <= HTTP_MAX_LENGTH) {\n                flags |= 0x04;\n            }\n            length = assertFrameHeader(frame, type, flags, streamId);\n            assertTrue(length <= headerBlock.readableBytes());\n            for (int i = 0; i < length; i++) {\n                assertEquals(headerBlock.readByte(), frame.readByte());\n            }\n        }\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertPingFrame(ByteBuf frame, boolean pong, long data) {\n        byte type = 0x06;\n        byte flags = 0x00;\n        if (pong) {\n            flags |= 0x01;\n        }\n        assertEquals(8, assertFrameHeader(frame, type, flags, 0));\n        assertEquals(data, frame.readLong());\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertGoAwayFrame(ByteBuf frame, int lastStreamId, int errorCode) {\n        byte type = 0x07;\n        byte flags = 0x00;\n        assertEquals(8, assertFrameHeader(frame, type, flags, 0));\n        assertEquals(lastStreamId, frame.readInt());\n        assertEquals(errorCode, frame.readInt());\n        assertFalse(frame.isReadable());\n    }\n\n    private static void assertWindowUpdateFrame(\n            ByteBuf frame, int streamId, int windowSizeIncrement\n    ) {\n        byte type = 0x08;\n        byte flags = 0x00;\n        assertEquals(4, assertFrameHeader(frame, type, flags, streamId));\n        assertEquals(windowSizeIncrement, frame.readInt());\n        assertFalse(frame.isReadable());\n    }\n\n    // Verifies the type, flag, and streamId in the frame header and returns the length.\n    private static int assertFrameHeader(ByteBuf frame, byte type, byte flags, int streamId) {\n        int length = frame.readUnsignedMedium();\n        assertEquals(type, frame.readByte());\n        assertEquals(flags, frame.readByte());\n        assertEquals(streamId, frame.readInt());\n        return length;\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/twitter/http2/HttpHeaderCompressionTest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.local.LocalAddress;\nimport io.netty.channel.local.LocalChannel;\nimport io.netty.channel.local.LocalEventLoopGroup;\nimport io.netty.channel.local.LocalServerChannel;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertTrue;\n\npublic class HttpHeaderCompressionTest {\n\n    @Test\n    public void testHttpHeadersFrame() throws Throwable {\n        HttpHeadersFrame httpHeadersFrame = new DefaultHttpHeadersFrame(1);\n        httpHeadersFrame.headers().add(\"name\", \"value\");\n        testHeaderEcho(httpHeadersFrame);\n    }\n\n    private void testHeaderEcho(HttpHeaderBlockFrame frame) throws Throwable {\n        final EchoHandler sh = new EchoHandler();\n        final TestHandler ch = new TestHandler(frame);\n\n        ServerBootstrap sb = new ServerBootstrap()\n                .group(new LocalEventLoopGroup())\n                .channel(LocalServerChannel.class)\n                .childHandler(new ChannelInitializer<LocalChannel>() {\n                    @Override\n                    public void initChannel(LocalChannel channel) throws Exception {\n                        channel.pipeline().addLast(new HttpConnectionHandler(true), sh);\n                    }\n                });\n        Bootstrap cb = new Bootstrap()\n                .group(new LocalEventLoopGroup())\n                .channel(LocalChannel.class)\n                .handler(new ChannelInitializer<LocalChannel>() {\n                    @Override\n                    public void initChannel(LocalChannel channel) throws Exception {\n                        channel.pipeline().addLast(new HttpConnectionHandler(false), ch);\n                    }\n                });\n\n        LocalAddress localAddress = new LocalAddress(\"HttpHeaderCompressionTest\");\n        Channel sc = sb.bind(localAddress).sync().channel();\n        ChannelFuture ccf = cb.connect(localAddress);\n        assertTrue(ccf.awaitUninterruptibly().isSuccess());\n\n        while (!ch.success) {\n            if (sh.exception.get() != null) {\n                break;\n            }\n            if (ch.exception.get() != null) {\n                break;\n            }\n\n            try {\n                Thread.sleep(1);\n            } catch (InterruptedException e) {\n                // Ignore.\n            }\n        }\n\n        sc.close().awaitUninterruptibly();\n        cb.group().shutdownGracefully();\n        sb.group().shutdownGracefully();\n\n        if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) {\n            throw sh.exception.get();\n        }\n        if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) {\n            throw ch.exception.get();\n        }\n        if (sh.exception.get() != null) {\n            throw sh.exception.get();\n        }\n        if (ch.exception.get() != null) {\n            throw ch.exception.get();\n        }\n    }\n\n    private static class EchoHandler extends ChannelInboundHandlerAdapter {\n        public final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            ctx.writeAndFlush(msg);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            if (exception.compareAndSet(null, cause)) {\n                ctx.close();\n            }\n        }\n    }\n\n    private static class TestHandler extends ChannelInboundHandlerAdapter {\n        private static final byte[] CONNECTION_HEADER =\n                \"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\".getBytes(StandardCharsets.US_ASCII);\n\n        public final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();\n        public final HttpHeaderBlockFrame frame;\n\n        public volatile boolean success;\n\n        public TestHandler(HttpHeaderBlockFrame frame) {\n            this.frame = frame;\n        }\n\n        @Override\n        public void channelActive(ChannelHandlerContext ctx) throws Exception {\n            ctx.write(Unpooled.wrappedBuffer(CONNECTION_HEADER));\n            ctx.writeAndFlush(frame);\n        }\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            assertTrue(msg instanceof HttpHeaderBlockFrame);\n            HttpHeaders actual = ((HttpHeaderBlockFrame) msg).headers();\n            HttpHeaders expected = frame.headers();\n            for (String name : expected.names()) {\n                List<String> expectedValues = new ArrayList<String>(expected.getAll(name));\n                List<String> actualValues = new ArrayList<String>(actual.getAll(name));\n                assertTrue(actualValues.containsAll(expectedValues));\n                actualValues.removeAll(expectedValues);\n                assertTrue(actualValues.isEmpty());\n                actual.remove(name);\n            }\n            assertTrue(actual.isEmpty());\n            success = true;\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            if (exception.compareAndSet(null, cause)) {\n                ctx.close();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/twitter/http2/HttpRequestProxyTest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpVersion;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertSame;\n\npublic class HttpRequestProxyTest {\n\n    private HttpRequest httpRequest = new DefaultHttpRequest(\n            HttpVersion.HTTP_1_0, HttpMethod.GET, \"/\");\n    private HttpRequestProxy httpRequestProxy = new HttpRequestProxy(httpRequest);\n\n    @Test\n    public void testHttpRequest() {\n        assertSame(httpRequest, httpRequestProxy.httpRequest());\n    }\n\n    @Test\n    public void testGetMethod() {\n        assertEquals(httpRequest.getMethod(), httpRequestProxy.getMethod());\n    }\n\n    @Test\n    public void testSetMethod() {\n        assertSame(httpRequestProxy, httpRequestProxy.setMethod(HttpMethod.POST));\n        assertEquals(httpRequest.getMethod(), httpRequestProxy.getMethod());\n    }\n\n    @Test\n    public void testGetUri() {\n        assertEquals(httpRequest.getUri(), httpRequestProxy.getUri());\n    }\n\n    @Test\n    public void testSetUri() {\n        assertSame(httpRequestProxy, httpRequestProxy.setUri(\"/path\"));\n        assertEquals(httpRequest.getUri(), httpRequestProxy.getUri());\n    }\n\n    @Test\n    public void testToString() {\n        assertEquals(httpRequest.toString(), httpRequestProxy.toString());\n    }\n\n    @Test\n    public void testGetProtocolVersion() {\n        assertEquals(httpRequest.getProtocolVersion(), httpRequestProxy.getProtocolVersion());\n    }\n\n    @Test\n    public void testSetProtocolVersion() {\n        assertSame(httpRequestProxy, httpRequestProxy.setProtocolVersion(HttpVersion.HTTP_1_1));\n        assertEquals(httpRequest.getProtocolVersion(), httpRequestProxy.getProtocolVersion());\n    }\n\n    @Test\n    public void testHeaders() {\n        assertSame(httpRequest.headers(), httpRequestProxy.headers());\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/twitter/http2/HttpResponseProxyTest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport io.netty.handler.codec.http.DefaultHttpResponse;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertSame;\n\npublic class HttpResponseProxyTest {\n\n    private HttpResponse httpResponse = new DefaultHttpResponse(\n            HttpVersion.HTTP_1_0, HttpResponseStatus.OK);\n    private HttpResponseProxy httpResponseProxy = new HttpResponseProxy(httpResponse);\n\n    @Test\n    public void testSetStatus() {\n        assertSame(httpResponseProxy, httpResponseProxy.setStatus(HttpResponseStatus.NOT_FOUND));\n        assertEquals(httpResponse.getStatus(), httpResponseProxy.getStatus());\n    }\n\n    @Test\n    public void testGetStatus() {\n        assertEquals(httpResponse.getStatus(), httpResponseProxy.getStatus());\n    }\n\n    @Test\n    public void testToString() {\n        assertEquals(httpResponse.toString(), httpResponseProxy.toString());\n    }\n\n    @Test\n    public void testGetProtocolVersion() {\n        assertEquals(httpResponse.getProtocolVersion(), httpResponseProxy.getProtocolVersion());\n    }\n\n    @Test\n    public void testSetProtocolVersion() {\n        assertSame(httpResponseProxy, httpResponseProxy.setProtocolVersion(HttpVersion.HTTP_1_1));\n        assertEquals(httpResponse.getProtocolVersion(), httpResponseProxy.getProtocolVersion());\n    }\n\n    @Test\n    public void testHeaders() {\n        assertSame(httpResponse.headers(), httpResponseProxy.headers());\n    }\n}\n"
  },
  {
    "path": "src/test/java/com/twitter/http2/PipeTest.java",
    "content": "/*\n * Copyright 2015 Twitter, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.twitter.http2;\n\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport io.netty.util.concurrent.Future;\n\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertSame;\nimport static org.junit.Assert.assertTrue;\n\npublic class PipeTest {\n\n    private static final Object MESSAGE = new Object();\n    private static final Object MESSAGE1 = new Object();\n    private static final Object MESSAGE2 = new Object();\n\n    //\n    // Test Plan\n    //\n    // o Choose from an alphabet, { S, C, R }, where the letters stand for Send, Close, and Receive.\n    // o The tests are named using the alphabet.\n    //   For example, testSR tests a Send then Receive.\n    //   At each point in the test, all the state in the current\n    //   and previous futures and values are tested as well.\n    //\n    // Some assumptions:\n    // o If a future is not done, then it will not be succeeded,\n    //   cancelled, or failed, as per the future spec.\n    // o If a future is done, then it will be only one of the three other future states.\n    //\n\n    private Pipe<Object> pipe;\n\n    @Before\n    public void createPipe() {\n        pipe = new Pipe<Object>();\n    }\n\n    @Test\n    public void testS() {\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertFalse(sendFuture.isDone());\n    }\n\n    @Test\n    public void testR() {\n        Future<Object> recvFuture = pipe.receive();\n        assertFalse(recvFuture.isDone());\n    }\n\n    @Test\n    public void testSR() {\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertFalse(sendFuture.isDone());\n\n        Future<Object> recvFuture = pipe.receive();\n        assertSame(MESSAGE, recvFuture.getNow());\n        assertTrue(sendFuture.isSuccess());\n    }\n\n    @Test\n    public void testRS() {\n        Future<Object> recvFuture = pipe.receive();\n        assertFalse(recvFuture.isDone());\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertTrue(sendFuture.isSuccess());\n        assertSame(MESSAGE, recvFuture.getNow());\n    }\n\n    @Test\n    public void testSS() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n    }\n\n\n    @Test\n    public void testRR() {\n        Future<Object> recvFuture1 = pipe.receive();\n        assertFalse(recvFuture1.isDone());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertFalse(recvFuture2.isDone());\n    }\n\n    @Test\n    public void testSRR() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture1 = pipe.receive();\n        assertSame(MESSAGE, recvFuture1.getNow());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertFalse(recvFuture2.isDone());\n        assertTrue(recvFuture1.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n    }\n\n    @Test\n    public void testSSR() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture = pipe.receive();\n        assertSame(MESSAGE1, recvFuture.getNow());\n        assertFalse(sendFuture2.isDone());\n        assertTrue(sendFuture1.isSuccess());\n    }\n\n    @Test\n    public void testSRS() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture = pipe.receive();\n        assertSame(MESSAGE1, recvFuture.getNow());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n        assertTrue(recvFuture.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n    }\n\n    @Test\n    public void testRSR() {\n        Future<Object> recvFuture1 = pipe.receive();\n        assertFalse(recvFuture1.isDone());\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertTrue(sendFuture.isSuccess());\n        assertSame(MESSAGE, recvFuture1.getNow());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertFalse(recvFuture2.isDone());\n        assertTrue(sendFuture.isSuccess());\n        assertTrue(recvFuture1.isSuccess());\n    }\n\n    @Test\n    public void testRSS() {\n        Future<Object> recvFuture = pipe.receive();\n        assertFalse(recvFuture.isDone());\n\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertTrue(sendFuture1.isSuccess());\n        assertSame(MESSAGE1, recvFuture.getNow());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n        assertTrue(sendFuture1.isSuccess());\n        assertTrue(recvFuture.isSuccess());\n    }\n\n    @Test\n    public void testRRS() {\n        Future<Object> recvFuture1 = pipe.receive();\n        assertFalse(recvFuture1.isDone());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertFalse(recvFuture2.isDone());\n        assertFalse(recvFuture1.isDone());\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertTrue(sendFuture.isSuccess());\n        assertFalse(recvFuture2.isDone());\n        assertSame(MESSAGE, recvFuture1.getNow());\n    }\n\n    @Test\n    public void testSSRR() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture1 = pipe.receive();\n        assertSame(MESSAGE1, recvFuture1.getNow());\n        assertFalse(sendFuture2.isDone());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertSame(MESSAGE2, recvFuture2.getNow());\n        assertTrue(recvFuture1.isSuccess());\n        assertTrue(sendFuture2.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n    }\n\n    @Test\n    public void testRRSS() {\n        Future<Object> recvFuture1 = pipe.receive();\n        assertFalse(recvFuture1.isDone());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertFalse(recvFuture2.isDone());\n        assertFalse(recvFuture1.isDone());\n\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertTrue(sendFuture1.isSuccess());\n        assertFalse(recvFuture2.isDone());\n        assertSame(MESSAGE1, recvFuture1.getNow());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertTrue(sendFuture2.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n        assertSame(MESSAGE2, recvFuture2.getNow());\n        assertSame(MESSAGE1, recvFuture1.getNow());\n    }\n\n    @Test\n    public void testRSSR() {\n        Future<Object> recvFuture1 = pipe.receive();\n        assertFalse(recvFuture1.isDone());\n\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertTrue(sendFuture1.isSuccess());\n        assertSame(MESSAGE1, recvFuture1.getNow());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n        assertTrue(sendFuture1.isSuccess());\n        assertTrue(recvFuture1.isSuccess());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertSame(MESSAGE2, recvFuture2.getNow());\n        assertTrue(sendFuture2.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n        assertTrue(recvFuture1.isSuccess());\n    }\n\n    @Test\n    public void testSRRS() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture1 = pipe.receive();\n        assertSame(MESSAGE1, recvFuture1.getNow());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertFalse(recvFuture2.isDone());\n        assertTrue(recvFuture1.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertTrue(sendFuture2.isSuccess());\n        assertSame(MESSAGE2, recvFuture2.getNow());\n        assertTrue(recvFuture1.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n    }\n\n    @Test\n    public void testSSRRR() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertFalse(sendFuture2.isDone());\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture1 = pipe.receive();\n        assertTrue(recvFuture1.isSuccess());\n        assertSame(MESSAGE1, recvFuture1.getNow());\n        assertFalse(sendFuture2.isDone());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertTrue(recvFuture2.isSuccess());\n        assertSame(MESSAGE2, recvFuture2.getNow());\n        assertTrue(sendFuture2.isSuccess());\n        assertTrue(recvFuture1.isSuccess());\n        assertTrue(sendFuture2.isSuccess());\n        assertTrue(sendFuture1.isSuccess());\n\n        Future<Object> recvFuture3 = pipe.receive();\n        assertFalse(recvFuture3.isDone());\n    }\n\n    @Test\n    public void testCS() {\n        pipe.close();\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertNotNull(sendFuture.cause());\n    }\n\n    @Test\n    public void testSC() {\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertFalse(sendFuture.isDone());\n\n        pipe.close();\n        assertFalse(sendFuture.isDone());\n    }\n\n    @Test\n    public void testCR() {\n        pipe.close();\n\n        Future<Object> recvFuture = pipe.receive();\n        assertNotNull(recvFuture.cause());\n    }\n\n    @Test\n    public void testRC() {\n        Future<Object> recvFuture = pipe.receive();\n        assertFalse(recvFuture.isDone());\n\n        pipe.close();\n        assertNotNull(recvFuture.isDone());\n    }\n\n    @Test\n    public void testCSR() {\n        pipe.close();\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertNotNull(sendFuture.cause());\n\n        Future<Object> recvFuture = pipe.receive();\n        assertNotNull(recvFuture.cause());\n        assertNotNull(sendFuture.cause());\n    }\n\n    @Test\n    public void testSCR() {\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertFalse(sendFuture.isDone());\n\n        pipe.close();\n        assertFalse(sendFuture.isDone());\n\n        Future<Object> recvFuture = pipe.receive();\n        assertSame(MESSAGE, recvFuture.getNow());\n        assertTrue(sendFuture.isSuccess());\n    }\n\n    @Test\n    public void testSRCS() {\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertFalse(sendFuture1.isDone());\n\n        Future<Object> recvFuture = pipe.receive();\n        assertSame(MESSAGE1, recvFuture.getNow());\n        assertTrue(sendFuture1.isSuccess());\n\n        pipe.close();\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertNotNull(sendFuture2.cause());\n    }\n\n    @Test\n    public void testSRCR() {\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertFalse(sendFuture.isDone());\n\n        Future<Object> recvFuture1 = pipe.receive();\n        assertSame(MESSAGE, recvFuture1.getNow());\n        assertTrue(sendFuture.isSuccess());\n\n        pipe.close();\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertNotNull(recvFuture2.cause());\n        assertTrue(sendFuture.isSuccess());\n    }\n\n    @Test\n    public void testCRS() {\n        pipe.close();\n\n        Future<Object> recvFuture = pipe.receive();\n        assertNotNull(recvFuture.cause());\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertNotNull(sendFuture.cause());\n        assertNotNull(recvFuture.cause());\n    }\n\n    @Test\n    public void testRCS() {\n        Future<Object> recvFuture = pipe.receive();\n        assertFalse(recvFuture.isDone());\n\n        pipe.close();\n        assertNotNull(recvFuture.cause());\n\n        Future<Void> sendFuture = pipe.send(MESSAGE);\n        assertNotNull(sendFuture.cause());\n        assertNotNull(recvFuture.cause());\n    }\n\n    @Test\n    public void testRSCR() {\n        Future<Object> recvFuture1 = pipe.receive();\n        assertFalse(recvFuture1.isDone());\n\n        Future<Void> sendFuture1 = pipe.send(MESSAGE);\n        assertTrue(sendFuture1.isSuccess());\n        assertSame(MESSAGE, recvFuture1.getNow());\n\n        pipe.close();\n\n        Future<Object> recvFuture2 = pipe.receive();\n        assertNotNull(recvFuture2.cause());\n        assertTrue(sendFuture1.isSuccess());\n        assertTrue(recvFuture1.isSuccess());\n    }\n\n    @Test\n    public void testRSCS() {\n        Future<Object> recvFuture = pipe.receive();\n        assertFalse(recvFuture.isDone());\n\n        Future<Void> sendFuture1 = pipe.send(MESSAGE1);\n        assertTrue(sendFuture1.isSuccess());\n        assertSame(MESSAGE1, recvFuture.getNow());\n\n        pipe.close();\n\n        Future<Void> sendFuture2 = pipe.send(MESSAGE2);\n        assertNotNull(sendFuture2.cause());\n        assertTrue(sendFuture1.isSuccess());\n        assertTrue(recvFuture.isSuccess());\n    }\n}\n"
  }
]