Initial HTTP2 work (single HTTP2 PDU)
authorJeff MAURY <jeffmaury@apache.org>
Tue, 17 Mar 2015 19:56:56 +0000 (20:56 +0100)
committerJeff MAURY <jeffmaury@apache.org>
Tue, 17 Mar 2015 19:56:56 +0000 (20:56 +0100)
31 files changed:
core/pom.xml
core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java
http/src/main/java/org/apache/mina/http/HttpRequestImpl.java
http/src/main/java/org/apache/mina/http/api/HttpRequest.java
http2/Http2Frames.ods [new file with mode: 0644]
http2/pom.xml [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/BytePartialDecoder.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/Http2Constants.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/Http2Frame.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/Http2Header.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/Http2NameValuePair.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/Http2Setting.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/IntPartialDecoder.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/LongPartialDecoder.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/PartialDecoder.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/api/StreamMessage.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/impl/HeadersEncoder.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/impl/Http2Connection.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/impl/Http2FrameDecoder.java [new file with mode: 0644]
http2/src/main/java/org/apache/mina/http2/impl/Http2ProtocolDecoder.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/BytePartialDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2ContinuationFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2DataFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2HeadersFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2PriorityFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2PushPromiseFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2RstStreamFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2SettingsFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Htp2UnknownFrameDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/Http2FrameHeaderPartialDecoderTest.java [new file with mode: 0644]
http2/src/test/java/org/apache/mina/http2/api/IntPartialDecoderTest.java [new file with mode: 0644]

index d5c7435..3bb9647 100644 (file)
       <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>
 
index 04228e1..b145d9c 100644 (file)
@@ -149,12 +149,12 @@ public class ProtocolCodecFilter<MESSAGE, ENCODED, ENCODING_STATE, DECODING_STAT
     // ----------- 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);
     }
 
index 0c78497..91639f4 100644 (file)
@@ -39,14 +39,14 @@ public class HttpRequestImpl implements HttpRequest {
 
     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 @@ public class HttpRequestImpl implements HttpRequest {
      * {@inheritDoc}
      */
     @Override
+    public String getTargetURI() {
+        return targetURI;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public String getContentType() {
         return headers.get("content-type");
     }
@@ -148,7 +156,7 @@ public class HttpRequestImpl implements HttpRequest {
 
         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");
 
index 9e51d72..d248770 100644 (file)
@@ -62,4 +62,18 @@ public interface HttpRequest extends HttpMessage {
      * @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 (file)
index 0000000..c2ca426
Binary files /dev/null and b/http2/Http2Frames.ods differ
diff --git a/http2/pom.xml b/http2/pom.xml
new file mode 100644 (file)
index 0000000..ecce674
--- /dev/null
@@ -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 &amp; 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 (file)
index 0000000..11582de
--- /dev/null
@@ -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 (file)
index 0000000..ba8a8fe
--- /dev/null
@@ -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 (file)
index 0000000..b6a3ed2
--- /dev/null
@@ -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 (file)
index 0000000..c0e5ab8
--- /dev/null
@@ -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 (file)
index 0000000..6e8572d
--- /dev/null
@@ -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 (file)
index 0000000..acccb65
--- /dev/null
@@ -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 (file)
index 0000000..5e6076d
--- /dev/null
@@ -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 (file)
index 0000000..dae0c2f
--- /dev/null
@@ -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 (file)
index 0000000..070b7ce
--- /dev/null
@@ -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 (file)
index 0000000..41f85b0
--- /dev/null
@@ -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 (file)
index 0000000..3afb72d
--- /dev/null
@@ -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 (file)
index 0000000..3019b20
--- /dev/null
@@ -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 (file)
index 0000000..ba32afc
--- /dev/null
@@ -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 (file)
index 0000000..8217166
--- /dev/null
@@ -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 (file)
index 0000000..7d09d11
--- /dev/null
@@ -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 (file)
index 0000000..12a661c
--- /dev/null
@@ -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 (file)
index 0000000..f6dad67
--- /dev/null
@@ -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 (file)
index 0000000..e974ce5
--- /dev/null
@@ -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 (file)
index 0000000..cd0c049
--- /dev/null
@@ -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 (file)
index 0000000..30b7299
--- /dev/null
@@ -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 (file)
index 0000000..717b0f6
--- /dev/null
@@ -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 (file)
index 0000000..073464e
--- /dev/null
@@ -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 (file)
index 0000000..ffc6149
--- /dev/null
@@ -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 (file)
index 0000000..bda0c77
--- /dev/null
@@ -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 (file)
index 0000000..78c9125
--- /dev/null
@@ -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());
+    }
+}