Author: markt Date: Tue May 12 22:29:45 2015 New Revision: 1679116 URL: http://svn.apache.org/r1679116 Log: Start to process the initial settings frame sent by the client.
Added: tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java (with props) Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Added: tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java?rev=1679116&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java (added) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java Tue May 12 22:29:45 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 java.io.IOException; + +public class Http2Exception extends IOException { + + private static final long serialVersionUID = 1L; + + public static final byte[] NO_ERROR = { 0x00, 0x00, 0x00, 0x00 }; + public static final byte[] PROTOCOL_ERROR = { 0x00, 0x00, 0x00, 0x01 }; + public static final byte[] INTERNAL_ERROR = { 0x00, 0x00, 0x00, 0x02 }; + public static final byte[] FLOW_CONTROL_ERROR = { 0x00, 0x00, 0x00, 0x03 }; + public static final byte[] SETTINGS_TIMEOUT = { 0x00, 0x00, 0x00, 0x04 }; + public static final byte[] STREAM_CLOSED = { 0x00, 0x00, 0x00, 0x05 }; + public static final byte[] FRAME_SIZE_ERROR = { 0x00, 0x00, 0x00, 0x06}; + public static final byte[] REFUSED_STREAM = { 0x00, 0x00, 0x00, 0x07}; + public static final byte[] CANCEL = { 0x00, 0x00, 0x00, 0x08}; + public static final byte[] COMPRESSION_ERROR= { 0x00, 0x00, 0x00, 0x09}; + public static final byte[] CONNECT_ERROR = { 0x00, 0x00, 0x00, 0x0a}; + public static final byte[] ENHANCE_YOUR_CALM = { 0x00, 0x00, 0x00, 0x0b}; + public static final byte[] INADEQUATE_SECURITY = { 0x00, 0x00, 0x00, 0x0c}; + public static final byte[] HTTP_1_1_REQUIRED = { 0x00, 0x00, 0x00, 0x0d}; + + + private final int streamId; + private final byte[] errorCode; + + + public Http2Exception(String msg, int streamId, byte[] errorCode) { + super(msg); + this.streamId = streamId; + this.errorCode = errorCode; + } + + + public int getStreamId() { + return streamId; + } + + + public byte[] getErrorCode() { + return errorCode; + } +} Propchange: tomcat/trunk/java/org/apache/coyote/http2/Http2Exception.java ------------------------------------------------------------------------------ svn:eol-style = native 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=1679116&r1=1679115&r2=1679116&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Tue May 12 22:29:45 2015 @@ -16,6 +16,7 @@ */ package org.apache.coyote.http2; +import java.io.EOFException; import java.io.IOException; import javax.servlet.http.WebConnection; @@ -46,11 +47,16 @@ public class Http2UpgradeHandler impleme private static final Log log = LogFactory.getLog(Http2UpgradeHandler.class); private static final StringManager sm = StringManager.getManager(Http2UpgradeHandler.class); + private static final int FRAME_SETTINGS = 4; + private volatile SocketWrapperBase<?> socketWrapper; private volatile boolean initialized = false; private volatile ConnectionPrefaceParser connectionPrefaceParser = new ConnectionPrefaceParser(); - private volatile boolean readFirstFrame = false; + private volatile boolean firstFrame = true; + private volatile boolean open = true; + + private volatile int settingsMaxFrameSize = 16 * 1024; @Override @@ -89,10 +95,19 @@ public class Http2UpgradeHandler impleme } connectionPrefaceParser = null; - while (processFrame()) { + try { + while (processFrame()) { + } + // TODO Catch the Http2Exception and reset the stream / close + // the connection as appropriate + } catch (IOException ioe) { + log.error("TODO: i18n - Frame processing error", ioe); + open = false; } - // TODO: CLOSED (GO_AWAY + no open streams apart from 0?) vs LONG + if (open) { + return SocketState.LONG; + } break; case OPEN_WRITE: @@ -124,11 +139,126 @@ public class Http2UpgradeHandler impleme } - private boolean processFrame() { + private boolean processFrame() throws IOException { + // TODO: Consider refactoring and making this a field to reduce GC. + byte[] frameHeader = new byte[9]; + if (!getFrameHeader(frameHeader)) { + return false; + } + + int frameType = getFrameType(frameHeader); + int streamId = getStreamIdentifier(frameHeader); + int payloadSize = getPayloadSize(streamId, frameHeader); + + switch (frameType) { + case FRAME_SETTINGS: + processFrameSettings(streamId, payloadSize); + break; + default: + // Unknown frame type. + processFrameUnknown(streamId, frameType, payloadSize); + } return false; } + private void processFrameSettings(int streamId, int payloadSize) throws IOException { + if (streamId != 0) { + // TODO i18n + throw new Http2Exception("", 0, Http2Exception.FRAME_SIZE_ERROR); + } + + if (payloadSize % 6 != 0) { + // TODO i18n + throw new Http2Exception("", 0, Http2Exception.FRAME_SIZE_ERROR); + } + } + + + private void processFrameUnknown(int streamId, int type, int payloadSize) throws IOException { + // Swallow the payload + log.info("Swallowing [" + payloadSize + "] bytes of unknown frame type + [" + type + + "] from stream [" + streamId + "]"); + swallowPayload(payloadSize); + } + + + private void swallowPayload(int payloadSize) throws IOException { + int read = 0; + byte[] buffer = new byte[8 * 1024]; + while (read < payloadSize) { + int toRead = Math.min(buffer.length, payloadSize - read); + int thisTime = socketWrapper.read(true, buffer, 0, toRead); + if (thisTime == -1) { + throw new IOException("TODO: i18n"); + } + read += thisTime; + } + } + + + private boolean getFrameHeader(byte[] frameHeader) throws IOException { + // All frames start with a fixed size header. + int headerBytesRead = socketWrapper.read(false, frameHeader, 0, frameHeader.length); + + // No frame header read. Non-blocking between frames, so return. + if (headerBytesRead == 0) { + return false; + } + + // Partial header read. Blocking within a frame to block while the + // remainder is read. + if (headerBytesRead < frameHeader.length) { + int read = socketWrapper.read(true, frameHeader, headerBytesRead, + frameHeader.length - headerBytesRead); + if (read == -1) { + // TODO i18n + throw new EOFException(); + } + } + + return true; + } + + + private int getFrameType(byte[] frameHeader) throws IOException { + int frameType = frameHeader[3] & 0xFF; + // Make sure the first frame is a settings frame + if (firstFrame) { + if (frameType != FRAME_SETTINGS) { + // TODO i18n + throw new Http2Exception("", 0, Http2Exception.PROTOCOL_ERROR); + } else { + firstFrame = false; + } + } + return frameType; + } + + + private int getStreamIdentifier(byte[] frameHeader) { + // MSB of [5] is reserved and must be ignored. + return ((frameHeader[5] & 0x7F) << 24) + ((frameHeader[6] & 0xFF) << 16) + + ((frameHeader[7] & 0xFF) << 6) + (frameHeader[8] & 0xFF); + } + + + private int getPayloadSize(int streamId, byte[] frameHeader) throws IOException { + // Make sure the payload size is valid + int payloadSize = ((frameHeader[0] & 0xFF) << 16) + + ((frameHeader[1] & 0xFF) << 8) + + (frameHeader[2] & 0xFF); + + if (payloadSize > settingsMaxFrameSize) { + swallowPayload(payloadSize); + // TODO i18n + throw new Http2Exception("", streamId, Http2Exception.FRAME_SIZE_ERROR); + } + + return payloadSize; + } + + @Override public void destroy() { // NO-OP --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org