Updated implementation to draft-14.

This commit is contained in:
Simone Bordet 2014-08-01 13:32:56 +02:00
parent 97d723c516
commit 6b6267ed31
23 changed files with 119 additions and 114 deletions

View File

@ -176,6 +176,7 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
return false;
Map<Integer, Integer> settings = frame.getSettings();
// TODO: handle other settings
if (settings.containsKey(SettingsFrame.MAX_CONCURRENT_STREAMS))
{
maxStreamCount = settings.get(SettingsFrame.MAX_CONCURRENT_STREAMS);
@ -187,7 +188,17 @@ public abstract class HTTP2Session implements ISession, Parser.Listener
int windowSize = settings.get(SettingsFrame.INITIAL_WINDOW_SIZE);
flowControl.updateInitialWindowSize(this, windowSize);
}
// TODO: handle other settings
if (settings.containsKey(SettingsFrame.MAX_FRAME_SIZE))
{
int maxFrameSize = settings.get(SettingsFrame.MAX_FRAME_SIZE);
// Spec: check the max frame size is sane.
if (maxFrameSize < Frame.DEFAULT_MAX_LENGTH || maxFrameSize > Frame.MAX_MAX_LENGTH)
{
onConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_settings_max_frame_size");
return false;
}
generator.setMaxFrameSize(maxFrameSize);
}
notifySettings(this, frame);
// SPEC: SETTINGS frame MUST be replied.

View File

@ -22,11 +22,8 @@ public interface Flag
{
public static final int NONE = 0x00;
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 ACK = 0x01;
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;
public static final int PADDING = 0x08;
public static final int PRIORITY = 0x20;
}

View File

@ -20,8 +20,9 @@ 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;
public static final int HEADER_LENGTH = 9;
public static final int DEFAULT_MAX_LENGTH = 0x40_00;
public static final int MAX_MAX_LENGTH = 0xFF_FF_FF;
private final FrameType type;

View File

@ -32,9 +32,7 @@ public enum FrameType
PING(6),
GO_AWAY(7),
WINDOW_UPDATE(8),
CONTINUATION(9),
ALTSVC(10),
BLOCKED(11);
CONTINUATION(9);
public static FrameType from(int type)
{

View File

@ -26,6 +26,7 @@ public class SettingsFrame extends Frame
public static final int ENABLE_PUSH = 2;
public static final int MAX_CONCURRENT_STREAMS = 3;
public static final int INITIAL_WINDOW_SIZE = 4;
public static final int MAX_FRAME_SIZE = 5;
private final Map<Integer, Integer> settings;
private final boolean reply;

View File

@ -48,7 +48,8 @@ public class DataGenerator
throw new IllegalArgumentException("Invalid stream id: " + streamId);
int dataLength = data.remaining();
if (dataLength <= maxLength && dataLength <= Frame.MAX_LENGTH)
int maxFrameSize = headerGenerator.getMaxFrameSize();
if (dataLength <= maxLength && dataLength <= maxFrameSize)
{
// Single frame.
generateFrame(lease, streamId, data, last);
@ -58,16 +59,15 @@ public class DataGenerator
// Other cases, we need to slice the original buffer into multiple frames.
int length = Math.min(maxLength, dataLength);
int dataBytesPerFrame = Frame.MAX_LENGTH;
int frames = length / dataBytesPerFrame;
if (frames * dataBytesPerFrame != length)
int frames = length / maxFrameSize;
if (frames * maxFrameSize != length)
++frames;
int begin = data.position();
int end = data.limit();
for (int i = 1; i <= frames; ++i)
{
int limit = begin + Math.min(dataBytesPerFrame * i, length);
int limit = begin + Math.min(maxFrameSize * i, length);
data.limit(limit);
ByteBuffer slice = data.slice();
data.position(limit);

View File

@ -39,4 +39,9 @@ public abstract class FrameGenerator
{
return headerGenerator.generate(lease, frameType, Frame.HEADER_LENGTH + length, length, flags, streamId);
}
public int getMaxFrameSize()
{
return headerGenerator.getMaxFrameSize();
}
}

View File

@ -28,6 +28,7 @@ public class Generator
{
private final ByteBufferPool byteBufferPool;
private final int headerTableSize;
private final HeaderGenerator headerGenerator;
private final FrameGenerator[] generators;
private final DataGenerator dataGenerator;
@ -41,7 +42,7 @@ public class Generator
this.byteBufferPool = byteBufferPool;
this.headerTableSize = headerTableSize;
HeaderGenerator headerGenerator = new HeaderGenerator();
headerGenerator = new HeaderGenerator();
HpackEncoder encoder = new HpackEncoder(headerTableSize);
this.generators = new FrameGenerator[FrameType.values().length];
@ -54,8 +55,6 @@ public class Generator
this.generators[FrameType.GO_AWAY.getType()] = new GoAwayGenerator(headerGenerator);
this.generators[FrameType.WINDOW_UPDATE.getType()] = new WindowUpdateGenerator(headerGenerator);
this.generators[FrameType.CONTINUATION.getType()] = null; // TODO
this.generators[FrameType.ALTSVC.getType()] = null; // TODO
this.generators[FrameType.BLOCKED.getType()] = null; // TODO
this.dataGenerator = new DataGenerator(headerGenerator);
}
@ -70,6 +69,11 @@ public class Generator
return headerTableSize;
}
public void setMaxFrameSize(int maxFrameSize)
{
headerGenerator.setMaxFrameSize(maxFrameSize);
}
public void control(ByteBufferPool.Lease lease, Frame frame)
{
generators[frame.getType().getType()].generate(lease, frame);

View File

@ -50,8 +50,8 @@ public class GoAwayGenerator extends FrameGenerator
// The last streamId + the error code.
int fixedLength = 4 + 4;
// Make sure we don't exceed the frame max length.
int maxPayloadLength = Frame.MAX_LENGTH - fixedLength;
// Make sure we don't exceed the default frame max length.
int maxPayloadLength = Frame.DEFAULT_MAX_LENGTH - fixedLength;
if (payload != null && payload.length > maxPayloadLength)
payload = Arrays.copyOfRange(payload, 0, maxPayloadLength);

View File

@ -20,18 +20,33 @@ package org.eclipse.jetty.http2.generator;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.io.ByteBufferPool;
public class HeaderGenerator
{
private int maxFrameSize = Frame.DEFAULT_MAX_LENGTH;
public ByteBuffer generate(ByteBufferPool.Lease lease, FrameType frameType, int capacity, int length, int flags, int streamId)
{
ByteBuffer header = lease.acquire(capacity, true);
header.putShort((short)length);
header.put((byte)((length & 0x00_FF_00_00) >>> 16));
header.put((byte)((length & 0x00_00_FF_00) >>> 8));
header.put((byte)((length & 0x00_00_00_FF)));
header.put((byte)frameType.getType());
header.put((byte)flags);
header.putInt(streamId);
return header;
}
public int getMaxFrameSize()
{
return maxFrameSize;
}
public void setMaxFrameSize(int maxFrameSize)
{
this.maxFrameSize = maxFrameSize;
}
}

View File

@ -51,10 +51,11 @@ public class HeadersGenerator extends FrameGenerator
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);
encoder.encode(metaData, lease,Frame.MAX_LENGTH);
int maxFrameSize = getMaxFrameSize();
encoder.encode(metaData, lease, maxFrameSize);
long length = lease.getTotalLength();
if (length > Frame.MAX_LENGTH)
if (length > maxFrameSize)
throw new IllegalArgumentException("Invalid headers, too big");
int flags = Flag.END_HEADERS;

View File

@ -53,13 +53,14 @@ public class PushPromiseGenerator extends FrameGenerator
if (promisedStreamId < 0)
throw new IllegalArgumentException("Invalid promised stream id: " + promisedStreamId);
encoder.encode(metaData, lease, Frame.MAX_LENGTH);
int maxFrameSize = getMaxFrameSize();
encoder.encode(metaData, lease, maxFrameSize);
// The promised streamId.
int fixedLength = 4;
long length = lease.getTotalLength();
if (length > Frame.MAX_LENGTH - fixedLength)
if (length > maxFrameSize - fixedLength)
throw new IllegalArgumentException("Invalid headers, too big");
// Space for the promised streamId.

View File

@ -44,17 +44,17 @@ public class SettingsGenerator extends FrameGenerator
public void generateSettings(ByteBufferPool.Lease lease, Map<Integer, Integer> settings, boolean reply)
{
// One byte for the identifier, 4 bytes for the value.
int entryLength = 1 + 4;
// Two bytes for the identifier, four bytes for the value.
int entryLength = 2 + 4;
int length = entryLength * settings.size();
if (length > Frame.MAX_LENGTH)
if (length > getMaxFrameSize())
throw new IllegalArgumentException("Invalid settings, too big");
ByteBuffer header = generateHeader(lease, FrameType.SETTINGS, length, reply ? Flag.ACK : Flag.NONE, 0);
for (Map.Entry<Integer, Integer> entry : settings.entrySet())
{
header.put(entry.getKey().byteValue());
header.putShort(entry.getKey().shortValue());
header.putInt(entry.getValue());
}

View File

@ -59,14 +59,9 @@ public abstract class BodyParser
return headerParser.hasFlag(bit);
}
protected boolean isPaddingHigh()
protected boolean isPadding()
{
return headerParser.hasFlag(Flag.PADDING_HIGH);
}
protected boolean isPaddingLow()
{
return headerParser.hasFlag(Flag.PADDING_LOW);
return headerParser.hasFlag(Flag.PADDING);
}
protected boolean isEndStream()

View File

@ -44,7 +44,7 @@ public class DataBodyParser extends BodyParser
@Override
protected boolean emptyBody()
{
if (isPaddingHigh() || isPaddingLow())
if (isPadding())
{
notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_data_frame");
return false;
@ -68,13 +68,9 @@ public class DataBodyParser extends BodyParser
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_data_frame");
}
length = getBodyLength();
if (isPaddingHigh())
if (isPadding())
{
state = State.PADDING_HIGH;
}
else if (isPaddingLow())
{
state = State.PADDING_LOW;
state = State.PADDING_LENGTH;
}
else
{
@ -82,20 +78,9 @@ public class DataBodyParser extends BodyParser
}
break;
}
case PADDING_HIGH:
case PADDING_LENGTH:
{
paddingLength = (buffer.get() & 0xFF) << 8;
--length;
if (length < 1 + 256)
{
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_data_frame_padding");
}
state = State.PADDING_LOW;
break;
}
case PADDING_LOW:
{
paddingLength += buffer.get() & 0xFF;
paddingLength = buffer.get() & 0xFF;
--length;
length -= paddingLength;
state = State.DATA;
@ -128,7 +113,6 @@ public class DataBodyParser extends BodyParser
}
else
{
// TODO: check the semantic of Flag.END_SEGMENT.
// We got partial data, simulate a smaller frame, and stay in DATA state.
if (onData(slice, true))
{
@ -166,6 +150,6 @@ public class DataBodyParser extends BodyParser
private enum State
{
PREPARE, PADDING_HIGH, PADDING_LOW, DATA, PADDING
PREPARE, PADDING_LENGTH, DATA, PADDING
}
}

View File

@ -59,12 +59,11 @@ public class HeaderParser
{
case LENGTH:
{
int halfShort = buffer.get() & 0xFF;
length = (length << 8) + halfShort;
if (++cursor == 2)
int octect = buffer.get() & 0xFF;
length = (length << 8) + octect;
if (++cursor == 3)
{
// First 2 most significant bits MUST be ignored as per specification.
length &= Frame.MAX_LENGTH;
length &= Frame.MAX_MAX_LENGTH;
state = State.TYPE;
}
break;

View File

@ -87,13 +87,9 @@ public class HeadersBodyParser extends BodyParser
length = getBodyLength();
if (isPaddingHigh())
if (isPadding())
{
state = State.PADDING_HIGH;
}
else if (isPaddingLow())
{
state = State.PADDING_LOW;
state = State.PADDING_LENGTH;
}
else if (hasFlag(Flag.PRIORITY))
{
@ -105,20 +101,9 @@ public class HeadersBodyParser extends BodyParser
}
break;
}
case PADDING_HIGH:
case PADDING_LENGTH:
{
paddingLength = (buffer.get() & 0xFF) << 8;
--length;
state = State.PADDING_LOW;
if (length < 1 + 256)
{
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame_padding");
}
break;
}
case PADDING_LOW:
{
paddingLength += buffer.get() & 0xFF;
paddingLength = buffer.get() & 0xFF;
--length;
length -= paddingLength;
state = hasFlag(Flag.PRIORITY) ? State.EXCLUSIVE : State.HEADERS;
@ -235,6 +220,6 @@ public class HeadersBodyParser extends BodyParser
private enum State
{
PREPARE, PADDING_HIGH, PADDING_LOW, EXCLUSIVE, STREAM_ID, STREAM_ID_BYTES, WEIGHT, HEADERS, PADDING
PREPARE, PADDING_LENGTH, EXCLUSIVE, STREAM_ID, STREAM_ID_BYTES, WEIGHT, HEADERS, PADDING
}
}

View File

@ -62,8 +62,6 @@ public class Parser
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()

View File

@ -72,13 +72,9 @@ public class PushPromiseBodyParser extends BodyParser
length = getBodyLength();
if (isPaddingHigh())
if (isPadding())
{
state = State.PADDING_HIGH;
}
else if (isPaddingLow())
{
state = State.PADDING_LOW;
state = State.PADDING_LENGTH;
}
else
{
@ -86,26 +82,15 @@ public class PushPromiseBodyParser extends BodyParser
}
break;
}
case PADDING_HIGH:
case PADDING_LENGTH:
{
paddingLength = (buffer.get() & 0xFF) << 8;
--length;
state = State.PADDING_LOW;
if (length < 1 + 256)
{
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_push_promise_frame_padding");
}
break;
}
case PADDING_LOW:
{
paddingLength += buffer.get() & 0xFF;
paddingLength = buffer.get() & 0xFF;
--length;
length -= paddingLength;
state = State.STREAM_ID;
if (length < 4)
{
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_push_promise_frame_padding");
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_push_promise_frame");
}
break;
}
@ -187,6 +172,6 @@ public class PushPromiseBodyParser extends BodyParser
private enum State
{
PREPARE, PADDING_HIGH, PADDING_LOW, STREAM_ID, STREAM_ID_BYTES, HEADERS, PADDING
PREPARE, PADDING_LENGTH, STREAM_ID, STREAM_ID_BYTES, HEADERS, PADDING
}
}

View File

@ -76,13 +76,38 @@ public class SettingsBodyParser extends BodyParser
}
case SETTING_ID:
{
settingId = buffer.get() & 0xFF;
if (buffer.remaining() >= 2)
{
settingId = buffer.getShort() & 0xFF_FF;
state = State.SETTING_VALUE;
length -= 2;
if (length <= 0)
{
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_settings_frame");
}
}
else
{
cursor = 2;
settingId = 0;
state = State.SETTING_ID_BYTES;
}
break;
}
case SETTING_ID_BYTES:
{
int currByte = buffer.get() & 0xFF;
--cursor;
settingId += currByte << (8 * cursor);
--length;
if (length <= 0)
{
return notifyConnectionFailure(ErrorCode.PROTOCOL_ERROR, "invalid_settings_frame");
}
if (cursor == 0)
{
state = State.SETTING_VALUE;
}
break;
}
case SETTING_VALUE:
@ -145,6 +170,6 @@ public class SettingsBodyParser extends BodyParser
private enum State
{
PREPARE, SETTING_ID, SETTING_VALUE, SETTING_VALUE_BYTES
PREPARE, SETTING_ID, SETTING_ID_BYTES, SETTING_VALUE, SETTING_VALUE_BYTES
}
}

View File

@ -72,7 +72,7 @@ public class DataGenerateParseTest
{
ByteBuffer content = ByteBuffer.wrap(largeContent);
List<DataFrame> frames = testGenerateParse(content);
Assert.assertEquals(9, frames.size());
Assert.assertEquals(8, frames.size());
ByteBuffer aggregate = ByteBuffer.allocate(content.remaining());
for (int i = 1; i <= frames.size(); ++i)
{

View File

@ -123,7 +123,7 @@ public class SettingsGenerateParseTest
generator.generateSettings(lease, settings1, true);
// Modify the length of the frame to make it invalid
ByteBuffer bytes = lease.getByteBuffers().get(0);
bytes.putShort(0, (short)(bytes.getShort(0) - 1));
bytes.putShort(1, (short)(bytes.getShort(1) - 1));
for (ByteBuffer buffer : lease.getByteBuffers())
{

View File

@ -42,7 +42,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
public AbstractHTTP2ServerConnectionFactory()
{
super("h2-12");
super("h2-14");
}
public int getMaxHeaderTableSize()