SPDY: remove frame size limit in frame decoder

This commit is contained in:
Jeff Pinner 2012-05-12 20:00:00 -07:00
parent 062a5943ab
commit 226c2f7243
5 changed files with 564 additions and 254 deletions

View File

@ -28,8 +28,7 @@ final class SpdyCodecUtil {
static final int SPDY_MAX_LENGTH = 0xFFFFFF; // Length is a 24-bit field 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_FIN = 0x01;
static final byte SPDY_DATA_FLAG_COMPRESS = 0x02;
static final int SPDY_SYN_STREAM_FRAME = 1; static final int SPDY_SYN_STREAM_FRAME = 1;
static final int SPDY_SYN_REPLY_FRAME = 2; static final int SPDY_SYN_REPLY_FRAME = 2;

View File

@ -45,13 +45,15 @@ public interface SpdyDataFrame {
void setLast(boolean last); void setLast(boolean last);
/** /**
* Returns {@code true} if the data in this frame has been compressed. * @deprecated Removed from SPDY specification.
*/ */
@Deprecated
boolean isCompressed(); boolean isCompressed();
/** /**
* Sets if the data in this frame has been compressed. * @deprecated Removed from SPDY specification.
*/ */
@Deprecated
void setCompressed(boolean compressed); void setCompressed(boolean compressed);
/** /**

View File

@ -19,7 +19,9 @@ import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.codec.frame.FrameDecoder; import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*; import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
@ -29,41 +31,71 @@ import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
public class SpdyFrameDecoder extends FrameDecoder { public class SpdyFrameDecoder extends FrameDecoder {
private final int maxChunkSize; private final int maxChunkSize;
private final int maxFrameSize;
private final int maxHeaderSize; private final int maxHeaderSize;
private final SpdyHeaderBlockDecompressor headerBlockDecompressor = private final SpdyHeaderBlockDecompressor headerBlockDecompressor =
SpdyHeaderBlockDecompressor.newInstance(); 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)}, * Creates a new instance with the default {@code maxChunkSize (8192)}
* {@code maxFrameSize (65536)}, and {@code maxHeaderSize (16384)}. * and {@code maxHeaderSize (16384)}.
*/ */
public SpdyFrameDecoder() { public SpdyFrameDecoder() {
this(8192, 65536, 16384); this(8192, 16384);
} }
/** /**
* Creates a new instance with the specified parameters. * Creates a new instance with the specified parameters.
*/ */
@Deprecated
public SpdyFrameDecoder( public SpdyFrameDecoder(
int maxChunkSize, int maxFrameSize, int maxHeaderSize) { int maxChunkSize, int maxFrameSize, int maxHeaderSize) {
super(true); // Enable unfold for data frames this(maxChunkSize, maxHeaderSize);
}
/**
* Creates a new instance with the specified parameters.
*/
public SpdyFrameDecoder(int maxChunkSize, int maxHeaderSize) {
super(false);
if (maxChunkSize <= 0) { if (maxChunkSize <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " + maxChunkSize); "maxChunkSize must be a positive integer: " + maxChunkSize);
} }
if (maxFrameSize <= 0) {
throw new IllegalArgumentException(
"maxFrameSize must be a positive integer: " + maxFrameSize);
}
if (maxHeaderSize <= 0) { if (maxHeaderSize <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"maxHeaderSize must be a positive integer: " + maxHeaderSize); "maxHeaderSize must be a positive integer: " + maxHeaderSize);
} }
this.maxChunkSize = maxChunkSize; this.maxChunkSize = maxChunkSize;
this.maxFrameSize = maxFrameSize;
this.maxHeaderSize = maxHeaderSize; this.maxHeaderSize = maxHeaderSize;
this.state = State.READ_COMMON_HEADER;
} }
@Override @Override
@ -77,174 +109,77 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
} }
@Override @Override
protected Object decode( protected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
throws Exception { throws Exception {
switch(state) {
// Must read common header to determine frame length case READ_COMMON_HEADER:
if (buffer.readableBytes() < SPDY_HEADER_SIZE) { state = readCommonHeader(buffer);
return null; if (state == State.FRAME_ERROR) {
} if (version != SPDY_VERSION) {
fireProtocolException(ctx, "Unsupported version: " + version);
// Get frame length from common header } else {
int frameOffset = buffer.readerIndex(); fireInvalidControlFrameException(ctx);
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);
} }
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; int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
} for (int i = 0; i < readableEntries; i ++) {
}
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 ++) {
// Chromium Issue 79156 // Chromium Issue 79156
// SPDY setting ids are not written in network byte order // SPDY setting ids are not written in network byte order
// Read id assuming the architecture is little endian // Read id assuming the architecture is little endian
int ID = (data.readByte() & 0xFF) | int ID = (buffer.readByte() & 0xFF) |
(data.readByte() & 0xFF) << 8 | (buffer.readByte() & 0xFF) << 8 |
(data.readByte() & 0xFF) << 16; (buffer.readByte() & 0xFF) << 16;
byte ID_flags = data.readByte(); byte ID_flags = buffer.readByte();
int value = getSignedInt(data, data.readerIndex()); int value = getSignedInt(buffer, buffer.readerIndex());
data.skipBytes(4); buffer.skipBytes(4);
// 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))) { if (!(spdySettingsFrame.isSet(ID))) {
boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0; boolean persistVal = (ID_flags & SPDY_SETTINGS_PERSIST_VALUE) != 0;
@ -253,134 +188,391 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
} }
return spdySettingsFrame; length -= 8 * readableEntries;
if (length == 0) {
case SPDY_NOOP_FRAME: state = State.READ_COMMON_HEADER;
if (data.readableBytes() != 0) { Object frame = spdySettingsFrame;
throw new SpdyProtocolException( spdySettingsFrame = null;
"Received invalid NOOP control frame"); return frame;
} }
return null; return null;
case SPDY_PING_FRAME: case READ_HEADER_BLOCK_FRAME:
if (data.readableBytes() != 4) { try {
throw new SpdyProtocolException( spdyHeaderBlock = readHeaderBlockFrame(buffer);
"Received invalid PING control frame"); 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); return new DefaultSpdyPingFrame(ID);
case SPDY_GOAWAY_FRAME: case SPDY_GOAWAY_FRAME:
if (data.readableBytes() != 4) { if (buffer.readableBytes() < 4) {
throw new SpdyProtocolException( return null;
"Received invalid GOAWAY control frame");
} }
int lastGoodStreamID = getUnsignedInt(data, data.readerIndex());
int lastGoodStreamID = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
return new DefaultSpdyGoAwayFrame(lastGoodStreamID); 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: 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) { if (decompressed.readableBytes() >= bytes) {
return true; return true;
} }
decompressed.discardReadBytes(); // Perhaps last call to decode filled output buffer
headerBlockDecompressor.decode(decompressed); headerBlockDecompressor.decode(decompressed);
return decompressed.readableBytes() >= bytes; return decompressed.readableBytes() >= bytes;
} }
private void decodeHeaderBlock(SpdyHeaderBlock headerFrame, ChannelBuffer headerBlock) private int readLengthField() {
throws Exception { return decompressed.readUnsignedShort();
if ((headerBlock.readableBytes() == 2) && }
(headerBlock.getShort(headerBlock.readerIndex()) == 0)) {
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; return;
} }
headerBlockDecompressor.setInput(headerBlock); int lengthFieldSize = 2; // SPDY/2 uses 16-bit length fields
ChannelBuffer decompressed = ChannelBuffers.dynamicBuffer(8192);
headerBlockDecompressor.decode(decompressed);
if (decompressed.readableBytes() < 2) { if (numHeaders == -1) {
throw new SpdyProtocolException( // Read number of Name/Value pairs
"Received invalid header block"); if (decompressed.readableBytes() < lengthFieldSize) {
} return;
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");
} }
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) { if (nameLength == 0) {
headerFrame.setInvalid(); spdyHeaderBlock.setInvalid();
return; return;
} }
headerSize += nameLength; headerSize += nameLength;
if (headerSize > maxHeaderSize) { if (headerSize > maxHeaderSize) {
throw new SpdyProtocolException( throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize); "Header block exceeds " + maxHeaderSize);
} }
if (!ensureBytes(decompressed, nameLength)) {
throw new SpdyProtocolException( // Try to read name
"Received invalid header block"); if (!ensureBytes(nameLength)) {
decompressed.resetReaderIndex();
decompressed.discardReadBytes();
return;
} }
byte[] nameBytes = new byte[nameLength]; byte[] nameBytes = new byte[nameLength];
decompressed.readBytes(nameBytes); decompressed.readBytes(nameBytes);
String name = new String(nameBytes, "UTF-8"); String name = new String(nameBytes, "UTF-8");
if (headerFrame.containsHeader(name)) {
throw new SpdyProtocolException( // Check for identically named headers
"Received duplicate header name: " + name); if (spdyHeaderBlock.containsHeader(name)) {
spdyHeaderBlock.setInvalid();
return;
} }
if (!ensureBytes(decompressed, 2)) {
throw new SpdyProtocolException( // Try to read length of value
"Received invalid header block"); 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) { if (valueLength == 0) {
headerFrame.setInvalid(); spdyHeaderBlock.setInvalid();
return; return;
} }
headerSize += valueLength; headerSize += valueLength;
if (headerSize > maxHeaderSize) { if (headerSize > maxHeaderSize) {
throw new SpdyProtocolException( throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize); "Header block exceeds " + maxHeaderSize);
} }
if (!ensureBytes(decompressed, valueLength)) {
throw new SpdyProtocolException( // Try to read value
"Received invalid header block"); if (!ensureBytes(valueLength)) {
decompressed.resetReaderIndex();
decompressed.discardReadBytes();
return;
} }
byte[] valueBytes = new byte[valueLength]; byte[] valueBytes = new byte[valueLength];
decompressed.readBytes(valueBytes); decompressed.readBytes(valueBytes);
// Add Name/Value pair to headers
int index = 0; int index = 0;
int offset = 0; int offset = 0;
while (index < valueLength) { while (index < valueLength) {
@ -389,14 +581,121 @@ public class SpdyFrameDecoder extends FrameDecoder {
} }
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) { if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
// Received multiple, in-sequence NULL characters // Received multiple, in-sequence NULL characters
headerFrame.setInvalid(); // Recipients of illegal value fields must issue a stream error
spdyHeaderBlock.setInvalid();
return; return;
} }
String value = new String(valueBytes, offset, index - offset, "UTF-8"); 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 ++; index ++;
offset = 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 void fireProtocolException(ChannelHandlerContext ctx, String message) {
Channels.fireExceptionCaught(ctx, new SpdyProtocolException(message));
}
} }

View File

@ -82,9 +82,6 @@ public class SpdyFrameEncoder extends OneToOneEncoder {
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg; SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
ChannelBuffer data = spdyDataFrame.getData(); ChannelBuffer data = spdyDataFrame.getData();
byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0; byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
if (spdyDataFrame.isCompressed()) {
flags |= SPDY_DATA_FLAG_COMPRESS;
}
ChannelBuffer header = ChannelBuffers.buffer( ChannelBuffer header = ChannelBuffers.buffer(
ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE); ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE);
header.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF); header.writeInt(spdyDataFrame.getStreamID() & 0x7FFFFFFF);

View File

@ -27,6 +27,7 @@ import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.Channels; import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler; import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
@ -253,6 +254,18 @@ public class SpdySessionHandler extends SimpleChannelUpstreamHandler
super.messageReceived(ctx, e); 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) public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
throws Exception { throws Exception {
if (evt instanceof ChannelStateEvent) { if (evt instanceof ChannelStateEvent) {