SPDY: remove frame size limit in frame decoder
This commit is contained in:
parent
3a7ed4b75c
commit
69d5be4225
@ -26,7 +26,6 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame {
|
||||
|
||||
private int streamID;
|
||||
private boolean last;
|
||||
private boolean compressed;
|
||||
private ChannelBuffer data = ChannelBuffers.EMPTY_BUFFER;
|
||||
|
||||
/**
|
||||
@ -58,14 +57,6 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
public boolean isCompressed() {
|
||||
return compressed;
|
||||
}
|
||||
|
||||
public void setCompressed(boolean compressed) {
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
public ChannelBuffer getData() {
|
||||
return data;
|
||||
}
|
||||
@ -87,8 +78,6 @@ public class DefaultSpdyDataFrame implements SpdyDataFrame {
|
||||
buf.append(getClass().getSimpleName());
|
||||
buf.append("(last: ");
|
||||
buf.append(isLast());
|
||||
buf.append("; compressed: ");
|
||||
buf.append(isCompressed());
|
||||
buf.append(')');
|
||||
buf.append(StringUtil.NEWLINE);
|
||||
buf.append("--> Stream-ID = ");
|
||||
|
@ -28,8 +28,7 @@ final class SpdyCodecUtil {
|
||||
|
||||
static final int SPDY_MAX_LENGTH = 0xFFFFFF; // Length is a 24-bit field
|
||||
|
||||
static final byte SPDY_DATA_FLAG_FIN = 0x01;
|
||||
static final byte SPDY_DATA_FLAG_COMPRESS = 0x02;
|
||||
static final byte SPDY_DATA_FLAG_FIN = 0x01;
|
||||
|
||||
static final int SPDY_SYN_STREAM_FRAME = 1;
|
||||
static final int SPDY_SYN_REPLY_FRAME = 2;
|
||||
@ -91,37 +90,37 @@ final class SpdyCodecUtil {
|
||||
* Reads a big-endian unsigned short integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedShort(ChannelBuffer buf, int offset) {
|
||||
return (int) ((buf.getByte(offset) & 0xFF) << 8 |
|
||||
(buf.getByte(offset + 1) & 0xFF));
|
||||
return (buf.getByte(offset) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 1) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian unsigned medium integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedMedium(ChannelBuffer buf, int offset) {
|
||||
return (int) ((buf.getByte(offset) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 8 |
|
||||
(buf.getByte(offset + 2) & 0xFF));
|
||||
return (buf.getByte(offset) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 2) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian (31-bit) integer from the buffer.
|
||||
*/
|
||||
static int getUnsignedInt(ChannelBuffer buf, int offset) {
|
||||
return (int) ((buf.getByte(offset) & 0x7F) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
(buf.getByte(offset + 3) & 0xFF));
|
||||
return (buf.getByte(offset) & 0x7F) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 3) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a big-endian signed integer from the buffer.
|
||||
*/
|
||||
static int getSignedInt(ChannelBuffer buf, int offset) {
|
||||
return (int) ((buf.getByte(offset) & 0xFF) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
(buf.getByte(offset + 3) & 0xFF));
|
||||
return (buf.getByte(offset) & 0xFF) << 24 |
|
||||
(buf.getByte(offset + 1) & 0xFF) << 16 |
|
||||
(buf.getByte(offset + 2) & 0xFF) << 8 |
|
||||
buf.getByte(offset + 3) & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,16 +44,6 @@ public interface SpdyDataFrame {
|
||||
*/
|
||||
void setLast(boolean last);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the data in this frame has been compressed.
|
||||
*/
|
||||
boolean isCompressed();
|
||||
|
||||
/**
|
||||
* Sets if the data in this frame has been compressed.
|
||||
*/
|
||||
void setCompressed(boolean compressed);
|
||||
|
||||
/**
|
||||
* Returns the data payload of this frame. If there is no data payload
|
||||
* {@link ChannelBuffers#EMPTY_BUFFER} is returned.
|
||||
|
@ -33,21 +33,21 @@ public class SpdyFrameCodec implements ChannelUpstreamHandler,
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default decoder and encoder options
|
||||
* ({@code maxChunkSize (8192)}, {@code maxFrameSize (65536)},
|
||||
* {@code maxHeaderSize (16384)}, {@code compressionLevel (6)},
|
||||
* {@code windowBits (15)}, and {@code memLevel (8)}).
|
||||
* ({@code maxChunkSize (8192)}, {@code maxHeaderSize (16384)},
|
||||
* {@code compressionLevel (6)}, {@code windowBits (15)}, and
|
||||
* {@code memLevel (8)}).
|
||||
*/
|
||||
public SpdyFrameCodec() {
|
||||
this(8192, 65536, 16384, 6, 15, 8);
|
||||
this(8192, 16384, 6, 15, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified decoder and encoder options.
|
||||
*/
|
||||
public SpdyFrameCodec(
|
||||
int maxChunkSize, int maxFrameSize, int maxHeaderSize,
|
||||
int maxChunkSize, int maxHeaderSize,
|
||||
int compressionLevel, int windowBits, int memLevel) {
|
||||
decoder = new SpdyFrameDecoder(maxChunkSize, maxFrameSize, maxHeaderSize);
|
||||
decoder = new SpdyFrameDecoder(maxChunkSize, maxHeaderSize);
|
||||
encoder = new SpdyFrameEncoder(compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
|
||||
|
@ -15,13 +15,15 @@
|
||||
*/
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.Channels;
|
||||
import io.netty.handler.codec.frame.FrameDecoder;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import io.netty.handler.codec.frame.TooLongFrameException;
|
||||
|
||||
/**
|
||||
* Decodes {@link ChannelBuffer}s into SPDY Data and Control Frames.
|
||||
@ -29,41 +31,62 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
public class SpdyFrameDecoder extends FrameDecoder {
|
||||
|
||||
private final int maxChunkSize;
|
||||
private final int maxFrameSize;
|
||||
private final int maxHeaderSize;
|
||||
|
||||
private final SpdyHeaderBlockDecompressor headerBlockDecompressor =
|
||||
SpdyHeaderBlockDecompressor.newInstance();
|
||||
|
||||
private State state;
|
||||
private SpdySettingsFrame spdySettingsFrame;
|
||||
private SpdyHeaderBlock spdyHeaderBlock;
|
||||
|
||||
// SPDY common header fields
|
||||
private byte flags;
|
||||
private int length;
|
||||
private int version;
|
||||
private int type;
|
||||
private int streamID;
|
||||
|
||||
// Header block decoding fields
|
||||
private int headerSize;
|
||||
private int numHeaders;
|
||||
private ChannelBuffer decompressed;
|
||||
|
||||
private static enum State {
|
||||
READ_COMMON_HEADER,
|
||||
READ_CONTROL_FRAME,
|
||||
READ_SETTINGS_FRAME,
|
||||
READ_HEADER_BLOCK_FRAME,
|
||||
READ_HEADER_BLOCK,
|
||||
READ_DATA_FRAME,
|
||||
DISCARD_FRAME,
|
||||
FRAME_ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default {@code maxChunkSize (8192)},
|
||||
* {@code maxFrameSize (65536)}, and {@code maxHeaderSize (16384)}.
|
||||
* Creates a new instance with the default {@code maxChunkSize (8192)}
|
||||
* and {@code maxHeaderSize (16384)}.
|
||||
*/
|
||||
public SpdyFrameDecoder() {
|
||||
this(8192, 65536, 16384);
|
||||
this(8192, 16384);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameDecoder(
|
||||
int maxChunkSize, int maxFrameSize, int maxHeaderSize) {
|
||||
super(true); // Enable unfold for data frames
|
||||
public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) {
|
||||
super(false);
|
||||
if (maxChunkSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxChunkSize must be a positive integer: " + maxChunkSize);
|
||||
}
|
||||
if (maxFrameSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxFrameSize must be a positive integer: " + maxFrameSize);
|
||||
}
|
||||
if (maxHeaderSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxHeaderSize must be a positive integer: " + maxHeaderSize);
|
||||
}
|
||||
this.maxChunkSize = maxChunkSize;
|
||||
this.maxFrameSize = maxFrameSize;
|
||||
this.maxHeaderSize = maxHeaderSize;
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -78,310 +101,470 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object decode(
|
||||
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
|
||||
throws Exception {
|
||||
|
||||
// Must read common header to determine frame length
|
||||
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get frame length from common header
|
||||
int frameOffset = buffer.readerIndex();
|
||||
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
|
||||
int dataLength = getUnsignedMedium(buffer, lengthOffset);
|
||||
int frameLength = SPDY_HEADER_SIZE + dataLength;
|
||||
|
||||
// Throw exception if frameLength exceeds maxFrameSize
|
||||
if (frameLength > maxFrameSize) {
|
||||
throw new SpdyProtocolException(
|
||||
"Frame length exceeds " + maxFrameSize + ": " + frameLength);
|
||||
}
|
||||
|
||||
// Wait until entire frame is readable
|
||||
if (buffer.readableBytes() < frameLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read common header fields
|
||||
boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
|
||||
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
|
||||
byte flags = buffer.getByte(flagsOffset);
|
||||
|
||||
if (control) {
|
||||
// Decode control frame common header
|
||||
int version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
|
||||
|
||||
// Spdy versioning spec is broken
|
||||
if (version != SPDY_VERSION) {
|
||||
buffer.skipBytes(frameLength);
|
||||
throw new SpdyProtocolException(
|
||||
"Unsupported version: " + version);
|
||||
}
|
||||
|
||||
int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
|
||||
int type = getUnsignedShort(buffer, typeOffset);
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
int readerIndex = buffer.readerIndex();
|
||||
buffer.skipBytes(dataLength);
|
||||
return decodeControlFrame(type, flags, buffer.slice(readerIndex, dataLength));
|
||||
} else {
|
||||
// Decode data frame common header
|
||||
int streamID = getUnsignedInt(buffer, frameOffset);
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
// Generate data frames that do not exceed maxChunkSize
|
||||
int numFrames = dataLength / maxChunkSize;
|
||||
if (dataLength % maxChunkSize != 0) {
|
||||
numFrames ++;
|
||||
}
|
||||
SpdyDataFrame[] frames = new SpdyDataFrame[numFrames];
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
int chunkSize = Math.min(maxChunkSize, dataLength);
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
spdyDataFrame.setCompressed((flags & SPDY_DATA_FLAG_COMPRESS) != 0);
|
||||
spdyDataFrame.setData(buffer.readBytes(chunkSize));
|
||||
dataLength -= chunkSize;
|
||||
if (dataLength == 0) {
|
||||
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
|
||||
switch(state) {
|
||||
case READ_COMMON_HEADER:
|
||||
state = readCommonHeader(buffer);
|
||||
if (state == State.FRAME_ERROR) {
|
||||
if (version != SPDY_VERSION) {
|
||||
fireProtocolException(ctx, "Unsupported version: " + version);
|
||||
} else {
|
||||
fireInvalidControlFrameException(ctx);
|
||||
}
|
||||
frames[i] = spdyDataFrame;
|
||||
}
|
||||
return null;
|
||||
|
||||
case READ_CONTROL_FRAME:
|
||||
try {
|
||||
Object frame = readControlFrame(buffer);
|
||||
if (frame != null) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return frame;
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
}
|
||||
return null;
|
||||
|
||||
case READ_SETTINGS_FRAME:
|
||||
if (spdySettingsFrame == null) {
|
||||
// Validate frame length against number of entries
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
int numEntries = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// Each ID/Value entry is 8 bytes
|
||||
if ((length & 0x07) != 0 || length >> 3 != numEntries) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
spdySettingsFrame = new DefaultSpdySettingsFrame();
|
||||
|
||||
boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
|
||||
spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
}
|
||||
|
||||
private Object decodeControlFrame(int type, byte flags, ChannelBuffer data)
|
||||
throws Exception {
|
||||
int streamID;
|
||||
boolean last;
|
||||
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
if (data.readableBytes() < 12) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SYN_STREAM control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
int associatedToStreamID = getUnsignedInt(data, data.readerIndex() + 4);
|
||||
byte priority = (byte) (data.getByte(data.readerIndex() + 8) >> 6 & 0x03);
|
||||
data.skipBytes(10);
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
|
||||
|
||||
last = (flags & SPDY_FLAG_FIN) != 0;
|
||||
boolean unid = (flags & SPDY_FLAG_UNIDIRECTIONAL) != 0;
|
||||
spdySynStreamFrame.setLast(last);
|
||||
spdySynStreamFrame.setUnidirectional(unid);
|
||||
|
||||
decodeHeaderBlock(spdySynStreamFrame, data);
|
||||
|
||||
return spdySynStreamFrame;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
if (data.readableBytes() < 8) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SYN_REPLY control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
data.skipBytes(6);
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame =
|
||||
new DefaultSpdySynReplyFrame(streamID);
|
||||
|
||||
last = (flags & SPDY_FLAG_FIN) != 0;
|
||||
spdySynReplyFrame.setLast(last);
|
||||
|
||||
decodeHeaderBlock(spdySynReplyFrame, data);
|
||||
|
||||
return spdySynReplyFrame;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
if (flags != 0 || data.readableBytes() != 8) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid RST_STREAM control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
int statusCode = getSignedInt(data, data.readerIndex() + 4);
|
||||
if (statusCode == 0) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid RST_STREAM status code");
|
||||
}
|
||||
|
||||
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
if (data.readableBytes() < 4) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SETTINGS control frame");
|
||||
}
|
||||
// Each ID/Value entry is 8 bytes
|
||||
// The number of entries cannot exceed SPDY_MAX_LENGTH / 8;
|
||||
int numEntries = getUnsignedInt(data, data.readerIndex());
|
||||
if ((numEntries > (SPDY_MAX_LENGTH - 4) / 8) ||
|
||||
(data.readableBytes() != numEntries * 8 + 4)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid SETTINGS control frame");
|
||||
}
|
||||
data.skipBytes(4);
|
||||
|
||||
SpdySettingsFrame spdySettingsFrame = new DefaultSpdySettingsFrame();
|
||||
|
||||
boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
|
||||
spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
|
||||
|
||||
for (int i = 0; i < numEntries; i ++) {
|
||||
int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
|
||||
for (int i = 0; i < readableEntries; i ++) {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Read id assuming the architecture is little endian
|
||||
int ID = (data.readByte() & 0xFF) |
|
||||
(data.readByte() & 0xFF) << 8 |
|
||||
(data.readByte() & 0xFF) << 16;
|
||||
byte ID_flags = data.readByte();
|
||||
int value = getSignedInt(data, data.readerIndex());
|
||||
data.skipBytes(4);
|
||||
int ID = buffer.readByte() & 0xFF |
|
||||
(buffer.readByte() & 0xFF) << 8 |
|
||||
(buffer.readByte() & 0xFF) << 16;
|
||||
byte ID_flags = buffer.readByte();
|
||||
int value = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
if (!(spdySettingsFrame.isSet(ID))) {
|
||||
// Check for invalid ID -- avoid IllegalArgumentException in setValue
|
||||
if (ID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
spdySettingsFrame = null;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!spdySettingsFrame.isSet(ID)) {
|
||||
boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0;
|
||||
boolean persisted = (ID_flags & SPDY_SETTINGS_PERSISTED) != 0;
|
||||
spdySettingsFrame.setValue(ID, value, persistVal, persisted);
|
||||
}
|
||||
}
|
||||
|
||||
return spdySettingsFrame;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
if (data.readableBytes() != 0) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid NOOP control frame");
|
||||
length -= 8 * readableEntries;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
Object frame = spdySettingsFrame;
|
||||
spdySettingsFrame = null;
|
||||
return frame;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
if (data.readableBytes() != 4) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid PING control frame");
|
||||
case READ_HEADER_BLOCK_FRAME:
|
||||
try {
|
||||
spdyHeaderBlock = readHeaderBlockFrame(buffer);
|
||||
if (spdyHeaderBlock != null) {
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
Object frame = spdyHeaderBlock;
|
||||
spdyHeaderBlock = null;
|
||||
return frame;
|
||||
}
|
||||
state = State.READ_HEADER_BLOCK;
|
||||
}
|
||||
return null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
return null;
|
||||
}
|
||||
int ID = getSignedInt(data, data.readerIndex());
|
||||
|
||||
case READ_HEADER_BLOCK:
|
||||
int compressedBytes = Math.min(buffer.readableBytes(), length);
|
||||
length -= compressedBytes;
|
||||
|
||||
try {
|
||||
decodeHeaderBlock(buffer.readSlice(compressedBytes));
|
||||
} catch (Exception e) {
|
||||
state = State.FRAME_ERROR;
|
||||
spdyHeaderBlock = null;
|
||||
decompressed = null;
|
||||
Channels.fireExceptionCaught(ctx, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (spdyHeaderBlock != null && spdyHeaderBlock.isInvalid()) {
|
||||
Object frame = spdyHeaderBlock;
|
||||
spdyHeaderBlock = null;
|
||||
decompressed = null;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
Object frame = spdyHeaderBlock;
|
||||
spdyHeaderBlock = null;
|
||||
state = State.READ_COMMON_HEADER;
|
||||
return frame;
|
||||
}
|
||||
return null;
|
||||
|
||||
case READ_DATA_FRAME:
|
||||
if (streamID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireProtocolException(ctx, "Received invalid data frame");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generate data frames that do not exceed maxChunkSize
|
||||
int dataLength = Math.min(maxChunkSize, length);
|
||||
|
||||
// Wait until entire frame is readable
|
||||
if (buffer.readableBytes() < dataLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
spdyDataFrame.setData(buffer.readBytes(dataLength));
|
||||
length -= dataLength;
|
||||
|
||||
if (length == 0) {
|
||||
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return spdyDataFrame;
|
||||
|
||||
case DISCARD_FRAME:
|
||||
int numBytes = Math.min(buffer.readableBytes(), length);
|
||||
buffer.skipBytes(numBytes);
|
||||
length -= numBytes;
|
||||
if (length == 0) {
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return null;
|
||||
|
||||
case FRAME_ERROR:
|
||||
buffer.skipBytes(buffer.readableBytes());
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
|
||||
private State readCommonHeader(ChannelBuffer buffer) {
|
||||
// Wait until entire header is readable
|
||||
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
|
||||
return State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
int frameOffset = buffer.readerIndex();
|
||||
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
|
||||
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
|
||||
buffer.skipBytes(SPDY_HEADER_SIZE);
|
||||
|
||||
// Read common header fields
|
||||
boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
|
||||
flags = buffer.getByte(flagsOffset);
|
||||
length = getUnsignedMedium(buffer, lengthOffset);
|
||||
|
||||
if (control) {
|
||||
// Decode control frame common header
|
||||
version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
|
||||
|
||||
int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
|
||||
type = getUnsignedShort(buffer, typeOffset);
|
||||
|
||||
// Check version first then validity
|
||||
if (version != SPDY_VERSION || !isValidControlFrameHeader()) {
|
||||
return State.FRAME_ERROR;
|
||||
}
|
||||
|
||||
// Make sure decoder will produce a frame or consume input
|
||||
State nextState;
|
||||
if (willGenerateControlFrame()) {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_HEADERS_FRAME:
|
||||
nextState = State.READ_HEADER_BLOCK_FRAME;
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
nextState = State.READ_SETTINGS_FRAME;
|
||||
break;
|
||||
|
||||
default:
|
||||
nextState = State.READ_CONTROL_FRAME;
|
||||
}
|
||||
} else if (length != 0) {
|
||||
nextState = State.DISCARD_FRAME;
|
||||
} else {
|
||||
nextState = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return nextState;
|
||||
} else {
|
||||
// Decode data frame common header
|
||||
streamID = getUnsignedInt(buffer, frameOffset);
|
||||
|
||||
return State.READ_DATA_FRAME;
|
||||
}
|
||||
}
|
||||
|
||||
private Object readControlFrame(ChannelBuffer buffer) {
|
||||
switch (type) {
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int ID = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
return new DefaultSpdyPingFrame(ID);
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
if (data.readableBytes() != 4) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid GOAWAY control frame");
|
||||
if (buffer.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
int lastGoodStreamID = getUnsignedInt(data, data.readerIndex());
|
||||
|
||||
int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
|
||||
return new DefaultSpdyGoAwayFrame(lastGoodStreamID);
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
// Protocol allows length 4 frame when there are no name/value pairs
|
||||
if (data.readableBytes() == 4) {
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
return new DefaultSpdyHeadersFrame(streamID);
|
||||
}
|
||||
|
||||
if (data.readableBytes() < 8) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid HEADERS control frame");
|
||||
}
|
||||
streamID = getUnsignedInt(data, data.readerIndex());
|
||||
data.skipBytes(6);
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
|
||||
|
||||
decodeHeaderBlock(spdyHeadersFrame, data);
|
||||
|
||||
return spdyHeadersFrame;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureBytes(ChannelBuffer decompressed, int bytes) throws Exception {
|
||||
private SpdyHeaderBlock readHeaderBlockFrame(ChannelBuffer buffer) {
|
||||
int streamID;
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
if (buffer.readableBytes() < 12) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int offset = buffer.readerIndex();
|
||||
streamID = getUnsignedInt(buffer, offset);
|
||||
int associatedToStreamID = getUnsignedInt(buffer, offset + 4);
|
||||
byte priority = (byte) (buffer.getByte(offset + 8) >> 6 & 0x03);
|
||||
buffer.skipBytes(10);
|
||||
length -= 10;
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
|
||||
spdySynStreamFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
spdySynStreamFrame.setUnidirectional((flags & SPDY_FLAG_UNIDIRECTIONAL) != 0);
|
||||
|
||||
return spdySynStreamFrame;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
if (buffer.readableBytes() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(6);
|
||||
length -= 6;
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (length == 2 && buffer.getShort(buffer.readerIndex()) == 0) {
|
||||
buffer.skipBytes(2);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
|
||||
return spdySynReplyFrame;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
// Protocol allows length 4 frame when there are no name/value pairs
|
||||
int minLength = length == 4 ? 4 : 8;
|
||||
if (buffer.readableBytes() < minLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
// SPDY/2 requires 16-bits of padding for empty header blocks
|
||||
if (length == 4 && buffer.getShort(buffer.readerIndex() + 2) == 0) {
|
||||
buffer.skipBytes(4);
|
||||
length = 0;
|
||||
} else if (length != 0) {
|
||||
buffer.skipBytes(2);
|
||||
length -= 2;
|
||||
}
|
||||
|
||||
return new DefaultSpdyHeadersFrame(streamID);
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureBytes(int bytes) throws Exception {
|
||||
if (decompressed.readableBytes() >= bytes) {
|
||||
return true;
|
||||
}
|
||||
decompressed.discardReadBytes();
|
||||
// Perhaps last call to decode filled output buffer
|
||||
headerBlockDecompressor.decode(decompressed);
|
||||
return decompressed.readableBytes() >= bytes;
|
||||
}
|
||||
|
||||
private void decodeHeaderBlock(SpdyHeaderBlock headerFrame, ChannelBuffer headerBlock)
|
||||
throws Exception {
|
||||
if ((headerBlock.readableBytes() == 2) &&
|
||||
(headerBlock.getShort(headerBlock.readerIndex()) == 0)) {
|
||||
private int readLengthField() {
|
||||
return decompressed.readUnsignedShort();
|
||||
}
|
||||
|
||||
private void decodeHeaderBlock(ChannelBuffer buffer) throws Exception {
|
||||
if (decompressed == null) {
|
||||
// First time we start to decode a header block
|
||||
// Initialize header block decoding fields
|
||||
headerSize = 0;
|
||||
numHeaders = -1;
|
||||
decompressed = ChannelBuffers.dynamicBuffer(8192);
|
||||
}
|
||||
|
||||
// Accumulate decompressed data
|
||||
headerBlockDecompressor.setInput(buffer);
|
||||
headerBlockDecompressor.decode(decompressed);
|
||||
|
||||
if (spdyHeaderBlock == null) {
|
||||
// Only decompressing data to keep decompression context in sync
|
||||
decompressed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
headerBlockDecompressor.setInput(headerBlock);
|
||||
ChannelBuffer decompressed = ChannelBuffers.dynamicBuffer(8192);
|
||||
headerBlockDecompressor.decode(decompressed);
|
||||
int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields
|
||||
|
||||
if (decompressed.readableBytes() < 2) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
}
|
||||
int headerSize = 0;
|
||||
int numEntries = decompressed.readUnsignedShort();
|
||||
for (int i = 0; i < numEntries; i ++) {
|
||||
if (!ensureBytes(decompressed, 2)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
if (numHeaders == -1) {
|
||||
// Read number of Name/Value pairs
|
||||
if (decompressed.readableBytes() < lengthFieldSize) {
|
||||
return;
|
||||
}
|
||||
int nameLength = decompressed.readUnsignedShort();
|
||||
numHeaders = readLengthField();
|
||||
}
|
||||
|
||||
while (numHeaders > 0) {
|
||||
int headerSize = this.headerSize;
|
||||
decompressed.markReaderIndex();
|
||||
|
||||
// Try to read length of name
|
||||
if (!ensureBytes(lengthFieldSize)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int nameLength = readLengthField();
|
||||
|
||||
// Recipients of a zero-length name must issue a stream error
|
||||
if (nameLength == 0) {
|
||||
headerFrame.setInvalid();
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
headerSize += nameLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new SpdyProtocolException(
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
if (!ensureBytes(decompressed, nameLength)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
|
||||
// Try to read name
|
||||
if (!ensureBytes(nameLength)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
decompressed.readBytes(nameBytes);
|
||||
String name = new String(nameBytes, "UTF-8");
|
||||
if (headerFrame.containsHeader(name)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received duplicate header name: " + name);
|
||||
|
||||
// Check for identically named headers
|
||||
if (spdyHeaderBlock.containsHeader(name)) {
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
if (!ensureBytes(decompressed, 2)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
|
||||
// Try to read length of value
|
||||
if (!ensureBytes(lengthFieldSize)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int valueLength = decompressed.readUnsignedShort();
|
||||
int valueLength = readLengthField();
|
||||
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
if (valueLength == 0) {
|
||||
headerFrame.setInvalid();
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
headerSize += valueLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new SpdyProtocolException(
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
if (!ensureBytes(decompressed, valueLength)) {
|
||||
throw new SpdyProtocolException(
|
||||
"Received invalid header block");
|
||||
|
||||
// Try to read value
|
||||
if (!ensureBytes(valueLength)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
decompressed.readBytes(valueBytes);
|
||||
|
||||
// Add Name/Value pair to headers
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
while (index < valueLength) {
|
||||
@ -390,14 +573,121 @@ public class SpdyFrameDecoder extends FrameDecoder {
|
||||
}
|
||||
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
|
||||
// Received multiple, in-sequence NULL characters
|
||||
headerFrame.setInvalid();
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
String value = new String(valueBytes, offset, index - offset, "UTF-8");
|
||||
headerFrame.addHeader(name, value);
|
||||
|
||||
try {
|
||||
spdyHeaderBlock.addHeader(name, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Name contains NULL or non-ascii characters
|
||||
spdyHeaderBlock.setInvalid();
|
||||
return;
|
||||
}
|
||||
index ++;
|
||||
offset = index;
|
||||
}
|
||||
numHeaders --;
|
||||
this.headerSize = headerSize;
|
||||
}
|
||||
decompressed = null;
|
||||
}
|
||||
|
||||
private boolean isValidControlFrameHeader() {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
return length >= 12;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
return length >= 8;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
return flags == 0 && length == 8;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
return length >= 4;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
return length == 0;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
return length == 4;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
return length == 4;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
return length == 4 || length >= 8;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean willGenerateControlFrame() {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
case SPDY_PING_FRAME:
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
case SPDY_HEADERS_FRAME:
|
||||
return true;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void fireInvalidControlFrameException(ChannelHandlerContext ctx) {
|
||||
String message = "Received invalid control frame";
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
message = "Received invalid SYN_STREAM control frame";
|
||||
break;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
message = "Received invalid SYN_REPLY control frame";
|
||||
break;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
message = "Received invalid RST_STREAM control frame";
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
message = "Received invalid SETTINGS control frame";
|
||||
break;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
message = "Received invalid NOOP control frame";
|
||||
break;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
message = "Received invalid PING control frame";
|
||||
break;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
message = "Received invalid GOAWAY control frame";
|
||||
break;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
message = "Received invalid HEADERS control frame";
|
||||
break;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
message = "Received invalid WINDOW_UPDATE control frame";
|
||||
break;
|
||||
}
|
||||
fireProtocolException(ctx, message);
|
||||
}
|
||||
|
||||
private static void fireProtocolException(ChannelHandlerContext ctx, String message) {
|
||||
Channels.fireExceptionCaught(ctx, new SpdyProtocolException(message));
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +82,6 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
ChannelBuffer data = spdyDataFrame.getData();
|
||||
byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
|
||||
if (spdyDataFrame.isCompressed()) {
|
||||
flags |= SPDY_DATA_FLAG_COMPRESS;
|
||||
}
|
||||
ChannelBuffer header = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
|
||||
header.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF);
|
||||
@ -102,7 +99,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
flags |= SPDY_FLAG_UNIDIRECTIONAL;
|
||||
}
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = (headerBlockLength == 0) ? 12 : 10 + headerBlockLength;
|
||||
int length = headerBlockLength == 0 ? 12 : 10 + headerBlockLength;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
@ -111,7 +108,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
frame.writeMedium(length);
|
||||
frame.writeInt(spdySynStreamFrame.getStreamID());
|
||||
frame.writeInt(spdySynStreamFrame.getAssociatedToStreamID());
|
||||
frame.writeShort(((short) spdySynStreamFrame.getPriority()) << 14);
|
||||
frame.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 14);
|
||||
if (data.readableBytes() == 0) {
|
||||
frame.writeShort(0);
|
||||
}
|
||||
@ -124,7 +121,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
encodeHeaderBlock(spdySynReplyFrame));
|
||||
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = (headerBlockLength == 0) ? 8 : 6 + headerBlockLength;
|
||||
int length = headerBlockLength == 0 ? 8 : 6 + headerBlockLength;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
@ -178,9 +175,9 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
// Chromium Issue 79156
|
||||
// SPDY setting ids are not written in network byte order
|
||||
// Write id assuming the architecture is little endian
|
||||
frame.writeByte((id >> 0) & 0xFF);
|
||||
frame.writeByte((id >> 8) & 0xFF);
|
||||
frame.writeByte((id >> 16) & 0xFF);
|
||||
frame.writeByte(id >> 0 & 0xFF);
|
||||
frame.writeByte(id >> 8 & 0xFF);
|
||||
frame.writeByte(id >> 16 & 0xFF);
|
||||
frame.writeByte(ID_flags);
|
||||
frame.writeInt(spdySettingsFrame.getValue(id));
|
||||
}
|
||||
@ -223,7 +220,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
ChannelBuffer data = compressHeaderBlock(
|
||||
encodeHeaderBlock(spdyHeadersFrame));
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length = (headerBlockLength == 0) ? 4 : 6 + headerBlockLength;
|
||||
int length = headerBlockLength == 0 ? 4 : 6 + headerBlockLength;
|
||||
ChannelBuffer frame = ChannelBuffers.buffer(
|
||||
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + length);
|
||||
frame.writeShort(SPDY_VERSION | 0x8000);
|
||||
@ -240,7 +237,7 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
|
||||
return msg;
|
||||
}
|
||||
|
||||
private ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame)
|
||||
private static ChannelBuffer encodeHeaderBlock(SpdyHeaderBlock headerFrame)
|
||||
throws Exception {
|
||||
Set<String> names = headerFrame.getHeaderNames();
|
||||
int numHeaders = names.size();
|
||||
|
@ -220,7 +220,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
private HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame)
|
||||
private static HttpRequest createHttpRequest(SpdyHeaderBlock requestFrame)
|
||||
throws Exception {
|
||||
// Create the first line of the request from the name/value pairs
|
||||
HttpMethod method = SpdyHeaders.getMethod(requestFrame);
|
||||
@ -250,7 +250,7 @@ public class SpdyHttpDecoder extends OneToOneDecoder {
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
private HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame)
|
||||
private static HttpResponse createHttpResponse(SpdyHeaderBlock responseFrame)
|
||||
throws Exception {
|
||||
// Create the first line of the response from the name/value pairs
|
||||
HttpResponseStatus status = SpdyHeaders.getStatus(responseFrame);
|
||||
|
@ -19,4 +19,5 @@ package io.netty.handler.codec.spdy;
|
||||
* A SPDY Protocol NOOP Control Frame
|
||||
*/
|
||||
public interface SpdyNoOpFrame {
|
||||
// Tag interface
|
||||
}
|
||||
|
@ -27,13 +27,14 @@ import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelStateEvent;
|
||||
import io.netty.channel.Channels;
|
||||
import io.netty.channel.ExceptionEvent;
|
||||
import io.netty.channel.MessageEvent;
|
||||
import io.netty.channel.SimpleChannelUpstreamHandler;
|
||||
|
||||
/**
|
||||
* Manages streams within a SPDY session.
|
||||
*/
|
||||
public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
implements ChannelDownstreamHandler {
|
||||
|
||||
private static final SpdyProtocolException PROTOCOL_EXCEPTION = new SpdyProtocolException();
|
||||
@ -217,12 +218,12 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
*/
|
||||
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
|
||||
|
||||
if (isRemoteInitiatedID(spdyPingFrame.getID())) {
|
||||
Channels.write(ctx, Channels.future(e.getChannel()), spdyPingFrame, e.getRemoteAddress());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Note: only checks that there are outstanding pings since uniqueness is not inforced
|
||||
if (pings.get() == 0) {
|
||||
return;
|
||||
@ -253,6 +254,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
super.messageReceived(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
|
||||
throws Exception {
|
||||
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof SpdyProtocolException) {
|
||||
issueSessionError(ctx, e.getChannel(), null);
|
||||
}
|
||||
|
||||
super.exceptionCaught(ctx, e);
|
||||
}
|
||||
|
||||
public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
|
||||
throws Exception {
|
||||
if (evt instanceof ChannelStateEvent) {
|
||||
@ -380,7 +393,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
|
||||
private boolean isRemoteInitiatedID(int ID) {
|
||||
boolean serverID = SpdyCodecUtil.isServerID(ID);
|
||||
return (server && !serverID) || (!server && serverID);
|
||||
return server && !serverID || !server && serverID;
|
||||
}
|
||||
|
||||
private synchronized void updateConcurrentStreams(SpdySettingsFrame settings, boolean remote) {
|
||||
@ -416,8 +429,8 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
if (receivedGoAwayFrame || sentGoAwayFrame) {
|
||||
return false;
|
||||
}
|
||||
if ((maxConcurrentStreams != 0) &&
|
||||
(spdySession.numActiveStreams() >= maxConcurrentStreams)) {
|
||||
if (maxConcurrentStreams != 0 &&
|
||||
spdySession.numActiveStreams() >= maxConcurrentStreams) {
|
||||
return false;
|
||||
}
|
||||
spdySession.acceptStream(streamID, remoteSideClosed, localSideClosed);
|
||||
@ -433,14 +446,14 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
} else {
|
||||
spdySession.closeLocalSide(streamID);
|
||||
}
|
||||
if ((closeSessionFuture != null) && spdySession.noActiveStreams()) {
|
||||
if (closeSessionFuture != null && spdySession.noActiveStreams()) {
|
||||
closeSessionFuture.setSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeStream(int streamID) {
|
||||
spdySession.removeStream(streamID);
|
||||
if ((closeSessionFuture != null) && spdySession.noActiveStreams()) {
|
||||
if (closeSessionFuture != null && spdySession.noActiveStreams()) {
|
||||
closeSessionFuture.setSuccess();
|
||||
}
|
||||
}
|
||||
@ -466,7 +479,7 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
|
||||
if (!sentGoAwayFrame) {
|
||||
sentGoAwayFrame = true;
|
||||
ChannelFuture future = Channels.future(channel);
|
||||
Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID));
|
||||
Channels.write(ctx, future, new DefaultSpdyGoAwayFrame(lastGoodStreamID), remoteAddress);
|
||||
return future;
|
||||
}
|
||||
return Channels.succeededFuture(channel);
|
||||
|
@ -18,9 +18,9 @@ package io.netty.handler.codec.spdy;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty.channel.Channels;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelStateEvent;
|
||||
import io.netty.channel.Channels;
|
||||
import io.netty.channel.MessageEvent;
|
||||
import io.netty.channel.SimpleChannelUpstreamHandler;
|
||||
import io.netty.handler.codec.embedder.DecoderEmbedder;
|
||||
@ -36,7 +36,7 @@ public class SpdySessionHandlerTest {
|
||||
closeMessage.setValue(closeSignal, 0);
|
||||
}
|
||||
|
||||
private void assertHeaderBlock(SpdyHeaderBlock received, SpdyHeaderBlock expected) {
|
||||
private static void assertHeaderBlock(SpdyHeaderBlock received, SpdyHeaderBlock expected) {
|
||||
for (String name: expected.getHeaderNames()) {
|
||||
List<String> expectedValues = expected.getHeaders(name);
|
||||
List<String> receivedValues = received.getHeaders(name);
|
||||
@ -48,7 +48,7 @@ public class SpdySessionHandlerTest {
|
||||
Assert.assertTrue(received.getHeaders().isEmpty());
|
||||
}
|
||||
|
||||
private void assertDataFrame(Object msg, int streamID, boolean last) {
|
||||
private static void assertDataFrame(Object msg, int streamID, boolean last) {
|
||||
Assert.assertNotNull(msg);
|
||||
Assert.assertTrue(msg instanceof SpdyDataFrame);
|
||||
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
|
||||
@ -56,7 +56,7 @@ public class SpdySessionHandlerTest {
|
||||
Assert.assertTrue(spdyDataFrame.isLast() == last);
|
||||
}
|
||||
|
||||
private void assertSynReply(Object msg, int streamID, boolean last, SpdyHeaderBlock headers) {
|
||||
private static void assertSynReply(Object msg, int streamID, boolean last, SpdyHeaderBlock headers) {
|
||||
Assert.assertNotNull(msg);
|
||||
Assert.assertTrue(msg instanceof SpdySynReplyFrame);
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
@ -65,7 +65,7 @@ public class SpdySessionHandlerTest {
|
||||
assertHeaderBlock(spdySynReplyFrame, headers);
|
||||
}
|
||||
|
||||
private void assertRstStream(Object msg, int streamID, SpdyStreamStatus status) {
|
||||
private static void assertRstStream(Object msg, int streamID, SpdyStreamStatus status) {
|
||||
Assert.assertNotNull(msg);
|
||||
Assert.assertTrue(msg instanceof SpdyRstStreamFrame);
|
||||
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
|
||||
@ -73,21 +73,21 @@ public class SpdySessionHandlerTest {
|
||||
Assert.assertTrue(spdyRstStreamFrame.getStatus().equals(status));
|
||||
}
|
||||
|
||||
private void assertPing(Object msg, int ID) {
|
||||
private static void assertPing(Object msg, int ID) {
|
||||
Assert.assertNotNull(msg);
|
||||
Assert.assertTrue(msg instanceof SpdyPingFrame);
|
||||
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
|
||||
Assert.assertTrue(spdyPingFrame.getID() == ID);
|
||||
}
|
||||
|
||||
private void assertGoAway(Object msg, int lastGoodStreamID) {
|
||||
private static void assertGoAway(Object msg, int lastGoodStreamID) {
|
||||
Assert.assertNotNull(msg);
|
||||
Assert.assertTrue(msg instanceof SpdyGoAwayFrame);
|
||||
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
|
||||
Assert.assertTrue(spdyGoAwayFrame.getLastGoodStreamID() == lastGoodStreamID);
|
||||
}
|
||||
|
||||
private void assertHeaders(Object msg, int streamID, SpdyHeaderBlock headers) {
|
||||
private static void assertHeaders(Object msg, int streamID, SpdyHeaderBlock headers) {
|
||||
Assert.assertNotNull(msg);
|
||||
Assert.assertTrue(msg instanceof SpdyHeadersFrame);
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
@ -263,8 +263,8 @@ public class SpdySessionHandlerTest {
|
||||
// Echo Handler opens 4 half-closed streams on session connection
|
||||
// and then sets the number of concurrent streams to 3
|
||||
private class EchoHandler extends SimpleChannelUpstreamHandler {
|
||||
private int closeSignal;
|
||||
private boolean server;
|
||||
private final int closeSignal;
|
||||
private final boolean server;
|
||||
|
||||
EchoHandler(int closeSignal, boolean server) {
|
||||
super();
|
||||
@ -299,9 +299,9 @@ public class SpdySessionHandlerTest {
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
Object msg = e.getMessage();
|
||||
if ((msg instanceof SpdyDataFrame) ||
|
||||
(msg instanceof SpdyPingFrame) ||
|
||||
(msg instanceof SpdyHeadersFrame)) {
|
||||
if (msg instanceof SpdyDataFrame ||
|
||||
msg instanceof SpdyPingFrame ||
|
||||
msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
Channels.write(e.getChannel(), msg, e.getRemoteAddress());
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user