Author: markt Date: Thu Jun 4 15:07:16 2015 New Revision: 1683572 URL: http://svn.apache.org/r1683572 Log: Add initial tests for section 5.1 Add placeholder for PUSH_PROMISE frames (should never be received) Expand state machine to start tracking allowed frames for each state Stream now informs the state machine if a data or Window update frame is received
Added: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_5_1.java (with props) Modified: tomcat/trunk/java/org/apache/coyote/http2/AbstractStream.java tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties tomcat/trunk/java/org/apache/coyote/http2/Stream.java tomcat/trunk/java/org/apache/coyote/http2/StreamStateMachine.java tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Modified: tomcat/trunk/java/org/apache/coyote/http2/AbstractStream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/AbstractStream.java?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/AbstractStream.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/AbstractStream.java Thu Jun 4 15:07:16 2015 @@ -100,7 +100,11 @@ abstract class AbstractStream { } - protected void incrementWindowSize(int increment) { + /** +s * @param increment + * @throws Http2Exception + */ + protected void incrementWindowSize(int increment) throws Http2Exception { windowSize.addAndGet(increment); } Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2Parser.java Thu Jun 4 15:07:16 2015 @@ -37,6 +37,7 @@ class Http2Parser { private static final int FRAME_TYPE_HEADERS = 1; private static final int FRAME_TYPE_PRIORITY = 2; private static final int FRAME_TYPE_SETTINGS = 4; + private static final int FRAME_TYPE_PUSH_PROMISE = 5; private static final int FRAME_TYPE_PING = 6; private static final int FRAME_TYPE_GOAWAY = 7; private static final int FRAME_TYPE_WINDOW_UPDATE = 8; @@ -127,6 +128,8 @@ class Http2Parser { case FRAME_TYPE_SETTINGS: readSettingsFrame(streamId, flags, payloadSize); break; + case FRAME_TYPE_PUSH_PROMISE: + readPushPromiseFrame(streamId, flags, payloadSize); case FRAME_TYPE_PING: readPingFrame(streamId, flags, payloadSize); break; @@ -322,6 +325,17 @@ class Http2Parser { } + private void readPushPromiseFrame(int streamId, int flags, int payloadSize) throws IOException { + if (log.isDebugEnabled()) { + log.debug(sm.getString("http2Parser.processFrame", connectionId, + Integer.toString(streamId), Integer.toString(flags), + Integer.toString(payloadSize))); + } + + // TODO: Should never be received by a server + } + + private void readPingFrame(int streamId, int flags, int payloadSize) throws IOException { if (log.isDebugEnabled()) { @@ -586,7 +600,7 @@ class Http2Parser { HpackDecoder getHpackDecoder(); // Data frames - ByteBuffer getInputByteBuffer(int streamId, int payloadSize); + ByteBuffer getInputByteBuffer(int streamId, int payloadSize) throws Http2Exception; void endOfStream(int streamId); // Header frames @@ -606,7 +620,7 @@ class Http2Parser { void goaway(int lastStreamId, long errorCode, String debugData); // Window size - void incrementWindowSize(int streamId, int increment); + void incrementWindowSize(int streamId, int increment) throws Http2Exception; // Testing void swallow(int streamId, int frameType, int flags, int size) throws IOException; Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Thu Jun 4 15:07:16 2015 @@ -453,7 +453,7 @@ public class Http2UpgradeHandler extends } - int reserveWindowSize(Stream stream, int toWrite) { + int reserveWindowSize(Stream stream, int toWrite) throws Http2Exception { int result; synchronized (backLogLock) { long windowSize = getWindowSize(); @@ -488,7 +488,7 @@ public class Http2UpgradeHandler extends @Override - protected void incrementWindowSize(int increment) { + protected void incrementWindowSize(int increment) throws Http2Exception { synchronized (backLogLock) { if (getWindowSize() == 0) { releaseBackLog(increment); @@ -706,7 +706,7 @@ public class Http2UpgradeHandler extends @Override - public ByteBuffer getInputByteBuffer(int streamId, int payloadSize) { + public ByteBuffer getInputByteBuffer(int streamId, int payloadSize) throws Http2Exception { Stream stream = getStream(streamId); if (stream == null) { return null; @@ -802,7 +802,7 @@ public class Http2UpgradeHandler extends @Override - public void incrementWindowSize(int streamId, int increment) { + public void incrementWindowSize(int streamId, int increment) throws Http2Exception { AbstractStream stream = getStream(streamId); if (stream != null) { stream.incrementWindowSize(increment); Modified: tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties Thu Jun 4 15:07:16 2015 @@ -63,6 +63,9 @@ stream.write=Connection [{0}], Stream [{ streamProcessor.httpupgrade.notsupported=HTTP upgrade is not supported within HTTP/2 streams +streamStateMachine.invalidFrame.windowUpdate=Connection [{0}], Received Data frame for stream [{1}] in state [{2}] +streamStateMachine.invalidFrame.windowUpdate=Connection [{0}], Received Window Update frame for stream [{1}] in state [{2}] + upgradeHandler.connectionError=An error occurred that requires the HTTP/2 connection to be closed. upgradeHandler.goaway.debug=Connection [{0}], Goaway, Last stream [{1}], Error code [{2}], Debug data [{3}] upgradeHandler.init=Connection [{0}] Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Thu Jun 4 15:07:16 2015 @@ -42,7 +42,7 @@ public class Stream extends AbstractStre private final Response coyoteResponse = new Response(); private final StreamInputBuffer inputBuffer = new StreamInputBuffer(); private final StreamOutputBuffer outputBuffer = new StreamOutputBuffer(); - private final StreamStateMachine state = new StreamStateMachine(); + private final StreamStateMachine state; public Stream(Integer identifier, Http2UpgradeHandler handler) { @@ -55,6 +55,7 @@ public class Stream extends AbstractStre this.handler = handler; setParentStream(handler); setWindowSize(handler.getRemoteSettings().getInitialWindowSize()); + state = new StreamStateMachine(this); this.coyoteRequest = coyoteRequest; this.coyoteRequest.setInputBuffer(inputBuffer); this.coyoteResponse.setOutputBuffer(outputBuffer); @@ -96,11 +97,14 @@ public class Stream extends AbstractStre @Override - public void incrementWindowSize(int windowSizeIncrement) { + public void incrementWindowSize(int windowSizeIncrement) throws Http2Exception { // If this is zero then any thread that has been trying to write for // this stream will be waiting. Notify that thread it can continue. Use // notify all even though only one thread is waiting to be on the safe // side. + if (windowSizeIncrement > 0) { + state.receivedWindowUpdate(); + } boolean notify = getWindowSize() == 0; super.incrementWindowSize(windowSizeIncrement); if (notify) { @@ -214,7 +218,8 @@ public class Stream extends AbstractStre } - ByteBuffer getInputByteBuffer() { + ByteBuffer getInputByteBuffer() throws Http2Exception { + state.receivedData(); return inputBuffer.getInBuffer(); } Modified: tomcat/trunk/java/org/apache/coyote/http2/StreamStateMachine.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/StreamStateMachine.java?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/StreamStateMachine.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/StreamStateMachine.java Thu Jun 4 15:07:16 2015 @@ -16,6 +16,8 @@ */ package org.apache.coyote.http2; +import org.apache.tomcat.util.res.StringManager; + /** * See <a href="https://tools.ietf.org/html/rfc7540#section-5.1">state * diagram</a> in RFC 7540. @@ -23,15 +25,22 @@ package org.apache.coyote.http2; * The following additions are supported by this state machine: * <ul> * <li>differentiate between closed (normal) and closed caused by reset</li> - * <li>allow a transition from idle to closed if reset is sent or received</li> * </ul> * */ public class StreamStateMachine { + private static final StringManager sm = StringManager.getManager(StreamStateMachine.class); + + private final Stream stream; private State state = State.IDLE; + public StreamStateMachine(Stream stream) { + this.stream = stream; + } + + public synchronized void sendPushPromise() { if (state == State.IDLE) { state = State.RESERVED_LOCAL; @@ -111,6 +120,11 @@ public class StreamStateMachine { public synchronized void recieveReset() { + if (state == State.IDLE) { + // This should never happen + // TODO: ProtocolExcpetion? i18n + throw new IllegalStateException(); + } state = State.CLOSED_RESET; } @@ -130,14 +144,52 @@ public class StreamStateMachine { } + public synchronized void receivedWindowUpdate() throws Http2Exception { + // No state change. Just checks state is valid for receiving window + // update. + if (!state.isWindowUpdatePermitted()) { + throw new Http2Exception(sm.getString("streamStateMachine.invalidFrame.windowUpdate", + stream.getConnectionId(), stream.getIdentifier(), state), + 0, ErrorCode.PROTOCOL_ERROR); + } + } + + + public synchronized void receivedData() throws Http2Exception { + // No state change. Just checks state is valid for receiving window + // update. + if (!state.isDataPermitted()) { + throw new Http2Exception(sm.getString("streamStateMachine.invalidFrame.data", + stream.getConnectionId(), stream.getIdentifier(), state), + 0, ErrorCode.PROTOCOL_ERROR); + } + } + + private enum State { - IDLE, - OPEN, - RESERVED_LOCAL, - RESERVED_REMOTE, - HALF_CLOSED_LOCAL, - HALF_CLOSED_REMOTE, - CLOSED, - CLOSED_RESET + IDLE (false, false), + OPEN ( true, true), + RESERVED_LOCAL ( true, false), + RESERVED_REMOTE (false, false), + HALF_CLOSED_LOCAL ( true, true), + HALF_CLOSED_REMOTE ( true, false), + CLOSED (false, false), + CLOSED_RESET ( true, true); + + private final boolean windowUpdatePermitted; + private final boolean dataPermitted; + + private State(boolean windowUpdatePermitted, boolean dataPermitted) { + this.windowUpdatePermitted = windowUpdatePermitted; + this.dataPermitted = dataPermitted; + } + + public boolean isWindowUpdatePermitted() { + return windowUpdatePermitted; + } + + public boolean isDataPermitted() { + return dataPermitted; + } } } Modified: tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java?rev=1683572&r1=1683571&r2=1683572&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java (original) +++ tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Thu Jun 4 15:07:16 2015 @@ -361,6 +361,37 @@ public abstract class Http2TestBase exte } + void sendWindowUpdate(int streamId, int increment) throws IOException { + byte[] updateFrame = new byte[13]; + // length is always 4 + updateFrame[2] = 0x04; + // type is always 8 + updateFrame[3] = 0x08; + // no flags + // Stream ID + ByteUtil.set31Bits(updateFrame, 5, streamId); + // Payload + ByteUtil.set31Bits(updateFrame, 9, increment); + + os.write(updateFrame); + os.flush(); + } + + + void sendData(int streamId, byte[] payload) throws IOException { + byte[] header = new byte[9]; + // length + ByteUtil.setThreeBytes(header, 0, payload.length); + // Type is zero + // No flags + // Stream ID + ByteUtil.set31Bits(header, 5, streamId); + + os.write(header); + os.write(payload); + os.flush(); + } + private static class TestInput implements Http2Parser.Input { private final InputStream is; Added: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_5_1.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_5_1.java?rev=1683572&view=auto ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_5_1.java (added) +++ tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_5_1.java Thu Jun 4 15:07:16 2015 @@ -0,0 +1,60 @@ +/* + * 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.coyote.http2; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests for Section 5.§ of + * <a href="https://tools.ietf.org/html/rfc7540">RFC 7540</a>. + * <br> + * The order of tests in this class is aligned with the order of the + * requirements in the RFC. + */ +public class TestHttp2Section_5_1 extends Http2TestBase { + + @Test + public void testIdleStateInvalidFrame01() throws Exception { + http2Connect(); + + sendWindowUpdate(3, 200); + + parser.readFrame(true); + + Assert.assertTrue(output.getTrace(), + output.getTrace().startsWith("0-Goaway-[2147483647]-[" + + ErrorCode.PROTOCOL_ERROR.getErrorCode() + "]-[")); + } + + + @Test + public void testIdleStateInvalidFrame02() throws Exception { + http2Connect(); + + sendData(3, new byte[] {}); + + parser.readFrame(true); + + Assert.assertTrue(output.getTrace(), + output.getTrace().startsWith("0-Goaway-[2147483647]-[" + + ErrorCode.PROTOCOL_ERROR.getErrorCode() + "]-[")); + } + + + // TODO: Invalid frames for each of the remaining states +} Propchange: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Section_5_1.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org