/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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 io.undertow.protocols.http2;

import static io.undertow.protocols.http2.Http2Channel.DATA_FLAG_END_STREAM;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_CONTINUATION;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_DATA;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_GOAWAY;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_HEADERS;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_PRIORITY;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_PUSH_PROMISE;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_RST_STREAM;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_SETTINGS;
import static io.undertow.protocols.http2.Http2Channel.FRAME_TYPE_WINDOW_UPDATE;
import static io.undertow.protocols.http2.Http2Channel.HEADERS_FLAG_END_HEADERS;
import static org.xnio.Bits.allAreClear;
import static org.xnio.Bits.anyAreClear;
import static org.xnio.Bits.anyAreSet;

import java.io.IOException;
import java.nio.ByteBuffer;

import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.server.protocol.framed.AbstractFramedStreamSourceChannel;
import io.undertow.server.protocol.framed.FrameHeaderData;

Author:Stuart Douglas
/** * @author Stuart Douglas */
class Http2FrameHeaderParser implements FrameHeaderData { final byte[] header = new byte[9]; int read = 0; int length; int type; int flags; int streamId; Http2PushBackParser parser = null; Http2HeadersParser continuationParser = null; private static final int SECOND_RESERVED_MASK = ~(1 << 7); private Http2Channel http2Channel; Http2FrameHeaderParser(Http2Channel http2Channel, Http2HeadersParser continuationParser) { this.http2Channel = http2Channel; this.continuationParser = continuationParser; } public boolean handle(final ByteBuffer byteBuffer) throws IOException { if (parser == null) { if (!parseFrameHeader(byteBuffer)) { return false; } if(continuationParser != null && type != FRAME_TYPE_CONTINUATION) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.expectedContinuationFrame()); } switch (type) { case FRAME_TYPE_DATA: { if (streamId == 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(Http2Channel.FRAME_TYPE_DATA)); } parser = new Http2DataFrameParser(length); break; } case FRAME_TYPE_HEADERS: { if (streamId == 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(Http2Channel.FRAME_TYPE_HEADERS)); } parser = new Http2HeadersParser(length, http2Channel.getDecoder(), http2Channel.isClient(), http2Channel.getMaxHeaders(), streamId, http2Channel.getMaxHeaderListSize()); if(allAreClear(flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { continuationParser = (Http2HeadersParser) parser; } break; } case FRAME_TYPE_RST_STREAM: { if(length != 4) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } parser = new Http2RstStreamParser(length); break; } case FRAME_TYPE_CONTINUATION: { if(continuationParser == null) { http2Channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); throw UndertowMessages.MESSAGES.http2ContinuationFrameNotExpected(); } if(continuationParser.getStreamId() != streamId) { http2Channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); throw UndertowMessages.MESSAGES.http2ContinuationFrameNotExpected(); } parser = continuationParser; continuationParser.moreData(length); break; } case FRAME_TYPE_PUSH_PROMISE: { parser = new Http2PushPromiseParser(length, http2Channel.getDecoder(), http2Channel.isClient(), http2Channel.getMaxHeaders(), streamId, http2Channel.getMaxHeaderListSize()); if(allAreClear(flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { continuationParser = (Http2HeadersParser) parser; } break; } case FRAME_TYPE_GOAWAY: { if (streamId != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_GOAWAY)); } parser = new Http2GoAwayParser(length); break; } case Http2Channel.FRAME_TYPE_PING: { if (length != 8) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.invalidPingSize()); } if (streamId != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_PING)); } parser = new Http2PingParser(length); break; } case FRAME_TYPE_SETTINGS: { if(length % 6 != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } if (streamId != 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustBeZeroForFrameType(Http2Channel.FRAME_TYPE_SETTINGS)); } parser = new Http2SettingsParser(length); break; } case FRAME_TYPE_WINDOW_UPDATE: { if(length != 4) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } parser = new Http2WindowUpdateParser(length); break; } case FRAME_TYPE_PRIORITY: { if(length != 5) { throw new ConnectionErrorException(Http2Channel.ERROR_FRAME_SIZE_ERROR, UndertowMessages.MESSAGES.incorrectFrameSize()); } if (streamId == 0) { throw new ConnectionErrorException(Http2Channel.ERROR_PROTOCOL_ERROR, UndertowMessages.MESSAGES.streamIdMustNotBeZeroForFrameType(Http2Channel.FRAME_TYPE_PRIORITY)); } parser = new Http2PriorityParser(length); break; } default: { parser = new Http2DiscardParser(length); break; } } } parser.parse(byteBuffer, this); if(continuationParser != null) { if(anyAreSet(flags, HEADERS_FLAG_END_HEADERS)) { continuationParser = null; } } return parser.isFinished(); } private boolean parseFrameHeader(ByteBuffer byteBuffer) { while (read < 9 && byteBuffer.hasRemaining()) { header[read++] = byteBuffer.get(); } if (read != 9) { return false; } length = (header[0] & 0xFF) << 16; length += (header[1] & 0xff) << 8; length += header[2] & 0xff; type = header[3] & 0xff; flags = header[4] & 0xff; streamId = (header[5] & SECOND_RESERVED_MASK & 0xFF) << 24; streamId += (header[6] & 0xFF) << 16; streamId += (header[7] & 0xFF) << 8; streamId += (header[8] & 0xFF); return true; } @Override public long getFrameLength() { //we only consider data frames to have length, all other frames are fully consumed by header parsing if (type != FRAME_TYPE_DATA) { return 0; } return length; } int getActualLength() { return length; } @Override public AbstractFramedStreamSourceChannel<?, ?, ?> getExistingChannel() { Http2StreamSourceChannel http2StreamSourceChannel; if (type == FRAME_TYPE_DATA || type == Http2Channel.FRAME_TYPE_CONTINUATION || type == Http2Channel.FRAME_TYPE_PRIORITY ) { if (anyAreSet(flags, Http2Channel.DATA_FLAG_END_STREAM)) { http2StreamSourceChannel = http2Channel.removeStreamSource(streamId); } else if (type == FRAME_TYPE_CONTINUATION) { http2StreamSourceChannel = http2Channel.getIncomingStream(streamId); if(http2StreamSourceChannel != null && http2StreamSourceChannel.isHeadersEndStream() && anyAreSet(flags, Http2Channel.CONTINUATION_FLAG_END_HEADERS)) { http2Channel.removeStreamSource(streamId); } } else { http2StreamSourceChannel = http2Channel.getIncomingStream(streamId); } if(type == FRAME_TYPE_DATA && http2StreamSourceChannel != null) { Http2DataFrameParser dataFrameParser = (Http2DataFrameParser) parser; http2StreamSourceChannel.updateContentSize(getFrameLength() - dataFrameParser.getPadding(), anyAreSet(flags, DATA_FLAG_END_STREAM)); } return http2StreamSourceChannel; } else if(type == FRAME_TYPE_HEADERS) { //headers can actually be a trailer Http2StreamSourceChannel channel = http2Channel.getIncomingStream(streamId); if(channel != null) { if(anyAreClear(flags, Http2Channel.HEADERS_FLAG_END_STREAM)) { //this is a protocol error UndertowLogger.REQUEST_IO_LOGGER.debug("Received HTTP/2 trailers header without end stream set"); http2Channel.sendGoAway(Http2Channel.ERROR_PROTOCOL_ERROR); } if (!channel.isHeadersEndStream() && anyAreSet(flags, Http2Channel.HEADERS_FLAG_END_HEADERS)) { http2Channel.removeStreamSource(streamId); } } return channel; } return null; } Http2PushBackParser getParser() { return parser; } Http2HeadersParser getContinuationParser() { return continuationParser; } }