Initial HTTP2 work (single HTTP2 PDU)
diff --git a/core/pom.xml b/core/pom.xml
index d5c7435..3bb9647 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -56,6 +56,18 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.1</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java b/core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java
index 04228e1..b145d9c 100644
--- a/core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java
+++ b/core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java
@@ -149,12 +149,12 @@
// ----------- Helper methods ---------------------------------------------
@SuppressWarnings("unchecked")
- private DECODING_STATE getDecodingState(IoSession session) {
+ protected DECODING_STATE getDecodingState(IoSession session) {
return (DECODING_STATE) session.getAttribute(DECODER);
}
@SuppressWarnings("unchecked")
- private ENCODING_STATE getEncodingState(IoSession session) {
+ protected ENCODING_STATE getEncodingState(IoSession session) {
return (ENCODING_STATE) session.getAttribute(ENCODER);
}
diff --git a/http/src/main/java/org/apache/mina/http/HttpRequestImpl.java b/http/src/main/java/org/apache/mina/http/HttpRequestImpl.java
index 0c78497..91639f4 100644
--- a/http/src/main/java/org/apache/mina/http/HttpRequestImpl.java
+++ b/http/src/main/java/org/apache/mina/http/HttpRequestImpl.java
@@ -39,14 +39,14 @@
private final HttpMethod method;
- private final String requestedPath;
+ private final String targetURI;
private final Map<String, String> headers;
- public HttpRequestImpl(HttpVersion version, HttpMethod method, String requestedPath, Map<String, String> headers) {
+ public HttpRequestImpl(HttpVersion version, HttpMethod method, String targetURI, Map<String, String> headers) {
this.version = version;
this.method = method;
- this.requestedPath = requestedPath;
+ this.targetURI = targetURI;
this.headers = Collections.unmodifiableMap(headers);
}
@@ -62,6 +62,14 @@
* {@inheritDoc}
*/
@Override
+ public String getTargetURI() {
+ return targetURI;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public String getContentType() {
return headers.get("content-type");
}
@@ -148,7 +156,7 @@
sb.append("HTTP REQUEST METHOD: ").append(method).append('\n');
sb.append("VERSION: ").append(version).append('\n');
- sb.append("PATH: ").append(requestedPath).append('\n');
+ sb.append("PATH: ").append(targetURI).append('\n');
sb.append("--- HEADER --- \n");
diff --git a/http/src/main/java/org/apache/mina/http/api/HttpRequest.java b/http/src/main/java/org/apache/mina/http/api/HttpRequest.java
index 9e51d72..d248770 100644
--- a/http/src/main/java/org/apache/mina/http/api/HttpRequest.java
+++ b/http/src/main/java/org/apache/mina/http/api/HttpRequest.java
@@ -62,4 +62,18 @@
* @return the method
*/
HttpMethod getMethod();
+
+ /**
+ * Return the HTTP protocol version of the request {@link HttpVersion}.
+ *
+ * @return the HTTP version
+ */
+ HttpVersion getProtocolVersion();
+
+ /**
+ * Return the target URI of the request.
+ *
+ * @return the target URI
+ */
+ String getTargetURI();
}
diff --git a/http2/Http2Frames.ods b/http2/Http2Frames.ods
new file mode 100644
index 0000000..c2ca426
--- /dev/null
+++ b/http2/Http2Frames.ods
Binary files differ
diff --git a/http2/pom.xml b/http2/pom.xml
new file mode 100644
index 0000000..ecce674
--- /dev/null
+++ b/http2/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.mina</groupId>
+ <artifactId>mina-parent</artifactId>
+ <version>3.0.0-M3-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>mina-http2</artifactId>
+ <name>Apache MINA HTTP2 ${project.version}</name>
+ <packaging>jar</packaging>
+ <description>Low level HTTP2 codec for building simple & fast HTTP2 server and clients</description>
+
+ <properties>
+ <symbolicName>${project.groupId}.http2</symbolicName>
+ <exportedPackage>${project.groupId}.http2.api</exportedPackage>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mina-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mina-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.twitter</groupId>
+ <artifactId>hpack</artifactId>
+ <version>0.10.0</version>
+ </dependency>
+ </dependencies>
+</project>
+
diff --git a/http2/src/main/java/org/apache/mina/http2/api/BytePartialDecoder.java b/http2/src/main/java/org/apache/mina/http2/api/BytePartialDecoder.java
new file mode 100644
index 0000000..11582de
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/BytePartialDecoder.java
@@ -0,0 +1,50 @@
+/**
+ *
+ */
+package org.apache.mina.http2.api;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public class BytePartialDecoder implements PartialDecoder<byte[]> {
+ private int offset;
+ private byte[] value;
+
+ /**
+ * Decode an byte array.
+ *
+ * @param size the size of the byte array to decode
+ */
+ public BytePartialDecoder(int size) {
+ this.offset = 0;
+ this.value = new byte[size];
+ }
+
+ public boolean consume(ByteBuffer buffer) {
+ if (value.length - offset == 0) {
+ throw new IllegalStateException();
+ }
+ int length = Math.min(buffer.remaining(), value.length - offset);
+ buffer.get(value, offset, length);
+ offset += length;
+ return value.length - offset == 0;
+ }
+
+ public byte[] getValue() {
+ if (value.length - offset > 0) {
+ throw new IllegalStateException();
+ }
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void reset() {
+ offset = 0;
+ }
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/Http2Constants.java b/http2/src/main/java/org/apache/mina/http2/api/Http2Constants.java
new file mode 100644
index 0000000..ba8a8fe
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/Http2Constants.java
@@ -0,0 +1,193 @@
+/**
+ *
+ */
+package org.apache.mina.http2.api;
+
+import java.nio.charset.Charset;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public final class Http2Constants {
+ /**
+ * Mask used when decoding on a 4 byte boundary, masking the reserved
+ * bit
+ */
+ public static final int HTTP2_31BITS_MASK = 0x7FFFFFFF;
+
+ /**
+ * Mask used when decoding on a 4 byte boundary, retrieving
+ * the exclusive bit
+ */
+ public static final int HTTP2_EXCLUSIVE_MASK = 0x80000000;
+
+ /*
+ * Frame types
+ */
+ /**
+ * DATA frame
+ */
+ public static final int FRAME_TYPE_DATA = 0x00;
+
+ /**
+ * HEADERS frame
+ */
+ public static final int FRAME_TYPE_HEADERS = 0x01;
+
+ /**
+ * PRIORITY frame
+ */
+ public static final int FRAME_TYPE_PRIORITY = 0x02;
+
+ /**
+ * RST_STREAM frame
+ */
+ public static final int FRAME_TYPE_RST_STREAM = 0x03;
+
+ /**
+ * SETTINGS stream
+ */
+ public static final int FRAME_TYPE_SETTINGS = 0x04;
+
+ /**
+ * PUSH_PROMISE frame
+ */
+ public static final int FRAME_TYPE_PUSH_PROMISE = 0x05;
+
+ /**
+ * PING frame
+ */
+ public static final int FRAME_TYPE_PING = 0x06;
+
+ /**
+ * GOAWAY frame
+ */
+ public static final int FRAME_TYPE_GOAWAY = 0x07;
+
+ /**
+ * WINDOW_UPDATE frame
+ */
+ public static final int FRAME_TYPE_WINDOW_UPDATE = 0x08;
+
+ /**
+ * CONTINUATION frame
+ */
+ public static final int FRAME_TYPE_CONTINUATION = 0x09;
+
+ /*
+ * Flags
+ */
+ public static final byte FLAGS_END_STREAM = 0x01;
+
+ public static final byte FLAGS_ACK = 0x01;
+
+ public static final byte FLAGS_END_HEADERS = 0x04;
+
+ public static final byte FLAGS_PADDING = 0x08;
+
+ public static final byte FLAGS_PRIORITY = 0x20;
+
+ /*
+ * Error codes
+ */
+ /**
+ * The associated condition is not as a result of an error. For example, a GOAWAY might include this code to indicate graceful shutdown of a connection.
+ */
+ public static final int NO_ERROR = 0x0;
+
+ /**
+ * The endpoint detected an unspecific protocol error. This error is for use when a more specific error code is not available.
+ */
+ public static final int PROTOCOL_ERROR = 0x1;
+
+ /**
+ * The endpoint encountered an unexpected internal error.
+ */
+ public static final int INTERNAL_ERROR = 0x2;
+
+ /**
+ * The endpoint detected that its peer violated the flow control protocol.
+ */
+ public static final int FLOW_CONTROL_ERROR = 0x3;
+
+ /**
+ * The endpoint sent a SETTINGS frame, but did not receive a response in a timely manner. See Settings Synchronization (Section 6.5.3).
+ */
+ public static final int SETTINGS_TIMEOUT = 0x4;
+
+ /**
+ * The endpoint received a frame after a stream was half closed.
+ */
+ public static final int STREAM_CLOSED = 0x5;
+
+ /**
+ * The endpoint received a frame with an invalid size.
+ */
+ public static final int FRAME_SIZE_ERROR = 0x6;
+
+ /**
+ * The endpoint refuses the stream prior to performing any application processing, see Section 8.1.4 for details.
+ */
+ public static final int REFUSED_STREAM = 0x7;
+
+ /**
+ * Used by the endpoint to indicate that the stream is no longer needed.
+ */
+ public static final int CANCEL = 0x8;
+
+ /**
+ * The endpoint is unable to maintain the header compression context for the connection.
+ */
+ public static final int COMPRESSION_ERROR = 0x9;
+
+ /**
+ * The connection established in response to a CONNECT request (Section 8.3) was reset or abnormally closed.
+ */
+ public static final int CONNECT_ERROR = 0xa;
+
+ /**
+ * The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load.
+ */
+ public static final int ENHANCE_YOUR_CALM = 0xb;
+
+ /**
+ * The underlying transport has properties that do not meet minimum security requirements (see Section 9.2).
+ */
+ public static final int INADEQUATE_SECURITY = 0xc;
+
+ /**
+ * The endpoint requires that HTTP/1.1 be used instead of HTTP/2.
+ */
+ public static final int HTTP_1_1_REQUIRED = 0xd;
+
+ /*
+ * Settings related stuff
+ */
+ public static final int SETTINGS_HEADER_TABLE_SIZE = 0x01;
+
+ public static final int SETTINGS_HEADER_TABLE_SIZE_DEFAULT = 4096;
+
+ public static final int SETTINGS_ENABLE_PUSH = 0x02;
+
+ public static final int SETTINGS_ENABLE_PUSH_DEFAULT = 1;
+
+ public static final int SETTINGS_MAX_CONCURRENT_STREAMS = 0x03;
+
+ public static final int SETTINGS_MAX_CONCURRENT_STREAMS_DEFAULT = Integer.MAX_VALUE;
+
+ public static final int SETTINGS_INITIAL_WINDOW_SIZE = 0x04;
+
+ public static final int SETTINGS_INITIAL_WINDOW_SIZE_DEFAULT = 65535;
+
+ public static final int SETTINGS_MAX_FRAME_SIZE = 0x05;
+
+ public static final int SETTINGS_MAX_FRAME_SIZE_DEFAULT = 16384;
+
+ public static final int SETTINGS_MAX_HEADER_LIST_SIZE = 0x06;
+
+ public static final int SETTINGS_MAX_HEADER_LIST_SIZE_DEFAULT = Integer.MAX_VALUE;
+
+ public static final Charset US_ASCII_CHARSET = Charset.forName("US-ASCII");
+
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/Http2Frame.java b/http2/src/main/java/org/apache/mina/http2/api/Http2Frame.java
new file mode 100644
index 0000000..b6a3ed2
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/Http2Frame.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.mina.http2.api;
+
+/**
+ * An SPY frame
+ *
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ *
+ */
+public class Http2Frame {
+ private int length;
+
+ private short type;
+
+ private short flags;
+
+ private int streamID;
+
+ public byte[] payload;
+
+ /**
+ * @return the length
+ */
+ public int getLength() {
+ return length;
+ }
+
+ /**
+ * @param length the length to set
+ */
+ public void setLength(int length) {
+ this.length = length;
+ }
+
+ /**
+ * @return the type
+ */
+ public short getType() {
+ return type;
+ }
+
+ /**
+ * @param type the type to set
+ */
+ public void setType(short type) {
+ this.type = type;
+ }
+
+ /**
+ * @return the flags
+ */
+ public short getFlags() {
+ return flags;
+ }
+
+ /**
+ * @param flags the flags to set
+ */
+ public void setFlags(short flags) {
+ this.flags = flags;
+ }
+
+ /**
+ * @return the streamID
+ */
+ public int getStreamID() {
+ return streamID;
+ }
+
+ /**
+ * @param streamID the streamID to set
+ */
+ public void setStreamID(int streamID) {
+ this.streamID = streamID;
+ }
+
+ /**
+ * @return the payload
+ */
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ /**
+ * @param payload the payload to set
+ */
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ }
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/Http2Header.java b/http2/src/main/java/org/apache/mina/http2/api/Http2Header.java
new file mode 100644
index 0000000..c0e5ab8
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/Http2Header.java
@@ -0,0 +1,39 @@
+package org.apache.mina.http2.api;
+
+public enum Http2Header {
+
+ METHOD(":method"),
+
+ PATH(":path"),
+
+ STATUS(":status"),
+
+ AUTHORITY(":authority"),
+
+ SCHEME(":scheme");
+
+ private final String name;
+
+ private Http2Header(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Check whether a header is an HTTP2 reserved one.
+ *
+ * @param name the name of the HTTP header
+ * @return true is this is a reserved HTTP2 header, false otherwise
+ */
+ public static boolean isHTTP2ReservedHeader(String name) {
+ for(Http2Header header : Http2Header.values()) {
+ if (header.getName().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/Http2NameValuePair.java b/http2/src/main/java/org/apache/mina/http2/api/Http2NameValuePair.java
new file mode 100644
index 0000000..6e8572d
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/Http2NameValuePair.java
@@ -0,0 +1,32 @@
+package org.apache.mina.http2.api;
+
+public class Http2NameValuePair {
+ private String name;
+ private String value;
+
+ /**
+ * Build a name/value pair given the name and value.
+ *
+ * @param name the name of the pair
+ * @param value the value of the pair
+ */
+ public Http2NameValuePair(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getValue() {
+ return value;
+ }
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/Http2Setting.java b/http2/src/main/java/org/apache/mina/http2/api/Http2Setting.java
new file mode 100644
index 0000000..acccb65
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/Http2Setting.java
@@ -0,0 +1,23 @@
+package org.apache.mina.http2.api;
+
+public class Http2Setting {
+ private int ID;
+
+ private long value;
+
+ public int getID() {
+ return ID;
+ }
+
+ public void setID(int iD) {
+ ID = iD;
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public void setValue(long value) {
+ this.value = value;
+ }
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/IntPartialDecoder.java b/http2/src/main/java/org/apache/mina/http2/api/IntPartialDecoder.java
new file mode 100644
index 0000000..5e6076d
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/IntPartialDecoder.java
@@ -0,0 +1,62 @@
+/**
+ *
+ */
+package org.apache.mina.http2.api;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public class IntPartialDecoder implements PartialDecoder<Integer> {
+ private int size;
+ private int remaining;
+ private int value;
+
+ /**
+ * Decode an integer whose size is different from the standard 4.
+ *
+ * @param size the size (1,2,3,4) to decode
+ */
+ public IntPartialDecoder(int size) {
+ this.remaining = size;
+ this.size = size;
+ }
+
+ /**
+ * Decode a 4 bytes integer
+ */
+ public IntPartialDecoder() {
+ this(4);
+ }
+
+ public boolean consume(ByteBuffer buffer) {
+ if (remaining == 0) {
+ throw new IllegalStateException();
+ }
+ while (remaining > 0 && buffer.hasRemaining()) {
+ value = (value << 8) + (buffer.get() & 0x00FF);
+ --remaining;
+ }
+ return remaining == 0;
+ }
+
+ public Integer getValue() {
+ if (remaining > 0) {
+ throw new IllegalStateException();
+ }
+ return value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.mina.http2.api.PartialDecoder#reset()
+ */
+ @Override
+ public void reset() {
+ remaining = size;
+ value = 0;
+ }
+
+
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/LongPartialDecoder.java b/http2/src/main/java/org/apache/mina/http2/api/LongPartialDecoder.java
new file mode 100644
index 0000000..dae0c2f
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/LongPartialDecoder.java
@@ -0,0 +1,62 @@
+/**
+ *
+ */
+package org.apache.mina.http2.api;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public class LongPartialDecoder implements PartialDecoder<Long> {
+ private int size;
+ private int remaining;
+ private long value;
+
+ /**
+ * Decode a long integer whose size is different from the standard 8.
+ *
+ * @param size the size (1 to 8) to decode
+ */
+ public LongPartialDecoder(int size) {
+ this.remaining = size;
+ this.size = size;
+ }
+
+ /**
+ * Decode a 8 bytes long integer
+ */
+ public LongPartialDecoder() {
+ this(8);
+ }
+
+ public boolean consume(ByteBuffer buffer) {
+ if (remaining == 0) {
+ throw new IllegalStateException();
+ }
+ while (remaining > 0 && buffer.hasRemaining()) {
+ value = (value << 8) + (buffer.get() & 0x00FF);
+ --remaining;
+ }
+ return remaining == 0;
+ }
+
+ public Long getValue() {
+ if (remaining > 0) {
+ throw new IllegalStateException();
+ }
+ return value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.apache.mina.http2.api.PartialDecoder#reset()
+ */
+ @Override
+ public void reset() {
+ remaining = size;
+ value = 0;
+ }
+
+
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/PartialDecoder.java b/http2/src/main/java/org/apache/mina/http2/api/PartialDecoder.java
new file mode 100644
index 0000000..070b7ce
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/PartialDecoder.java
@@ -0,0 +1,34 @@
+/**
+ *
+ */
+package org.apache.mina.http2.api;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public interface PartialDecoder<T> {
+ /**
+ * Consume the buffer so as to decode a value. Not all the input buffer
+ * may be consumed.
+ *
+ * @param buffer the input buffer to decode
+ * @return true if a value is available false if more data is requested
+ */
+ public boolean consume(ByteBuffer buffer);
+
+ /**
+ * Return the decoded value.
+ *
+ * @return the decoded value
+ */
+ public T getValue();
+
+ /**
+ * Reset the internal state of the decoder to that new decoding can take place.
+ */
+ public void reset();
+
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/api/StreamMessage.java b/http2/src/main/java/org/apache/mina/http2/api/StreamMessage.java
new file mode 100644
index 0000000..41f85b0
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/api/StreamMessage.java
@@ -0,0 +1,21 @@
+package org.apache.mina.http2.api;
+
+/**
+ * Marker interface for messages that are attached to a specific stream.
+ * That may not be a start of HTTP PDU (request or response) as they are the
+ * one that creates new streams.
+ * The use of this interface is not mandatory but not using it will cause
+ * request and responses to be pipelined.
+ *
+ * @author jeffmaury
+ *
+ */
+public interface StreamMessage {
+
+ /**
+ * Return the stream ID the message is attached to.
+ *
+ * @return the stream ID
+ */
+ public int getStreamID();
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/impl/HeadersEncoder.java b/http2/src/main/java/org/apache/mina/http2/impl/HeadersEncoder.java
new file mode 100644
index 0000000..3afb72d
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/impl/HeadersEncoder.java
@@ -0,0 +1,68 @@
+package org.apache.mina.http2.impl;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import org.apache.mina.http.api.HttpMessage;
+import org.apache.mina.http.api.HttpRequest;
+import org.apache.mina.http.api.HttpResponse;
+import org.apache.mina.http2.api.Http2Header;
+
+import com.twitter.hpack.Encoder;
+
+import static org.apache.mina.http2.api.Http2Constants.US_ASCII_CHARSET;
+
+public class HeadersEncoder {
+
+ private final Encoder encoder;
+
+ public HeadersEncoder(int maxHeaderTableSize) {
+ encoder = new Encoder(maxHeaderTableSize);
+ }
+
+ private static String getMethod(HttpMessage message) {
+ String method = null;
+ if (message instanceof HttpRequest) {
+ ((HttpRequest)message).getMethod().name();
+ }
+ return method;
+ }
+
+ private static String getPath(HttpMessage message) {
+ String path = null;
+ if (message instanceof HttpRequest) {
+ path = ((HttpRequest)message).getTargetURI();
+ }
+ return path;
+ }
+
+ public void encode(HttpMessage message, OutputStream out) throws IOException {
+ String value = getMethod(message);
+ if (value != null) {
+ encoder.encodeHeader(out,
+ Http2Header.METHOD.getName().getBytes(US_ASCII_CHARSET),
+ value.getBytes(US_ASCII_CHARSET),
+ false);
+ }
+ value = getPath(message);
+ if (value != null) {
+ encoder.encodeHeader(out,
+ Http2Header.PATH.getName().getBytes(US_ASCII_CHARSET),
+ value.getBytes(US_ASCII_CHARSET),
+ false);
+ }
+ if (message instanceof HttpResponse) {
+ encoder.encodeHeader(out,
+ Http2Header.STATUS.getName().getBytes(US_ASCII_CHARSET),
+ Integer.toString(((HttpResponse)message).getStatus().code()).getBytes(US_ASCII_CHARSET),
+ false);
+ }
+ for(String name : message.getHeaders().keySet()) {
+ if (!Http2Header.isHTTP2ReservedHeader(name)) {
+ encoder.encodeHeader(out,
+ name.getBytes(US_ASCII_CHARSET),
+ message.getHeaders().get(name).getBytes(US_ASCII_CHARSET),
+ false);
+ }
+ }
+ }
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/impl/Http2Connection.java b/http2/src/main/java/org/apache/mina/http2/impl/Http2Connection.java
new file mode 100644
index 0000000..3019b20
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/impl/Http2Connection.java
@@ -0,0 +1,26 @@
+package org.apache.mina.http2.impl;
+
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.api.Http2Frame;
+
+public class Http2Connection {
+
+ private final Http2FrameDecoder decoder = new Http2FrameDecoder();
+
+ /**
+ * Decode the incoming message and if all frame data has been received,
+ * then the decoded frame will be returned. Otherwise, null is returned.
+ *
+ * @param input the input buffer to decode
+ * @return the decoder HTTP2 frame or null if more data are required
+ */
+ public Http2Frame decode(ByteBuffer input) {
+ Http2Frame frame = null;
+ if (decoder.consume(input)) {
+ frame = decoder.getValue();
+ decoder.reset();
+ }
+ return frame;
+ }
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/impl/Http2FrameDecoder.java b/http2/src/main/java/org/apache/mina/http2/impl/Http2FrameDecoder.java
new file mode 100644
index 0000000..ba32afc
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/impl/Http2FrameDecoder.java
@@ -0,0 +1,88 @@
+/**
+ *
+ */
+package org.apache.mina.http2.impl;
+
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.api.BytePartialDecoder;
+import org.apache.mina.http2.api.Http2Frame;
+import org.apache.mina.http2.api.LongPartialDecoder;
+import org.apache.mina.http2.api.PartialDecoder;
+
+import static org.apache.mina.http2.api.Http2Constants.HTTP2_31BITS_MASK;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public class Http2FrameDecoder implements PartialDecoder<Http2Frame> {
+
+ private static enum State {
+ LENGTH_TYPE_FLAGS,
+ STREAMID,
+ PAYLOAD
+ }
+
+ private State state;
+
+ private PartialDecoder<?> decoder;
+
+ private Http2Frame frame;
+
+ private boolean frameComplete;
+
+ public Http2FrameDecoder() {
+ reset();
+ }
+
+ @Override
+ public boolean consume(ByteBuffer buffer) {
+ while (!frameComplete && buffer.remaining() > 0) {
+ switch (state) {
+ case LENGTH_TYPE_FLAGS:
+ if (decoder.consume(buffer)) {
+ long val = ((LongPartialDecoder)decoder).getValue();
+ frame.setLength((int) ((val >> 16) & 0xFFFFFFL));
+ frame.setType((short) ((val >> 8) & 0xFF));
+ frame.setFlags((short) (val & 0xFF));
+ state = State.STREAMID;
+ decoder = new LongPartialDecoder(4);
+ }
+ break;
+ case STREAMID:
+ if (decoder.consume(buffer)) {
+ frame.setStreamID((int) (((LongPartialDecoder)decoder).getValue() & HTTP2_31BITS_MASK));
+ if (frame.getLength() > 0) {
+ decoder = new BytePartialDecoder(frame.getLength());
+ state = State.PAYLOAD;
+ } else {
+ frameComplete = true;
+ }
+ }
+ break;
+ case PAYLOAD:
+ if (decoder.consume(buffer)) {
+ frame.setPayload(((BytePartialDecoder)decoder).getValue());
+ frameComplete = true;
+ }
+ break;
+ }
+ }
+ return frameComplete;
+ }
+
+ @Override
+ public Http2Frame getValue() {
+ return frame;
+ }
+
+ @Override
+ public void reset() {
+ state = State.LENGTH_TYPE_FLAGS;
+ decoder = new LongPartialDecoder(5);
+ frame = new Http2Frame();
+ frameComplete = false;
+ }
+
+}
diff --git a/http2/src/main/java/org/apache/mina/http2/impl/Http2ProtocolDecoder.java b/http2/src/main/java/org/apache/mina/http2/impl/Http2ProtocolDecoder.java
new file mode 100644
index 0000000..8217166
--- /dev/null
+++ b/http2/src/main/java/org/apache/mina/http2/impl/Http2ProtocolDecoder.java
@@ -0,0 +1,30 @@
+/**
+ *
+ */
+package org.apache.mina.http2.impl;
+
+import java.nio.ByteBuffer;
+
+import org.apache.mina.codec.ProtocolDecoder;
+import org.apache.mina.http2.api.Http2Frame;
+
+/**
+ * @author jeffmaury
+ *
+ */
+public class Http2ProtocolDecoder implements ProtocolDecoder<ByteBuffer, Http2Frame, Http2Connection> {
+
+ @Override
+ public Http2Connection createDecoderState() {
+ return new Http2Connection();
+ }
+
+ @Override
+ public Http2Frame decode(ByteBuffer input, Http2Connection context) {
+ return context.decode(input);
+ }
+
+ @Override
+ public void finishDecode(Http2Connection context) {
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/BytePartialDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/BytePartialDecoderTest.java
new file mode 100644
index 0000000..7d09d11
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/BytePartialDecoderTest.java
@@ -0,0 +1,51 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+
+public class BytePartialDecoderTest {
+
+ private static final byte[] SAMPLE_VALUE_1 = new byte[] {0x74, 0x18, 0x4F, 0x68};
+ private static final byte[] SAMPLE_VALUE_2 = new byte[] {0x74, 0x18, 0x4F, 0x68, 0x0F};
+
+ @Test
+ public void checkSimpleValue() {
+ BytePartialDecoder decoder = new BytePartialDecoder(4);
+ ByteBuffer buffer = ByteBuffer.wrap(SAMPLE_VALUE_1);
+ assertTrue(decoder.consume(buffer));
+ assertArrayEquals(SAMPLE_VALUE_1, decoder.getValue());
+ }
+
+ @Test
+ public void checkNotenoughData() {
+ BytePartialDecoder decoder = new BytePartialDecoder(4);
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00});
+ assertFalse(decoder.consume(buffer));
+ }
+
+ @Test
+ public void checkTooMuchData() {
+ BytePartialDecoder decoder = new BytePartialDecoder(4);
+ ByteBuffer buffer = ByteBuffer.wrap(SAMPLE_VALUE_2);
+ assertTrue(decoder.consume(buffer));
+ assertArrayEquals(SAMPLE_VALUE_1, decoder.getValue());
+ assertEquals(1, buffer.remaining());
+ }
+
+ @Test
+ public void checkDecodingIn2Steps() {
+ BytePartialDecoder decoder = new BytePartialDecoder(4);
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {SAMPLE_VALUE_2[0], SAMPLE_VALUE_2[1]});
+ assertFalse(decoder.consume(buffer));
+ buffer = ByteBuffer.wrap(new byte[] {SAMPLE_VALUE_2[2], SAMPLE_VALUE_2[3], SAMPLE_VALUE_2[4]});
+ assertTrue(decoder.consume(buffer));
+ assertArrayEquals(SAMPLE_VALUE_1, decoder.getValue());
+ assertEquals(1, buffer.remaining());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2ContinuationFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2ContinuationFrameDecoderTest.java
new file mode 100644
index 0000000..12a661c
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2ContinuationFrameDecoderTest.java
@@ -0,0 +1,46 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2ContinuationFrameDecoderTest {
+
+ @Test
+ public void checkContinuationNoHeaderFragment() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, /*length*/
+ 0x09, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x32 /*streamID*/});
+ Http2ContinuationFrame frame = (Http2ContinuationFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(0, frame.getLength());
+ assertEquals(9, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(50, frame.getStreamID());
+ assertEquals(0, frame.getHeaderBlockFragment().length);
+ }
+
+ @Test
+ public void checkContinuationWithHeaderFragment() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x0A, /*length*/
+ 0x09, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x32, /*streamID*/
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A /*headerFragment*/});
+ Http2ContinuationFrame frame = (Http2ContinuationFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(10, frame.getLength());
+ assertEquals(9, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(50, frame.getStreamID());
+ assertEquals(10, frame.getHeaderBlockFragment().length);
+ assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, frame.getHeaderBlockFragment());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2DataFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2DataFrameDecoderTest.java
new file mode 100644
index 0000000..f6dad67
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2DataFrameDecoderTest.java
@@ -0,0 +1,89 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2DataFrameDecoderTest {
+
+ @Test
+ public void checkDataNoPayloadNoPadding() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, /*length*/
+ 0x00, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x32 /*streamID*/});
+ Http2DataFrame frame = (Http2DataFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(0, frame.getLength());
+ assertEquals(0, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(50, frame.getStreamID());
+ assertEquals(0, frame.getData().length);
+ assertEquals(0, frame.getPadding().length);
+ }
+
+ @Test
+ public void checkDataWithPayloadNoPadding() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x0A, /*length*/
+ 0x00, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x32, /*streamID*/
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A /*headerFragment*/});
+ Http2DataFrame frame = (Http2DataFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(10, frame.getLength());
+ assertEquals(0, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(50, frame.getStreamID());
+ assertEquals(10, frame.getData().length);
+ assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, frame.getData());
+ assertEquals(0, frame.getPadding().length);
+ }
+
+ @Test
+ public void checkDataNoPayloadPadding() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x03, /*length*/
+ 0x00, /*type*/
+ 0x08, /*flags*/
+ 0x00, 0x00, 0x00, 0x32, /*streamID*/
+ 0x02, 0x0E, 0x28 /*padding*/});
+ Http2DataFrame frame = (Http2DataFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(3, frame.getLength());
+ assertEquals(0, frame.getType());
+ assertEquals(0x08, frame.getFlags());
+ assertEquals(50, frame.getStreamID());
+ assertEquals(0,frame.getData().length);
+ assertEquals(2, frame.getPadding().length);
+ assertArrayEquals(new byte[] {0x0E, 0x28}, frame.getPadding());
+ }
+
+ @Test
+ public void checkDataWithPayloadPadding() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x0D, /*length*/
+ 0x00, /*type*/
+ 0x08, /*flags*/
+ 0x00, 0x00, 0x00, 0x32, /*streamID*/
+ 0x02, /*padLength*/
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, /*data*/
+ 0x0E, 0x28 /*padding*/});
+ Http2DataFrame frame = (Http2DataFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(13, frame.getLength());
+ assertEquals(0, frame.getType());
+ assertEquals(0x08, frame.getFlags());
+ assertEquals(50, frame.getStreamID());
+ assertEquals(10, frame.getData().length);
+ assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, frame.getData());
+ assertEquals(2, frame.getPadding().length);
+ assertArrayEquals(new byte[] {0x0E, 0x28}, frame.getPadding());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2HeadersFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2HeadersFrameDecoderTest.java
new file mode 100644
index 0000000..e974ce5
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2HeadersFrameDecoderTest.java
@@ -0,0 +1,78 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2HeadersFrameDecoderTest {
+
+ @Test
+ public void checkHeadersFrameWithNotPadding() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x01, /*length*/
+ 0x01, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x01, /*streamID*/
+ (byte) 0x0082 /*headerFragment*/});
+ Http2HeadersFrame frame = (Http2HeadersFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(1, frame.getLength());
+ assertEquals(1, frame.getType());
+ assertEquals(0, frame.getFlags());
+ assertEquals(1, frame.getStreamID());
+ assertEquals(1, frame.getHeaderBlockFragment().length);
+ assertEquals(0x0082, frame.getHeaderBlockFragment()[0] & 0x00FF);
+ }
+
+
+ @Test
+ public void checkHeadersFramePaddingPriority() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x17, /*length*/
+ 0x01, /*type*/
+ 0x28, /*flags*/
+ 0x00, 0x00, 0x00, 0x03, /*streamID*/
+ 0x10, /*padding length*/
+ (byte)0x0080, 0x00, 0x00, 0x14, /*stream dependency*/
+ 0x09, /*weight*/
+ (byte) 0x0082, /*headerFragment*/
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E /*padding*/});
+ Http2HeadersFrame frame = (Http2HeadersFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(23, frame.getLength());
+ assertEquals(1, frame.getType());
+ assertEquals(0x28, frame.getFlags());
+ assertEquals(3, frame.getStreamID());
+ assertEquals(10, frame.getWeight());
+ assertEquals(1, frame.getHeaderBlockFragment().length);
+ assertEquals(0x0082, frame.getHeaderBlockFragment()[0] & 0x00FF);
+ assertEquals(16, frame.getPadding().length);
+ assertArrayEquals(new byte[] {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E}, frame.getPadding());
+ }
+
+ @Test
+ public void checkHeadersFramePaddingNoPriority() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, /*length*/
+ 0x01, /*type*/
+ 0x08, /*flags*/
+ 0x00, 0x00, 0x00, 0x03, /*streamID*/
+ 0x10, /*padding length*/
+ (byte) 0x0082, /*headerFragment*/
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E /*padding*/});
+ Http2HeadersFrame frame = (Http2HeadersFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(18, frame.getLength());
+ assertEquals(1, frame.getType());
+ assertEquals(0x08, frame.getFlags());
+ assertEquals(3, frame.getStreamID());
+ assertEquals(1, frame.getHeaderBlockFragment().length);
+ assertEquals(0x0082, frame.getHeaderBlockFragment()[0] & 0x00FF);
+ assertEquals(16, frame.getPadding().length);
+ assertArrayEquals(new byte[] {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E}, frame.getPadding());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2PriorityFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2PriorityFrameDecoderTest.java
new file mode 100644
index 0000000..cd0c049
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2PriorityFrameDecoderTest.java
@@ -0,0 +1,52 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2PriorityFrameDecoderTest {
+
+ @Test
+ public void checkPriorityFrameNoExclusive() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x05, /*length*/
+ 0x02, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ 0x00, 0x00, 0x01, 0x00, /*streamDependency*/
+ 0x01});
+ Http2PriorityFrame frame = (Http2PriorityFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(5, frame.getLength());
+ assertEquals(2, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(256, frame.getStreamDependencyID());
+ assertFalse(frame.getExclusiveMode());
+ }
+
+ @Test
+ public void checkPriorityFrameExclusive() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x05, /*length*/
+ 0x02, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ (byte) 0x0080, 0x00, 0x01, 0x00, /*streamDependency*/
+ 0x01});
+ Http2PriorityFrame frame = (Http2PriorityFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(5, frame.getLength());
+ assertEquals(2, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(256, frame.getStreamDependencyID());
+ assertTrue(frame.getExclusiveMode());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2PushPromiseFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2PushPromiseFrameDecoderTest.java
new file mode 100644
index 0000000..30b7299
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2PushPromiseFrameDecoderTest.java
@@ -0,0 +1,80 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2PushPromiseFrameDecoderTest {
+
+ @Test
+ public void checkHeadersFrameWithNotPadding() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x05, /*length*/
+ 0x01, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x01, /*streamID*/
+ 0x00, 0x00, 0x01, 0x00, /*promisedStreamID*/
+ (byte) 0x0082 /*headerFragment*/});
+ Http2PushPromiseFrame frame = (Http2PushPromiseFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(5, frame.getLength());
+ assertEquals(5, frame.getType());
+ assertEquals(0, frame.getFlags());
+ assertEquals(1, frame.getStreamID());
+ assertEquals(256, frame.getPromisedStreamID());
+ assertEquals(1, frame.getHeaderBlockFragment().length);
+ assertEquals(0x0082, frame.getHeaderBlockFragment()[0] & 0x00FF);
+ }
+
+
+ @Test
+ public void checkHeadersFramePaddingPriority() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x17, /*length*/
+ 0x01, /*type*/
+ 0x28, /*flags*/
+ 0x00, 0x00, 0x00, 0x03, /*streamID*/
+ 0x10, /*padding length*/
+ (byte)0x0080, 0x00, 0x00, 0x14, /*stream dependency*/
+ 0x09, /*weight*/
+ (byte) 0x0082, /*headerFragment*/
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E /*padding*/});
+ Http2HeadersFrame frame = (Http2HeadersFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(23, frame.getLength());
+ assertEquals(1, frame.getType());
+ assertEquals(0x28, frame.getFlags());
+ assertEquals(3, frame.getStreamID());
+ assertEquals(10, frame.getWeight());
+ assertEquals(1, frame.getHeaderBlockFragment().length);
+ assertEquals(0x0082, frame.getHeaderBlockFragment()[0] & 0x00FF);
+ assertEquals(16, frame.getPadding().length);
+ assertArrayEquals(new byte[] {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E}, frame.getPadding());
+ }
+
+ @Test
+ public void checkHeadersFramePaddingNoPriority() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x12, /*length*/
+ 0x01, /*type*/
+ 0x08, /*flags*/
+ 0x00, 0x00, 0x00, 0x03, /*streamID*/
+ 0x10, /*padding length*/
+ (byte) 0x0082, /*headerFragment*/
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E /*padding*/});
+ Http2HeadersFrame frame = (Http2HeadersFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(18, frame.getLength());
+ assertEquals(1, frame.getType());
+ assertEquals(0x08, frame.getFlags());
+ assertEquals(3, frame.getStreamID());
+ assertEquals(1, frame.getHeaderBlockFragment().length);
+ assertEquals(0x0082, frame.getHeaderBlockFragment()[0] & 0x00FF);
+ assertEquals(16, frame.getPadding().length);
+ assertArrayEquals(new byte[] {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6E, 0x67, 0x2E}, frame.getPadding());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2RstStreamFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2RstStreamFrameDecoderTest.java
new file mode 100644
index 0000000..717b0f6
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2RstStreamFrameDecoderTest.java
@@ -0,0 +1,84 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2RstStreamFrameDecoderTest {
+
+ @Test
+ public void checkRstStreamNoExtraPayload() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x04, /*length*/
+ 0x03, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ 0x00, 0x00, 0x01, 0x00, /*errorCode*/});
+ Http2RstStreamFrame frame = (Http2RstStreamFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(4, frame.getLength());
+ assertEquals(3, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(256, frame.getErrorCode());
+ }
+
+ @Test
+ public void checkRstStreamHighestValueNoExtraPayload() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x04, /*length*/
+ 0x03, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, /*errorCode*/});
+ Http2RstStreamFrame frame = (Http2RstStreamFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(4, frame.getLength());
+ assertEquals(3, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(0x00FFFFFFFFL, frame.getErrorCode());
+ }
+
+ @Test
+ public void checkRstStreamWithExtraPayload() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x06, /*length*/
+ 0x03, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ 0x00, 0x00, 0x01, 0x00, /*errorCode*/
+ 0x0E, 0x28});
+ Http2RstStreamFrame frame = (Http2RstStreamFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(6, frame.getLength());
+ assertEquals(3, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(256, frame.getErrorCode());
+ }
+
+ @Test
+ public void checkRstStreamHighestValueWithExtraPayload() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x06, /*length*/
+ 0x03, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, /*errorCode*/
+ 0x0E, 0x28});
+ Http2RstStreamFrame frame = (Http2RstStreamFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(6, frame.getLength());
+ assertEquals(3, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(0x00FFFFFFFFL, frame.getErrorCode());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2SettingsFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2SettingsFrameDecoderTest.java
new file mode 100644
index 0000000..073464e
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2SettingsFrameDecoderTest.java
@@ -0,0 +1,74 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2SettingsFrameDecoderTest {
+
+ @Test
+ public void checkRstStream() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x06, /*length*/
+ 0x04, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ 0x00, 0x01, /*ID*/
+ 0x01, 0x02, 0x03, 0x04, /*value*/});
+ Http2SettingsFrame frame = (Http2SettingsFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(6, frame.getLength());
+ assertEquals(4, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(1, frame.getSettings().size());
+ Http2Setting setting = frame.getSettings().iterator().next();
+ assertEquals(1, setting.getID());
+ assertEquals(0x01020304L, setting.getValue());
+ }
+
+ @Test
+ public void checkRstStreamHighestID() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x06, /*length*/
+ 0x04, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ (byte) 0xFF, (byte) 0xFF, /*ID*/
+ 0x01, 0x02, 0x03, 0x04, /*value*/});
+ Http2SettingsFrame frame = (Http2SettingsFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(6, frame.getLength());
+ assertEquals(4, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(1, frame.getSettings().size());
+ Http2Setting setting = frame.getSettings().iterator().next();
+ assertEquals(0x00FFFF, setting.getID());
+ assertEquals(0x01020304L, setting.getValue());
+ }
+
+ @Test
+ public void checkRstStreamHighestValue() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x06, /*length*/
+ 0x04, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ 0x00, 0x01, /*ID*/
+ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, /*value*/});
+ Http2SettingsFrame frame = (Http2SettingsFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(6, frame.getLength());
+ assertEquals(4, frame.getType());
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(1, frame.getSettings().size());
+ Http2Setting setting = frame.getSettings().iterator().next();
+ assertEquals(1, setting.getID());
+ assertEquals(0xFFFFFFFFL, setting.getValue());
+ }
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Htp2UnknownFrameDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Htp2UnknownFrameDecoderTest.java
new file mode 100644
index 0000000..ffc6149
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Htp2UnknownFrameDecoderTest.java
@@ -0,0 +1,47 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.impl.Http2Connection;
+import org.junit.Test;
+
+public class Htp2UnknownFrameDecoderTest {
+
+ @Test
+ public void checkUnknownFrame() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x02, /*length*/
+ (byte) 0x00FF, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20, /*streamID*/
+ 0x0E, 0x18});
+ Http2UnknownFrame frame = (Http2UnknownFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(2, frame.getLength());
+ assertEquals(255, frame.getType() & 0x00FF);
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(2, frame.getPayload().length);
+ assertArrayEquals(new byte[] {0x0E, 0x18}, frame.getPayload());
+ }
+
+ @Test
+ public void checkUnknownFrameWithoutPayload() {
+ Http2Connection connection = new Http2Connection();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, /*length*/
+ (byte) 0x00FF, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x20 /*streamID*/});
+ Http2UnknownFrame frame = (Http2UnknownFrame) connection.decode(buffer);
+ assertNotNull(frame);
+ assertEquals(0, frame.getLength());
+ assertEquals(255, frame.getType() & 0x00FF);
+ assertEquals(0x00, frame.getFlags());
+ assertEquals(32, frame.getStreamID());
+ assertEquals(0, frame.getPayload().length);
+ }
+
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/Http2FrameHeaderPartialDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/Http2FrameHeaderPartialDecoderTest.java
new file mode 100644
index 0000000..bda0c77
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/Http2FrameHeaderPartialDecoderTest.java
@@ -0,0 +1,65 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.apache.mina.http2.api.Http2FrameHeadePartialDecoder.Http2FrameHeader;
+import org.junit.Test;
+
+public class Http2FrameHeaderPartialDecoderTest {
+
+ @Test
+ public void checkStandardValue() {
+ Http2FrameHeadePartialDecoder decoder = new Http2FrameHeadePartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, /*length*/
+ 0x00, /*type*/
+ 0x00, /*flags*/
+ 0x00, 0x00, 0x00, 0x01 /*streamID*/});
+ assertTrue(decoder.consume(buffer));
+ Http2FrameHeader header = decoder.getValue();
+ assertNotNull(header);
+ assertEquals(0, header.getLength());
+ assertEquals(0, header.getType());
+ assertEquals(0, header.getFlags());
+ assertEquals(1, header.getStreamID());
+ }
+
+ @Test
+ public void checkReservedBitIsNotTransmitted() {
+ Http2FrameHeadePartialDecoder decoder = new Http2FrameHeadePartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, /*length*/
+ 0x00, /*type*/
+ 0x00, /*flags*/
+ (byte)0x80, 0x00, 0x00, 0x01 /*streamID*/});
+ assertTrue(decoder.consume(buffer));
+ Http2FrameHeader header = decoder.getValue();
+ assertNotNull(header);
+ assertEquals(0, header.getLength());
+ assertEquals(0, header.getType());
+ assertEquals(0, header.getFlags());
+ assertEquals(1, header.getStreamID());
+ }
+
+ @Test
+ public void checkPayLoadIsTransmitted() {
+ Http2FrameHeadePartialDecoder decoder = new Http2FrameHeadePartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x01, /*length*/
+ 0x00, /*type*/
+ 0x00, /*flags*/
+ (byte)0x80, 0x00, 0x00, 0x01, /*streamID*/
+ 0x40});
+ assertTrue(decoder.consume(buffer));
+ Http2FrameHeader header = decoder.getValue();
+ assertNotNull(header);
+ assertEquals(1, header.getLength());
+ assertEquals(0, header.getType());
+ assertEquals(0, header.getFlags());
+ assertEquals(1, header.getStreamID());
+ assertEquals(1, header.getPayload().length);
+ assertEquals(0x40, header.getPayload()[0] );
+ }
+
+}
diff --git a/http2/src/test/java/org/apache/mina/http2/api/IntPartialDecoderTest.java b/http2/src/test/java/org/apache/mina/http2/api/IntPartialDecoderTest.java
new file mode 100644
index 0000000..78c9125
--- /dev/null
+++ b/http2/src/test/java/org/apache/mina/http2/api/IntPartialDecoderTest.java
@@ -0,0 +1,47 @@
+package org.apache.mina.http2.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+
+public class IntPartialDecoderTest {
+
+ @Test
+ public void checkSimpleValue() {
+ IntPartialDecoder decoder = new IntPartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, 0x00});
+ assertTrue(decoder.consume(buffer));
+ assertEquals(0, decoder.getValue().intValue());
+ }
+
+ @Test
+ public void checkNotenoughData() {
+ IntPartialDecoder decoder = new IntPartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00});
+ assertFalse(decoder.consume(buffer));
+ }
+
+ @Test
+ public void checkTooMuchData() {
+ IntPartialDecoder decoder = new IntPartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00});
+ assertTrue(decoder.consume(buffer));
+ assertEquals(0, decoder.getValue().intValue());
+ assertEquals(1, buffer.remaining());
+ }
+
+ @Test
+ public void checkDecodingIn2Steps() {
+ IntPartialDecoder decoder = new IntPartialDecoder();
+ ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00});
+ assertFalse(decoder.consume(buffer));
+ buffer = ByteBuffer.wrap(new byte[] {0x00, 0x00, 0x00});
+ assertTrue(decoder.consume(buffer));
+ assertEquals(0, decoder.getValue().intValue());
+ assertEquals(1, buffer.remaining());
+ }
+}