Repository: netty/netty
Issue: Strange behavior of HTTP/2 example server

I ran nghttp HTTP/2 client (https://nghttp2.org) to HTTP/2 server (io.netty.example.http2.server).  I noticed following strange behavior of the server:
1. Server rejects stream ID = 1
   Stream ID = 1 initiated by client must be accepted according to the HTTP/2 specification.
   The error message from the server:
       [Incorrect next stream ID requested: 1])
2. Server sends SETTINGS_ENABLE_PUSH = 1
   The HTTP/2 specification says that enabling server push _to_ server is protocol error.
   Server must not send SETTINGS_ENABLE_PUSH = 1.
3. Server sends priority field with weight 17.
   I don't know this is really a bug, but it may be a case that netty forget to decrease
   the value by 1 because default weight is 16.  This adjustment is required because weight range is [1, 256], but
   the weight field in a frame is 8 bits.
   
   A bit off topic: In my opinion, priority information is solely sent from client and server just obeys
   them.  It is no point to send or echo back priority information from server.
   But this is not described in the HTTP/2 specification. So including priority in
   response HEADERS is not violation to the protocol at the moment.


Issue comments:
Forgot to mention that I used nightly build from oss.sonatype.org: netty-example-5.0.0.Alpha2-20140508.064249-382.jar and netty-all-5.0.0.Alpha2-20140508.064500-374.jar

@nmittler could you check ?

Yup, will take a look tomorrow.  Should be easy enough to fix.  Thanks for catching this @tatsuhiro-t 

Created PR https://github.com/netty/netty/pull/2480

I think this should be fixed now that #2480 is pulled in


Pull request review comments:

Patch that resolved the issue:
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java
index fc05e1299fb..8b54bd2a38e 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/AbstractHttp2ConnectionHandler.java
@@ -75,8 +75,7 @@ protected AbstractHttp2ConnectionHandler(boolean server, boolean allowCompressio
     }
 
     protected AbstractHttp2ConnectionHandler(Http2Connection connection) {
-        this(connection, new DefaultHttp2FrameReader(connection.isServer()),
-                new DefaultHttp2FrameWriter(connection.isServer()),
+        this(connection, new DefaultHttp2FrameReader(), new DefaultHttp2FrameWriter(),
                 new DefaultHttp2InboundFlowController(), new DefaultHttp2OutboundFlowController());
     }
 
@@ -165,9 +164,12 @@ public final Http2Settings settings() {
         Http2Settings settings = new Http2Settings();
         settings.allowCompressedData(connection.local().allowCompressedData());
         settings.initialWindowSize(inboundFlow.initialInboundWindowSize());
-        settings.pushEnabled(connection.local().allowPushTo());
         settings.maxConcurrentStreams(connection.remote().maxStreams());
         settings.maxHeaderTableSize(frameReader.maxHeaderTableSize());
+        if (!connection.isServer()) {
+            // Only set the pushEnabled flag if this is a client endpoint.
+            settings.pushEnabled(connection.local().allowPushTo());
+        }
         return settings;
     }
 
@@ -295,14 +297,17 @@ protected ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise
                 throw protocolError("Sending settings after connection going away.");
             }
 
-            if (settings.hasAllowCompressedData()) {
-                connection.local().allowCompressedData(settings.allowCompressedData());
-            }
-
             if (settings.hasPushEnabled()) {
+                if (connection.isServer()) {
+                    throw protocolError("Server sending SETTINGS frame with ENABLE_PUSH specified");
+                }
                 connection.local().allowPushTo(settings.pushEnabled());
             }
 
+            if (settings.hasAllowCompressedData()) {
+                connection.local().allowCompressedData(settings.allowCompressedData());
+            }
+
             if (settings.hasMaxConcurrentStreams()) {
                 connection.remote().maxStreams(settings.maxConcurrentStreams());
             }
@@ -358,6 +363,9 @@ protected ChannelFuture writePushPromise(ChannelHandlerContext ctx, ChannelPromi
     protected ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
             int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin)
             throws Http2Exception {
+        if (!connection.isServer()) {
+            throw protocolError("Client sending ALT_SVC frame");
+        }
         return frameWriter.writeAltSvc(ctx, promise, streamId, maxAge, port, protocolId, host,
                         origin);
     }
@@ -658,6 +666,13 @@ public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
         @Override
         public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
                 throws Http2Exception {
+            if (settings.hasPushEnabled()) {
+                if (!connection.isServer()) {
+                    throw protocolError("Client received SETTINGS frame with ENABLE_PUSH specified");
+                }
+                connection.remote().allowPushTo(settings.pushEnabled());
+            }
+
             if (settings.hasAllowCompressedData()) {
                 connection.remote().allowCompressedData(settings.allowCompressedData());
             }
@@ -666,10 +681,6 @@ public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings)
                 connection.local().maxStreams(settings.maxConcurrentStreams());
             }
 
-            if (settings.hasPushEnabled()) {
-                connection.remote().allowPushTo(settings.pushEnabled());
-            }
-
             if (settings.hasMaxHeaderTableSize()) {
                 frameWriter.maxHeaderTableSize(settings.maxHeaderTableSize());
             }
@@ -759,6 +770,9 @@ public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId,
         @Override
         public void onAltSvcRead(ChannelHandlerContext ctx, int streamId, long maxAge, int port,
                 ByteBuf protocolId, String host, String origin) throws Http2Exception {
+            if (connection.isServer()) {
+                throw protocolError("Server received ALT_SVC frame");
+            }
             AbstractHttp2ConnectionHandler.this.onAltSvcRead(ctx, streamId, maxAge, port,
                     protocolId, host, origin);
         }
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java
index db7656d0d04..03ac306a22a 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2Connection.java
@@ -41,17 +41,15 @@ public class DefaultHttp2Connection implements Http2Connection {
     private final DefaultEndpoint remoteEndpoint;
     private boolean goAwaySent;
     private boolean goAwayReceived;
-    private boolean server;
 
     public DefaultHttp2Connection(boolean server, boolean allowCompressedData) {
-        this.server = server;
         localEndpoint = new DefaultEndpoint(server, allowCompressedData);
         remoteEndpoint = new DefaultEndpoint(!server, false);
     }
 
     @Override
     public boolean isServer() {
-        return server;
+        return localEndpoint.isServer();
     }
 
     @Override
@@ -216,19 +214,26 @@ public boolean localSideOpen() {
      * Simple endpoint implementation.
      */
     private final class DefaultEndpoint implements Endpoint {
+        private final boolean server;
         private int nextStreamId;
         private int lastStreamCreated;
-        private int maxStreams = Integer.MAX_VALUE;
-        private boolean pushToAllowed = true;
+        private int maxStreams;
+        private boolean pushToAllowed;
         private boolean allowCompressedData;
 
-        DefaultEndpoint(boolean serverEndpoint, boolean allowCompressedData) {
-            // Determine the starting stream ID for this endpoint. Zero is reserved for the
-            // connection and 1 is reserved for responding to an upgrade from HTTP 1.1.
-            // Client-initiated streams use odd identifiers and server-initiated streams use
-            // even.
-            nextStreamId = serverEndpoint ? 2 : 3;
+        DefaultEndpoint(boolean server, boolean allowCompressedData) {
             this.allowCompressedData = allowCompressedData;
+            this.server = server;
+
+            // Determine the starting stream ID for this endpoint. Client-initiated streams
+            // are odd and server-initiated streams are even. Zero is reserved for the
+            // connection. Stream 1 is reserved client-initiated stream for responding to an
+            // upgrade from HTTP 1.1.
+            nextStreamId = server ? 2 : 1;
+
+            // Push is disallowed by default for servers and allowed for clients.
+            pushToAllowed = !server;
+            maxStreams = Integer.MAX_VALUE;
         }
 
         @Override
@@ -244,7 +249,7 @@ public DefaultStream createStream(int streamId, boolean halfClosed) throws Http2
             }
 
             // Update the next and last stream IDs.
-            nextStreamId += 2;
+            nextStreamId = streamId + 2;
             lastStreamCreated = streamId;
 
             // Register the stream and mark it as active.
@@ -253,6 +258,11 @@ public DefaultStream createStream(int streamId, boolean halfClosed) throws Http2
             return stream;
         }
 
+        @Override
+        public boolean isServer() {
+            return server;
+        }
+
         @Override
         public DefaultStream reservePushStream(int streamId, Http2Stream parent)
                 throws Http2Exception {
@@ -271,7 +281,7 @@ public DefaultStream reservePushStream(int streamId, Http2Stream parent)
             stream.state = isLocal() ? State.RESERVED_LOCAL : State.RESERVED_REMOTE;
 
             // Update the next and last stream IDs.
-            nextStreamId += 2;
+            nextStreamId = streamId + 2;
             lastStreamCreated = streamId;
 
             // Register the stream.
@@ -281,6 +291,9 @@ public DefaultStream reservePushStream(int streamId, Http2Stream parent)
 
         @Override
         public void allowPushTo(boolean allow) {
+            if (allow && server) {
+                throw new IllegalArgumentException("Servers do not allow push");
+            }
             pushToAllowed = allow;
         }
 
@@ -323,14 +336,24 @@ private void checkNewStreamAllowed(int streamId) throws Http2Exception {
             if (isGoAway()) {
                 throw protocolError("Cannot create a stream since the connection is going away");
             }
+            verifyStreamId(streamId);
+            if (streamMap.size() + 1 > maxStreams) {
+                throw protocolError("Maximum streams exceeded for this endpoint.");
+            }
+        }
+
+        private void verifyStreamId(int streamId) throws Http2Exception {
             if (nextStreamId < 0) {
                 throw protocolError("No more streams can be created on this connection");
             }
-            if (streamId != nextStreamId) {
-                throw protocolError("Incorrect next stream ID requested: %d", streamId);
+            if (streamId < nextStreamId) {
+                throw protocolError("Request stream %d is behind the next expected stream %d",
+                        streamId, nextStreamId);
             }
-            if (streamMap.size() + 1 > maxStreams) {
-                throw protocolError("Maximum streams exceeded for this endpoint.");
+            boolean even = (streamId & 1) == 0;
+            if (server != even) {
+                throw protocolError("Request stream %d is not correct for %s connection", streamId,
+                        server ? "server" : "client");
             }
         }
 
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java
index d9fd082b4d2..cf67a14ebc3 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java
@@ -44,7 +44,6 @@ private enum State {
         ERROR
     }
 
-    private final boolean server;
     private final Http2HeadersDecoder headersDecoder;
 
     private State state = State.FRAME_HEADER;
@@ -54,12 +53,11 @@ private enum State {
     private int payloadLength;
     private HeadersContinuation headersContinuation;
 
-    public DefaultHttp2FrameReader(boolean server) {
-        this(server, new DefaultHttp2HeadersDecoder());
+    public DefaultHttp2FrameReader() {
+        this(new DefaultHttp2HeadersDecoder());
     }
 
-    public DefaultHttp2FrameReader(boolean server, Http2HeadersDecoder headersDecoder) {
-        this.server = server;
+    public DefaultHttp2FrameReader(Http2HeadersDecoder headersDecoder) {
         this.headersDecoder = headersDecoder;
     }
 
@@ -373,10 +371,6 @@ private void verifyAltSvcFrame() throws Http2Exception {
         verifyStreamOrConnectionId(streamId, "Stream ID");
         verifyPayloadLength(payloadLength);
 
-        if (server) {
-            throw protocolError("ALT_SVC frames must not be received by servers");
-        }
-
         if (payloadLength < 8) {
             throw protocolError("Frame length too small." + payloadLength);
         }
@@ -420,7 +414,7 @@ private void readHeadersFrame(final ChannelHandlerContext ctx, ByteBuf payload,
             long word1 = payload.readUnsignedInt();
             final boolean exclusive = (word1 & 0x80000000L) > 0;
             final int streamDependency = (int) (word1 & 0x7FFFFFFFL);
-            final short headersWeight = payload.readUnsignedByte();
+            final short weight = (short) (payload.readUnsignedByte() + 1);
             final ByteBuf fragment = payload.readSlice(payload.readableBytes() - padding);
 
             // Create a handler that invokes the observer when the header block is complete.
@@ -437,7 +431,7 @@ public void processFragment(boolean endOfHeaders, ByteBuf fragment, int padding,
                     if (endOfHeaders) {
                         Http2Headers headers = builder().buildHeaders();
                         observer.onHeadersRead(ctx, headersStreamId, headers, streamDependency,
-                                headersWeight, exclusive, padding, headersFlags.endOfStream(),
+                                weight, exclusive, padding, headersFlags.endOfStream(),
                                 headersFlags.endOfSegment());
                         close();
                     }
@@ -480,7 +474,7 @@ private void readPriorityFrame(ChannelHandlerContext ctx, ByteBuf payload,
         long word1 = payload.readUnsignedInt();
         boolean exclusive = (word1 & 0x80000000L) > 0;
         int streamDependency = (int) (word1 & 0x7FFFFFFFL);
-        short weight = payload.readUnsignedByte();
+        short weight = (short) (payload.readUnsignedByte() + 1);
         observer.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
     }
 
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java
index 2d4c03608ea..a26eefe44d4 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameWriter.java
@@ -21,6 +21,8 @@
 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_INT;
 import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_SHORT;
+import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
+import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
 import static io.netty.handler.codec.http2.Http2CodecUtil.PRIORITY_ENTRY_LENGTH;
 import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_COMPRESS_DATA;
 import static io.netty.handler.codec.http2.Http2CodecUtil.SETTINGS_ENABLE_PUSH;
@@ -43,15 +45,13 @@
  */
 public class DefaultHttp2FrameWriter implements Http2FrameWriter {
 
-    private final boolean server;
     private final Http2HeadersEncoder headersEncoder;
 
-    public DefaultHttp2FrameWriter(boolean server) {
-        this(server, new DefaultHttp2HeadersEncoder());
+    public DefaultHttp2FrameWriter() {
+        this(new DefaultHttp2HeadersEncoder());
     }
 
-    public DefaultHttp2FrameWriter(boolean server, Http2HeadersEncoder headersEncoder) {
-        this.server = server;
+    public DefaultHttp2FrameWriter(Http2HeadersEncoder headersEncoder) {
         this.headersEncoder = headersEncoder;
     }
 
@@ -133,7 +133,9 @@ public ChannelFuture writePriority(ChannelHandlerContext ctx, ChannelPromise pro
                     Http2Flags.EMPTY, streamId);
             long word1 = exclusive ? (0x80000000L | streamDependency) : streamDependency;
             writeUnsignedInt(word1, frame);
-            frame.writeByte(weight);
+
+            // Adjust the weight so that it fits into a single byte on the wire.
+            frame.writeByte(weight - 1);
             return ctx.writeAndFlush(frame, promise);
         } catch (RuntimeException e) {
             throw failAndThrow(promise, e);
@@ -189,6 +191,7 @@ public ChannelFuture writeSettings(ChannelHandlerContext ctx, ChannelPromise pro
                 writeUnsignedInt(settings.maxConcurrentStreams(), frame);
             }
             if (settings.hasPushEnabled()) {
+                // Only write the enable push flag from client endpoints.
                 frame.writeByte(SETTINGS_ENABLE_PUSH);
                 writeUnsignedInt(settings.pushEnabled() ? 1L : 0L, frame);
             }
@@ -327,9 +330,6 @@ public ChannelFuture writeWindowUpdate(ChannelHandlerContext ctx, ChannelPromise
     public ChannelFuture writeAltSvc(ChannelHandlerContext ctx, ChannelPromise promise,
             int streamId, long maxAge, int port, ByteBuf protocolId, String host, String origin) {
         try {
-            if (!server) {
-                throw new IllegalArgumentException("ALT_SVC frames must not be sent by clients");
-            }
             verifyStreamOrConnectionId(streamId, "Stream ID");
             verifyMaxAge(maxAge);
             verifyPort(port);
@@ -428,7 +428,9 @@ private ChannelFuture writeHeadersInternal(ChannelHandlerContext ctx, ChannelPro
             if (hasPriority) {
                 long word1 = exclusive ? (0x80000000L | streamDependency) : streamDependency;
                 writeUnsignedInt(word1, firstFrame);
-                firstFrame.writeByte(weight);
+
+                // Adjust the weight so that it fits into a single byte on the wire.
+                firstFrame.writeByte(weight - 1);
             }
 
             // Write the first fragment.
@@ -542,7 +544,7 @@ private static void verifyPayloadLength(int payloadLength) {
     }
 
     private static void verifyWeight(short weight) {
-        if (weight < 1 || weight > MAX_UNSIGNED_BYTE) {
+        if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
             throw new IllegalArgumentException("Invalid weight: " + weight);
         }
     }
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2PriorityTree.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2PriorityTree.java
index 4aca82aa782..e834a82caab 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2PriorityTree.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2PriorityTree.java
@@ -16,8 +16,8 @@
 package io.netty.handler.codec.http2;
 
 import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
-import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_UNSIGNED_BYTE;
-import io.netty.handler.codec.http2.Http2PriorityTree.Priority;
+import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_WEIGHT;
+import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_WEIGHT;
 
 import java.util.ArrayDeque;
 import java.util.Collections;
@@ -62,7 +62,7 @@ public Priority<T> prioritize(int streamId, int parent, short weight, boolean ex
         if (parent < 0) {
             throw new IllegalArgumentException("Parent stream ID must be >= 0");
         }
-        if (weight < 1 || weight > MAX_UNSIGNED_BYTE) {
+        if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) {
             throw new IllegalArgumentException("Invalid weight: " + weight);
         }
 
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java
index 48f0500a213..b40931878c5 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2CodecUtil.java
@@ -43,6 +43,8 @@ public final class Http2CodecUtil {
     public static final int SETTING_ENTRY_LENGTH = 5;
     public static final int PRIORITY_ENTRY_LENGTH = 5;
     public static final int INT_FIELD_LENGTH = 4;
+    public static final short MAX_WEIGHT = (short) 256;
+    public static final short MIN_WEIGHT = (short) 1;
 
     public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
     public static final short SETTINGS_ENABLE_PUSH = 2;
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Connection.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Connection.java
index c88687f791b..15d07543646 100644
--- a/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Connection.java
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/Http2Connection.java
@@ -55,13 +55,19 @@ interface Endpoint {
          */
         Http2Stream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception;
 
+        /**
+         * Indicates whether or not this endpoint is the server-side of the connection.
+         */
+        boolean isServer();
+
         /**
          * Sets whether server push is allowed to this endpoint.
          */
         void allowPushTo(boolean allow);
 
         /**
-         * Gets whether or not server push is allowed to this endpoint.
+         * Gets whether or not server push is allowed to this endpoint. This is always false
+         * for a server endpoint.
          */
         boolean allowPushTo();
 
@@ -97,7 +103,7 @@ interface Endpoint {
     }
 
     /**
-     * Indicates whether or not this endpoint is the server-side of the connection.
+     * Indicates whether or not the local endpoint for this connection is the server.
      */
     boolean isServer();
 
diff --git a/example/src/main/java/io/netty/example/http2/client/Http2ClientConnectionHandler.java b/example/src/main/java/io/netty/example/http2/client/Http2ClientConnectionHandler.java
index dfd590db6aa..8f09b738daf 100644
--- a/example/src/main/java/io/netty/example/http2/client/Http2ClientConnectionHandler.java
+++ b/example/src/main/java/io/netty/example/http2/client/Http2ClientConnectionHandler.java
@@ -192,10 +192,10 @@ public BlockingQueue<ChannelFuture> queue() {
     }
 
     private static Http2FrameReader frameReader() {
-        return new Http2InboundFrameLogger(new DefaultHttp2FrameReader(false), logger);
+        return new Http2InboundFrameLogger(new DefaultHttp2FrameReader(), logger);
     }
 
     private static Http2FrameWriter frameWriter() {
-        return new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(false), logger);
+        return new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(), logger);
     }
 }


Test patch:
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionTest.java
index 21526af9474..e3344c2f9c3 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2ConnectionTest.java
@@ -116,17 +116,27 @@ public void serverReservePushStreamShouldSucceed() throws Http2Exception {
 
     @Test
     public void clientReservePushStreamShouldSucceed() throws Http2Exception {
-        Http2Stream stream = client.remote().createStream(2, true);
-        Http2Stream pushStream = client.local().reservePushStream(3, stream);
-        assertEquals(3, pushStream.id());
+        Http2Stream stream = server.remote().createStream(3, true);
+        Http2Stream pushStream = server.local().reservePushStream(4, stream);
+        assertEquals(4, pushStream.id());
         assertEquals(State.RESERVED_LOCAL, pushStream.state());
-        assertEquals(1, client.activeStreams().size());
-        assertEquals(3, client.local().lastStreamCreated());
+        assertEquals(1, server.activeStreams().size());
+        assertEquals(4, server.local().lastStreamCreated());
+    }
+
+    @Test(expected = Http2Exception.class)
+    public void newStreamBehindExpectedShouldThrow() throws Http2Exception {
+        server.local().createStream(0, true);
+    }
+
+    @Test(expected = Http2Exception.class)
+    public void newStreamNotForServerShouldThrow() throws Http2Exception {
+        server.local().createStream(11, true);
     }
 
     @Test(expected = Http2Exception.class)
-    public void createStreamWithInvalidIdShouldThrow() throws Http2Exception {
-        server.remote().createStream(1, true);
+    public void newStreamNotForClientShouldThrow() throws Http2Exception {
+        client.local().createStream(10, true);
     }
 
     @Test(expected = Http2Exception.class)
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java
index bd619529e0a..9912716a04a 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DefaultHttp2FrameIOTest.java
@@ -62,8 +62,8 @@ public void setup() {
 
         when(ctx.alloc()).thenReturn(alloc);
 
-        reader = new DefaultHttp2FrameReader(false);
-        writer = new DefaultHttp2FrameWriter(true);
+        reader = new DefaultHttp2FrameReader();
+        writer = new DefaultHttp2FrameWriter();
     }
 
     @Test
@@ -118,7 +118,7 @@ public void emptySettingsShouldRoundtrip() throws Exception {
     }
 
     @Test
-    public void settingsShouldRoundtrip() throws Exception {
+    public void settingsShouldStripShouldRoundtrip() throws Exception {
         Http2Settings settings = new Http2Settings();
         settings.pushEnabled(true);
         settings.maxHeaderTableSize(4096);
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java
index a6549eedfb7..97bf53a4f3d 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/DelegatingHttp2ConnectionHandlerTest.java
@@ -396,8 +396,17 @@ public void settingsReadWithAckShouldNotifyObserver() throws Exception {
         verify(observer).onSettingsAckRead(eq(ctx));
     }
 
+    @Test(expected = Http2Exception.class)
+    public void clientSettingsReadWithPushShouldThrow() throws Exception {
+        when(connection.isServer()).thenReturn(false);
+        Http2Settings settings = new Http2Settings();
+        settings.pushEnabled(true);
+        decode().onSettingsRead(ctx, settings);
+    }
+
     @Test
     public void settingsReadShouldSetValues() throws Exception {
+        when(connection.isServer()).thenReturn(true);
         Http2Settings settings = new Http2Settings();
         settings.pushEnabled(true);
         settings.initialWindowSize(123);
@@ -416,12 +425,28 @@ public void settingsReadShouldSetValues() throws Exception {
     }
 
     @Test
-    public void goAwayShoultShouldUpdateConnectionState() throws Exception {
+    public void goAwayShouldReadShouldUpdateConnectionState() throws Exception {
         decode().onGoAwayRead(ctx, 1, 2, EMPTY_BUFFER);
         verify(connection).goAwayReceived();
         verify(observer).onGoAwayRead(eq(ctx), eq(1), eq(2L), eq(EMPTY_BUFFER));
     }
 
+    @Test(expected = Http2Exception.class)
+    public void serverAltSvcReadShouldThrow() throws Exception {
+        when(connection.isServer()).thenReturn(true);
+        decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, Unpooled.EMPTY_BUFFER, "www.example.com", null);
+    }
+
+    @Test
+    public void clientAltSvcReadShouldNotifyObserver() throws Exception {
+        String host = "www.host.com";
+        String origin = "www.origin.com";
+        when(connection.isServer()).thenReturn(false);
+        decode().onAltSvcRead(ctx, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
+        verify(observer).onAltSvcRead(eq(ctx), eq(STREAM_ID), eq(1L), eq(2), eq(EMPTY_BUFFER),
+                eq(host), eq(origin));
+    }
+
     @Test(expected = Http2Exception.class)
     public void dataWriteAfterGoAwayShouldFail() throws Exception {
         when(connection.isGoAway()).thenReturn(true);
@@ -576,6 +601,23 @@ public void settingsWriteShouldUpdateSettings() throws Exception {
         verify(reader).maxHeaderTableSize(2000);
     }
 
+    @Test(expected = Http2Exception.class)
+    public void clientWriteAltSvcShouldThrow() throws Exception {
+        when(connection.isServer()).thenReturn(false);
+        handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, Unpooled.EMPTY_BUFFER,
+                "www.example.com", null);
+    }
+
+    @Test
+    public void serverWriteAltSvcShouldSucceed() throws Exception {
+        String host = "www.host.com";
+        String origin = "www.origin.com";
+        when(connection.isServer()).thenReturn(true);
+        handler.writeAltSvc(ctx, promise, STREAM_ID, 1, 2, EMPTY_BUFFER, host, origin);
+        verify(writer).writeAltSvc(eq(ctx), eq(promise), eq(STREAM_ID), eq(1L), eq(2),
+                eq(EMPTY_BUFFER), eq(host), eq(origin));
+    }
+
     private static ByteBuf dummyData() {
         // The buffer is purposely 8 bytes so it will even work for a ping frame.
         return Unpooled.wrappedBuffer("abcdefgh".getBytes(UTF_8));
diff --git a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java
index e5df13c08c3..3a7f261a4da 100644
--- a/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java
+++ b/codec-http2/src/test/java/io/netty/handler/codec/http2/Http2FrameRoundtripTest.java
@@ -69,7 +69,7 @@ public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         requestLatch = new CountDownLatch(1);
-        frameWriter = new DefaultHttp2FrameWriter(false);
+        frameWriter = new DefaultHttp2FrameWriter();
         dataCaptor = ArgumentCaptor.forClass(ByteBuf.class);
 
         sb = new ServerBootstrap();
@@ -259,7 +259,7 @@ private final class FrameAdapter extends ByteToMessageDecoder {
 
         FrameAdapter(Http2FrameObserver observer) {
             this.observer = observer;
-            reader = new DefaultHttp2FrameReader(observer != null);
+            reader = new DefaultHttp2FrameReader();
         }
 
         @Override