From 1da95c974db3f57b8b2ec6756bb81ed2e1e7077a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 6 Jun 2014 16:32:31 +0200 Subject: [PATCH] Implemented parser and generator for HEADERS frame. --- jetty-http2/pom.xml | 10 + .../eclipse/jetty/http2/frames/DataFrame.java | 20 +- .../org/eclipse/jetty/http2/frames/Flag.java | 31 +++ .../org/eclipse/jetty/http2/frames/Frame.java | 25 ++ .../jetty/http2/frames/HeadersFrame.java | 37 +++ .../jetty/http2/generator/Generator.java | 175 +++++++------- .../jetty/http2/parser/BodyParser.java | 21 +- .../jetty/http2/parser/HeaderParser.java | 4 +- .../jetty/http2/parser/HeadersBodyParser.java | 216 ++++++++++++++++++ .../eclipse/jetty/http2/parser/Parser.java | 14 ++ .../jetty/http2/parser/PingBodyParser.java | 3 +- .../http2/parser/SettingsBodyParser.java | 3 +- .../http2/frames/DataGenerateParseTest.java | 14 +- .../http2/frames/GoAwayGenerateParseTest.java | 8 +- .../frames/HeadersGenerateParseTest.java | 34 +++ .../http2/frames/PingGenerateParseTest.java | 8 +- .../frames/PriorityGenerateParseTest.java | 8 +- .../http2/frames/ResetGenerateParseTest.java | 8 +- .../frames/SettingsGenerateParseTest.java | 14 +- .../frames/WindowUpdateGenerateParseTest.java | 8 +- 20 files changed, 531 insertions(+), 130 deletions(-) create mode 100644 jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Flag.java create mode 100644 jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Frame.java create mode 100644 jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java create mode 100644 jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java create mode 100644 jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java diff --git a/jetty-http2/pom.xml b/jetty-http2/pom.xml index f1178b35ded..9c5d3da7bbe 100644 --- a/jetty-http2/pom.xml +++ b/jetty-http2/pom.xml @@ -13,6 +13,16 @@ ${project.groupId}.http + + org.eclipse.jetty + jetty-http + ${project.version} + + + org.eclipse.jetty + jetty-hpack + ${project.version} + org.eclipse.jetty jetty-io diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/DataFrame.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/DataFrame.java index 84128a89ef1..9badd6fe9eb 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/DataFrame.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/DataFrame.java @@ -20,19 +20,17 @@ package org.eclipse.jetty.http2.frames; import java.nio.ByteBuffer; -public class DataFrame +public class DataFrame extends Frame { - public static final int MAX_LENGTH = 0x3F_FF; - private final int streamId; private final ByteBuffer data; - private boolean end; + private boolean endStream; - public DataFrame(int streamId, ByteBuffer data, boolean end) + public DataFrame(int streamId, ByteBuffer data, boolean endStream) { this.streamId = streamId; this.data = data; - this.end = end; + this.endStream = endStream; } public int getStreamId() @@ -40,13 +38,13 @@ public class DataFrame return streamId; } - public boolean isEnd() - { - return end; - } - public ByteBuffer getData() { return data; } + + public boolean isEndStream() + { + return endStream; + } } diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Flag.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Flag.java new file mode 100644 index 00000000000..f29256241c6 --- /dev/null +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Flag.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +public interface Flag +{ + public static final int END_STREAM = 0x01; + public static final int ACK = END_STREAM; + public static final int END_SEGMENT = 0x02; + public static final int END_HEADERS = 0x04; + public static final int PADDING_LOW = 0x08; + public static final int PADDING_HIGH = 0x10; + public static final int COMPRESS = 0x20; + public static final int PRIORITY = COMPRESS; +} diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Frame.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Frame.java new file mode 100644 index 00000000000..0d3bf269ab5 --- /dev/null +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/Frame.java @@ -0,0 +1,25 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +public abstract class Frame +{ + public static final int HEADER_LENGTH = 8; + public static final int MAX_LENGTH = 0x3F_FF; +} diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java new file mode 100644 index 00000000000..01df9ab37ac --- /dev/null +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/frames/HeadersFrame.java @@ -0,0 +1,37 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +import org.eclipse.jetty.http.HttpFields; + +public class HeadersFrame +{ + private final int streamId; + private final HttpFields fields; + private final PriorityFrame priority; + private final boolean endStream; + + public HeadersFrame(int streamId, HttpFields fields, PriorityFrame priority, boolean endStream) + { + this.streamId = streamId; + this.fields = fields; + this.priority = priority; + this.endStream = endStream; + } +} diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/generator/Generator.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/generator/Generator.java index 868ebec77cd..6fefe05c0f2 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/generator/Generator.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/generator/Generator.java @@ -19,11 +19,12 @@ package org.eclipse.jetty.http2.generator; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.hpack.HpackEncoder; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http2.frames.Flag; +import org.eclipse.jetty.http2.frames.Frame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; @@ -31,19 +32,21 @@ import org.eclipse.jetty.util.BufferUtil; public class Generator { private final ByteBufferPool byteBufferPool; + private final HpackEncoder encoder; public Generator(ByteBufferPool byteBufferPool) { this.byteBufferPool = byteBufferPool; + this.encoder = new HpackEncoder(byteBufferPool); } - public Result generateData(int streamId, ByteBuffer data, boolean last, boolean compress, byte[] paddingBytes) + public ByteBufferPool.Lease generateData(int streamId, ByteBuffer data, boolean last, boolean compress, byte[] paddingBytes) { if (streamId < 0) throw new IllegalArgumentException("Invalid stream id: " + streamId); int paddingLength = paddingBytes == null ? 0 : paddingBytes.length; // Leave space for at least one byte of content. - if (paddingLength > DataFrame.MAX_LENGTH - 3) + if (paddingLength > Frame.MAX_LENGTH - 3) throw new IllegalArgumentException("Invalid padding length: " + paddingLength); int extraPaddingBytes = paddingLength > 0xFF ? 2 : paddingLength > 0 ? 1 : 0; @@ -52,16 +55,16 @@ public class Generator int dataLength = data.remaining(); - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); // Can we fit just one frame ? - if (dataLength + extraPaddingBytes + paddingLength <= DataFrame.MAX_LENGTH) + if (dataLength + extraPaddingBytes + paddingLength <= Frame.MAX_LENGTH) { - generateData(result, streamId, data, last, compress, extraPaddingBytes, paddingBytes); + generateData(lease, streamId, data, last, compress, extraPaddingBytes, paddingBytes); } else { - int dataBytesPerFrame = DataFrame.MAX_LENGTH - extraPaddingBytes - paddingLength; + int dataBytesPerFrame = Frame.MAX_LENGTH - extraPaddingBytes - paddingLength; int frames = dataLength / dataBytesPerFrame; if (frames * dataBytesPerFrame != dataLength) { @@ -73,20 +76,69 @@ public class Generator data.limit(Math.min(dataBytesPerFrame * i, limit)); ByteBuffer slice = data.slice(); data.position(data.limit()); - generateData(result, streamId, slice, i == frames && last, compress, extraPaddingBytes, paddingBytes); + generateData(lease, streamId, slice, i == frames && last, compress, extraPaddingBytes, paddingBytes); } } - return result; + return lease; } - public Result generatePriority(int streamId, int dependentStreamId, int weight, boolean exclusive) + public ByteBufferPool.Lease generateHeaders(int streamId, HttpFields headers, boolean contentFollows, byte[] paddingBytes) + { + if (streamId < 0) + throw new IllegalArgumentException("Invalid stream id: " + streamId); + int paddingLength = paddingBytes == null ? 0 : paddingBytes.length; + // Leave space for at least one byte of content. + if (paddingLength > Frame.MAX_LENGTH - 3) + throw new IllegalArgumentException("Invalid padding length: " + paddingLength); + + int extraPaddingBytes = paddingLength > 0xFF ? 2 : paddingLength > 0 ? 1 : 0; + + ByteBufferPool.Lease hpackBuffers = encoder.encode(headers); + + long hpackLength = hpackBuffers.getTotalLength(); + + long length = extraPaddingBytes + hpackLength + paddingLength; + if (length > Frame.MAX_LENGTH) + throw new IllegalArgumentException("Invalid headers, too big"); + + int flags = Flag.END_HEADERS; + if (!contentFollows) + flags |= Flag.END_STREAM; + if (extraPaddingBytes > 0) + flags |= Flag.PADDING_LOW; + if (extraPaddingBytes > 1) + flags |= Flag.PADDING_HIGH; + + ByteBuffer header = generateHeader(FrameType.HEADERS, Frame.HEADER_LENGTH + extraPaddingBytes, (int)length, flags, streamId); + + if (extraPaddingBytes == 2) + header.putShort((short)paddingLength); + else if (extraPaddingBytes == 1) + header.put((byte)paddingLength); + + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + + BufferUtil.flipToFlush(header, 0); + lease.add(header, true); + + lease.merge(hpackBuffers); + + if (paddingBytes != null) + { + lease.add(ByteBuffer.wrap(paddingBytes), false); + } + + return lease; + } + + public ByteBufferPool.Lease generatePriority(int streamId, int dependentStreamId, int weight, boolean exclusive) { if (streamId < 0) throw new IllegalArgumentException("Invalid stream id: " + streamId); if (dependentStreamId < 0) throw new IllegalArgumentException("Invalid dependent stream id: " + dependentStreamId); - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); ByteBuffer header = generateHeader(FrameType.PRIORITY, 5, 0, dependentStreamId); @@ -98,31 +150,31 @@ public class Generator header.put((byte)weight); BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - return result; + return lease; } - public Result generateReset(int streamId, int error) + public ByteBufferPool.Lease generateReset(int streamId, int error) { if (streamId < 0) throw new IllegalArgumentException("Invalid stream id: " + streamId); - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); ByteBuffer header = generateHeader(FrameType.RST_STREAM, 4, 0, streamId); header.putInt(error); BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - return result; + return lease; } - public Result generateSettings(Map settings, boolean reply) + public ByteBufferPool.Lease generateSettings(Map settings, boolean reply) { - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); ByteBuffer header = generateHeader(FrameType.SETTINGS, 5 * settings.size(), reply ? 0x01 : 0x00, 0); @@ -133,34 +185,34 @@ public class Generator } BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - return result; + return lease; } - public Result generatePing(byte[] payload, boolean reply) + public ByteBufferPool.Lease generatePing(byte[] payload, boolean reply) { if (payload.length != 8) throw new IllegalArgumentException("Invalid payload length: " + payload.length); - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); ByteBuffer header = generateHeader(FrameType.PING, 8, reply ? 0x01 : 0x00, 0); header.put(payload); BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - return result; + return lease; } - public Result generateGoAway(int lastStreamId, int error, byte[] payload) + public ByteBufferPool.Lease generateGoAway(int lastStreamId, int error, byte[] payload) { if (lastStreamId < 0) throw new IllegalArgumentException("Invalid last stream id: " + lastStreamId); - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); int length = 4 + 4 + (payload != null ? payload.length : 0); ByteBuffer header = generateHeader(FrameType.GO_AWAY, length, 0, 0); @@ -174,46 +226,46 @@ public class Generator } BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - return result; + return lease; } - public Result generateWindowUpdate(int streamId, int windowUpdate) + public ByteBufferPool.Lease generateWindowUpdate(int streamId, int windowUpdate) { if (streamId < 0) throw new IllegalArgumentException("Invalid stream id: " + streamId); if (windowUpdate < 0) throw new IllegalArgumentException("Invalid window update: " + windowUpdate); - Result result = new Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); ByteBuffer header = generateHeader(FrameType.WINDOW_UPDATE, 4, 0, streamId); header.putInt(windowUpdate); BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - return result; + return lease; } - private void generateData(Result result, int streamId, ByteBuffer data, boolean last, boolean compress, int extraPaddingBytes, byte[] paddingBytes) + private void generateData(ByteBufferPool.Lease lease, int streamId, ByteBuffer data, boolean last, boolean compress, int extraPaddingBytes, byte[] paddingBytes) { int paddingLength = paddingBytes == null ? 0 : paddingBytes.length; int length = extraPaddingBytes + data.remaining() + paddingLength; int flags = 0; if (last) - flags |= 0x01; + flags |= Flag.END_STREAM; if (extraPaddingBytes > 0) - flags |= 0x08; + flags |= Flag.PADDING_LOW; if (extraPaddingBytes > 1) - flags |= 0x10; + flags |= Flag.PADDING_HIGH; if (compress) - flags |= 0x20; + flags |= Flag.COMPRESS; - ByteBuffer header = generateHeader(FrameType.DATA, 8 + extraPaddingBytes, length, flags, streamId); + ByteBuffer header = generateHeader(FrameType.DATA, Frame.HEADER_LENGTH + extraPaddingBytes, length, flags, streamId); if (extraPaddingBytes == 2) header.putShort((short)paddingLength); @@ -221,19 +273,19 @@ public class Generator header.put((byte)paddingLength); BufferUtil.flipToFlush(header, 0); - result.add(header, true); + lease.add(header, true); - result.add(data, false); + lease.add(data, false); if (paddingBytes != null) { - result.add(ByteBuffer.wrap(paddingBytes), false); + lease.add(ByteBuffer.wrap(paddingBytes), false); } } private ByteBuffer generateHeader(FrameType frameType, int length, int flags, int streamId) { - return generateHeader(frameType, 8 + length, length, flags, streamId); + return generateHeader(frameType, Frame.HEADER_LENGTH + length, length, flags, streamId); } private ByteBuffer generateHeader(FrameType frameType, int capacity, int length, int flags, int streamId) @@ -248,37 +300,4 @@ public class Generator return header; } - - public static class Result - { - private final ByteBufferPool byteBufferPool; - private final List buffers; - private final List recycles; - - public Result(ByteBufferPool byteBufferPool) - { - this.byteBufferPool = byteBufferPool; - this.buffers = new ArrayList<>(); - this.recycles = new ArrayList<>(); - } - - public void add(ByteBuffer buffer, boolean recycle) - { - buffers.add(buffer); - recycles.add(recycle); - } - - public List getByteBuffers() - { - return buffers; - } - - public Result merge(Result that) - { - assert byteBufferPool == that.byteBufferPool; - buffers.addAll(that.buffers); - recycles.addAll(that.recycles); - return this; - } - } } diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java index 1d9040c152f..51fcfda05b9 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/BodyParser.java @@ -21,7 +21,9 @@ package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.Flag; import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PingFrame; import org.eclipse.jetty.http2.frames.PriorityFrame; import org.eclipse.jetty.http2.frames.ResetFrame; @@ -58,17 +60,17 @@ public abstract class BodyParser protected boolean isPaddingHigh() { - return headerParser.hasFlag(0x10); + return headerParser.hasFlag(Flag.PADDING_HIGH); } protected boolean isPaddingLow() { - return headerParser.hasFlag(0x8); + return headerParser.hasFlag(Flag.PADDING_LOW); } protected boolean isEndStream() { - return headerParser.hasFlag(0x1); + return headerParser.hasFlag(Flag.END_STREAM); } protected int getStreamId() @@ -94,6 +96,19 @@ public abstract class BodyParser } } + protected boolean notifyHeaders(HeadersFrame frame) + { + try + { + return listener.onHeaders(frame); + } + catch (Throwable x) + { + LOG.info("Failure while notifying listener " + listener, x); + return false; + } + } + protected boolean notifyPriority(PriorityFrame frame) { try diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java index fe72e1c2962..00088233665 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeaderParser.java @@ -20,7 +20,7 @@ package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; -import org.eclipse.jetty.http2.frames.DataFrame; +import org.eclipse.jetty.http2.frames.Frame; public class HeaderParser { @@ -64,7 +64,7 @@ public class HeaderParser if (++cursor == 2) { // First 2 most significant bits MUST be ignored as per specification. - length &= DataFrame.MAX_LENGTH; + length &= Frame.MAX_LENGTH; state = State.TYPE; } break; diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java new file mode 100644 index 00000000000..0d92dd40bd3 --- /dev/null +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/HeadersBodyParser.java @@ -0,0 +1,216 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.parser; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http2.frames.Flag; +import org.eclipse.jetty.http2.frames.HeadersFrame; +import org.eclipse.jetty.http2.frames.PriorityFrame; + +public class HeadersBodyParser extends BodyParser +{ + private State state = State.PREPARE; + private int cursor; + private int length; + private int paddingLength; + private boolean exclusive; + private int streamId; + private int weight; + private HttpFields fields; + + public HeadersBodyParser(HeaderParser headerParser, Parser.Listener listener) + { + super(headerParser, listener); + } + + private void reset() + { + state = State.PREPARE; + cursor = 0; + length = 0; + paddingLength = 0; + exclusive = false; + streamId = 0; + weight = 0; + fields = null; + } + + @Override + public Result parse(ByteBuffer buffer) + { + while (buffer.hasRemaining()) + { + switch (state) + { + case PREPARE: + { + length = getBodyLength(); + fields = new HttpFields(); + if (isPaddingHigh()) + { + state = State.PADDING_HIGH; + } + else if (isPaddingLow()) + { + state = State.PADDING_LOW; + } + else if (hasFlag(Flag.PRIORITY)) + { + state = State.EXCLUSIVE; + } + else + { + state = State.HEADERS; + } + break; + } + case PADDING_HIGH: + { + paddingLength = (buffer.get() & 0xFF) << 8; + length -= 1; + if (length < 1 + 256) + { + return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame_padding"); + } + state = State.PADDING_LOW; + break; + } + case PADDING_LOW: + { + paddingLength += buffer.get() & 0xFF; + length -= 1; + if (length < paddingLength) + { + return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame_padding"); + } + length -= paddingLength; + state = hasFlag(Flag.PRIORITY) ? State.EXCLUSIVE : State.HEADERS; + break; + } + case EXCLUSIVE: + { + // We must only peek the first byte and not advance the buffer + // because the 31 least significant bits represent the stream id. + int currByte = buffer.get(buffer.position()); + exclusive = (currByte & 0x80) == 0x80; + state = State.STREAM_ID; + break; + } + case STREAM_ID: + { + if (buffer.remaining() >= 4) + { + streamId = buffer.getInt(); + streamId &= 0x7F_FF_FF_FF; + length -= 4; + state = State.WEIGHT; + if (length < 1) + { + return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame"); + } + } + else + { + state = State.STREAM_ID_BYTES; + cursor = 4; + } + break; + } + case STREAM_ID_BYTES: + { + int currByte = buffer.get() & 0xFF; + --cursor; + streamId += currByte << (8 * cursor); + --length; + if (cursor > 0 && length <= 0) + { + return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame"); + } + if (cursor == 0) + { + streamId &= 0x7F_FF_FF_FF; + state = State.WEIGHT; + if (length <= 0) + { + return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame"); + } + } + break; + } + case WEIGHT: + { + weight = buffer.get() & 0xFF; + --length; + state = State.HEADERS; + if (length <= 0) + { + return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame"); + } + break; + } + case HEADERS: + { + // TODO: use HpackDecoder + + state = State.PADDING; + if (onHeaders(streamId, weight, exclusive, fields)) + { + return Result.ASYNC; + } + break; + } + case PADDING: + { + int size = Math.min(buffer.remaining(), paddingLength); + buffer.position(buffer.position() + size); + paddingLength -= size; + if (paddingLength == 0) + { + reset(); + return Result.COMPLETE; + } + break; + } + default: + { + throw new IllegalStateException(); + } + } + } + return Result.PENDING; + } + + private boolean onHeaders(int streamId, int weight, boolean exclusive, HttpFields fields) + { + PriorityFrame priorityFrame = null; + if (hasFlag(Flag.PRIORITY)) + { + priorityFrame = new PriorityFrame(streamId, getStreamId(), weight, exclusive); + } + HeadersFrame frame = new HeadersFrame(getStreamId(), fields, priorityFrame, isEndStream()); + return notifyHeaders(frame); + } + + private enum State + { + PREPARE, PADDING_HIGH, PADDING_LOW, EXCLUSIVE, STREAM_ID, STREAM_ID_BYTES, WEIGHT, HEADERS, PADDING + } +} diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/Parser.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/Parser.java index 01281c21bca..bca7252f306 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/Parser.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/Parser.java @@ -23,6 +23,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http2.frames.DataFrame; import org.eclipse.jetty.http2.frames.FrameType; import org.eclipse.jetty.http2.frames.GoAwayFrame; +import org.eclipse.jetty.http2.frames.HeadersFrame; import org.eclipse.jetty.http2.frames.PingFrame; import org.eclipse.jetty.http2.frames.PriorityFrame; import org.eclipse.jetty.http2.frames.ResetFrame; @@ -44,12 +45,17 @@ public class Parser { this.listener = listener; bodyParsers[FrameType.DATA.getType()] = new DataBodyParser(headerParser, listener); + bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener); bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener); bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener); bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener); + bodyParsers[FrameType.PUSH_PROMISE.getType()] = null; // TODO bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener); bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener); bodyParsers[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateBodyParser(headerParser, listener); + bodyParsers[FrameType.CONTINUATION.getType()] = null; // TODO + bodyParsers[FrameType.ALTSVC.getType()] = null; // TODO + bodyParsers[FrameType.BLOCKED.getType()] = null; // TODO } private void reset() @@ -142,6 +148,8 @@ public class Parser { public boolean onData(DataFrame frame); + public boolean onHeaders(HeadersFrame frame); + public boolean onPriority(PriorityFrame frame); public boolean onReset(ResetFrame frame); @@ -164,6 +172,12 @@ public class Parser return false; } + @Override + public boolean onHeaders(HeadersFrame frame) + { + return false; + } + @Override public boolean onPriority(PriorityFrame frame) { diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java index 108f98314b0..746d29deaaa 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/PingBodyParser.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.http2.parser; import java.nio.ByteBuffer; +import org.eclipse.jetty.http2.frames.Flag; import org.eclipse.jetty.http2.frames.PingFrame; public class PingBodyParser extends BodyParser @@ -93,7 +94,7 @@ public class PingBodyParser extends BodyParser private Result onPing(byte[] payload) { - PingFrame frame = new PingFrame(payload, hasFlag(0x1)); + PingFrame frame = new PingFrame(payload, hasFlag(Flag.ACK)); reset(); return notifyPing(frame) ? Result.ASYNC : Result.COMPLETE; } diff --git a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java index 7f613960fe1..d1649fc4dbd 100644 --- a/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java +++ b/jetty-http2/src/main/java/org/eclipse/jetty/http2/parser/SettingsBodyParser.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import org.eclipse.jetty.http2.frames.Flag; import org.eclipse.jetty.http2.frames.SettingsFrame; public class SettingsBodyParser extends BodyParser @@ -132,7 +133,7 @@ public class SettingsBodyParser extends BodyParser private Result onSettings(Map settings) { - SettingsFrame frame = new SettingsFrame(settings, hasFlag(0x1)); + SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flag.ACK)); reset(); return notifySettings(frame) ? Result.ASYNC : Result.COMPLETE; } diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java index 8f0b2ce1730..1d1256b5fea 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java @@ -91,7 +91,7 @@ public class DataGenerateParseTest Assert.assertEquals(1, frames.size()); DataFrame frame = frames.get(0); Assert.assertTrue(frame.getStreamId() != 0); - Assert.assertTrue(frame.isEnd()); + Assert.assertTrue(frame.isEndStream()); Assert.assertEquals(content, frame.getData()); } @@ -123,7 +123,7 @@ public class DataGenerateParseTest { DataFrame frame = frames.get(i - 1); Assert.assertTrue(frame.getStreamId() != 0); - Assert.assertEquals(i == frames.size(), frame.isEnd()); + Assert.assertEquals(i == frames.size(), frame.isEndStream()); aggregate.put(frame.getData()); } aggregate.flip(); @@ -138,10 +138,10 @@ public class DataGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = new Generator.Result(byteBufferPool); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); for (int j = 1; j <= data.length; ++j) { - result = result.merge(generator.generateData(13, data[j - 1].slice(), j == data.length, false, new byte[paddingLength])); + lease = lease.merge(generator.generateData(13, data[j - 1].slice(), j == data.length, false, new byte[paddingLength])); } Parser parser = new Parser(new Parser.Listener.Adapter() @@ -155,7 +155,7 @@ public class DataGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { parser.parse(buffer); } @@ -169,7 +169,7 @@ public class DataGenerateParseTest { Generator generator = new Generator(byteBufferPool); - Generator.Result result = generator.generateData(13, ByteBuffer.wrap(largeContent).slice(), true, false, new byte[1024]); + ByteBufferPool.Lease lease = generator.generateData(13, ByteBuffer.wrap(largeContent).slice(), true, false, new byte[1024]); final List frames = new ArrayList<>(); Parser parser = new Parser(new Parser.Listener.Adapter() @@ -182,7 +182,7 @@ public class DataGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java index dfc363d1ed6..21f7ad9e8d7 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java @@ -46,7 +46,7 @@ public class GoAwayGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = generator.generateGoAway(lastStreamId, error, null); + ByteBufferPool.Lease lease = generator.generateGoAway(lastStreamId, error, null); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -58,7 +58,7 @@ public class GoAwayGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -85,7 +85,7 @@ public class GoAwayGenerateParseTest new Random().nextBytes(payload); final List frames = new ArrayList<>(); - Generator.Result result = generator.generateGoAway(lastStreamId, error, payload); + ByteBufferPool.Lease lease = generator.generateGoAway(lastStreamId, error, payload); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -96,7 +96,7 @@ public class GoAwayGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java new file mode 100644 index 00000000000..0ec83ca16ec --- /dev/null +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java @@ -0,0 +1,34 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.http2.frames; + +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.MappedByteBufferPool; +import org.junit.Test; + +public class HeadersGenerateParseTest +{ + private final ByteBufferPool byteBufferPool = new MappedByteBufferPool(); + + @Test + public void testGenerateParse() throws Exception + { + // TODO + } +} diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java index 63f2b0adf21..a5b8e27bbc8 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java @@ -46,7 +46,7 @@ public class PingGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = generator.generatePing(payload, true); + ByteBufferPool.Lease lease = generator.generatePing(payload, true); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -58,7 +58,7 @@ public class PingGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -82,7 +82,7 @@ public class PingGenerateParseTest new Random().nextBytes(payload); final List frames = new ArrayList<>(); - Generator.Result result = generator.generatePing(payload, true); + ByteBufferPool.Lease lease = generator.generatePing(payload, true); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -93,7 +93,7 @@ public class PingGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java index dcffe449e6a..e094bdd5685 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java @@ -47,7 +47,7 @@ public class PriorityGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = generator.generatePriority(streamId, dependentStreamId, weight, exclusive); + ByteBufferPool.Lease lease = generator.generatePriority(streamId, dependentStreamId, weight, exclusive); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -59,7 +59,7 @@ public class PriorityGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -87,7 +87,7 @@ public class PriorityGenerateParseTest boolean exclusive = true; final List frames = new ArrayList<>(); - Generator.Result result = generator.generatePriority(streamId, dependentStreamId, weight, exclusive); + ByteBufferPool.Lease lease = generator.generatePriority(streamId, dependentStreamId, weight, exclusive); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -98,7 +98,7 @@ public class PriorityGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java index 2f946031466..69f27bd1135 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java @@ -45,7 +45,7 @@ public class ResetGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = generator.generateReset(streamId, error); + ByteBufferPool.Lease lease = generator.generateReset(streamId, error); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -57,7 +57,7 @@ public class ResetGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -81,7 +81,7 @@ public class ResetGenerateParseTest int error = 17; final List frames = new ArrayList<>(); - Generator.Result result = generator.generateReset(streamId, error); + ByteBufferPool.Lease lease = generator.generateReset(streamId, error); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -92,7 +92,7 @@ public class ResetGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java index f52f383b3e9..9e07554ad31 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java @@ -75,7 +75,7 @@ public class SettingsGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = generator.generateSettings(settings, true); + ByteBufferPool.Lease lease = generator.generateSettings(settings, true); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -87,7 +87,7 @@ public class SettingsGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -105,9 +105,9 @@ public class SettingsGenerateParseTest Generator generator = new Generator(byteBufferPool); Map settings1 = new HashMap<>(); settings1.put(13, 17); - Generator.Result result = generator.generateSettings(settings1, true); + ByteBufferPool.Lease lease = generator.generateSettings(settings1, true); // Modify the length of the frame to make it invalid - ByteBuffer bytes = result.getByteBuffers().get(0); + ByteBuffer bytes = lease.getByteBuffers().get(0); bytes.putShort(0, (short)(bytes.getShort(0) - 1)); final AtomicInteger errorRef = new AtomicInteger(); @@ -120,7 +120,7 @@ public class SettingsGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -142,7 +142,7 @@ public class SettingsGenerateParseTest settings1.put(key, value); final List frames = new ArrayList<>(); - Generator.Result result = generator.generateSettings(settings1, true); + ByteBufferPool.Lease lease = generator.generateSettings(settings1, true); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -153,7 +153,7 @@ public class SettingsGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { diff --git a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java index cfb9816af6e..273b59a148f 100644 --- a/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java +++ b/jetty-http2/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java @@ -45,7 +45,7 @@ public class WindowUpdateGenerateParseTest final List frames = new ArrayList<>(); for (int i = 0; i < 2; ++i) { - Generator.Result result = generator.generateWindowUpdate(streamId, windowUpdate); + ByteBufferPool.Lease lease = generator.generateWindowUpdate(streamId, windowUpdate); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -57,7 +57,7 @@ public class WindowUpdateGenerateParseTest }); frames.clear(); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) { @@ -81,7 +81,7 @@ public class WindowUpdateGenerateParseTest int windowUpdate = 17; final List frames = new ArrayList<>(); - Generator.Result result = generator.generateWindowUpdate(streamId, windowUpdate); + ByteBufferPool.Lease lease = generator.generateWindowUpdate(streamId, windowUpdate); Parser parser = new Parser(new Parser.Listener.Adapter() { @Override @@ -92,7 +92,7 @@ public class WindowUpdateGenerateParseTest } }); - for (ByteBuffer buffer : result.getByteBuffers()) + for (ByteBuffer buffer : lease.getByteBuffers()) { while (buffer.hasRemaining()) {