diff --git a/tomee/apache-tomee/src/patch/java/org/apache/coyote/http2/Http2Parser.java b/tomee/apache-tomee/src/patch/java/org/apache/coyote/http2/Http2Parser.java
new file mode 100644
index 00000000000..72c30532e12
--- /dev/null
+++ b/tomee/apache-tomee/src/patch/java/org/apache/coyote/http2/Http2Parser.java
@@ -0,0 +1,839 @@
+/*
+ * 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;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.servlet.http.WebConnection;
+
+import org.apache.coyote.ProtocolException;
+import org.apache.coyote.http2.HpackDecoder.HeaderEmitter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.ByteBufferUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+class Http2Parser {
+
+ protected static final Log log = LogFactory.getLog(Http2Parser.class);
+ protected static final StringManager sm = StringManager.getManager(Http2Parser.class);
+
+ static final byte[] CLIENT_PREFACE_START =
+ "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);
+
+ protected final String connectionId;
+ protected final Input input;
+ private final Output output;
+ private final byte[] frameHeaderBuffer = new byte[9];
+
+ private volatile HpackDecoder hpackDecoder;
+ private volatile ByteBuffer headerReadBuffer =
+ ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
+ private volatile int headersCurrentStream = -1;
+ private volatile boolean headersEndStream = false;
+
+ Http2Parser(String connectionId, Input input, Output output) {
+ this.connectionId = connectionId;
+ this.input = input;
+ this.output = output;
+ }
+
+
+ /**
+ * Read and process a single frame. Once the start of a frame is read, the
+ * remainder will be read using blocking IO.
+ *
+ * @param block Should this method block until a frame is available if no
+ * frame is available immediately?
+ *
+ * @return true
if a frame was read otherwise
+ * false
+ *
+ * @throws IOException If an IO error occurs while trying to read a frame
+ *
+ * @deprecated Unused. Will be removed in Tomcat 11 onwards.
+ */
+ @Deprecated
+ boolean readFrame(boolean block) throws Http2Exception, IOException {
+ return readFrame(block, null);
+ }
+
+
+ /**
+ * Read and process a single frame. The initial read is non-blocking to
+ * determine if a frame is present. Once the start of a frame is read, the
+ * remainder will be read using blocking IO.
+ *
+ * @return true
if a frame was read otherwise
+ * false
+ *
+ * @throws IOException If an IO error occurs while trying to read a frame
+ */
+ boolean readFrame() throws Http2Exception, IOException {
+ return readFrame(false, null);
+ }
+
+
+ protected boolean readFrame(boolean block, FrameType expected)
+ throws IOException, Http2Exception {
+
+ if (!input.fill(block, frameHeaderBuffer)) {
+ return false;
+ }
+
+ int payloadSize = ByteUtil.getThreeBytes(frameHeaderBuffer, 0);
+ int frameTypeId = ByteUtil.getOneByte(frameHeaderBuffer, 3);
+ FrameType frameType = FrameType.valueOf(frameTypeId);
+ int flags = ByteUtil.getOneByte(frameHeaderBuffer, 4);
+ int streamId = ByteUtil.get31Bits(frameHeaderBuffer, 5);
+
+ try {
+ validateFrame(expected, frameType, streamId, flags, payloadSize);
+ } catch (StreamException se) {
+ swallowPayload(streamId, frameTypeId, payloadSize, false, null);
+ throw se;
+ }
+
+ switch (frameType) {
+ case DATA:
+ readDataFrame(streamId, flags, payloadSize, null);
+ break;
+ case HEADERS:
+ readHeadersFrame(streamId, flags, payloadSize, null);
+ break;
+ case PRIORITY:
+ readPriorityFrame(streamId, null);
+ break;
+ case RST:
+ readRstFrame(streamId, null);
+ break;
+ case SETTINGS:
+ readSettingsFrame(flags, payloadSize, null);
+ break;
+ case PUSH_PROMISE:
+ readPushPromiseFrame(streamId, flags, payloadSize, null);
+ break;
+ case PING:
+ readPingFrame(flags, null);
+ break;
+ case GOAWAY:
+ readGoawayFrame(payloadSize, null);
+ break;
+ case WINDOW_UPDATE:
+ readWindowUpdateFrame(streamId, null);
+ break;
+ case CONTINUATION:
+ readContinuationFrame(streamId, flags, payloadSize, null);
+ break;
+ case UNKNOWN:
+ readUnknownFrame(streamId, frameTypeId, flags, payloadSize, null);
+ }
+
+ return true;
+ }
+
+ protected void readDataFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+ throws Http2Exception, IOException {
+ // Process the Stream
+ int padLength = 0;
+
+ boolean endOfStream = Flags.isEndOfStream(flags);
+
+ int dataLength;
+ if (Flags.hasPadding(flags)) {
+ if (buffer == null) {
+ byte[] b = new byte[1];
+ input.fill(true, b);
+ padLength = b[0] & 0xFF;
+ } else {
+ padLength = buffer.get() & 0xFF;
+ }
+
+ if (padLength >= payloadSize) {
+ throw new ConnectionException(
+ sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
+ Integer.toString(streamId), Integer.toString(padLength),
+ Integer.toString(payloadSize)), Http2Error.PROTOCOL_ERROR);
+ }
+ // +1 is for the padding length byte we just read above
+ dataLength = payloadSize - (padLength + 1);
+ } else {
+ dataLength = payloadSize;
+ }
+
+ if (log.isDebugEnabled()) {
+ String padding;
+ if (Flags.hasPadding(flags)) {
+ padding = Integer.toString(padLength);
+ } else {
+ padding = "none";
+ }
+ log.debug(sm.getString("http2Parser.processFrameData.lengths", connectionId,
+ Integer.toString(streamId), Integer.toString(dataLength), padding));
+ }
+
+ ByteBuffer dest = output.startRequestBodyFrame(streamId, payloadSize, endOfStream);
+ if (dest == null) {
+ swallowPayload(streamId, FrameType.DATA.getId(), dataLength, false, buffer);
+ // Process padding before sending any notifications in case padding
+ // is invalid.
+ if (Flags.hasPadding(flags)) {
+ swallowPayload(streamId, FrameType.DATA.getId(), padLength, true, buffer);
+ }
+ if (endOfStream) {
+ output.receivedEndOfStream(streamId);
+ }
+ } else {
+ synchronized (dest) {
+ if (dest.remaining() < payloadSize) {
+ // Client has sent more data than permitted by Window size
+ swallowPayload(streamId, FrameType.DATA.getId(), dataLength, false, buffer);
+ if (Flags.hasPadding(flags)) {
+ swallowPayload(streamId, FrameType.DATA.getId(), padLength, true, buffer);
+ }
+ throw new StreamException(sm.getString("http2Parser.processFrameData.window", connectionId),
+ Http2Error.FLOW_CONTROL_ERROR, streamId);
+ }
+ if (buffer == null) {
+ input.fill(true, dest, dataLength);
+ } else {
+ int oldLimit = buffer.limit();
+ buffer.limit(buffer.position() + dataLength);
+ dest.put(buffer);
+ buffer.limit(oldLimit);
+ }
+ // Process padding before sending any notifications in case
+ // padding is invalid.
+ if (Flags.hasPadding(flags)) {
+ swallowPayload(streamId, FrameType.DATA.getId(), padLength, true, buffer);
+ }
+ if (endOfStream) {
+ output.receivedEndOfStream(streamId);
+ }
+ output.endRequestBodyFrame(streamId, dataLength);
+ }
+ }
+ }
+
+
+ protected void readHeadersFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+ throws Http2Exception, IOException {
+
+ headersEndStream = Flags.isEndOfStream(flags);
+
+ if (hpackDecoder == null) {
+ hpackDecoder = output.getHpackDecoder();
+ }
+ try {
+ hpackDecoder.setHeaderEmitter(output.headersStart(streamId, headersEndStream));
+ } catch (StreamException se) {
+ swallowPayload(streamId, FrameType.HEADERS.getId(), payloadSize, false, buffer);
+ throw se;
+ }
+
+ int padLength = 0;
+ boolean padding = Flags.hasPadding(flags);
+ boolean priority = Flags.hasPriority(flags);
+ int optionalLen = 0;
+ if (padding) {
+ optionalLen = 1;
+ }
+ if (priority) {
+ optionalLen += 5;
+ }
+ if (optionalLen > 0) {
+ byte[] optional = new byte[optionalLen];
+ if (buffer == null) {
+ input.fill(true, optional);
+ } else {
+ buffer.get(optional);
+ }
+ int optionalPos = 0;
+ if (padding) {
+ padLength = ByteUtil.getOneByte(optional, optionalPos++);
+ if (padLength >= payloadSize) {
+ throw new ConnectionException(
+ sm.getString("http2Parser.processFrame.tooMuchPadding", connectionId,
+ Integer.toString(streamId), Integer.toString(padLength),
+ Integer.toString(payloadSize)), Http2Error.PROTOCOL_ERROR);
+ }
+ }
+ if (priority) {
+ boolean exclusive = ByteUtil.isBit7Set(optional[optionalPos]);
+ int parentStreamId = ByteUtil.get31Bits(optional, optionalPos);
+ int weight = ByteUtil.getOneByte(optional, optionalPos + 4) + 1;
+ output.reprioritise(streamId, parentStreamId, exclusive, weight);
+ }
+
+ payloadSize -= optionalLen;
+ payloadSize -= padLength;
+ }
+
+ readHeaderPayload(streamId, payloadSize, buffer);
+
+ swallowPayload(streamId, FrameType.HEADERS.getId(), padLength, true, buffer);
+
+ if (Flags.isEndOfHeaders(flags)) {
+ onHeadersComplete(streamId);
+ } else {
+ headersCurrentStream = streamId;
+ }
+ }
+
+
+ protected void readPriorityFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
+ byte[] payload = new byte[5];
+ if (buffer == null) {
+ input.fill(true, payload);
+ } else {
+ buffer.get(payload);
+ }
+
+ boolean exclusive = ByteUtil.isBit7Set(payload[0]);
+ int parentStreamId = ByteUtil.get31Bits(payload, 0);
+ int weight = ByteUtil.getOneByte(payload, 4) + 1;
+
+ if (streamId == parentStreamId) {
+ throw new StreamException(sm.getString("http2Parser.processFramePriority.invalidParent",
+ connectionId, Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR, streamId);
+ }
+
+ output.reprioritise(streamId, parentStreamId, exclusive, weight);
+ }
+
+
+ protected void readRstFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
+ byte[] payload = new byte[4];
+ if (buffer == null) {
+ input.fill(true, payload);
+ } else {
+ buffer.get(payload);
+ }
+
+ long errorCode = ByteUtil.getFourBytes(payload, 0);
+ output.reset(streamId, errorCode);
+ headersCurrentStream = -1;
+ headersEndStream = false;
+ }
+
+
+ protected void readSettingsFrame(int flags, int payloadSize, ByteBuffer buffer) throws Http2Exception, IOException {
+ boolean ack = Flags.isAck(flags);
+ if (payloadSize > 0 && ack) {
+ throw new ConnectionException(sm.getString(
+ "http2Parser.processFrameSettings.ackWithNonZeroPayload"),
+ Http2Error.FRAME_SIZE_ERROR);
+ }
+
+ if (payloadSize == 0 && !ack) {
+ // Ensure empty SETTINGS frame increments the overhead count
+ output.setting(null, 0);
+ } else {
+ // Process the settings
+ byte[] setting = new byte[6];
+ for (int i = 0; i < payloadSize / 6; i++) {
+ if (buffer == null) {
+ input.fill(true, setting);
+ } else {
+ buffer.get(setting);
+ }
+ int id = ByteUtil.getTwoBytes(setting, 0);
+ long value = ByteUtil.getFourBytes(setting, 2);
+ Setting key = Setting.valueOf(id);
+ if (key == Setting.UNKNOWN) {
+ log.warn(sm.getString("connectionSettings.unknown",
+ connectionId, Integer.toString(id), Long.toString(value)));
+ }
+ output.setting(key, value);
+ }
+ }
+ output.settingsEnd(ack);
+ }
+
+
+ /**
+ * This default server side implementation always throws an exception. If
+ * re-used for client side parsing, this method should be overridden with an
+ * appropriate implementation.
+ *
+ * @param streamId The pushed stream
+ * @param flags The flags set in the frame header
+ * @param payloadSize The size of the payload in bytes
+ * @param buffer The payload, if available
+ *
+ * @throws Http2Exception Always
+ * @throws IOException May be thrown by sub-classes that parse this frame
+ */
+ protected void readPushPromiseFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+ throws Http2Exception, IOException {
+ throw new ConnectionException(sm.getString("http2Parser.processFramePushPromise",
+ connectionId, Integer.valueOf(streamId)), Http2Error.PROTOCOL_ERROR);
+ }
+
+
+ protected void readPingFrame(int flags, ByteBuffer buffer) throws IOException {
+ // Read the payload
+ byte[] payload = new byte[8];
+ if (buffer == null) {
+ input.fill(true, payload);
+ } else {
+ buffer.get(payload);
+ }
+ output.pingReceive(payload, Flags.isAck(flags));
+ }
+
+
+ protected void readGoawayFrame(int payloadSize, ByteBuffer buffer) throws IOException {
+ byte[] payload = new byte[payloadSize];
+ if (buffer == null) {
+ input.fill(true, payload);
+ } else {
+ buffer.get(payload);
+ }
+
+ int lastStreamId = ByteUtil.get31Bits(payload, 0);
+ long errorCode = ByteUtil.getFourBytes(payload, 4);
+ String debugData = null;
+ if (payloadSize > 8) {
+ debugData = new String(payload, 8, payloadSize - 8, StandardCharsets.UTF_8);
+ }
+ output.goaway(lastStreamId, errorCode, debugData);
+ }
+
+
+ protected void readWindowUpdateFrame(int streamId, ByteBuffer buffer) throws Http2Exception, IOException {
+ byte[] payload = new byte[4];
+ if (buffer == null) {
+ input.fill(true, payload);
+ } else {
+ buffer.get(payload);
+ }
+ int windowSizeIncrement = ByteUtil.get31Bits(payload, 0);
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("http2Parser.processFrameWindowUpdate.debug", connectionId,
+ Integer.toString(streamId), Integer.toString(windowSizeIncrement)));
+ }
+
+ // Validate the data
+ if (windowSizeIncrement == 0) {
+ if (streamId == 0) {
+ throw new ConnectionException(
+ sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement",
+ connectionId, Integer.toString(streamId)),
+ Http2Error.PROTOCOL_ERROR);
+ } else {
+ throw new StreamException(
+ sm.getString("http2Parser.processFrameWindowUpdate.invalidIncrement",
+ connectionId, Integer.toString(streamId)),
+ Http2Error.PROTOCOL_ERROR, streamId);
+ }
+ }
+
+ output.incrementWindowSize(streamId, windowSizeIncrement);
+ }
+
+
+ protected void readContinuationFrame(int streamId, int flags, int payloadSize, ByteBuffer buffer)
+ throws Http2Exception, IOException {
+ if (headersCurrentStream == -1) {
+ // No headers to continue
+ throw new ConnectionException(sm.getString(
+ "http2Parser.processFrameContinuation.notExpected", connectionId,
+ Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
+ }
+
+ boolean endOfHeaders = Flags.isEndOfHeaders(flags);
+
+ // Used to detect abusive clients sending large numbers of small
+ // continuation frames
+ output.headersContinue(payloadSize, endOfHeaders);
+
+ readHeaderPayload(streamId, payloadSize, buffer);
+
+ if (endOfHeaders) {
+ headersCurrentStream = -1;
+ onHeadersComplete(streamId);
+ }
+ }
+
+
+ protected void readHeaderPayload(int streamId, int payloadSize, ByteBuffer buffer)
+ throws Http2Exception, IOException {
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("http2Parser.processFrameHeaders.payload", connectionId,
+ Integer.valueOf(streamId), Integer.valueOf(payloadSize)));
+ }
+
+ int remaining = payloadSize;
+
+ while (remaining > 0) {
+ if (headerReadBuffer.remaining() == 0) {
+ // Buffer needs expansion
+ int newSize;
+ if (headerReadBuffer.capacity() < payloadSize) {
+ // First step, expand to the current payload. That should
+ // cover most cases.
+ newSize = payloadSize;
+ } else {
+ // Header must be spread over multiple frames. Keep doubling
+ // buffer size until the header can be read.
+ newSize = headerReadBuffer.capacity() * 2;
+ }
+ headerReadBuffer = ByteBufferUtils.expand(headerReadBuffer, newSize);
+ }
+ int toRead = Math.min(headerReadBuffer.remaining(), remaining);
+ // headerReadBuffer in write mode
+ if (buffer == null) {
+ input.fill(true, headerReadBuffer, toRead);
+ } else {
+ int oldLimit = buffer.limit();
+ buffer.limit(buffer.position() + toRead);
+ headerReadBuffer.put(buffer);
+ buffer.limit(oldLimit);
+ }
+ // switch to read mode
+ headerReadBuffer.flip();
+ try {
+ hpackDecoder.decode(headerReadBuffer);
+ } catch (HpackException hpe) {
+ throw new ConnectionException(
+ sm.getString("http2Parser.processFrameHeaders.decodingFailed"),
+ Http2Error.COMPRESSION_ERROR, hpe);
+ }
+
+ // switches to write mode
+ headerReadBuffer.compact();
+ remaining -= toRead;
+
+ if (hpackDecoder.isHeaderCountExceeded()) {
+ StreamException headerException = new StreamException(sm.getString(
+ "http2Parser.headerLimitCount", connectionId, Integer.valueOf(streamId)),
+ Http2Error.ENHANCE_YOUR_CALM, streamId);
+ hpackDecoder.getHeaderEmitter().setHeaderException(headerException);
+ }
+
+ if (hpackDecoder.isHeaderSizeExceeded(headerReadBuffer.position())) {
+ StreamException headerException = new StreamException(sm.getString(
+ "http2Parser.headerLimitSize", connectionId, Integer.valueOf(streamId)),
+ Http2Error.ENHANCE_YOUR_CALM, streamId);
+ hpackDecoder.getHeaderEmitter().setHeaderException(headerException);
+ }
+
+ if (hpackDecoder.isHeaderSwallowSizeExceeded(headerReadBuffer.position())) {
+ throw new ConnectionException(sm.getString("http2Parser.headerLimitSize",
+ connectionId, Integer.valueOf(streamId)), Http2Error.ENHANCE_YOUR_CALM);
+ }
+ }
+ }
+
+
+ protected void readUnknownFrame(int streamId, int frameTypeId, int flags, int payloadSize, ByteBuffer buffer)
+ throws IOException {
+ try {
+ swallowPayload(streamId, frameTypeId, payloadSize, false, buffer);
+ } catch (ConnectionException e) {
+ // Will never happen because swallowPayload() is called with isPadding set
+ // to false
+ } finally {
+ output.onSwallowedUnknownFrame(streamId, frameTypeId, flags, payloadSize);
+ }
+ }
+
+
+ /**
+ * Swallow some or all of the bytes from the payload of an HTTP/2 frame.
+ *
+ * @param streamId Stream being swallowed
+ * @param frameTypeId Type of HTTP/2 frame for which the bytes will be
+ * swallowed
+ * @param len Number of bytes to swallow
+ * @param isPadding Are the bytes to be swallowed padding bytes?
+ * @param byteBuffer Used with {@link Http2AsyncParser} to access the
+ * data that has already been read
+ *
+ * @throws IOException If an I/O error occurs reading additional bytes into
+ * the input buffer.
+ * @throws ConnectionException If the swallowed bytes are expected to have a
+ * value of zero but do not
+ */
+ protected void swallowPayload(int streamId, int frameTypeId, int len, boolean isPadding, ByteBuffer byteBuffer)
+ throws IOException, ConnectionException {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("http2Parser.swallow.debug", connectionId,
+ Integer.toString(streamId), Integer.toString(len)));
+ }
+ try {
+ if (len == 0) {
+ return;
+ }
+ if (!isPadding && byteBuffer != null) {
+ byteBuffer.position(byteBuffer.position() + len);
+ } else {
+ int read = 0;
+ byte[] buffer = new byte[1024];
+ while (read < len) {
+ int thisTime = Math.min(buffer.length, len - read);
+ if (byteBuffer == null) {
+ input.fill(true, buffer, 0, thisTime);
+ } else {
+ byteBuffer.get(buffer, 0, thisTime);
+ }
+ if (isPadding) {
+ // Validate the padding is zero since receiving non-zero padding
+ // is a strong indication of either a faulty client or a server
+ // side bug.
+ for (int i = 0; i < thisTime; i++) {
+ if (buffer[i] != 0) {
+ throw new ConnectionException(sm.getString("http2Parser.nonZeroPadding",
+ connectionId, Integer.toString(streamId)), Http2Error.PROTOCOL_ERROR);
+ }
+ }
+ }
+ read += thisTime;
+ }
+ }
+ } finally {
+ if (FrameType.DATA.getIdByte() == frameTypeId) {
+ if (isPadding) {
+ // Need to add 1 for the padding length bytes that was also
+ // part of the payload.
+ len += 1;
+ }
+ if (len > 0) {
+ output.onSwallowedDataFramePayload(streamId, len);
+ }
+ }
+ }
+ }
+
+
+ protected void onHeadersComplete(int streamId) throws Http2Exception {
+ // Any left over data is a compression error
+ if (headerReadBuffer.position() > 0) {
+ throw new ConnectionException(
+ sm.getString("http2Parser.processFrameHeaders.decodingDataLeft"),
+ Http2Error.COMPRESSION_ERROR);
+ }
+
+ // Delay validation (and triggering any exception) until this point
+ // since all the headers still have to be read if a StreamException is
+ // going to be thrown.
+ hpackDecoder.getHeaderEmitter().validateHeaders();
+
+ synchronized (output) {
+ output.headersEnd(streamId);
+
+ if (headersEndStream) {
+ output.receivedEndOfStream(streamId);
+ headersEndStream = false;
+ }
+ }
+
+ // Reset size for new request if the buffer was previously expanded
+ if (headerReadBuffer.capacity() > Constants.DEFAULT_HEADER_READ_BUFFER_SIZE) {
+ headerReadBuffer = ByteBuffer.allocate(Constants.DEFAULT_HEADER_READ_BUFFER_SIZE);
+ }
+ }
+
+
+ /*
+ * Implementation note:
+ * Validation applicable to all incoming frames should be implemented here.
+ * Frame type specific validation should be performed in the appropriate
+ * readXxxFrame() method.
+ * For validation applicable to some but not all frame types, use your
+ * judgement.
+ */
+ protected void validateFrame(FrameType expected, FrameType frameType, int streamId, int flags,
+ int payloadSize) throws Http2Exception {
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("http2Parser.processFrame", connectionId,
+ Integer.toString(streamId), frameType, Integer.toString(flags),
+ Integer.toString(payloadSize)));
+ }
+
+ if (expected != null && frameType != expected) {
+ throw new StreamException(sm.getString("http2Parser.processFrame.unexpectedType",
+ expected, frameType), Http2Error.PROTOCOL_ERROR, streamId);
+ }
+
+ int maxFrameSize = input.getMaxFrameSize();
+ if (payloadSize > maxFrameSize) {
+ throw new ConnectionException(sm.getString("http2Parser.payloadTooBig",
+ Integer.toString(payloadSize), Integer.toString(maxFrameSize)),
+ Http2Error.FRAME_SIZE_ERROR);
+ }
+
+ if (headersCurrentStream != -1) {
+ if (headersCurrentStream != streamId) {
+ throw new ConnectionException(sm.getString("http2Parser.headers.wrongStream",
+ connectionId, Integer.toString(headersCurrentStream),
+ Integer.toString(streamId)), Http2Error.COMPRESSION_ERROR);
+ }
+ if (frameType == FrameType.RST) {
+ // NO-OP: RST is OK here
+ } else if (frameType != FrameType.CONTINUATION) {
+ throw new ConnectionException(sm.getString("http2Parser.headers.wrongFrameType",
+ connectionId, Integer.toString(headersCurrentStream),
+ frameType), Http2Error.COMPRESSION_ERROR);
+ }
+ }
+
+ frameType.check(streamId, payloadSize);
+ }
+
+
+ /**
+ * Read and validate the connection preface from input using blocking IO.
+ * @param webConnection The connection
+ * @param stream The current stream
+ */
+ void readConnectionPreface(WebConnection webConnection, Stream stream) throws Http2Exception {
+ byte[] data = new byte[CLIENT_PREFACE_START.length];
+ try {
+ input.fill(true, data);
+
+ for (int i = 0; i < CLIENT_PREFACE_START.length; i++) {
+ if (CLIENT_PREFACE_START[i] != data[i]) {
+ throw new ProtocolException(sm.getString("http2Parser.preface.invalid"));
+ }
+ }
+
+ // Must always be followed by a settings frame
+ readFrame(true, FrameType.SETTINGS);
+ } catch (IOException ioe) {
+ throw new ProtocolException(sm.getString("http2Parser.preface.io"), ioe);
+ }
+ }
+
+
+ /**
+ * Interface that must be implemented by the source of data for the parser.
+ */
+ static interface Input {
+
+ /**
+ * Fill the given array with data unless non-blocking is requested and
+ * no data is available. If any data is available then the buffer will
+ * be filled using blocking I/O.
+ *
+ * @param block Should the first read into the provided buffer be a
+ * blocking read or not.
+ * @param data Buffer to fill
+ * @param offset Position in buffer to start writing
+ * @param length Number of bytes to read
+ *
+ * @return true
if the buffer was filled otherwise
+ * false
+ *
+ * @throws IOException If an I/O occurred while obtaining data with
+ * which to fill the buffer
+ */
+ boolean fill(boolean block, byte[] data, int offset, int length) throws IOException;
+
+ default boolean fill(boolean block, byte[] data) throws IOException {
+ return fill(block, data, 0, data.length);
+ }
+
+ default boolean fill(boolean block, ByteBuffer data, int len) throws IOException {
+ boolean result = fill(block, data.array(), data.arrayOffset() + data.position(), len);
+ if (result) {
+ data.position(data.position() + len);
+ }
+ return result;
+ }
+
+ int getMaxFrameSize();
+ }
+
+
+ /**
+ * Interface that must be implemented to receive notifications from the
+ * parser as it processes incoming frames.
+ */
+ static interface Output {
+
+ HpackDecoder getHpackDecoder();
+
+ // Data frames
+ ByteBuffer startRequestBodyFrame(int streamId, int payloadSize, boolean endOfStream) throws Http2Exception;
+ void endRequestBodyFrame(int streamId, int dataLength) throws Http2Exception, IOException;
+ void receivedEndOfStream(int streamId) throws ConnectionException;
+ /**
+ * Notification triggered when the parser swallows some or all of a DATA
+ * frame payload without writing it to the ByteBuffer returned by
+ * {@link #startRequestBodyFrame(int, int, boolean)}.
+ *
+ * @param streamId The stream on which the payload that has been
+ * swallowed was received
+ * @param swallowedDataBytesCount The number of bytes that the parser
+ * swallowed.
+ *
+ * @throws ConnectionException If an error fatal to the HTTP/2
+ * connection occurs while swallowing the payload
+ * @throws IOException If an I/O occurred while swallowing the payload
+ */
+ void onSwallowedDataFramePayload(int streamId, int swallowedDataBytesCount) throws ConnectionException, IOException;
+
+ // Header frames
+ HeaderEmitter headersStart(int streamId, boolean headersEndStream)
+ throws Http2Exception, IOException;
+ void headersContinue(int payloadSize, boolean endOfHeaders);
+ void headersEnd(int streamId) throws Http2Exception;
+
+ // Priority frames (also headers)
+ void reprioritise(int streamId, int parentStreamId, boolean exclusive, int weight)
+ throws Http2Exception;
+
+ // Reset frames
+ void reset(int streamId, long errorCode) throws Http2Exception;
+
+ // Settings frames
+ void setting(Setting setting, long value) throws ConnectionException;
+ void settingsEnd(boolean ack) throws IOException;
+
+ // Ping frames
+ void pingReceive(byte[] payload, boolean ack) throws IOException;
+
+ // Goaway
+ void goaway(int lastStreamId, long errorCode, String debugData);
+
+ // Window size
+ void incrementWindowSize(int streamId, int increment) throws Http2Exception;
+
+ /**
+ * Notification triggered when the parser swallows the payload of an
+ * unknown frame.
+ *
+ * @param streamId The stream on which the swallowed frame was
+ * received
+ * @param frameTypeId The (unrecognised) type of swallowed frame
+ * @param flags The flags set in the header of the swallowed
+ * frame
+ * @param size The payload size of the swallowed frame
+ *
+ * @throws IOException If an I/O occurred while swallowing the unknown
+ * frame
+ */
+ void onSwallowedUnknownFrame(int streamId, int frameTypeId, int flags, int size) throws IOException;
+ }
+}