diff --git a/codec-http2/pom.xml b/codec-http2/pom.xml
new file mode 100644
index 0000000000..7eeb7fb54a
--- /dev/null
+++ b/codec-http2/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+
+ 4.0.0
+
+ io.netty
+ netty-parent
+ 5.0.0.Alpha2-SNAPSHOT
+
+
+ netty-codec-http2
+ jar
+
+ Netty/Codec/HTTP2
+
+
+
+ ${project.groupId}
+ netty-codec-http
+ ${project.version}
+
+
+ ${project.groupId}
+ netty-handler
+ ${project.version}
+
+
+ com.twitter
+ hpack
+ 0.6.0
+
+
+ com.google.guava
+ guava
+ 16.0.1
+
+
+ org.mockito
+ mockito-all
+ 1.9.5
+ test
+
+
+
+
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Error.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Error.java
new file mode 100644
index 0000000000..a1cac42cc8
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Error.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10;
+
+/**
+ * All error codes identified by the HTTP2 spec.
+ */
+public enum Http2Error {
+ NO_ERROR(0),
+ PROTOCOL_ERROR(1),
+ INTERNAL_ERROR(2),
+ FLOW_CONTROL_ERROR(3),
+ SETTINGS_TIMEOUT(4),
+ STREAM_CLOSED(5),
+ FRAME_SIZE_ERROR(6),
+ REFUSED_STREAM(7),
+ CANCEL(8),
+ COMPRESSION_ERROR(9),
+ CONNECT_ERROR(10),
+ ENHANCE_YOUR_CALM(11),
+ INADEQUATE_SECURITY(12);
+
+ private final int code;
+
+ private Http2Error(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return this.code;
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Exception.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Exception.java
new file mode 100644
index 0000000000..5cdd174e32
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Exception.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10;
+
+/**
+ * Exception thrown when an HTTP2 error was encountered.
+ */
+public class Http2Exception extends Exception {
+ private static final long serialVersionUID = -2292608019080068769L;
+
+ private final Http2Error error;
+
+ public Http2Exception(Http2Error error) {
+ this.error = error;
+ }
+
+ public Http2Exception(Http2Error error, String message) {
+ super(message);
+ this.error = error;
+ }
+
+ public Http2Error getError() {
+ return error;
+ }
+
+ public static Http2Exception format(Http2Error error, String fmt, Object... args) {
+ return new Http2Exception(error, String.format(fmt, args));
+ }
+
+ public static Http2Exception protocolError(String fmt, Object... args) {
+ return format(Http2Error.PROTOCOL_ERROR, fmt, args);
+ }
+
+ public static Http2Exception flowControlError(String fmt, Object... args) {
+ return format(Http2Error.FLOW_CONTROL_ERROR, fmt, args);
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Headers.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Headers.java
new file mode 100644
index 0000000000..65826c4b76
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2Headers.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableMultimap;
+
+public final class Http2Headers implements Iterable> {
+
+ /**
+ * HTTP2 header names.
+ */
+ public enum HttpName {
+ /**
+ * {@code :method}.
+ */
+ METHOD(":method"),
+
+ /**
+ * {@code :scheme}.
+ */
+ SCHEME(":scheme"),
+
+ /**
+ * {@code :authority}.
+ */
+ AUTHORITY(":authority"),
+
+ /**
+ * {@code :path}.
+ */
+ PATH(":path"),
+
+ /**
+ * {@code :status}.
+ */
+ STATUS(":status");
+
+ private final String value;
+
+ private HttpName(String value) {
+ this.value = value;
+ }
+
+ public String value() {
+ return this.value;
+ }
+ }
+
+ private final ImmutableMultimap headers;
+
+ private Http2Headers(Builder builder) {
+ this.headers = builder.map.build();
+ }
+
+ public String getHeader(String name) {
+ ImmutableCollection col = getHeaders(name);
+ return col.isEmpty() ? null : col.iterator().next();
+ }
+
+ public ImmutableCollection getHeaders(String name) {
+ return headers.get(name);
+ }
+
+ public String getMethod() {
+ return getHeader(HttpName.METHOD.value());
+ }
+
+ public String getScheme() {
+ return getHeader(HttpName.SCHEME.value());
+ }
+
+ public String getAuthority() {
+ return getHeader(HttpName.AUTHORITY.value());
+ }
+
+ public String getPath() {
+ return getHeader(HttpName.PATH.value());
+ }
+
+ public String getStatus() {
+ return getHeader(HttpName.STATUS.value());
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return headers.entries().iterator();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((headers == null) ? 0 : headers.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Http2Headers other = (Http2Headers) obj;
+ if (headers == null) {
+ if (other.headers != null) {
+ return false;
+ }
+ } else if (!headers.equals(other.headers)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return headers.toString();
+ }
+
+ public static class Builder {
+ private ImmutableMultimap.Builder map = ImmutableMultimap.builder();
+
+ public Builder clear() {
+ map = ImmutableMultimap.builder();
+ return this;
+ }
+
+ public Builder addHeaders(Http2Headers headers) {
+ if (headers == null) {
+ throw new IllegalArgumentException("headers must not be null.");
+ }
+ map.putAll(headers.headers);
+ return this;
+ }
+
+ public Builder addHeader(String name, String value) {
+ // Use interning on the header name to save space.
+ map.put(name.intern(), value);
+ return this;
+ }
+
+ public Builder addHeader(byte[] name, byte[] value) {
+ addHeader(new String(name, Charsets.UTF_8), new String(value, Charsets.UTF_8));
+ return this;
+ }
+
+ public Builder setMethod(String value) {
+ return addHeader(HttpName.METHOD.value(), value);
+ }
+
+ public Builder setScheme(String value) {
+ return addHeader(HttpName.SCHEME.value(), value);
+ }
+
+ public Builder setAuthority(String value) {
+ return addHeader(HttpName.AUTHORITY.value(), value);
+ }
+
+ public Builder setPath(String value) {
+ return addHeader(HttpName.PATH.value(), value);
+ }
+
+ public Builder setStatus(String value) {
+ return addHeader(HttpName.STATUS.value(), value);
+ }
+
+ public Http2Headers build() {
+ return new Http2Headers(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2StreamException.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2StreamException.java
new file mode 100644
index 0000000000..d5ce51f9d8
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/Http2StreamException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10;
+
+public class Http2StreamException extends Http2Exception {
+
+ private static final long serialVersionUID = -7658235659648480024L;
+ private final int streamId;
+
+ public Http2StreamException(int streamId, Http2Error error, String message) {
+ super(error, message);
+ this.streamId = streamId;
+ }
+
+ public Http2StreamException(int streamId, Http2Error error) {
+ super(error);
+ this.streamId = streamId;
+ }
+
+ public int getStreamId() {
+ return streamId;
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultHttp2Connection.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultHttp2Connection.java
new file mode 100644
index 0000000000..b1966207c7
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultHttp2Connection.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import static io.netty.handler.codec.http2.draft10.Http2Error.NO_ERROR;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
+import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toByteBuf;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.draft10.Http2Error;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.connection.Http2Stream.State;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2GoAwayFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.TreeMultiset;
+
+public class DefaultHttp2Connection implements Http2Connection {
+
+ private final List listeners = Lists.newArrayList();
+ private final Map streamMap = Maps.newHashMap();
+ private final Multiset activeStreams = TreeMultiset.create();
+ private final DefaultEndpoint localEndpoint;
+ private final DefaultEndpoint remoteEndpoint;
+ private boolean goAwaySent;
+ private boolean goAwayReceived;
+ private ChannelFutureListener closeListener;
+
+ public DefaultHttp2Connection(boolean server) {
+ this.localEndpoint = new DefaultEndpoint(server);
+ this.remoteEndpoint = new DefaultEndpoint(!server);
+ }
+
+ @Override
+ public void addListener(Listener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(Listener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public Http2Stream getStreamOrFail(int streamId) throws Http2Exception {
+ Http2Stream stream = getStream(streamId);
+ if (stream == null) {
+ throw protocolError("Stream does not exist %d", streamId);
+ }
+ return stream;
+ }
+
+ @Override
+ public Http2Stream getStream(int streamId) {
+ return streamMap.get(streamId);
+ }
+
+ @Override
+ public List getActiveStreams() {
+ // Copy the list in case any operation on the returned streams causes the activeStreams set
+ // to change.
+ return ImmutableList.copyOf(activeStreams);
+ }
+
+ @Override
+ public Endpoint local() {
+ return localEndpoint;
+ }
+
+ @Override
+ public Endpoint remote() {
+ return remoteEndpoint;
+ }
+
+ @Override
+ public void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause) {
+ closeListener = getOrCreateCloseListener(ctx, promise);
+ ChannelFuture future;
+ if (!goAwaySent) {
+ goAwaySent = true;
+
+ int errorCode = cause != null ? cause.getError().getCode() : NO_ERROR.getCode();
+ ByteBuf debugData = toByteBuf(ctx, cause);
+
+ Http2GoAwayFrame frame = new DefaultHttp2GoAwayFrame.Builder().setErrorCode(errorCode)
+ .setLastStreamId(remote().getLastStreamCreated()).setDebugData(debugData).build();
+ future = ctx.writeAndFlush(frame);
+ } else {
+ future = ctx.newSucceededFuture();
+ }
+
+ // If there are no active streams, close immediately after the send is complete.
+ // Otherwise wait until all streams are inactive.
+ if (cause != null || activeStreams.isEmpty()) {
+ future.addListener(closeListener);
+ }
+ }
+
+ @Override
+ public void goAwayReceived() {
+ goAwayReceived = true;
+ }
+
+ @Override
+ public boolean isGoAwaySent() {
+ return goAwaySent;
+ }
+
+ @Override
+ public boolean isGoAwayReceived() {
+ return goAwayReceived;
+ }
+
+ @Override
+ public boolean isGoAway() {
+ return isGoAwaySent() || isGoAwayReceived();
+ }
+
+ private ChannelFutureListener getOrCreateCloseListener(final ChannelHandlerContext ctx,
+ final ChannelPromise promise) {
+ if (closeListener == null) {
+ closeListener = new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ ctx.close(promise);
+ }
+ };
+ }
+ return closeListener;
+ }
+
+ private void notifyStreamClosed(int id) {
+ for (Listener listener : listeners) {
+ listener.streamClosed(id);
+ }
+ }
+
+ private void notifyStreamCreated(int id) {
+ for (Listener listener : listeners) {
+ listener.streamCreated(id);
+ }
+ }
+
+ /**
+ * Simple stream implementation. Streams can be compared to each other by priority.
+ */
+ private class DefaultStream implements Http2Stream {
+ private final int id;
+ private State state = State.IDLE;
+ private int priority;
+
+ public DefaultStream(int id) {
+ this.id = id;
+ this.priority = DEFAULT_STREAM_PRIORITY;
+ }
+
+ @Override
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public State getState() {
+ return state;
+ }
+
+ @Override
+ public int compareTo(Http2Stream other) {
+ // Sort streams with the same priority by their ID.
+ if (priority == other.getPriority()) {
+ return id - other.getId();
+ }
+ return priority - other.getPriority();
+ }
+
+ @Override
+ public void verifyState(Http2Error error, State... allowedStates) throws Http2Exception {
+ Predicate predicate = Predicates.in(Arrays.asList(allowedStates));
+ if (!predicate.apply(state)) {
+ throw format(error, "Stream %d in unexpected state: %s", id, state);
+ }
+ }
+
+ @Override
+ public void setPriority(int priority) throws Http2Exception {
+ if (priority < 0) {
+ throw protocolError("Invalid priority: %d", priority);
+ }
+
+ // If it was active, we must remove it from the set before changing the priority.
+ // Otherwise it won't be able to locate the stream in the set.
+ boolean wasActive = activeStreams.remove(this);
+ this.priority = priority;
+
+ // If this stream was in the active set, re-add it so that it's properly sorted.
+ if (wasActive) {
+ activeStreams.add(this);
+ }
+ }
+
+ @Override
+ public int getPriority() {
+ return priority;
+ }
+
+ @Override
+ public void openForPush() throws Http2Exception {
+ switch (state) {
+ case RESERVED_LOCAL:
+ state = State.HALF_CLOSED_REMOTE;
+ break;
+ case RESERVED_REMOTE:
+ state = State.HALF_CLOSED_LOCAL;
+ break;
+ default:
+ throw protocolError("Attempting to open non-reserved stream for push");
+ }
+ }
+
+ @Override
+ public void close(ChannelHandlerContext ctx, ChannelFuture future) {
+ if (state == State.CLOSED) {
+ return;
+ }
+
+ state = State.CLOSED;
+ activeStreams.remove(this);
+ streamMap.remove(id);
+ notifyStreamClosed(id);
+
+ // If this connection is closing and there are no longer any
+ // active streams, close after the current operation completes.
+ if (closeListener != null && activeStreams.isEmpty()) {
+ future.addListener(closeListener);
+ }
+ }
+
+ @Override
+ public void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future) {
+ switch (state) {
+ case OPEN:
+ case HALF_CLOSED_LOCAL:
+ state = State.HALF_CLOSED_LOCAL;
+ break;
+ case HALF_CLOSED_REMOTE:
+ case RESERVED_LOCAL:
+ case RESERVED_REMOTE:
+ case IDLE:
+ case CLOSED:
+ default:
+ close(ctx, future);
+ break;
+ }
+ }
+
+ @Override
+ public void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future) {
+ switch (state) {
+ case OPEN:
+ case HALF_CLOSED_REMOTE:
+ state = State.HALF_CLOSED_REMOTE;
+ break;
+ case RESERVED_LOCAL:
+ case RESERVED_REMOTE:
+ case IDLE:
+ case HALF_CLOSED_LOCAL:
+ case CLOSED:
+ default:
+ close(ctx, future);
+ break;
+ }
+ }
+
+ @Override
+ public boolean isRemoteSideOpen() {
+ switch (state) {
+ case HALF_CLOSED_LOCAL:
+ case OPEN:
+ case RESERVED_REMOTE:
+ return true;
+ case IDLE:
+ case RESERVED_LOCAL:
+ case HALF_CLOSED_REMOTE:
+ case CLOSED:
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isLocalSideOpen() {
+ switch (state) {
+ case HALF_CLOSED_REMOTE:
+ case OPEN:
+ case RESERVED_LOCAL:
+ return true;
+ case IDLE:
+ case RESERVED_REMOTE:
+ case HALF_CLOSED_LOCAL:
+ case CLOSED:
+ default:
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Simple endpoint implementation.
+ */
+ private class DefaultEndpoint implements Endpoint {
+ private int nextStreamId;
+ private int lastStreamCreated;
+ private int maxStreams = Integer.MAX_VALUE;
+ private boolean pushToAllowed = true;
+
+ public DefaultEndpoint(boolean serverEndpoint) {
+ // 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;
+ }
+
+ @Override
+ public DefaultStream createStream(int streamId, int priority, boolean halfClosed)
+ throws Http2Exception {
+ checkNewStreamAllowed(streamId);
+
+ // Create and initialize the stream.
+ DefaultStream stream = new DefaultStream(streamId);
+ stream.setPriority(priority);
+ if (halfClosed) {
+ stream.state = isLocal() ? State.HALF_CLOSED_LOCAL : State.HALF_CLOSED_REMOTE;
+ } else {
+ stream.state = State.OPEN;
+ }
+
+ // Update the next and last stream IDs.
+ nextStreamId += 2;
+ lastStreamCreated = streamId;
+
+ // Register the stream and mark it as active.
+ streamMap.put(streamId, stream);
+ activeStreams.add(stream);
+
+ notifyStreamCreated(streamId);
+ return stream;
+ }
+
+ @Override
+ public DefaultStream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception {
+ if (parent == null) {
+ throw protocolError("Parent stream missing");
+ }
+ if (isLocal() ? !parent.isLocalSideOpen() : !parent.isRemoteSideOpen()) {
+ throw protocolError("Stream %d is not open for sending push promise", parent.getId());
+ }
+ if (!opposite().isPushToAllowed()) {
+ throw protocolError("Server push not allowed to opposite endpoint.");
+ }
+
+ // Create and initialize the stream.
+ DefaultStream stream = new DefaultStream(streamId);
+ stream.setPriority(parent.getPriority() + 1);
+ stream.state = isLocal() ? State.RESERVED_LOCAL : State.RESERVED_REMOTE;
+
+ // Update the next and last stream IDs.
+ nextStreamId += 2;
+ lastStreamCreated = streamId;
+
+ // Register the stream.
+ streamMap.put(streamId, stream);
+
+ notifyStreamCreated(streamId);
+ return stream;
+ }
+
+ @Override
+ public void setPushToAllowed(boolean allow) {
+ this.pushToAllowed = allow;
+ }
+
+ @Override
+ public boolean isPushToAllowed() {
+ return pushToAllowed;
+ }
+
+ @Override
+ public int getMaxStreams() {
+ return maxStreams;
+ }
+
+ @Override
+ public void setMaxStreams(int maxStreams) {
+ this.maxStreams = maxStreams;
+ }
+
+ @Override
+ public int getLastStreamCreated() {
+ return lastStreamCreated;
+ }
+
+ @Override
+ public Endpoint opposite() {
+ return isLocal() ? remoteEndpoint : localEndpoint;
+ }
+
+ private void checkNewStreamAllowed(int streamId) throws Http2Exception {
+ if (isGoAway()) {
+ throw protocolError("Cannot create a stream since the connection is going away");
+ }
+ 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 (streamMap.size() + 1 > maxStreams) {
+ // TODO(nathanmittler): is this right?
+ throw protocolError("Maximum streams exceeded for this endpoint.");
+ }
+ }
+
+ private boolean isLocal() {
+ return this == localEndpoint;
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultInboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultInboundFlowController.java
new file mode 100644
index 0000000000..b00fbd19c0
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultInboundFlowController.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import static io.netty.handler.codec.http2.draft10.Http2Exception.flowControlError;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
+import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2WindowUpdateFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
+
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Basic implementation of {@link InboundFlowController}.
+ */
+public class DefaultInboundFlowController implements InboundFlowController {
+
+ private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
+ private StreamWindow connectionWindow = new StreamWindow(CONNECTION_STREAM_ID);
+ private final Map streamWindows = Maps.newHashMap();
+
+ public DefaultInboundFlowController(Http2Connection connection) {
+ connection.addListener(new Http2Connection.Listener() {
+ @Override
+ public void streamCreated(int streamId) {
+ streamWindows.put(streamId, new StreamWindow(streamId));
+ }
+
+ @Override
+ public void streamClosed(int streamId) {
+ streamWindows.remove(streamId);
+ }
+ });
+ }
+
+ @Override
+ public void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception {
+ int deltaWindowSize = newWindowSize - initialWindowSize;
+ initialWindowSize = newWindowSize;
+
+ // Apply the delta to all of the windows.
+ connectionWindow.addAndGet(deltaWindowSize);
+ for (StreamWindow window : streamWindows.values()) {
+ window.updatedInitialWindowSize(deltaWindowSize);
+ }
+ }
+
+ @Override
+ public void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
+ throws Http2Exception {
+ applyConnectionFlowControl(dataFrame, frameWriter);
+ applyStreamFlowControl(dataFrame, frameWriter);
+ }
+
+ /**
+ * Apply connection-wide flow control to the incoming data frame.
+ */
+ private void applyConnectionFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
+ throws Http2Exception {
+ // Remove the data length from the available window size. Throw if the lower bound
+ // was exceeded.
+ connectionWindow.addAndGet(-dataFrame.content().readableBytes());
+
+ // If less than the window update threshold remains, restore the window size
+ // to the initial value and send a window update to the remote endpoint indicating
+ // the new window size.
+ if (connectionWindow.getSize() <= getWindowUpdateThreshold()) {
+ connectionWindow.updateWindow(frameWriter);
+ }
+ }
+
+ /**
+ * Apply stream-based flow control to the incoming data frame.
+ */
+ private void applyStreamFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
+ throws Http2Exception {
+ // Remove the data length from the available window size. Throw if the lower bound
+ // was exceeded.
+ StreamWindow window = getWindowOrFail(dataFrame.getStreamId());
+ window.addAndGet(-dataFrame.content().readableBytes());
+
+ // If less than the window update threshold remains, restore the window size
+ // to the initial value and send a window update to the remote endpoint indicating
+ // the new window size.
+ if (window.getSize() <= getWindowUpdateThreshold() && !dataFrame.isEndOfStream()) {
+ window.updateWindow(frameWriter);
+ }
+ }
+
+ /**
+ * Gets the threshold for a window size below which a window update should be issued.
+ */
+ private int getWindowUpdateThreshold() {
+ return initialWindowSize / 2;
+ }
+
+ /**
+ * Gets the window for the stream or raises a {@code PROTOCOL_ERROR} if not found.
+ */
+ private StreamWindow getWindowOrFail(int streamId) throws Http2Exception {
+ StreamWindow window = streamWindows.get(streamId);
+ if (window == null) {
+ throw protocolError("Flow control window missing for stream: %d", streamId);
+ }
+ return window;
+ }
+
+ /**
+ * Flow control window state for an individual stream.
+ */
+ private final class StreamWindow {
+ private int windowSize;
+ private int lowerBound;
+ private final int streamId;
+
+ public StreamWindow(int streamId) {
+ this.streamId = streamId;
+ this.windowSize = initialWindowSize;
+ }
+
+ public int getSize() {
+ return windowSize;
+ }
+
+ /**
+ * Adds the given delta to the window size and returns the new value.
+ *
+ * @param delta the delta in the initial window size.
+ * @throws Http2Exception thrown if the new window is less than the allowed lower bound.
+ */
+ public int addAndGet(int delta) throws Http2Exception {
+ // Apply the delta. Even if we throw an exception we want to have taken this delta into
+ // account.
+ windowSize += delta;
+ if (delta > 0) {
+ lowerBound = 0;
+ }
+
+ // Window size can become negative if we sent a SETTINGS frame that reduces the
+ // size of the transfer window after the peer has written data frames.
+ // The value is bounded by the length that SETTINGS frame decrease the window.
+ // This difference is stored for the connection when writing the SETTINGS frame
+ // and is cleared once we send a WINDOW_UPDATE frame.
+ if (windowSize < lowerBound) {
+ if (streamId == CONNECTION_STREAM_ID) {
+ throw protocolError("Connection flow control window exceeded");
+ } else {
+ throw flowControlError("Flow control window exceeded for stream: %d", streamId);
+ }
+ }
+
+ return windowSize;
+ }
+
+ /**
+ * Called when sending a SETTINGS frame with a new initial window size. If the window has gotten
+ * smaller (i.e. deltaWindowSize < 0), the lower bound is set to that value. This will
+ * temporarily allow for receipt of data frames which were sent by the remote endpoint before
+ * receiving the SETTINGS frame.
+ *
+ * @param delta the delta in the initial window size.
+ * @throws Http2Exception thrown if integer overflow occurs on the window.
+ */
+ public void updatedInitialWindowSize(int delta) throws Http2Exception {
+ windowSize += delta;
+ if (delta > 0 && windowSize < Integer.MIN_VALUE + delta) {
+ // Integer overflow.
+ throw flowControlError("Flow control window overflowed for stream: %d", streamId);
+ }
+
+ if (delta < 0) {
+ lowerBound = delta;
+ }
+ }
+
+ /**
+ * Called to perform a window update for this stream (or connection). Updates the window size
+ * back to the size of the initial window and sends a window update frame to the remote
+ * endpoint.
+ */
+ public void updateWindow(FrameWriter frameWriter) throws Http2Exception {
+ // Expand the window for this stream back to the size of the initial window.
+ int deltaWindowSize = initialWindowSize - getSize();
+ addAndGet(deltaWindowSize);
+
+ // Send a window update for the stream/connection.
+ Http2WindowUpdateFrame updateFrame = new DefaultHttp2WindowUpdateFrame.Builder()
+ .setStreamId(streamId).setWindowSizeIncrement(deltaWindowSize).build();
+ frameWriter.writeFrame(updateFrame);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultOutboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultOutboundFlowController.java
new file mode 100644
index 0000000000..aec0cc98c3
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/DefaultOutboundFlowController.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import static io.netty.handler.codec.http2.draft10.Http2Error.FLOW_CONTROL_ERROR;
+import static io.netty.handler.codec.http2.draft10.Http2Error.STREAM_CLOSED;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
+import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.CONNECTION_STREAM_ID;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.Http2StreamException;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
+
+import java.util.Map;
+import java.util.Queue;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Basic implementation of {@link OutboundFlowController}.
+ */
+public class DefaultOutboundFlowController implements OutboundFlowController {
+
+ private final Http2Connection connection;
+ private final Map streamStates = Maps.newHashMap();
+ private int initialWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
+ private int connectionWindowSize = DEFAULT_FLOW_CONTROL_WINDOW_SIZE;
+
+ public DefaultOutboundFlowController(Http2Connection connection) {
+ this.connection = connection;
+ connection.addListener(new Http2Connection.Listener() {
+ @Override
+ public void streamCreated(int streamId) {
+ streamStates.put(streamId, new StreamState(streamId));
+ }
+
+ @Override
+ public void streamClosed(int streamId) {
+ StreamState state = streamStates.remove(streamId);
+ if (state != null) {
+ state.clearPendingWrites();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception {
+ int delta = newWindowSize - initialWindowSize;
+ initialWindowSize = newWindowSize;
+ addAndGetConnectionWindowSize(delta);
+ for (StreamState window : streamStates.values()) {
+ // Verify that the maximum value is not exceeded by this change.
+ window.addAndGetWindow(delta);
+ }
+
+ if (delta > 0) {
+ // The window size increased, send any pending frames for all streams.
+ writePendingFrames();
+ }
+ }
+
+ @Override
+ public void updateOutboundWindowSize(int streamId, int delta) throws Http2Exception {
+ StreamState streamWindow = null;
+ if (streamId == CONNECTION_STREAM_ID) {
+ // Update the connection window and write any pending frames for all streams.
+ addAndGetConnectionWindowSize(delta);
+ writePendingFrames();
+ } else {
+ // Update the stream window and write any pending frames for the stream.
+ streamWindow = getStateOrFail(streamId);
+ streamWindow.addAndGetWindow(delta);
+ streamWindow.writePendingFrames(Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter)
+ throws Http2Exception {
+
+ StreamState streamState = getStateOrFail(frame.getStreamId());
+
+ int dataLength = frame.content().readableBytes();
+ if (streamState.writableWindow() >= dataLength) {
+ // Window size is large enough to send entire data frame
+ writeFrame(frame, streamState, frameWriter);
+ return;
+ }
+
+ // Enqueue the frame to be written when the window size permits.
+ streamState.addPendingWrite(new PendingWrite(frame, frameWriter));
+
+ if (streamState.writableWindow() <= 0) {
+ // Stream is stalled, don't send anything now.
+ return;
+ }
+
+ // Create and send a partial frame up to the window size.
+ Http2DataFrame partialFrame = readPartialFrame(frame, streamState.writableWindow());
+ writeFrame(partialFrame, streamState, frameWriter);
+ }
+
+ /**
+ * Attempts to get the {@link StreamState} for the given stream. If not available, raises a
+ * {@code PROTOCOL_ERROR}.
+ */
+ private StreamState getStateOrFail(int streamId) throws Http2Exception {
+ StreamState streamState = streamStates.get(streamId);
+ if (streamState == null) {
+ throw protocolError("Missing flow control window for stream: %d", streamId);
+ }
+ return streamState;
+ }
+
+ /**
+ * Writes the frame and decrements the stream and connection window sizes.
+ */
+ private void writeFrame(Http2DataFrame frame, StreamState state, FrameWriter frameWriter)
+ throws Http2Exception {
+ int dataLength = frame.content().readableBytes();
+ connectionWindowSize -= dataLength;
+ state.addAndGetWindow(-dataLength);
+ frameWriter.writeFrame(frame);
+ }
+
+ /**
+ * Creates a view of the given frame starting at the current read index with the given number of
+ * bytes. The reader index on the input frame is then advanced by the number of bytes. The
+ * returned frame will not have end-of-stream set.
+ */
+ private Http2DataFrame readPartialFrame(Http2DataFrame frame, int numBytes) {
+ return new DefaultHttp2DataFrame.Builder().setStreamId(frame.getStreamId())
+ .setContent(frame.content().readSlice(numBytes).retain()).build();
+ }
+
+ /**
+ * Indicates whether applying the delta to the given value will cause an integer overflow.
+ */
+ private boolean isIntegerOverflow(int previousValue, int delta) {
+ return delta > 0 && (Integer.MAX_VALUE - delta) < previousValue;
+ }
+
+ /**
+ * Increments the connectionWindowSize and returns the new value.
+ */
+ private int addAndGetConnectionWindowSize(int delta) throws Http2Exception {
+ if (isIntegerOverflow(connectionWindowSize, delta)) {
+ throw format(FLOW_CONTROL_ERROR, "Window update exceeds maximum for connection");
+ }
+ return connectionWindowSize += delta;
+ }
+
+ /**
+ * Writes any pending frames for the entire connection.
+ */
+ private void writePendingFrames() throws Http2Exception {
+ // The request for for the entire connection, write any pending frames across
+ // all active streams. Active streams are already sorted by their priority.
+ for (Http2Stream stream : connection.getActiveStreams()) {
+ StreamState state = getStateOrFail(stream.getId());
+ state.writePendingFrames(1);
+ }
+ }
+
+ /**
+ * The outbound flow control state for a single stream.
+ */
+ private class StreamState {
+ private final int streamId;
+ private final Queue pendingWriteQueue = Lists.newLinkedList();
+ private int windowSize = initialWindowSize;
+
+ public StreamState(int streamId) {
+ this.streamId = streamId;
+ }
+
+ public int addAndGetWindow(int delta) throws Http2Exception {
+ if (isIntegerOverflow(windowSize, delta)) {
+ throw new Http2StreamException(streamId, FLOW_CONTROL_ERROR,
+ "Window size overflow for stream");
+ }
+ windowSize += delta;
+ return windowSize;
+ }
+
+ public int writableWindow() {
+ return Math.min(windowSize, connectionWindowSize);
+ }
+
+ public void addPendingWrite(PendingWrite pendingWrite) {
+ pendingWriteQueue.offer(pendingWrite);
+ }
+
+ public boolean hasPendingWrite() {
+ return !pendingWriteQueue.isEmpty();
+ }
+
+ public PendingWrite peekPendingWrite() {
+ if (windowSize > 0) {
+ return pendingWriteQueue.peek();
+ }
+ return null;
+ }
+
+ public void removePendingWrite() {
+ pendingWriteQueue.poll();
+ }
+
+ public void clearPendingWrites() {
+ while (true) {
+ PendingWrite pendingWrite = pendingWriteQueue.poll();
+ if (pendingWrite == null) {
+ break;
+ }
+ pendingWrite.writeError(
+ format(STREAM_CLOSED, "Stream closed before write could take place"));
+ }
+ }
+
+ /**
+ * Sends all pending writes for this stream so long as there is space the the stream and
+ * connection windows.
+ *
+ * @param maxFrames the maximum number of frames to send.
+ */
+ public void writePendingFrames(int maxFrames) throws Http2Exception {
+ while (maxFrames > 0 && writableWindow() > 0 && hasPendingWrite()) {
+ maxFrames--;
+ PendingWrite pendingWrite = peekPendingWrite();
+
+ if (writableWindow() >= pendingWrite.size()) {
+ // Window size is large enough to send entire data frame
+ removePendingWrite();
+ writeFrame(pendingWrite.frame(), this, pendingWrite.writer());
+ } else {
+ // We can send a partial frame
+ Http2DataFrame partialDataFrame =
+ readPartialFrame(pendingWrite.frame(), writableWindow());
+ writeFrame(partialDataFrame, this, pendingWrite.writer());
+ }
+ }
+ }
+ }
+
+ /**
+ * Pending write for a single data frame.
+ */
+ private class PendingWrite {
+ private final Http2DataFrame frame;
+ private final FrameWriter writer;
+
+ public PendingWrite(Http2DataFrame frame, FrameWriter writer) {
+ this.frame = frame;
+ this.writer = writer;
+ }
+
+ public Http2DataFrame frame() {
+ return frame;
+ }
+
+ public FrameWriter writer() {
+ return writer;
+ }
+
+ public int size() {
+ return frame.content().readableBytes();
+ }
+
+ public void writeError(Http2Exception cause) {
+ frame.release();
+ writer.setFailure(cause);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2Connection.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2Connection.java
new file mode 100644
index 0000000000..fd9bc92351
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2Connection.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+
+import java.util.List;
+
+public interface Http2Connection {
+
+ /**
+ * A view of the connection from one endpoint (local or remote).
+ */
+ interface Endpoint {
+
+ /**
+ * Creates a stream initiated by this endpoint and notifies all listeners. This could fail for
+ * the following reasons:
+ *
+ * - The requested stream ID is not the next sequential ID for this endpoint.
+ * - The stream already exists.
+ * - The number of concurrent streams is above the allowed threshold for this endpoint.
+ * - The connection is marked as going away}.
+ * - The provided priority is < 0.
+ *
+ * @param streamId The ID of the stream
+ * @param priority the priority of the stream
+ * @param halfClosed if true, the stream is created in the half-closed state with respect to
+ * this endpoint. Otherwise it's created in the open state.
+ */
+ Http2Stream createStream(int streamId, int priority, boolean halfClosed) throws Http2Exception;
+
+ /**
+ * Creates a push stream in the reserved state for this endpoint and notifies all listeners.
+ * This could fail for the following reasons:
+ *
+ * - Server push is not allowed to the opposite endpoint.
+ * - The requested stream ID is not the next sequential stream ID for this endpoint.
+ * - The number of concurrent streams is above the allowed threshold for this endpoint.
+ * - The connection is marked as going away.
+ * - The parent stream ID does not exist or is not open from the side sending the push promise.
+ *
+ * - Could not set a valid priority for the new stream.
+ *
+ * @param streamId the ID of the push stream
+ * @param parent the parent stream used to initiate the push stream.
+ */
+ Http2Stream reservePushStream(int streamId, Http2Stream parent) throws Http2Exception;
+
+ /**
+ * Sets whether server push is allowed to this endpoint.
+ */
+ void setPushToAllowed(boolean allow);
+
+ /**
+ * Gets whether or not server push is allowed to this endpoint.
+ */
+ boolean isPushToAllowed();
+
+ /**
+ * Gets the maximum number of concurrent streams allowed by this endpoint.
+ */
+ int getMaxStreams();
+
+ /**
+ * Sets the maximum number of concurrent streams allowed by this endpoint.
+ */
+ void setMaxStreams(int maxStreams);
+
+ /**
+ * Gets the ID of the stream last successfully created by this endpoint.
+ */
+ int getLastStreamCreated();
+
+ /**
+ * Gets the {@link Endpoint} opposite this one.
+ */
+ Endpoint opposite();
+ }
+
+ /**
+ * A listener of the connection for stream events.
+ */
+ interface Listener {
+ /**
+ * Called when a new stream with the given ID is created.
+ */
+ void streamCreated(int streamId);
+
+ /**
+ * Called when the stream with the given ID is closed.
+ */
+ void streamClosed(int streamId);
+ }
+
+ /**
+ * Adds a listener of this connection.
+ */
+ void addListener(Listener listener);
+
+ /**
+ * Removes a listener of this connection.
+ */
+ void removeListener(Listener listener);
+
+ /**
+ * Attempts to get the stream for the given ID. If it doesn't exist, throws.
+ */
+ Http2Stream getStreamOrFail(int streamId) throws Http2Exception;
+
+ /**
+ * Gets the stream if it exists. If not, returns {@code null}.
+ */
+ Http2Stream getStream(int streamId);
+
+ /**
+ * Gets all streams that are currently either open or half-closed. The returned collection is
+ * sorted by priority.
+ */
+ List getActiveStreams();
+
+ /**
+ * Gets a view of this connection from the local {@link Endpoint}.
+ */
+ Endpoint local();
+
+ /**
+ * Gets a view of this connection from the remote {@link Endpoint}.
+ */
+ Endpoint remote();
+
+ /**
+ * Marks that a GoAway frame has been sent on this connection. After calling this, both
+ * {@link #isGoAwaySent()} and {@link #isGoAway()} will be {@code true}.
+ */
+ void sendGoAway(ChannelHandlerContext ctx, ChannelPromise promise, Http2Exception cause);
+
+ /**
+ * Marks that a GoAway frame has been received on this connection. After calling this, both
+ * {@link #isGoAwayReceived()} and {@link #isGoAway()} will be {@code true}.
+ */
+ void goAwayReceived();
+
+ /**
+ * Indicates that this connection received a GoAway message.
+ */
+ boolean isGoAwaySent();
+
+ /**
+ * Indicates that this connection send a GoAway message.
+ */
+ boolean isGoAwayReceived();
+
+ /**
+ * Indicates whether or not this endpoint is going away. This is a short form for
+ * {@link #isGoAwaySent()} || {@link #isGoAwayReceived()}.
+ */
+ boolean isGoAway();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2ConnectionHandler.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2ConnectionHandler.java
new file mode 100644
index 0000000000..d7573bac1f
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2ConnectionHandler.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import static io.netty.handler.codec.http2.draft10.Http2Error.PROTOCOL_ERROR;
+import static io.netty.handler.codec.http2.draft10.Http2Error.STREAM_CLOSED;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
+import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toHttp2Exception;
+import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.HALF_CLOSED_LOCAL;
+import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.HALF_CLOSED_REMOTE;
+import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.OPEN;
+import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_LOCAL;
+import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_REMOTE;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerAdapter;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.Http2StreamException;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2PingFrame;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2RstStreamFrame;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2SettingsFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
+import io.netty.handler.codec.http2.draft10.frame.Http2GoAwayFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2HeadersFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2PingFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2PriorityFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2PushPromiseFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2RstStreamFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2SettingsFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2StreamFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
+import io.netty.util.ReferenceCountUtil;
+
+public class Http2ConnectionHandler extends ChannelHandlerAdapter {
+
+ private final Http2Connection connection;
+ private final InboundFlowController inboundFlow;
+ private final OutboundFlowController outboundFlow;
+
+ public Http2ConnectionHandler(boolean server) {
+ this(new DefaultHttp2Connection(server));
+ }
+
+ public Http2ConnectionHandler(Http2Connection connection) {
+ this(connection, new DefaultInboundFlowController(connection),
+ new DefaultOutboundFlowController(connection));
+ }
+
+ public Http2ConnectionHandler(final Http2Connection connection,
+ final InboundFlowController inboundFlow, final OutboundFlowController outboundFlow) {
+ this.connection = connection;
+ this.inboundFlow = inboundFlow;
+ this.outboundFlow = outboundFlow;
+ }
+
+ @Override
+ public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+ // Avoid NotYetConnectedException
+ if (!ctx.channel().isActive()) {
+ ctx.close(promise);
+ return;
+ }
+
+ connection.sendGoAway(ctx, promise, null);
+ }
+
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+ for (Http2Stream stream : connection.getActiveStreams()) {
+ stream.close(ctx, ctx.newSucceededFuture());
+ }
+ ctx.fireChannelInactive();
+ }
+
+ /**
+ * Handles {@link Http2Exception} objects that were thrown from other handlers. Ignores all other
+ * exceptions.
+ */
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+ if (cause instanceof Http2Exception) {
+ processHttp2Exception(ctx, (Http2Exception) cause);
+ }
+
+ ctx.fireExceptionCaught(cause);
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object inMsg) throws Exception {
+ try {
+ if (inMsg instanceof Http2DataFrame) {
+ handleInboundData(ctx, (Http2DataFrame) inMsg);
+ } else if (inMsg instanceof Http2HeadersFrame) {
+ handleInboundHeaders(ctx, (Http2HeadersFrame) inMsg);
+ } else if (inMsg instanceof Http2PushPromiseFrame) {
+ handleInboundPushPromise(ctx, (Http2PushPromiseFrame) inMsg);
+ } else if (inMsg instanceof Http2PriorityFrame) {
+ handleInboundPriority(ctx, (Http2PriorityFrame) inMsg);
+ } else if (inMsg instanceof Http2RstStreamFrame) {
+ handleInboundRstStream(ctx, (Http2RstStreamFrame) inMsg);
+ } else if (inMsg instanceof Http2PingFrame) {
+ handleInboundPing(ctx, (Http2PingFrame) inMsg);
+ } else if (inMsg instanceof Http2GoAwayFrame) {
+ handleInboundGoAway(ctx, (Http2GoAwayFrame) inMsg);
+ } else if (inMsg instanceof Http2WindowUpdateFrame) {
+ handleInboundWindowUpdate(ctx, (Http2WindowUpdateFrame) inMsg);
+ } else if (inMsg instanceof Http2SettingsFrame) {
+ handleInboundSettings(ctx, (Http2SettingsFrame) inMsg);
+ } else {
+ ctx.fireChannelRead(inMsg);
+ }
+
+ } catch (Http2Exception e) {
+ ReferenceCountUtil.release(inMsg);
+ processHttp2Exception(ctx, e);
+ }
+ }
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+ throws Exception {
+ try {
+ if (msg instanceof Http2DataFrame) {
+ handleOutboundData(ctx, (Http2DataFrame) msg, promise);
+ } else if (msg instanceof Http2HeadersFrame) {
+ handleOutboundHeaders(ctx, (Http2HeadersFrame) msg, promise);
+ } else if (msg instanceof Http2PushPromiseFrame) {
+ handleOutboundPushPromise(ctx, (Http2PushPromiseFrame) msg, promise);
+ } else if (msg instanceof Http2PriorityFrame) {
+ handleOutboundPriority(ctx, (Http2PriorityFrame) msg, promise);
+ } else if (msg instanceof Http2RstStreamFrame) {
+ handleOutboundRstStream(ctx, (Http2RstStreamFrame) msg, promise);
+ } else if (msg instanceof Http2PingFrame) {
+ handleOutboundPing(ctx, (Http2PingFrame) msg, promise);
+ } else if (msg instanceof Http2GoAwayFrame) {
+ handleOutboundGoAway();
+ } else if (msg instanceof Http2WindowUpdateFrame) {
+ handleOutboundWindowUpdate();
+ } else if (msg instanceof Http2SettingsFrame) {
+ handleOutboundSettings(ctx, (Http2SettingsFrame) msg, promise);
+ } else {
+ ctx.write(msg, promise);
+ return;
+ }
+
+ } catch (Throwable e) {
+ ReferenceCountUtil.release(msg);
+ promise.setFailure(e);
+ }
+ }
+
+ /**
+ * Processes the given exception. Depending on the type of exception, delegates to either
+ * {@link #processConnectionError} or {@link #processStreamError}.
+ */
+ private void processHttp2Exception(ChannelHandlerContext ctx, Http2Exception e) {
+ if (e instanceof Http2StreamException) {
+ processStreamError(ctx, (Http2StreamException) e);
+ } else {
+ processConnectionError(ctx, e);
+ }
+ }
+
+ private void processConnectionError(ChannelHandlerContext ctx, Http2Exception cause) {
+ connection.sendGoAway(ctx, ctx.newPromise(), cause);
+ }
+
+ private void processStreamError(ChannelHandlerContext ctx, Http2StreamException cause) {
+ // Close the stream if it was open.
+ int streamId = cause.getStreamId();
+ ChannelPromise promise = ctx.newPromise();
+ Http2Stream stream = connection.getStream(streamId);
+ if (stream != null) {
+ stream.close(ctx, promise);
+ }
+
+ // Send the Rst frame to the remote endpoint.
+ Http2RstStreamFrame frame = new DefaultHttp2RstStreamFrame.Builder().setStreamId(streamId)
+ .setErrorCode(cause.getError().getCode()).build();
+ ctx.writeAndFlush(frame, promise);
+ }
+
+ private void handleInboundData(final ChannelHandlerContext ctx, Http2DataFrame frame)
+ throws Http2Exception {
+
+ // Check if we received a data frame for a stream which is half-closed
+ Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
+ stream.verifyState(STREAM_CLOSED, OPEN, HALF_CLOSED_LOCAL);
+
+ // Apply flow control.
+ inboundFlow.applyInboundFlowControl(frame, new InboundFlowController.FrameWriter() {
+ @Override
+ public void writeFrame(Http2WindowUpdateFrame frame) {
+ ctx.writeAndFlush(frame);
+ }
+ });
+
+ if (isInboundStreamAfterGoAway(frame)) {
+ // Ignore frames for any stream created after we sent a go-away.
+ frame.release();
+ return;
+ }
+
+ if (frame.isEndOfStream()) {
+ stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
+ }
+
+ // Allow this frame to continue other handlers.
+ ctx.fireChannelRead(frame);
+ }
+
+ private void handleInboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame)
+ throws Http2Exception {
+ if (isInboundStreamAfterGoAway(frame)) {
+ return;
+ }
+
+ int streamId = frame.getStreamId();
+ Http2Stream stream = connection.getStream(streamId);
+ if (stream == null) {
+ // Create the new stream.
+ connection.remote().createStream(frame.getStreamId(), frame.getPriority(),
+ frame.isEndOfStream());
+ } else {
+ // If the stream already exists, it must be a reserved push stream. If so, open
+ // it for push to the local endpoint.
+ stream.verifyState(PROTOCOL_ERROR, RESERVED_REMOTE);
+ stream.openForPush();
+
+ // If the headers completes this stream, close it.
+ if (frame.isEndOfStream()) {
+ stream.closeRemoteSide(ctx, ctx.newSucceededFuture());
+ }
+ }
+
+ ctx.fireChannelRead(frame);
+ }
+
+ private void handleInboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame)
+ throws Http2Exception {
+ if (isInboundStreamAfterGoAway(frame)) {
+ // Ignore frames for any stream created after we sent a go-away.
+ return;
+ }
+
+ // Reserve the push stream based with a priority based on the current stream's priority.
+ Http2Stream parentStream = connection.getStreamOrFail(frame.getStreamId());
+ connection.remote().reservePushStream(frame.getPromisedStreamId(), parentStream);
+
+ ctx.fireChannelRead(frame);
+ }
+
+ private void handleInboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame)
+ throws Http2Exception {
+ if (isInboundStreamAfterGoAway(frame)) {
+ // Ignore frames for any stream created after we sent a go-away.
+ return;
+ }
+
+ Http2Stream stream = connection.getStream(frame.getStreamId());
+ if (stream == null) {
+ // Priority frames must be ignored for closed streams.
+ return;
+ }
+
+ stream.verifyState(PROTOCOL_ERROR, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, OPEN, RESERVED_LOCAL);
+
+ // Set the priority on the frame.
+ stream.setPriority(frame.getPriority());
+
+ ctx.fireChannelRead(frame);
+ }
+
+ private void handleInboundWindowUpdate(ChannelHandlerContext ctx, Http2WindowUpdateFrame frame)
+ throws Http2Exception {
+ if (isInboundStreamAfterGoAway(frame)) {
+ // Ignore frames for any stream created after we sent a go-away.
+ return;
+ }
+
+ int streamId = frame.getStreamId();
+ if (streamId > 0) {
+ Http2Stream stream = connection.getStream(streamId);
+ if (stream == null) {
+ // Window Update frames must be ignored for closed streams.
+ return;
+ }
+ stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
+ }
+
+ // Update the outbound flow controller.
+ outboundFlow.updateOutboundWindowSize(streamId, frame.getWindowSizeIncrement());
+
+ ctx.fireChannelRead(frame);
+ }
+
+ private void handleInboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame) {
+ if (isInboundStreamAfterGoAway(frame)) {
+ // Ignore frames for any stream created after we sent a go-away.
+ return;
+ }
+
+ Http2Stream stream = connection.getStream(frame.getStreamId());
+ if (stream == null) {
+ // RstStream frames must be ignored for closed streams.
+ return;
+ }
+
+ stream.close(ctx, ctx.newSucceededFuture());
+
+ ctx.fireChannelRead(frame);
+ }
+
+ private void handleInboundPing(ChannelHandlerContext ctx, Http2PingFrame frame) {
+ if (frame.isAck()) {
+ // The remote enpoint is responding to an Ack that we sent.
+ ctx.fireChannelRead(frame);
+ return;
+ }
+
+ // The remote endpoint is sending the ping. Acknowledge receipt.
+ DefaultHttp2PingFrame ack = new DefaultHttp2PingFrame.Builder().setAck(true)
+ .setData(frame.content().duplicate().retain()).build();
+ ctx.writeAndFlush(ack);
+ }
+
+ private void handleInboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame)
+ throws Http2Exception {
+ if (frame.isAck()) {
+ // The remote endpoint is acknowledging the settings - fire this up to the next
+ // handler.
+ ctx.fireChannelRead(frame);
+ return;
+ }
+
+ // It's not an ack, apply the settings.
+ if (frame.getHeaderTableSize() != null) {
+ // TODO(nathanmittler): what's the right thing handle this?
+ // headersEncoder.setHeaderTableSize(frame.getHeaderTableSize());
+ }
+
+ if (frame.getPushEnabled() != null) {
+ connection.remote().setPushToAllowed(frame.getPushEnabled());
+ }
+
+ if (frame.getMaxConcurrentStreams() != null) {
+ int value = Math.max(0, (int) Math.min(Integer.MAX_VALUE, frame.getMaxConcurrentStreams()));
+ connection.local().setMaxStreams(value);
+ }
+
+ if (frame.getInitialWindowSize() != null) {
+ outboundFlow.setInitialOutboundWindowSize(frame.getInitialWindowSize());
+ }
+
+ // Acknowledge receipt of the settings.
+ Http2Frame ack = new DefaultHttp2SettingsFrame.Builder().setAck(true).build();
+ ctx.writeAndFlush(ack);
+ }
+
+ private void handleInboundGoAway(ChannelHandlerContext ctx, Http2GoAwayFrame frame) {
+ // Don't allow any more connections to be created.
+ connection.goAwayReceived();
+ ctx.fireChannelRead(frame);
+ }
+
+ /**
+ * Determines whether or not the stream was created after we sent a go-away frame. Frames from
+ * streams created after we sent a go-away should be ignored. Frames for the connection stream ID
+ * (i.e. 0) will always be allowed.
+ */
+ private boolean isInboundStreamAfterGoAway(Http2StreamFrame frame) {
+ return connection.isGoAwaySent()
+ && connection.remote().getLastStreamCreated() <= frame.getStreamId();
+ }
+
+ private void handleOutboundData(final ChannelHandlerContext ctx, Http2DataFrame frame,
+ final ChannelPromise promise) throws Http2Exception {
+ if (connection.isGoAway()) {
+ throw format(PROTOCOL_ERROR, "Sending data after connection going away.");
+ }
+
+ Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
+ stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_REMOTE);
+
+ // Hand control of the frame to the flow controller.
+ outboundFlow.sendFlowControlled(frame, new OutboundFlowController.FrameWriter() {
+ @Override
+ public void writeFrame(Http2DataFrame frame) {
+ ChannelFuture future = ctx.writeAndFlush(frame, promise);
+
+ // Close the connection on write failures that leave the outbound flow control
+ // window in a corrupt state.
+ future.addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ if (!future.isSuccess()) {
+ processHttp2Exception(ctx, toHttp2Exception(future.cause()));
+ }
+ }
+ });
+
+ // Close the local side of the stream if this is the last frame
+ if (frame.isEndOfStream()) {
+ Http2Stream stream = connection.getStream(frame.getStreamId());
+ stream.closeLocalSide(ctx, promise);
+ }
+ }
+
+ @Override
+ public void setFailure(Throwable cause) {
+ promise.setFailure(cause);
+ }
+ });
+ }
+
+ private void handleOutboundHeaders(ChannelHandlerContext ctx, Http2HeadersFrame frame,
+ ChannelPromise promise) throws Http2Exception {
+ if (connection.isGoAway()) {
+ throw format(PROTOCOL_ERROR, "Sending headers after connection going away.");
+ }
+
+ Http2Stream stream = connection.getStream(frame.getStreamId());
+ if (stream == null) {
+ // Creates a new locally-initiated stream.
+ stream = connection.local().createStream(frame.getStreamId(), frame.getPriority(),
+ frame.isEndOfStream());
+ } else {
+ // If the stream already exists, it must be a reserved push stream. If so, open
+ // it for push to the remote endpoint.
+ stream.verifyState(PROTOCOL_ERROR, RESERVED_LOCAL);
+ stream.openForPush();
+
+ // If the headers are the end of the stream, close it now.
+ if (frame.isEndOfStream()) {
+ stream.closeLocalSide(ctx, promise);
+ }
+ }
+
+ // Flush to send all of the frames.
+ ctx.writeAndFlush(frame, promise);
+ }
+
+ private void handleOutboundPushPromise(ChannelHandlerContext ctx, Http2PushPromiseFrame frame,
+ ChannelPromise promise) throws Http2Exception {
+ if (connection.isGoAway()) {
+ throw format(PROTOCOL_ERROR, "Sending push promise after connection going away.");
+ }
+
+ // Reserve the promised stream.
+ Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
+ connection.local().reservePushStream(frame.getPromisedStreamId(), stream);
+
+ // Write the frame.
+ ctx.writeAndFlush(frame, promise);
+ }
+
+ private void handleOutboundPriority(ChannelHandlerContext ctx, Http2PriorityFrame frame,
+ ChannelPromise promise) throws Http2Exception {
+ if (connection.isGoAway()) {
+ throw format(PROTOCOL_ERROR, "Sending priority after connection going away.");
+ }
+
+ // Set the priority on the stream and forward the frame.
+ Http2Stream stream = connection.getStreamOrFail(frame.getStreamId());
+ stream.setPriority(frame.getPriority());
+ ctx.writeAndFlush(frame, promise);
+ }
+
+ private void handleOutboundRstStream(ChannelHandlerContext ctx, Http2RstStreamFrame frame,
+ ChannelPromise promise) {
+ Http2Stream stream = connection.getStream(frame.getStreamId());
+ if (stream == null) {
+ // The stream may already have been closed ... ignore.
+ promise.setSuccess();
+ return;
+ }
+
+ stream.close(ctx, promise);
+ ctx.writeAndFlush(frame, promise);
+ }
+
+ private void handleOutboundPing(ChannelHandlerContext ctx, Http2PingFrame frame,
+ ChannelPromise promise) throws Http2Exception {
+ if (connection.isGoAway()) {
+ throw format(PROTOCOL_ERROR, "Sending ping after connection going away.");
+ }
+
+ if (frame.isAck()) {
+ throw format(PROTOCOL_ERROR, "Another handler attempting to send ping ack");
+ }
+
+ // Just pass the frame through.
+ ctx.writeAndFlush(frame, promise);
+ }
+
+ private void handleOutboundGoAway() throws Http2Exception {
+ // Why is this being sent? Intercept it and fail the write.
+ // Should have sent a CLOSE ChannelStateEvent
+ throw format(PROTOCOL_ERROR, "Another handler attempted to send GoAway.");
+ }
+
+ private void handleOutboundWindowUpdate() throws Http2Exception {
+ // Why is this being sent? Intercept it and fail the write.
+ throw format(PROTOCOL_ERROR, "Another handler attempted to send window update.");
+ }
+
+ private void handleOutboundSettings(ChannelHandlerContext ctx, Http2SettingsFrame frame,
+ ChannelPromise promise) throws Http2Exception {
+ if (connection.isGoAway()) {
+ throw format(PROTOCOL_ERROR, "Sending settings after connection going away.");
+ }
+
+ if (frame.isAck()) {
+ throw format(PROTOCOL_ERROR, "Another handler attempting to send settings ack");
+ }
+
+ if (frame.getPushEnabled() != null) {
+ // Enable/disable server push to this endpoint.
+ connection.local().setPushToAllowed(frame.getPushEnabled());
+ }
+ if (frame.getHeaderTableSize() != null) {
+ // TODO(nathanmittler): what's the right way to handle this?
+ // headersDecoder.setHeaderTableSize(frame.getHeaderTableSize());
+ }
+ if (frame.getMaxConcurrentStreams() != null) {
+ // Update maximum number of streams the remote endpoint can initiate.
+ if (frame.getMaxConcurrentStreams() < 0L
+ || frame.getMaxConcurrentStreams() > Integer.MAX_VALUE) {
+ throw format(PROTOCOL_ERROR, "Invalid value for max concurrent streams: %d",
+ frame.getMaxConcurrentStreams());
+ }
+ connection.remote().setMaxStreams(frame.getMaxConcurrentStreams().intValue());
+ }
+ if (frame.getInitialWindowSize() != null) {
+ // Update the initial window size for inbound traffic.
+ if (frame.getInitialWindowSize() < 0) {
+ throw format(PROTOCOL_ERROR, "Invalid value for initial window size: %d",
+ frame.getInitialWindowSize());
+ }
+ inboundFlow.setInitialInboundWindowSize(frame.getInitialWindowSize());
+ }
+ ctx.writeAndFlush(frame, promise);
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2ConnectionUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2ConnectionUtil.java
new file mode 100644
index 0000000000..7cf0a84969
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2ConnectionUtil.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import static io.netty.handler.codec.http2.draft10.Http2Error.INTERNAL_ERROR;
+import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+
+/**
+ * Constants and utility method used for encoding/decoding HTTP2 frames.
+ */
+public final class Http2ConnectionUtil {
+
+ public static final int DEFAULT_FLOW_CONTROL_WINDOW_SIZE = 65535;
+ public static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
+ public static final int DEFAULT_MAX_HEADER_SIZE = 4096;
+
+ /**
+ * Converts the given cause to a {@link Http2Exception} if it isn't already.
+ */
+ public static Http2Exception toHttp2Exception(Throwable cause) {
+ if (cause instanceof Http2Exception) {
+ return (Http2Exception) cause;
+ }
+ String msg = cause != null ? cause.getMessage() : "Failed writing the data frame.";
+ return format(INTERNAL_ERROR, msg);
+ }
+
+ /**
+ * Creates a buffer containing the error message from the given exception. If the cause is
+ * {@code null} returns an empty buffer.
+ */
+ public static ByteBuf toByteBuf(ChannelHandlerContext ctx, Throwable cause) {
+ ByteBuf debugData = Unpooled.EMPTY_BUFFER;
+ if (cause != null) {
+ // Create the debug message.
+ byte[] msg = cause.getMessage().getBytes();
+ debugData = ctx.alloc().buffer(msg.length);
+ debugData.writeBytes(msg);
+ }
+ return debugData;
+ }
+
+ private Http2ConnectionUtil() {
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2Stream.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2Stream.java
new file mode 100644
index 0000000000..dc7a490cd3
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/Http2Stream.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http2.draft10.Http2Error;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+
+/**
+ * A single stream within an HTTP2 connection. Streams are compared to each other by priority.
+ */
+public interface Http2Stream extends Comparable {
+
+ /**
+ * The allowed states of an HTTP2 stream.
+ */
+ enum State {
+ IDLE, RESERVED_LOCAL, RESERVED_REMOTE, OPEN, HALF_CLOSED_LOCAL, HALF_CLOSED_REMOTE, CLOSED;
+ }
+
+ /**
+ * Gets the unique identifier for this stream within the connection.
+ */
+ int getId();
+
+ /**
+ * Gets the state of this stream.
+ */
+ State getState();
+
+ /**
+ * Verifies that the stream is in one of the given allowed states.
+ */
+ void verifyState(Http2Error error, State... allowedStates) throws Http2Exception;
+
+ /**
+ * Sets the priority of this stream. A value of zero is the highest priority and a value of
+ * {@link Integer#MAX_VALUE} is the lowest.
+ */
+ void setPriority(int priority) throws Http2Exception;
+
+ /**
+ * Gets the priority of this stream. A value of zero is the highest priority and a value of
+ * {@link Integer#MAX_VALUE} is the lowest.
+ */
+ int getPriority();
+
+ /**
+ * If this is a reserved push stream, opens the stream for push in one direction.
+ */
+ void openForPush() throws Http2Exception;
+
+ /**
+ * Closes the stream.
+ */
+ void close(ChannelHandlerContext ctx, ChannelFuture future);
+
+ /**
+ * Closes the local side of this stream. If this makes the stream closed, the child is closed as
+ * well.
+ */
+ void closeLocalSide(ChannelHandlerContext ctx, ChannelFuture future);
+
+ /**
+ * Closes the remote side of this stream. If this makes the stream closed, the child is closed as
+ * well.
+ */
+ void closeRemoteSide(ChannelHandlerContext ctx, ChannelFuture future);
+
+ /**
+ * Indicates whether the remote side of this stream is open (i.e. the state is either
+ * {@link State#OPEN} or {@link State#HALF_CLOSED_LOCAL}).
+ */
+ boolean isRemoteSideOpen();
+
+ /**
+ * Indicates whether the local side of this stream is open (i.e. the state is either
+ * {@link State#OPEN} or {@link State#HALF_CLOSED_REMOTE}).
+ */
+ boolean isLocalSideOpen();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/InboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/InboundFlowController.java
new file mode 100644
index 0000000000..3d48353ff8
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/InboundFlowController.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
+
+/**
+ * Controls the inbound flow of data frames from the remote endpoint.
+ */
+public interface InboundFlowController {
+
+ /**
+ * A writer of window update frames.
+ */
+ interface FrameWriter {
+
+ /**
+ * Writes a window update frame to the remote endpoint.
+ */
+ void writeFrame(Http2WindowUpdateFrame frame);
+ }
+
+ /**
+ * Sets the initial inbound flow control window size and updates all stream window sizes by the
+ * delta. This is called as part of the processing for an outbound SETTINGS frame.
+ *
+ * @param newWindowSize the new initial window size.
+ * @throws Http2Exception thrown if any protocol-related error occurred.
+ */
+ void setInitialInboundWindowSize(int newWindowSize) throws Http2Exception;
+
+ /**
+ * Applies flow control for the received data frame.
+ *
+ * @param dataFrame the flow controlled frame
+ * @param frameWriter allows this flow controller to send window updates to the remote endpoint.
+ * @throws Http2Exception thrown if any protocol-related error occurred.
+ */
+ void applyInboundFlowControl(Http2DataFrame dataFrame, FrameWriter frameWriter)
+ throws Http2Exception;
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/OutboundFlowController.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/OutboundFlowController.java
new file mode 100644
index 0000000000..78fbaf6e43
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/OutboundFlowController.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.connection;
+
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
+
+/**
+ * Controls the outbound flow of data frames to the remote endpoint.
+ */
+public interface OutboundFlowController {
+
+ /**
+ * Interface that abstracts the writing of {@link Http2Frame} objects to the remote endpoint.
+ */
+ interface FrameWriter {
+
+ /**
+ * Writes a single data frame to the remote endpoint.
+ */
+ void writeFrame(Http2DataFrame frame);
+
+ /**
+ * Called if an error occurred before the write could take place. Sets the failure on the
+ * channel promise.
+ */
+ void setFailure(Throwable cause);
+ }
+
+ /**
+ * Sets the initial size of the connection's outbound flow control window. The outbound flow
+ * control windows for all streams are updated by the delta in the initial window size. This is
+ * called as part of the processing of a SETTINGS frame received from the remote endpoint.
+ *
+ * @param newWindowSize the new initial window size.
+ */
+ void setInitialOutboundWindowSize(int newWindowSize) throws Http2Exception;
+
+ /**
+ * Updates the size of the stream's outbound flow control window. This is called upon receiving a
+ * WINDOW_UPDATE frame from the remote endpoint.
+ *
+ * @param streamId the ID of the stream, or zero if the window is for the entire connection.
+ * @param deltaWindowSize the change in size of the outbound flow control window.
+ * @throws Http2Exception thrown if a protocol-related error occurred.
+ */
+ void updateOutboundWindowSize(int streamId, int deltaWindowSize) throws Http2Exception;
+
+ /**
+ * Sends the frame with outbound flow control applied. The frame may be written at a later time,
+ * depending on whether the remote endpoint can receive the frame now.
+ *
+ * Data frame flow control processing requirements:
+ *
+ * Sender must not send a data frame with data length greater than the transfer window size. After
+ * sending each data frame, the stream's transfer window size is decremented by the amount of data
+ * transmitted. When the window size becomes less than or equal to 0, the sender must pause
+ * transmitting data frames.
+ *
+ * @param frame the frame to send.
+ * @param frameWriter peforms to the write of the frame to the remote endpoint.
+ * @throws Http2Exception thrown if a protocol-related error occurred.
+ */
+ void sendFlowControlled(Http2DataFrame frame, FrameWriter frameWriter) throws Http2Exception;
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/package-info.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/package-info.java
new file mode 100644
index 0000000000..5e03678bb2
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/connection/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * Connection-level services (stream management, flow control) for HTTP2.
+ */
+package io.netty.handler.codec.http2.draft10.connection;
+
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2DataFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2DataFrame.java
new file mode 100644
index 0000000000..c9b56ac886
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2DataFrame.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_SHORT;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.DefaultByteBufHolder;
+import io.netty.buffer.Unpooled;
+
+/**
+ * Default implementation of {@link Http2DataFrame}.
+ */
+public final class DefaultHttp2DataFrame extends DefaultByteBufHolder implements Http2DataFrame {
+
+ private final int paddingLength;
+ private final int streamId;
+ private final boolean endOfStream;
+
+ private DefaultHttp2DataFrame(Builder builder) {
+ super(builder.content);
+ this.streamId = builder.streamId;
+ this.endOfStream = builder.endOfStream;
+ this.paddingLength = builder.paddingLength;
+ }
+
+ @Override
+ public int getStreamId() {
+ return streamId;
+ }
+
+ @Override
+ public boolean isEndOfStream() {
+ return endOfStream;
+ }
+
+ @Override
+ public int getPaddingLength() {
+ return paddingLength;
+ }
+
+ @Override
+ public DefaultHttp2DataFrame copy() {
+ return copyBuilder().setContent(content().copy()).build();
+ }
+
+ @Override
+ public DefaultHttp2DataFrame duplicate() {
+ return copyBuilder().setContent(content().duplicate()).build();
+ }
+
+ @Override
+ public DefaultHttp2DataFrame retain() {
+ super.retain();
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2DataFrame retain(int increment) {
+ super.retain(increment);
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2DataFrame touch() {
+ super.touch();
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2DataFrame touch(Object hint) {
+ super.touch(hint);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = content().hashCode();
+ result = prime * result + (endOfStream ? 1231 : 1237);
+ result = prime * result + paddingLength;
+ result = prime * result + streamId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2DataFrame other = (DefaultHttp2DataFrame) obj;
+ if (endOfStream != other.endOfStream) {
+ return false;
+ }
+ if (paddingLength != other.paddingLength) {
+ return false;
+ }
+ if (streamId != other.streamId) {
+ return false;
+ }
+ if (!content().equals(other.content())) {
+ return false;
+ }
+ return true;
+ }
+
+ private Builder copyBuilder() {
+ return new Builder().setStreamId(streamId).setPaddingLength(paddingLength)
+ .setEndOfStream(endOfStream);
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2DataFrame}.
+ */
+ public static class Builder {
+ private int streamId;
+ private boolean endOfStream;
+ private ByteBuf content = Unpooled.EMPTY_BUFFER;
+ private int paddingLength;
+
+ public Builder setStreamId(int streamId) {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be > 0.");
+ }
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Builder setEndOfStream(boolean endOfStream) {
+ this.endOfStream = endOfStream;
+ return this;
+ }
+
+ /**
+ * Sets the content for the data frame, excluding any padding. This buffer will be retained when
+ * the frame is built.
+ */
+ public Builder setContent(ByteBuf content) {
+ if (content == null) {
+ throw new IllegalArgumentException("content must not be null");
+ }
+ verifyLength(paddingLength, content);
+ this.content = content;
+ return this;
+ }
+
+ public Builder setPaddingLength(int paddingLength) {
+ if (paddingLength < 0 || paddingLength > MAX_UNSIGNED_SHORT) {
+ throw new IllegalArgumentException("Padding length invalid.");
+ }
+ verifyLength(paddingLength, content);
+ this.paddingLength = paddingLength;
+ return this;
+ }
+
+ public DefaultHttp2DataFrame build() {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be set.");
+ }
+
+ verifyLength(paddingLength, content);
+
+ return new DefaultHttp2DataFrame(this);
+ }
+
+ private void verifyLength(int paddingLength, ByteBuf data) {
+ int maxLength = MAX_FRAME_PAYLOAD_LENGTH;
+ maxLength -= paddingLength;
+ if (data.readableBytes() > maxLength) {
+ throw new IllegalArgumentException("Header block fragment length too big.");
+ }
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2GoAwayFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2GoAwayFrame.java
new file mode 100644
index 0000000000..8bfa5fea97
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2GoAwayFrame.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_INT;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.DefaultByteBufHolder;
+import io.netty.buffer.Unpooled;
+
+/**
+ * Default implementation of {@link Http2GoAwayFrame}.
+ */
+public final class DefaultHttp2GoAwayFrame extends DefaultByteBufHolder implements
+ Http2GoAwayFrame {
+ private final int lastStreamId;
+ private final long errorCode;
+
+ private DefaultHttp2GoAwayFrame(Builder builder) {
+ super(builder.debugData);
+ this.lastStreamId = builder.lastStreamId;
+ this.errorCode = builder.errorCode;
+ }
+
+ @Override
+ public int getLastStreamId() {
+ return lastStreamId;
+ }
+
+ @Override
+ public long getErrorCode() {
+ return errorCode;
+ }
+
+ @Override
+ public DefaultHttp2GoAwayFrame copy() {
+ return copyBuilder().setDebugData(content().copy()).build();
+ }
+
+ @Override
+ public DefaultHttp2GoAwayFrame duplicate() {
+ return copyBuilder().setDebugData(content().duplicate()).build();
+ }
+
+ @Override
+ public DefaultHttp2GoAwayFrame retain() {
+ super.retain();
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2GoAwayFrame retain(int increment) {
+ super.retain(increment);
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2GoAwayFrame touch() {
+ super.touch();
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2GoAwayFrame touch(Object hint) {
+ super.touch(hint);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = content().hashCode();
+ result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
+ result = prime * result + lastStreamId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2GoAwayFrame other = (DefaultHttp2GoAwayFrame) obj;
+ if (errorCode != other.errorCode) {
+ return false;
+ }
+ if (lastStreamId != other.lastStreamId) {
+ return false;
+ }
+ if (!content().equals(other.content())) {
+ return false;
+ }
+ return true;
+ }
+
+ private Builder copyBuilder() {
+ return new Builder().setErrorCode(errorCode).setLastStreamId(lastStreamId);
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2GoAwayFrame}.
+ */
+ public static class Builder {
+ private int lastStreamId = -1;
+ private long errorCode = -1;
+ private ByteBuf debugData = Unpooled.EMPTY_BUFFER;
+
+ public Builder setLastStreamId(int lastStreamId) {
+ if (lastStreamId < 0) {
+ throw new IllegalArgumentException("Invalid lastStreamId.");
+ }
+ this.lastStreamId = lastStreamId;
+ return this;
+ }
+
+ public Builder setErrorCode(long errorCode) {
+ if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
+ throw new IllegalArgumentException("Invalid error code.");
+ }
+ this.errorCode = errorCode;
+ return this;
+ }
+
+ public Builder setDebugData(ByteBuf debugData) {
+ if (debugData == null) {
+ throw new IllegalArgumentException("debugData must not be null");
+ }
+ if (debugData.readableBytes() > MAX_FRAME_PAYLOAD_LENGTH - 8) {
+ throw new IllegalArgumentException("Invalid debug data size.");
+ }
+
+ this.debugData = debugData;
+ return this;
+ }
+
+ public DefaultHttp2GoAwayFrame build() {
+ if (lastStreamId < 0) {
+ throw new IllegalArgumentException("LastStreamId must be set");
+ }
+ if (errorCode < 0) {
+ throw new IllegalArgumentException("ErrorCode must be set.");
+ }
+
+ return new DefaultHttp2GoAwayFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2HeadersFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2HeadersFrame.java
new file mode 100644
index 0000000000..2083277884
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2HeadersFrame.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.DEFAULT_STREAM_PRIORITY;
+import io.netty.handler.codec.http2.draft10.Http2Headers;
+
+public final class DefaultHttp2HeadersFrame implements Http2HeadersFrame {
+
+ private final int streamId;
+ private final int priority;
+ private final boolean endOfStream;
+ private final Http2Headers headers;
+
+ private DefaultHttp2HeadersFrame(Builder builder) {
+ this.streamId = builder.streamId;
+ this.priority = builder.priority;
+ this.headers = builder.headersBuilder.build();
+ this.endOfStream = builder.endOfStream;
+ }
+
+ @Override
+ public int getStreamId() {
+ return streamId;
+ }
+
+ @Override
+ public boolean isEndOfStream() {
+ return endOfStream;
+ }
+
+ @Override
+ public int getPriority() {
+ return priority;
+ }
+
+ @Override
+ public Http2Headers getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (endOfStream ? 1231 : 1237);
+ result = prime * result + ((headers == null) ? 0 : headers.hashCode());
+ result = prime * result + priority;
+ result = prime * result + streamId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2HeadersFrame other = (DefaultHttp2HeadersFrame) obj;
+ if (endOfStream != other.endOfStream) {
+ return false;
+ }
+ if (headers == null) {
+ if (other.headers != null) {
+ return false;
+ }
+ } else if (!headers.equals(other.headers)) {
+ return false;
+ }
+ if (priority != other.priority) {
+ return false;
+ }
+ if (streamId != other.streamId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultHttp2HeadersFrame [streamId=" + streamId + ", priority=" + priority
+ + ", endOfStream=" + endOfStream + ", headers=" + headers + "]";
+ }
+
+ public static class Builder {
+ private int streamId;
+ private int priority = DEFAULT_STREAM_PRIORITY;
+ private Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
+ private boolean endOfStream;
+
+ public Builder setStreamId(int streamId) {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be > 0.");
+ }
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Builder setEndOfStream(boolean endOfStream) {
+ this.endOfStream = endOfStream;
+ return this;
+ }
+
+ public Builder setPriority(int priority) {
+ if (priority < 0) {
+ throw new IllegalArgumentException("Priority must be >= 0");
+ }
+ this.priority = priority;
+ return this;
+ }
+
+ public Http2Headers.Builder headers() {
+ return headersBuilder;
+ }
+
+ public Builder setHeaders(Http2Headers headers) {
+ this.headersBuilder.addHeaders(headers);
+ return this;
+ }
+
+ public DefaultHttp2HeadersFrame build() {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be set.");
+ }
+ return new DefaultHttp2HeadersFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PingFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PingFrame.java
new file mode 100644
index 0000000000..29a542f9cb
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PingFrame.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.PING_FRAME_PAYLOAD_LENGTH;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.DefaultByteBufHolder;
+
+/**
+ * Default implementation of {@link Http2PingFrame}.
+ */
+public final class DefaultHttp2PingFrame extends DefaultByteBufHolder implements Http2PingFrame {
+
+ private final boolean ack;
+
+ private DefaultHttp2PingFrame(Builder builder) {
+ super(builder.data);
+ this.ack = builder.ack;
+ }
+
+ @Override
+ public boolean isAck() {
+ return ack;
+ }
+
+ @Override
+ public DefaultHttp2PingFrame copy() {
+ return new Builder().setAck(ack).setData(content().copy()).build();
+ }
+
+ @Override
+ public DefaultHttp2PingFrame duplicate() {
+ return new Builder().setAck(ack).setData(content().duplicate()).build();
+ }
+
+ @Override
+ public DefaultHttp2PingFrame retain() {
+ super.retain();
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2PingFrame retain(int increment) {
+ super.retain(increment);
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2PingFrame touch() {
+ super.touch();
+ return this;
+ }
+
+ @Override
+ public DefaultHttp2PingFrame touch(Object hint) {
+ super.touch(hint);
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = content().hashCode();
+ result = prime * result + (ack ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2PingFrame other = (DefaultHttp2PingFrame) obj;
+ if (ack != other.ack) {
+ return false;
+ }
+ if (!content().equals(other.content())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2PingFrame}.
+ */
+ public static class Builder {
+ private boolean ack;
+ private ByteBuf data;
+
+ /**
+ * Sets the data for this ping. This buffer will be retained when the frame is built.
+ */
+ public Builder setData(ByteBuf data) {
+ if (data == null) {
+ throw new IllegalArgumentException("data must not be null.");
+ }
+ if (data.readableBytes() != PING_FRAME_PAYLOAD_LENGTH) {
+ throw new IllegalArgumentException(String.format(
+ "Incorrect data length for ping. Expected %d, found %d",
+ PING_FRAME_PAYLOAD_LENGTH, data.readableBytes()));
+ }
+ this.data = data;
+ return this;
+ }
+
+ public Builder setAck(boolean ack) {
+ this.ack = ack;
+ return this;
+ }
+
+ public DefaultHttp2PingFrame build() {
+ if (data == null) {
+ throw new IllegalArgumentException("debug data must be provided");
+ }
+
+ return new DefaultHttp2PingFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PriorityFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PriorityFrame.java
new file mode 100644
index 0000000000..57536cfec2
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PriorityFrame.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * Default implementation of {@link Http2PriorityFrame}.
+ */
+public final class DefaultHttp2PriorityFrame implements Http2PriorityFrame {
+
+ private final int streamId;
+ private final int priority;
+
+ private DefaultHttp2PriorityFrame(Builder builder) {
+ this.streamId = builder.streamId;
+ this.priority = builder.priority;
+ }
+
+ @Override
+ public int getStreamId() {
+ return streamId;
+ }
+
+ @Override
+ public int getPriority() {
+ return priority;
+ }
+
+ @Override
+ public boolean isEndOfStream() {
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + priority;
+ result = prime * result + streamId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2PriorityFrame other = (DefaultHttp2PriorityFrame) obj;
+ if (priority != other.priority) {
+ return false;
+ }
+ if (streamId != other.streamId) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2PriorityFrame}.
+ */
+ public static class Builder {
+ private int streamId;
+ private int priority = -1;
+
+ public Builder setStreamId(int streamId) {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be > 0.");
+ }
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Builder setPriority(int priority) {
+ if (priority < 0) {
+ throw new IllegalArgumentException("Invalid priority.");
+ }
+ this.priority = priority;
+ return this;
+ }
+
+ public DefaultHttp2PriorityFrame build() {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be set.");
+ }
+ if (priority < 0) {
+ throw new IllegalArgumentException("Priority must be set.");
+ }
+ return new DefaultHttp2PriorityFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PushPromiseFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PushPromiseFrame.java
new file mode 100644
index 0000000000..15ac028472
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2PushPromiseFrame.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.handler.codec.http2.draft10.Http2Headers;
+
+public final class DefaultHttp2PushPromiseFrame implements Http2PushPromiseFrame {
+
+ private final int streamId;
+ private final int promisedStreamId;
+ private final Http2Headers headers;
+
+ private DefaultHttp2PushPromiseFrame(Builder builder) {
+ this.streamId = builder.streamId;
+ this.promisedStreamId = builder.promisedStreamId;
+ this.headers = builder.headers;
+ }
+
+ @Override
+ public int getStreamId() {
+ return streamId;
+ }
+
+ @Override
+ public boolean isEndOfStream() {
+ return false;
+ }
+
+ @Override
+ public int getPromisedStreamId() {
+ return promisedStreamId;
+ }
+
+ @Override
+ public Http2Headers getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((headers == null) ? 0 : headers.hashCode());
+ result = prime * result + promisedStreamId;
+ result = prime * result + streamId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2PushPromiseFrame other = (DefaultHttp2PushPromiseFrame) obj;
+ if (headers == null) {
+ if (other.headers != null) {
+ return false;
+ }
+ } else if (!headers.equals(other.headers)) {
+ return false;
+ }
+ if (promisedStreamId != other.promisedStreamId) {
+ return false;
+ }
+ if (streamId != other.streamId) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultHttp2PushPromiseFrame [streamId=" + streamId + ", promisedStreamId="
+ + promisedStreamId + ", headers=" + headers + "]";
+ }
+
+ public static class Builder {
+ private int streamId;
+ private int promisedStreamId;
+ private Http2Headers headers;
+
+ public Builder setStreamId(int streamId) {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be > 0.");
+ }
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Builder setPromisedStreamId(int promisedStreamId) {
+ if (promisedStreamId <= 0) {
+ throw new IllegalArgumentException("promisedStreamId must be > 0.");
+ }
+ this.promisedStreamId = promisedStreamId;
+ return this;
+ }
+
+ public Builder setHeaders(Http2Headers headers) {
+ if (headers == null) {
+ throw new IllegalArgumentException("headers must not be null.");
+ }
+ this.headers = headers;
+ return this;
+ }
+
+ public DefaultHttp2PushPromiseFrame build() {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be set.");
+ }
+ if (promisedStreamId <= 0) {
+ throw new IllegalArgumentException("promisedStreamId must be set.");
+ }
+ if (headers == null) {
+ throw new IllegalArgumentException("headers must be set.");
+ }
+ return new DefaultHttp2PushPromiseFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2RstStreamFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2RstStreamFrame.java
new file mode 100644
index 0000000000..727d666a2c
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2RstStreamFrame.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_UNSIGNED_INT;
+
+/**
+ * Default implementation of {@link Http2RstStreamFrame}.
+ */
+public final class DefaultHttp2RstStreamFrame implements Http2RstStreamFrame {
+ private final int streamId;
+ private final long errorCode;
+
+ private DefaultHttp2RstStreamFrame(Builder builder) {
+ this.streamId = builder.streamId;
+ this.errorCode = builder.errorCode;
+ }
+
+ @Override
+ public int getStreamId() {
+ return streamId;
+ }
+
+ @Override
+ public long getErrorCode() {
+ return errorCode;
+ }
+
+ @Override
+ public boolean isEndOfStream() {
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (int) (errorCode ^ (errorCode >>> 32));
+ result = prime * result + streamId;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2RstStreamFrame other = (DefaultHttp2RstStreamFrame) obj;
+ if (errorCode != other.errorCode) {
+ return false;
+ }
+ if (streamId != other.streamId) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2RstStreamFrame}.
+ */
+ public static class Builder {
+ private int streamId;
+ private long errorCode = -1L;
+
+ public Builder setStreamId(int streamId) {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be > 0.");
+ }
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Builder setErrorCode(long errorCode) {
+ if (errorCode < 0 || errorCode > MAX_UNSIGNED_INT) {
+ throw new IllegalArgumentException("Invalid errorCode value.");
+ }
+ this.errorCode = errorCode;
+ return this;
+ }
+
+ public DefaultHttp2RstStreamFrame build() {
+ if (streamId <= 0) {
+ throw new IllegalArgumentException("StreamId must be set.");
+ }
+ if (errorCode < 0L) {
+ throw new IllegalArgumentException("ErrorCode must be set.");
+ }
+ return new DefaultHttp2RstStreamFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2SettingsFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2SettingsFrame.java
new file mode 100644
index 0000000000..5a40829eb3
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2SettingsFrame.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * Default implementation of {@link Http2SettingsFrame}.
+ */
+public final class DefaultHttp2SettingsFrame implements Http2SettingsFrame {
+
+ private final boolean ack;
+ private final Integer headerTableSize;
+ private final Boolean pushEnabled;
+ private final Long maxConcurrentStreams;
+ private final Integer initialWindowSize;
+
+ private DefaultHttp2SettingsFrame(Builder builder) {
+ this.ack = builder.ack;
+ this.headerTableSize = builder.headerTableSize;
+ this.pushEnabled = builder.pushEnabled;
+ this.maxConcurrentStreams = builder.maxConcurrentStreams;
+ this.initialWindowSize = builder.initialWindowSize;
+ }
+
+ @Override
+ public boolean isAck() {
+ return ack;
+ }
+
+ @Override
+ public Integer getHeaderTableSize() {
+ return headerTableSize;
+ }
+
+ @Override
+ public Boolean getPushEnabled() {
+ return pushEnabled;
+ }
+
+ @Override
+ public Long getMaxConcurrentStreams() {
+ return maxConcurrentStreams;
+ }
+
+ @Override
+ public Integer getInitialWindowSize() {
+ return initialWindowSize;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (ack ? 1231 : 1237);
+ result = prime * result + ((headerTableSize == null) ? 0 : headerTableSize.hashCode());
+ result = prime * result + ((initialWindowSize == null) ? 0 : initialWindowSize.hashCode());
+ result =
+ prime * result + ((maxConcurrentStreams == null) ? 0 : maxConcurrentStreams.hashCode());
+ result = prime * result + ((pushEnabled == null) ? 0 : pushEnabled.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2SettingsFrame other = (DefaultHttp2SettingsFrame) obj;
+ if (ack != other.ack) {
+ return false;
+ }
+ if (headerTableSize == null) {
+ if (other.headerTableSize != null) {
+ return false;
+ }
+ } else if (!headerTableSize.equals(other.headerTableSize)) {
+ return false;
+ }
+ if (initialWindowSize == null) {
+ if (other.initialWindowSize != null) {
+ return false;
+ }
+ } else if (!initialWindowSize.equals(other.initialWindowSize)) {
+ return false;
+ }
+ if (maxConcurrentStreams == null) {
+ if (other.maxConcurrentStreams != null) {
+ return false;
+ }
+ } else if (!maxConcurrentStreams.equals(other.maxConcurrentStreams)) {
+ return false;
+ }
+ if (pushEnabled == null) {
+ if (other.pushEnabled != null) {
+ return false;
+ }
+ } else if (!pushEnabled.equals(other.pushEnabled)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2SettingsFrame}.
+ */
+ public static class Builder {
+ private boolean ack;
+ private Integer headerTableSize;
+ private Boolean pushEnabled;
+ private Long maxConcurrentStreams;
+ private Integer initialWindowSize;
+
+ public Builder setAck(boolean ack) {
+ this.ack = ack;
+ return this;
+ }
+
+ public Builder setHeaderTableSize(int headerTableSize) {
+ this.headerTableSize = headerTableSize;
+ return this;
+ }
+
+ public Builder setPushEnabled(boolean pushEnabled) {
+ this.pushEnabled = pushEnabled;
+ return this;
+ }
+
+ public Builder setMaxConcurrentStreams(long maxConcurrentStreams) {
+ this.maxConcurrentStreams = maxConcurrentStreams;
+ return this;
+ }
+
+ public Builder setInitialWindowSize(int initialWindowSize) {
+ this.initialWindowSize = initialWindowSize;
+ return this;
+ }
+
+ public DefaultHttp2SettingsFrame build() {
+ if (ack && (headerTableSize != null || pushEnabled != null || maxConcurrentStreams != null
+ || initialWindowSize != null)) {
+ throw new IllegalArgumentException("Ack frame must not contain settings");
+ }
+ return new DefaultHttp2SettingsFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2WindowUpdateFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2WindowUpdateFrame.java
new file mode 100644
index 0000000000..9ffdd24046
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/DefaultHttp2WindowUpdateFrame.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * Default implementation of {@link Http2WindowUpdateFrame}.
+ */
+public final class DefaultHttp2WindowUpdateFrame implements Http2WindowUpdateFrame {
+
+ private final int streamId;
+ private final int windowSizeIncrement;
+
+ private DefaultHttp2WindowUpdateFrame(Builder builder) {
+ this.streamId = builder.streamId;
+ this.windowSizeIncrement = builder.windowSizeIncrement;
+ }
+
+ @Override
+ public int getStreamId() {
+ return streamId;
+ }
+
+ @Override
+ public boolean isEndOfStream() {
+ return false;
+ }
+
+ @Override
+ public int getWindowSizeIncrement() {
+ return windowSizeIncrement;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + streamId;
+ result = prime * result + windowSizeIncrement;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultHttp2WindowUpdateFrame other = (DefaultHttp2WindowUpdateFrame) obj;
+ if (streamId != other.streamId) {
+ return false;
+ }
+ if (windowSizeIncrement != other.windowSizeIncrement) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Builds instances of {@link DefaultHttp2WindowUpdateFrame}.
+ */
+ public static class Builder {
+ private int streamId;
+ private int windowSizeIncrement;
+
+ public Builder setStreamId(int streamId) {
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Builder setWindowSizeIncrement(int windowSizeIncrement) {
+ this.windowSizeIncrement = windowSizeIncrement;
+ return this;
+ }
+
+ public DefaultHttp2WindowUpdateFrame build() {
+ if (streamId < 0) {
+ throw new IllegalArgumentException("StreamId must be >= 0.");
+ }
+ if (windowSizeIncrement < 0) {
+ throw new IllegalArgumentException("SindowSizeIncrement must be >= 0.");
+ }
+ return new DefaultHttp2WindowUpdateFrame(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2DataFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2DataFrame.java
new file mode 100644
index 0000000000..fa04eefd63
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2DataFrame.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufHolder;
+
+/**
+ * An HTTP2 data frame.
+ */
+public interface Http2DataFrame extends Http2StreamFrame, ByteBufHolder {
+
+ /**
+ * The amount of padding to follow the header data in the frame.
+ */
+ int getPaddingLength();
+
+ /**
+ * Returns the data payload of this frame.
+ */
+ @Override
+ ByteBuf content();
+
+ @Override
+ Http2DataFrame copy();
+
+ @Override
+ Http2DataFrame duplicate();
+
+ @Override
+ Http2DataFrame retain();
+
+ @Override
+ Http2DataFrame retain(int increment);
+
+ @Override
+ Http2DataFrame touch();
+
+ @Override
+ Http2DataFrame touch(Object hint);
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2Flags.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2Flags.java
new file mode 100644
index 0000000000..8ef0c37dec
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2Flags.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_ACK;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_HEADERS;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_SEGMENT;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_END_STREAM;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PAD_HIGH;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PAD_LOW;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FLAG_PRIORITY;
+
+/**
+ * Provides utility methods for accessing specific flags as defined by the HTTP2 spec.
+ */
+public class Http2Flags {
+ private final short value;
+
+ public Http2Flags(short value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the underlying flags value.
+ */
+ public short getValue() {
+ return value;
+ }
+
+ /**
+ * Determines whether the end-of-stream flag is set.
+ */
+ public boolean isEndOfStream() {
+ return isSet(FLAG_END_STREAM);
+ }
+
+ /**
+ * Determines whether the end-of-segment flag is set.
+ */
+ public boolean isEndOfSegment() {
+ return isSet(FLAG_END_SEGMENT);
+ }
+
+ /**
+ * Determines whether the end-of-headers flag is set.
+ */
+ public boolean isEndOfHeaders() {
+ return isSet(FLAG_END_HEADERS);
+ }
+
+ /**
+ * Determines whether the flag is set indicating the presence of the priority field in a HEADERS
+ * frame.
+ */
+ public boolean isPriorityPresent() {
+ return isSet(FLAG_PRIORITY);
+ }
+
+ /**
+ * Determines whether the flag is set indicating that this frame is an ACK.
+ */
+ public boolean isAck() {
+ return isSet(FLAG_ACK);
+ }
+
+ /**
+ * For frames that include padding, indicates if the pad low field is present.
+ */
+ public boolean isPadLowPresent() {
+ return isSet(FLAG_PAD_LOW);
+ }
+
+ /**
+ * For frames that include padding, indicates if the pad high field is present.
+ */
+ public boolean isPadHighPresent() {
+ return isSet(FLAG_PAD_HIGH);
+ }
+
+ /**
+ * Indicates whether the padding flags are set properly. If pad high is set, pad low must also be
+ * set.
+ */
+ public boolean isPaddingLengthValid() {
+ return isPadHighPresent() ? isPadLowPresent() : true;
+ }
+
+ /**
+ * Gets the number of bytes expected in the padding length field of the payload. This is
+ * determined by the {@link #isPadHighPresent()} and {@link #isPadLowPresent()} flags.
+ */
+ public int getNumPaddingLengthBytes() {
+ return (isPadHighPresent() ? 1 : 0) + (isPadLowPresent() ? 1 : 0);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + value;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Http2Flags other = (Http2Flags) obj;
+ if (value != other.value) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isSet(short mask) {
+ return (value & mask) != 0;
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2Frame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2Frame.java
new file mode 100644
index 0000000000..f54dc8febe
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2Frame.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * Marker interface for all HTTP2 frame types.
+ */
+public interface Http2Frame {
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameCodec.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameCodec.java
new file mode 100644
index 0000000000..c347ed5d49
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameCodec.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.channel.ChannelHandlerAppender;
+import io.netty.handler.codec.http2.draft10.frame.decoder.Http2FrameDecoder;
+import io.netty.handler.codec.http2.draft10.frame.decoder.Http2FrameUnmarshaller;
+import io.netty.handler.codec.http2.draft10.frame.encoder.Http2FrameEncoder;
+import io.netty.handler.codec.http2.draft10.frame.encoder.Http2FrameMarshaller;
+
+/**
+ * A combination of {@link Http2FrameEncoder} and {@link Http2FrameDecoder}.
+ */
+public class Http2FrameCodec extends ChannelHandlerAppender {
+
+ public Http2FrameCodec(Http2FrameMarshaller frameMarshaller,
+ Http2FrameUnmarshaller frameUnmarshaller) {
+ super(new Http2FrameEncoder(frameMarshaller), new Http2FrameDecoder(frameUnmarshaller));
+ }
+
+ public Http2FrameCodec() {
+ super(new Http2FrameEncoder(), new Http2FrameDecoder());
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameCodecUtil.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameCodecUtil.java
new file mode 100644
index 0000000000..e840d3ff52
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameCodecUtil.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * Constants and utility method used for encoding/decoding HTTP2 frames.
+ */
+public final class Http2FrameCodecUtil {
+ public static final int CONNECTION_STREAM_ID = 0;
+
+ public static final int DEFAULT_STREAM_PRIORITY = 0x40000000; // 2^30
+
+ public static final int MAX_FRAME_PAYLOAD_LENGTH = 16383;
+ public static final int PING_FRAME_PAYLOAD_LENGTH = 8;
+ public static final short MAX_UNSIGNED_BYTE = 0xFF;
+ public static final int MAX_UNSIGNED_SHORT = 0xFFFF;
+ public static final long MAX_UNSIGNED_INT = 0xFFFFFFFFL;
+ public static final int FRAME_HEADER_LENGTH = 8;
+ public static final int FRAME_LENGTH_MASK = 0x3FFF;
+
+ public static final short FRAME_TYPE_DATA = 0x0;
+ public static final short FRAME_TYPE_HEADERS = 0x1;
+ public static final short FRAME_TYPE_PRIORITY = 0x2;
+ public static final short FRAME_TYPE_RST_STREAM = 0x3;
+ public static final short FRAME_TYPE_SETTINGS = 0x4;
+ public static final short FRAME_TYPE_PUSH_PROMISE = 0x5;
+ public static final short FRAME_TYPE_PING = 0x6;
+ public static final short FRAME_TYPE_GO_AWAY = 0x7;
+ public static final short FRAME_TYPE_WINDOW_UPDATE = 0x8;
+ public static final short FRAME_TYPE_CONTINUATION = 0x9;
+
+ public static final short SETTINGS_HEADER_TABLE_SIZE = 1;
+ public static final short SETTINGS_ENABLE_PUSH = 2;
+ public static final short SETTINGS_MAX_CONCURRENT_STREAMS = 3;
+ public static final short SETTINGS_INITIAL_WINDOW_SIZE = 4;
+
+ public static final short FLAG_END_STREAM = 0x1;
+ public static final short FLAG_END_SEGMENT = 0x2;
+ public static final short FLAG_END_HEADERS = 0x4;
+ public static final short FLAG_PRIORITY = 0x8;
+ public static final short FLAG_ACK = 0x1;
+ public static final short FLAG_PAD_LOW = 0x10;
+ public static final short FLAG_PAD_HIGH = 0x20;
+
+ /**
+ * Reads a big-endian (31-bit) integer from the buffer.
+ */
+ public static int readUnsignedInt(ByteBuf buf) {
+ int offset = buf.readerIndex();
+ int value = (buf.getByte(offset + 0) & 0x7F) << 24 | (buf.getByte(offset + 1) & 0xFF) << 16
+ | (buf.getByte(offset + 2) & 0xFF) << 8 | buf.getByte(offset + 3) & 0xFF;
+ buf.skipBytes(4);
+ return value;
+ }
+
+ /**
+ * Writes a big-endian (32-bit) unsigned integer to the buffer.
+ */
+ public static void writeUnsignedInt(long value, ByteBuf out) {
+ out.writeByte((int) ((value >> 24) & 0xFF));
+ out.writeByte((int) ((value >> 16) & 0xFF));
+ out.writeByte((int) ((value >> 8) & 0xFF));
+ out.writeByte((int) ((value & 0xFF)));
+ }
+
+ /**
+ * Reads the variable-length padding length field from the payload.
+ */
+ public static int readPaddingLength(Http2Flags flags, ByteBuf payload) {
+ int paddingLength = 0;
+ if (flags.isPadHighPresent()) {
+ paddingLength += payload.readUnsignedByte() * 256;
+ }
+ if (flags.isPadLowPresent()) {
+ paddingLength += payload.readUnsignedByte();
+ }
+ return paddingLength;
+ }
+
+ /**
+ * Sets the padding flags in the given flags value as appropriate based on the padding length.
+ * Returns the new flags value after any padding flags have been set.
+ */
+ public static short setPaddingFlags(short flags, int paddingLength) {
+ if (paddingLength > 255) {
+ flags |= Http2FrameCodecUtil.FLAG_PAD_HIGH;
+ }
+ if (paddingLength > 0) {
+ flags |= Http2FrameCodecUtil.FLAG_PAD_LOW;
+ }
+ return flags;
+ }
+
+ /**
+ * Writes the padding length field to the output buffer.
+ */
+ public static void writePaddingLength(int paddingLength, ByteBuf out) {
+ if (paddingLength > 255) {
+ int padHigh = paddingLength / 256;
+ out.writeByte(padHigh);
+ }
+ if (paddingLength > 0) {
+ int padLow = paddingLength % 256;
+ out.writeByte(padLow);
+ }
+ }
+
+ private Http2FrameCodecUtil() {
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameHeader.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameHeader.java
new file mode 100644
index 0000000000..a3cab38111
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2FrameHeader.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+
+/**
+ * Encapsulates the content of an HTTP2 frame header.
+ */
+public final class Http2FrameHeader {
+ private final int payloadLength;
+ private final int type;
+ private final Http2Flags flags;
+ private final int streamId;
+
+ private Http2FrameHeader(Builder builder) {
+ this.payloadLength = builder.payloadLength;
+ this.type = builder.type;
+ this.flags = builder.flags;
+ this.streamId = builder.streamId;
+ }
+
+ public int getPayloadLength() {
+ return payloadLength;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public Http2Flags getFlags() {
+ return flags;
+ }
+
+ public int getStreamId() {
+ return streamId;
+ }
+
+ /**
+ * Builds instances of {@link Http2FrameHeader}.
+ */
+ public static class Builder {
+ private int payloadLength;
+ private int type;
+ private Http2Flags flags = new Http2Flags((short) 0);
+ private int streamId;
+
+ public Builder setPayloadLength(int payloadLength) {
+ this.payloadLength = payloadLength;
+ return this;
+ }
+
+ public Builder setType(int type) {
+ this.type = type;
+ return this;
+ }
+
+ public Builder setFlags(Http2Flags flags) {
+ this.flags = flags;
+ return this;
+ }
+
+ public Builder setStreamId(int streamId) {
+ this.streamId = streamId;
+ return this;
+ }
+
+ public Http2FrameHeader build() {
+ return new Http2FrameHeader(this);
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2GoAwayFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2GoAwayFrame.java
new file mode 100644
index 0000000000..ecf39387f0
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2GoAwayFrame.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufHolder;
+
+/**
+ * An HTTP2 GO_AWAY frame indicating that the remote peer should stop creating streams for the
+ * connection.
+ */
+public interface Http2GoAwayFrame extends Http2Frame, ByteBufHolder {
+ /**
+ * The highest numbered stream identifier for which the sender of the GOAWAY frame has received
+ * frames on and might have taken some action on.
+ */
+ int getLastStreamId();
+
+ /**
+ * The error code containing the reason for closing the connection.
+ */
+ long getErrorCode();
+
+ /**
+ * Returns the debug data.
+ */
+ @Override
+ ByteBuf content();
+
+ @Override
+ Http2GoAwayFrame copy();
+
+ @Override
+ Http2GoAwayFrame duplicate();
+
+ @Override
+ Http2GoAwayFrame retain();
+
+ @Override
+ Http2GoAwayFrame retain(int increment);
+
+ @Override
+ Http2GoAwayFrame touch();
+
+ @Override
+ Http2GoAwayFrame touch(Object hint);
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2HeadersFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2HeadersFrame.java
new file mode 100644
index 0000000000..f50bb9ab23
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2HeadersFrame.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.handler.codec.http2.draft10.Http2Headers;
+
+/**
+ * The decoded form of a complete headers block for a HEADERS frame.
+ */
+public interface Http2HeadersFrame extends Http2StreamFrame {
+
+ /**
+ * Gets the priority of the stream being created.
+ */
+ int getPriority();
+
+ /**
+ * Gets the decoded HTTP headers.
+ */
+ Http2Headers getHeaders();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PingFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PingFrame.java
new file mode 100644
index 0000000000..6ca439f0ca
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PingFrame.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufHolder;
+
+/**
+ * An HTTP2 connection PING frame.
+ */
+public interface Http2PingFrame extends Http2Frame, ByteBufHolder {
+ /**
+ * Indicates whether this frame is an acknowledgment of a PING sent by the peer.
+ */
+ boolean isAck();
+
+ /**
+ * Returns the opaque data of this frame.
+ */
+ @Override
+ ByteBuf content();
+
+ @Override
+ Http2PingFrame copy();
+
+ @Override
+ Http2PingFrame duplicate();
+
+ @Override
+ Http2PingFrame retain();
+
+ @Override
+ Http2PingFrame retain(int increment);
+
+ @Override
+ Http2PingFrame touch();
+
+ @Override
+ Http2PingFrame touch(Object hint);
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PriorityFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PriorityFrame.java
new file mode 100644
index 0000000000..e71a2f908f
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PriorityFrame.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * An HTTP2 priority frame indicating the sender-advised priority for the stream.
+ */
+public interface Http2PriorityFrame extends Http2StreamFrame {
+ /**
+ * The advised priority for the stream.
+ */
+ int getPriority();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PushPromiseFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PushPromiseFrame.java
new file mode 100644
index 0000000000..9899d0dfef
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2PushPromiseFrame.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+import io.netty.handler.codec.http2.draft10.Http2Headers;
+
+/**
+ * A decoded form of the completed headers block for a PUSH_PROMISE frame.
+ */
+public interface Http2PushPromiseFrame extends Http2StreamFrame {
+
+ /**
+ * The ID of the stream that the endpoint intends to start sending frames for.
+ */
+ int getPromisedStreamId();
+
+ /**
+ * Gets the decoded HTTP headers.
+ */
+ Http2Headers getHeaders();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2RstStreamFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2RstStreamFrame.java
new file mode 100644
index 0000000000..ea3e360200
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2RstStreamFrame.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * HTTP2 RST_STREAM frame that indicates abnormal termination of a stream.
+ */
+public interface Http2RstStreamFrame extends Http2StreamFrame {
+ /**
+ * The error code containing the reason for the stream being terminated.
+ */
+ long getErrorCode();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2SettingsFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2SettingsFrame.java
new file mode 100644
index 0000000000..f08c205de9
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2SettingsFrame.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * HTTP2 SETTINGS frame providing configuration parameters that affect how endpoints communicate.
+ *
+ */
+public interface Http2SettingsFrame extends Http2Frame {
+
+ /**
+ * Indicates whether this is an acknowledgment of the settings sent by the peer.
+ */
+ boolean isAck();
+
+ /**
+ * Gets the sender's header compression table size, or {@code null} if not set.
+ */
+ Integer getHeaderTableSize();
+
+ /**
+ * Gets whether or not the sender allows server push, or {@code null} if not set.
+ */
+ Boolean getPushEnabled();
+
+ /**
+ * Gets the maximum number of streams the receiver is allowed to create, or {@code null} if not
+ * set.
+ */
+ Long getMaxConcurrentStreams();
+
+ /**
+ * Gets the sender's initial flow control window in bytes, or {@code null} if not set.
+ *
+ * @return
+ */
+ Integer getInitialWindowSize();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2StreamFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2StreamFrame.java
new file mode 100644
index 0000000000..49a7e381ff
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2StreamFrame.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * Base interface for all frames that are associated to a stream.
+ */
+public interface Http2StreamFrame extends Http2Frame {
+ /**
+ * Gets the identifier of the associated stream.
+ */
+ int getStreamId();
+
+ /**
+ * Indicates whether this frame represents the last frame for the stream.
+ */
+ boolean isEndOfStream();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2WindowUpdateFrame.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2WindowUpdateFrame.java
new file mode 100644
index 0000000000..c2bcca466c
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/Http2WindowUpdateFrame.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame;
+
+/**
+ * HTTP2 WINDOW_UPDATE frame used to implement flow control.
+ */
+public interface Http2WindowUpdateFrame extends Http2StreamFrame {
+ /**
+ * Gets the number of bytes that the sender can transmit in addition to the existing flow control
+ * window.
+ */
+ int getWindowSizeIncrement();
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/AbstractHeadersUnmarshaller.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/AbstractHeadersUnmarshaller.java
new file mode 100644
index 0000000000..5091a50280
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/AbstractHeadersUnmarshaller.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame.decoder;
+
+import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_CONTINUATION;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
+import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
+import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
+
+public abstract class AbstractHeadersUnmarshaller extends AbstractHttp2FrameUnmarshaller {
+
+ /**
+ * A builder for a headers/push_promise frame.
+ */
+ protected abstract class FrameBuilder {
+ protected ByteBuf headerBlock;
+
+ abstract int getStreamId();
+
+ final void addHeaderFragment(ByteBuf fragment, ByteBufAllocator alloc) {
+ if (headerBlock == null) {
+ headerBlock = alloc.buffer(fragment.readableBytes());
+ headerBlock.writeBytes(fragment);
+ } else {
+ ByteBuf buf = alloc.buffer(headerBlock.readableBytes() + fragment.readableBytes());
+ buf.writeBytes(headerBlock);
+ buf.writeBytes(fragment);
+ headerBlock.release();
+ headerBlock = buf;
+ }
+ }
+
+ abstract Http2Frame buildFrame() throws Http2Exception;
+ }
+
+ private FrameBuilder frameBuilder;
+
+ @Override
+ protected final void validate(Http2FrameHeader frameHeader) throws Http2Exception {
+ if (frameBuilder == null) {
+ // This frame is the beginning of a headers/push_promise.
+ validateStartOfHeaderBlock(frameHeader);
+ return;
+ }
+
+ // Validate the continuation of a headers block.
+ if (frameHeader.getType() != FRAME_TYPE_CONTINUATION) {
+ throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
+ }
+ if (frameBuilder.getStreamId() != frameHeader.getStreamId()) {
+ throw protocolError("Continuation received for wrong stream. Expected %d, found %d",
+ frameBuilder.getStreamId(), frameHeader.getStreamId());
+ }
+ Http2Flags flags = frameHeader.getFlags();
+ if (!flags.isPaddingLengthValid()) {
+ throw protocolError("Pad high is set but pad low is not");
+ }
+ if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
+ throw protocolError("Frame length %d to small.", frameHeader.getPayloadLength());
+ }
+ if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
+ throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
+ }
+ }
+
+ @Override
+ protected final Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
+ ByteBufAllocator alloc) throws Http2Exception {
+ Http2Flags flags = header.getFlags();
+ if (frameBuilder == null) {
+ // This is the start of a headers/push_promise frame. Delegate to the subclass to create
+ // the appropriate builder for the frame.
+ frameBuilder = createFrameBuilder(header, payload, alloc);
+ } else {
+ // Processing a continuation frame for a headers/push_promise. Update the current frame
+ // builder with the new fragment.
+
+ int paddingLength = readPaddingLength(flags, payload);
+
+ // Determine how much data there is to read by removing the trailing
+ // padding.
+ int dataLength = payload.readableBytes() - paddingLength;
+ if (dataLength < 0) {
+ throw protocolError("Payload too small for padding.");
+ }
+
+ // The remainder of this frame is the headers block.
+ frameBuilder.addHeaderFragment(payload, alloc);
+ }
+
+ // If the headers are complete, build the frame.
+ Http2Frame frame = null;
+ if (flags.isEndOfHeaders()) {
+ frame = frameBuilder.buildFrame();
+ frameBuilder = null;
+ }
+
+ return frame;
+ }
+
+ protected abstract void validateStartOfHeaderBlock(Http2FrameHeader frameHeader)
+ throws Http2Exception;
+
+ protected abstract FrameBuilder createFrameBuilder(Http2FrameHeader header, ByteBuf payload,
+ ByteBufAllocator alloc) throws Http2Exception;
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/AbstractHttp2FrameUnmarshaller.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/AbstractHttp2FrameUnmarshaller.java
new file mode 100644
index 0000000000..9280b3b14a
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/AbstractHttp2FrameUnmarshaller.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame.decoder;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
+import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
+
+/**
+ * Abstract base class for all {@link Http2FrameUnmarshaller} classes.
+ */
+public abstract class AbstractHttp2FrameUnmarshaller implements Http2FrameUnmarshaller {
+ private Http2FrameHeader header;
+
+ @Override
+ public final Http2FrameUnmarshaller unmarshall(Http2FrameHeader header) throws Http2Exception {
+ if (header == null) {
+ throw new IllegalArgumentException("header must be non-null.");
+ }
+
+ validate(header);
+ this.header = header;
+ return this;
+ }
+
+ @Override
+ public final Http2Frame from(ByteBuf payload, ByteBufAllocator alloc) throws Http2Exception {
+ if (header == null) {
+ throw new IllegalStateException("header must be set before calling from().");
+ }
+
+ return doUnmarshall(header, payload, alloc);
+ }
+
+ /**
+ * Verifies that the given frame header is valid for the frame type(s) supported by this decoder.
+ */
+ protected abstract void validate(Http2FrameHeader frameHeader) throws Http2Exception;
+
+ /**
+ * Unmarshalls the frame.
+ *
+ * @param header the frame header
+ * @param payload the payload of the frame.
+ * @param alloc an allocator for new buffers
+ * @return the frame
+ * @throws Http2Exception thrown if any protocol error was encountered.
+ */
+ protected abstract Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
+ ByteBufAllocator alloc) throws Http2Exception;
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/DefaultHttp2HeadersDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/DefaultHttp2HeadersDecoder.java
new file mode 100644
index 0000000000..386e524cd3
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/DefaultHttp2HeadersDecoder.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame.decoder;
+
+import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_HEADER_TABLE_SIZE;
+import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.DEFAULT_MAX_HEADER_SIZE;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufInputStream;
+import io.netty.handler.codec.http2.draft10.Http2Error;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.Http2Headers;
+
+import java.io.IOException;
+
+import com.twitter.hpack.Decoder;
+import com.twitter.hpack.HeaderListener;
+
+public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder {
+
+ private final Decoder decoder;
+
+ public DefaultHttp2HeadersDecoder() {
+ this.decoder = new Decoder(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE);
+ }
+
+ @Override
+ public void setHeaderTableSize(int size) throws Http2Exception {
+ // TODO: can we throw away the decoder and create a new one?
+ }
+
+ @Override
+ public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
+ try {
+ final Http2Headers.Builder headersBuilder = new Http2Headers.Builder();
+ HeaderListener listener = new HeaderListener() {
+ @Override
+ public void emitHeader(byte[] key, byte[] value) {
+ headersBuilder.addHeader(key, value);
+ }
+ };
+
+ decoder.decode(new ByteBufInputStream(headerBlock), listener);
+ decoder.endHeaderBlock(listener);
+
+ return headersBuilder.build();
+ } catch (IOException e) {
+ throw new Http2Exception(Http2Error.COMPRESSION_ERROR, e.getMessage());
+ }
+ }
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/Http2DataFrameUnmarshaller.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/Http2DataFrameUnmarshaller.java
new file mode 100644
index 0000000000..f72cc4f99c
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/Http2DataFrameUnmarshaller.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame.decoder;
+
+import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_TYPE_DATA;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.MAX_FRAME_PAYLOAD_LENGTH;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readPaddingLength;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.DefaultHttp2DataFrame;
+import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
+import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
+import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
+
+/**
+ * An unmarshaller for {@link Http2DataFrame} instances. The buffer contained in the frames is a
+ * slice of the original input buffer. If the frame needs to be persisted it should be copied.
+ */
+public class Http2DataFrameUnmarshaller extends AbstractHttp2FrameUnmarshaller {
+
+ @Override
+ protected void validate(Http2FrameHeader frameHeader) throws Http2Exception {
+ if (frameHeader.getType() != FRAME_TYPE_DATA) {
+ throw protocolError("Unsupported frame type: %d.", frameHeader.getType());
+ }
+ if (frameHeader.getStreamId() <= 0) {
+ throw protocolError("A stream ID must be > 0.");
+ }
+ Http2Flags flags = frameHeader.getFlags();
+ if (!flags.isPaddingLengthValid()) {
+ throw protocolError("Pad high is set but pad low is not");
+ }
+ if (frameHeader.getPayloadLength() < flags.getNumPaddingLengthBytes()) {
+ throw protocolError("Frame length %d too small.", frameHeader.getPayloadLength());
+ }
+ if (frameHeader.getPayloadLength() > MAX_FRAME_PAYLOAD_LENGTH) {
+ throw protocolError("Frame length %d too big.", frameHeader.getPayloadLength());
+ }
+ }
+
+ @Override
+ protected Http2Frame doUnmarshall(Http2FrameHeader header, ByteBuf payload,
+ ByteBufAllocator alloc) throws Http2Exception {
+ DefaultHttp2DataFrame.Builder builder = new DefaultHttp2DataFrame.Builder();
+ builder.setStreamId(header.getStreamId());
+
+ Http2Flags flags = header.getFlags();
+ builder.setEndOfStream(flags.isEndOfStream());
+
+ // Read the padding length.
+ int paddingLength = readPaddingLength(flags, payload);
+ builder.setPaddingLength(paddingLength);
+
+ // Determine how much data there is to read by removing the trailing
+ // padding.
+ int dataLength = payload.readableBytes() - paddingLength;
+ if (dataLength < 0) {
+ throw protocolError("Frame payload too small for padding.");
+ }
+
+ // Copy the remaining data into the frame.
+ ByteBuf data = payload.slice(payload.readerIndex(), dataLength).retain();
+ builder.setContent(data);
+
+ // Skip the rest of the bytes in the payload.
+ payload.skipBytes(payload.readableBytes());
+
+ return builder.build();
+ }
+
+}
diff --git a/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/Http2FrameDecoder.java b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/Http2FrameDecoder.java
new file mode 100644
index 0000000000..199c8b9bed
--- /dev/null
+++ b/codec-http2/src/main/java/io/netty/handler/codec/http2/draft10/frame/decoder/Http2FrameDecoder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.handler.codec.http2.draft10.frame.decoder;
+
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_LENGTH_MASK;
+import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.handler.codec.http2.draft10.Http2Exception;
+import io.netty.handler.codec.http2.draft10.frame.Http2Flags;
+import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
+import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
+
+import java.util.List;
+
+/**
+ * Decodes {@link Http2Frame} objects from an input {@link ByteBuf}. The frames that this handler
+ * emits can be configured by providing a {@link Http2FrameUnmarshaller}. By default, the
+ * {@link Http2StandardFrameUnmarshaller} is used to handle all frame types - see the documentation
+ * for details.
+ *
+ * @see Http2StandardFrameUnmarshaller
+ */
+public class Http2FrameDecoder extends ByteToMessageDecoder {
+
+ private enum State {
+ FRAME_HEADER,
+ FRAME_PAYLOAD,
+ ERROR
+ }
+
+ private State state;
+ private Http2FrameUnmarshaller frameUnmarshaller;
+ private int payloadLength;
+
+ public Http2FrameDecoder() {
+ this(new Http2StandardFrameUnmarshaller());
+ }
+
+ public Http2FrameDecoder(Http2FrameUnmarshaller frameUnmarshaller) {
+ this.frameUnmarshaller = frameUnmarshaller;
+ this.state = State.FRAME_HEADER;
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List