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()); + } +}