SPDY: better encapsulation of header encoding/decoding

This commit is contained in:
Mike Schore 2013-06-16 23:01:56 -07:00 committed by Trustin Lee
parent 14f2e29af9
commit 0d9aecbbc1
10 changed files with 515 additions and 440 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -29,16 +29,16 @@ final class SpdyCodecUtil {
static final byte SPDY_DATA_FLAG_FIN = 0x01;
static final int SPDY_DATA_FRAME = 0;
static final int SPDY_SYN_STREAM_FRAME = 1;
static final int SPDY_SYN_REPLY_FRAME = 2;
static final int SPDY_RST_STREAM_FRAME = 3;
static final int SPDY_SETTINGS_FRAME = 4;
static final int SPDY_NOOP_FRAME = 5;
static final int SPDY_PUSH_PROMISE_FRAME = 5;
static final int SPDY_PING_FRAME = 6;
static final int SPDY_GOAWAY_FRAME = 7;
static final int SPDY_HEADERS_FRAME = 8;
static final int SPDY_WINDOW_UPDATE_FRAME = 9;
static final int SPDY_CREDENTIAL_FRAME = 10;
static final byte SPDY_FLAG_FIN = 0x01;
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;

View File

@ -16,11 +16,9 @@
package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.MessageList;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.TooLongFrameException;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
@ -29,11 +27,13 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
*/
public class SpdyFrameDecoder extends ByteToMessageDecoder {
private static final SpdyProtocolException INVALID_FRAME =
new SpdyProtocolException("Received invalid frame");
private final int spdyVersion;
private final int maxChunkSize;
private final int maxHeaderSize;
private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
private final SpdyHeaderBlockDecoder headerBlockDecoder;
private State state;
private SpdySettingsFrame spdySettingsFrame;
@ -44,12 +44,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
private int length;
private int version;
private int type;
private int streamID;
// Header block decoding fields
private int headerSize;
private int numHeaders;
private ByteBuf decompressed;
private int streamId;
private enum State {
READ_COMMON_HEADER,
@ -74,6 +69,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
* Creates a new instance with the specified parameters.
*/
public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
this(version, maxChunkSize, SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize));
}
protected SpdyFrameDecoder(
int version, int maxChunkSize, SpdyHeaderBlockDecoder headerBlockDecoder) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
@ -82,14 +82,9 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " + maxChunkSize);
}
if (maxHeaderSize <= 0) {
throw new IllegalArgumentException(
"maxHeaderSize must be a positive integer: " + maxHeaderSize);
}
spdyVersion = version;
this.maxChunkSize = maxChunkSize;
this.maxHeaderSize = maxHeaderSize;
headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
this.headerBlockDecoder = headerBlockDecoder;
state = State.READ_COMMON_HEADER;
}
@ -98,7 +93,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
try {
decode(ctx, in, out);
} finally {
headerBlockDecompressor.end();
headerBlockDecoder.end();
}
}
@ -111,7 +106,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
if (version != spdyVersion) {
fireProtocolException(ctx, "Unsupported version: " + version);
} else {
fireInvalidControlFrameException(ctx);
fireInvalidFrameException(ctx);
}
}
@ -119,13 +114,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
// All length 0 frames must be generated now
if (length == 0) {
if (state == State.READ_DATA_FRAME) {
if (streamID == 0) {
state = State.FRAME_ERROR;
fireProtocolException(ctx, "Received invalid data frame");
return;
}
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId);
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
state = State.READ_COMMON_HEADER;
out.add(spdyDataFrame);
@ -147,7 +136,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return;
} catch (IllegalArgumentException e) {
state = State.FRAME_ERROR;
fireInvalidControlFrameException(ctx);
fireInvalidFrameException(ctx);
}
return;
@ -164,7 +153,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
// Each ID/Value entry is 8 bytes
if ((length & 0x07) != 0 || length >> 3 != numEntries) {
state = State.FRAME_ERROR;
fireInvalidControlFrameException(ctx);
fireInvalidFrameException(ctx);
return;
}
@ -198,7 +187,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
if (ID == 0) {
state = State.FRAME_ERROR;
spdySettingsFrame = null;
fireInvalidControlFrameException(ctx);
fireInvalidFrameException(ctx);
return;
}
@ -235,7 +224,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return;
} catch (IllegalArgumentException e) {
state = State.FRAME_ERROR;
fireInvalidControlFrameException(ctx);
fireInvalidFrameException(ctx);
return;
}
@ -244,11 +233,10 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
length -= compressedBytes;
try {
decodeHeaderBlock(buffer.readSlice(compressedBytes));
headerBlockDecoder.decode(buffer.readSlice(compressedBytes), spdyHeadersFrame);
} catch (Exception e) {
state = State.FRAME_ERROR;
spdyHeadersFrame = null;
decompressed = null;
ctx.fireExceptionCaught(e);
return;
}
@ -256,8 +244,8 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
if (spdyHeadersFrame != null && spdyHeadersFrame.isInvalid()) {
Object frame = spdyHeadersFrame;
spdyHeadersFrame = null;
decompressed = null;
if (length == 0) {
headerBlockDecoder.reset();
state = State.READ_COMMON_HEADER;
}
out.add(frame);
@ -267,6 +255,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
if (length == 0) {
Object frame = spdyHeadersFrame;
spdyHeadersFrame = null;
headerBlockDecoder.reset();
state = State.READ_COMMON_HEADER;
out.add(frame);
return;
@ -274,7 +263,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return;
case READ_DATA_FRAME:
if (streamID == 0) {
if (streamId == 0) {
state = State.FRAME_ERROR;
fireProtocolException(ctx, "Received invalid data frame");
return;
@ -290,7 +279,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
ByteBuf data = ctx.alloc().buffer(dataLength);
data.writeBytes(buffer, dataLength);
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID, data);
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
length -= dataLength;
if (length == 0) {
@ -341,44 +330,51 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
type = getUnsignedShort(buffer, typeOffset);
// Check version first then validity
if (version != spdyVersion || !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;
streamId = 0;
} else {
// Decode data frame common header
streamID = getUnsignedInt(buffer, frameOffset);
version = spdyVersion; // Default to expected version
return State.READ_DATA_FRAME;
type = SPDY_DATA_FRAME;
streamId = getUnsignedInt(buffer, frameOffset);
}
// Check version first then validity
if (version != spdyVersion || !isValidFrameHeader()) {
return State.FRAME_ERROR;
}
// Make sure decoder will produce a frame or consume input
State nextState;
if (willGenerateFrame()) {
switch (type) {
case SPDY_DATA_FRAME:
nextState = State.READ_DATA_FRAME;
break;
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;
}
private Object readControlFrame(ByteBuf buffer) {
int streamID;
int streamId;
int statusCode;
switch (type) {
case SPDY_RST_STREAM_FRAME:
@ -386,11 +382,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return null;
}
streamID = getUnsignedInt(buffer, buffer.readerIndex());
streamId = getUnsignedInt(buffer, buffer.readerIndex());
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
return new DefaultSpdyRstStreamFrame(streamId, statusCode);
case SPDY_PING_FRAME:
if (buffer.readableBytes() < 4) {
@ -425,11 +421,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return null;
}
streamID = getUnsignedInt(buffer, buffer.readerIndex());
streamId = getUnsignedInt(buffer, buffer.readerIndex());
int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
return new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize);
return new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
default:
throw new Error("Shouldn't reach here.");
@ -438,7 +434,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
private SpdyHeadersFrame readHeaderBlockFrame(ByteBuf buffer) {
int minLength;
int streamID;
int streamId;
switch (type) {
case SPDY_SYN_STREAM_FRAME:
minLength = version < 3 ? 12 : 10;
@ -447,7 +443,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
}
int offset = buffer.readerIndex();
streamID = getUnsignedInt(buffer, offset);
streamId = getUnsignedInt(buffer, offset);
int associatedToStreamId = getUnsignedInt(buffer, offset + 4);
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
if (version < 3) {
@ -463,7 +459,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
}
SpdySynStreamFrame spdySynStreamFrame =
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
spdySynStreamFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
spdySynStreamFrame.setUnidirectional((flags & SPDY_FLAG_UNIDIRECTIONAL) != 0);
@ -475,7 +471,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return null;
}
streamID = getUnsignedInt(buffer, buffer.readerIndex());
streamId = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
length -= 4;
@ -491,7 +487,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
length = 0;
}
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
return spdySynReplyFrame;
@ -506,7 +502,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
return null;
}
streamID = getUnsignedInt(buffer, buffer.readerIndex());
streamId = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
length -= 4;
@ -522,7 +518,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
length = 0;
}
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
return spdyHeadersFrame;
@ -532,175 +528,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
}
}
private boolean ensureBytes(int bytes) throws Exception {
if (decompressed.readableBytes() >= bytes) {
return true;
}
// Perhaps last call to decode filled output buffer
int numBytes;
boolean done;
do {
numBytes = headerBlockDecompressor.decode(decompressed);
done = decompressed.readableBytes() >= bytes;
} while (!done && numBytes > 0);
return done;
}
private int readLengthField() {
if (version < 3) {
return decompressed.readUnsignedShort();
} else {
return decompressed.readInt();
}
}
private void decodeHeaderBlock(ByteBuf 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 = Unpooled.buffer(8192);
}
// Accumulate decompressed data
headerBlockDecompressor.setInput(buffer);
headerBlockDecompressor.decode(decompressed);
if (spdyHeadersFrame == null) {
// Only decompressing data to keep decompression context in sync
decompressed = null;
return;
}
int lengthFieldSize = version < 3 ? 2 : 4;
if (numHeaders == -1) {
// Read number of Name/Value pairs
if (decompressed.readableBytes() < lengthFieldSize) {
return;
}
numHeaders = readLengthField();
if (numHeaders < 0) {
spdyHeadersFrame.setInvalid();
return;
}
}
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) {
spdyHeadersFrame.setInvalid();
return;
}
headerSize += nameLength;
if (headerSize > maxHeaderSize) {
throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize);
}
// 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");
// Check for identically named headers
if (spdyHeadersFrame.headers().contains(name)) {
spdyHeadersFrame.setInvalid();
return;
}
// Try to read length of value
if (!ensureBytes(lengthFieldSize)) {
decompressed.resetReaderIndex();
decompressed.discardReadBytes();
return;
}
int valueLength = readLengthField();
// Recipients of illegal value fields must issue a stream error
if (valueLength < 0) {
spdyHeadersFrame.setInvalid();
return;
}
// SPDY/3 allows zero-length (empty) header values
if (valueLength == 0) {
if (version < 3) {
spdyHeadersFrame.setInvalid();
return;
} else {
spdyHeadersFrame.headers().add(name, "");
numHeaders --;
this.headerSize = headerSize;
continue;
}
}
headerSize += valueLength;
if (headerSize > maxHeaderSize) {
throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize);
}
// 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) {
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
index ++;
}
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
// Received multiple, in-sequence NULL characters
// Recipients of illegal value fields must issue a stream error
spdyHeadersFrame.setInvalid();
return;
}
String value = new String(valueBytes, offset, index - offset, "UTF-8");
try {
spdyHeadersFrame.headers().add(name, value);
} catch (IllegalArgumentException e) {
// Name contains NULL or non-ascii characters
spdyHeadersFrame.setInvalid();
return;
}
index ++;
offset = index;
}
numHeaders --;
this.headerSize = headerSize;
}
decompressed = null;
}
private boolean isValidControlFrameHeader() {
private boolean isValidFrameHeader() {
switch (type) {
case SPDY_DATA_FRAME:
return streamId != 0;
case SPDY_SYN_STREAM_FRAME:
return version < 3 ? length >= 12 : length >= 10;
@ -713,9 +545,6 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
case SPDY_SETTINGS_FRAME:
return length >= 4;
case SPDY_NOOP_FRAME:
return length == 0;
case SPDY_PING_FRAME:
return length == 4;
@ -732,14 +561,14 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
case SPDY_WINDOW_UPDATE_FRAME:
return length == 8;
case SPDY_CREDENTIAL_FRAME:
default:
return true;
}
}
private boolean willGenerateControlFrame() {
private boolean willGenerateFrame() {
switch (type) {
case SPDY_DATA_FRAME:
case SPDY_SYN_STREAM_FRAME:
case SPDY_SYN_REPLY_FRAME:
case SPDY_RST_STREAM_FRAME:
@ -750,57 +579,13 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
case SPDY_WINDOW_UPDATE_FRAME:
return true;
case SPDY_NOOP_FRAME:
case SPDY_CREDENTIAL_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;
case SPDY_CREDENTIAL_FRAME:
message = "Received invalid CREDENTIAL control frame";
break;
}
fireProtocolException(ctx, message);
private static void fireInvalidFrameException(ChannelHandlerContext ctx) {
ctx.fireExceptionCaught(INVALID_FRAME);
}
private static void fireProtocolException(ChannelHandlerContext ctx, String message) {

View File

@ -34,8 +34,7 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
private final int version;
private boolean finished;
private final SpdyHeaderBlockCompressor headerBlockCompressor;
private final SpdyHeaderBlockEncoder headerBlockEncoder;
/**
* Creates a new instance with the specified {@code version} and the
@ -50,13 +49,17 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
* Creates a new instance with the specified parameters.
*/
public SpdyFrameEncoder(int version, int compressionLevel, int windowBits, int memLevel) {
this(version, SpdyHeaderBlockEncoder.newInstance(
version, compressionLevel, windowBits, memLevel));
}
protected SpdyFrameEncoder(int version, SpdyHeaderBlockEncoder headerBlockEncoder) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unknown version: " + version);
}
this.version = version;
headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(
version, compressionLevel, windowBits, memLevel);
this.headerBlockEncoder = headerBlockEncoder;
}
@Override
@ -64,12 +67,8 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
synchronized (headerBlockCompressor) {
if (finished) {
return;
}
finished = true;
headerBlockCompressor.end();
synchronized (headerBlockEncoder) {
headerBlockEncoder.end();
}
}
});
@ -91,8 +90,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
} else if (msg instanceof SpdySynStreamFrame) {
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
ByteBuf data = compressHeaderBlock(
encodeHeaderBlock(version, spdySynStreamFrame));
ByteBuf data = headerBlockEncoder.encode(spdySynStreamFrame);
byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
if (spdySynStreamFrame.isUnidirectional()) {
flags |= SPDY_FLAG_UNIDIRECTIONAL;
@ -129,8 +127,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
} else if (msg instanceof SpdySynReplyFrame) {
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
ByteBuf data = compressHeaderBlock(
encodeHeaderBlock(version, spdySynReplyFrame));
ByteBuf data = headerBlockEncoder.encode(spdySynReplyFrame);
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
int headerBlockLength = data.readableBytes();
int length;
@ -227,8 +224,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
} else if (msg instanceof SpdyHeadersFrame) {
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
ByteBuf data = compressHeaderBlock(
encodeHeaderBlock(version, spdyHeadersFrame));
ByteBuf data = headerBlockEncoder.encode(spdyHeadersFrame);
byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
int headerBlockLength = data.readableBytes();
int length;
@ -261,83 +257,4 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
throw new UnsupportedMessageTypeException(msg);
}
}
private static void writeLengthField(int version, ByteBuf buffer, int length) {
if (version < 3) {
buffer.writeShort(length);
} else {
buffer.writeInt(length);
}
}
private static void setLengthField(int version, ByteBuf buffer, int writerIndex, int length) {
if (version < 3) {
buffer.setShort(writerIndex, length);
} else {
buffer.setInt(writerIndex, length);
}
}
private static ByteBuf encodeHeaderBlock(int version, SpdyHeadersFrame headerFrame)
throws Exception {
Set<String> names = headerFrame.headers().names();
int numHeaders = names.size();
if (numHeaders == 0) {
return Unpooled.EMPTY_BUFFER;
}
if (numHeaders > SPDY_MAX_NV_LENGTH) {
throw new IllegalArgumentException(
"header block contains too many headers");
}
ByteBuf headerBlock = Unpooled.buffer();
writeLengthField(version, headerBlock, numHeaders);
for (String name: names) {
byte[] nameBytes = name.getBytes(CharsetUtil.UTF_8);
writeLengthField(version, headerBlock, nameBytes.length);
headerBlock.writeBytes(nameBytes);
int savedIndex = headerBlock.writerIndex();
int valueLength = 0;
writeLengthField(version, headerBlock, valueLength);
for (String value: headerFrame.headers().getAll(name)) {
byte[] valueBytes = value.getBytes(CharsetUtil.UTF_8);
if (valueBytes.length > 0) {
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
valueLength += valueBytes.length + 1;
}
}
if (valueLength == 0) {
if (version < 3) {
throw new IllegalArgumentException(
"header value cannot be empty: " + name);
}
} else {
valueLength --;
}
if (valueLength > SPDY_MAX_NV_LENGTH) {
throw new IllegalArgumentException(
"header exceeds allowable length: " + name);
}
if (valueLength > 0) {
setLengthField(version, headerBlock, savedIndex, valueLength);
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
}
}
return headerBlock;
}
private synchronized ByteBuf compressHeaderBlock(
ByteBuf uncompressed) throws Exception {
if (uncompressed.readableBytes() == 0) {
return Unpooled.EMPTY_BUFFER;
}
ByteBuf compressed = Unpooled.buffer();
synchronized (headerBlockCompressor) {
if (!finished) {
headerBlockCompressor.setInput(uncompressed);
headerBlockCompressor.encode(compressed);
}
}
return compressed;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -17,13 +17,13 @@ package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf;
abstract class SpdyHeaderBlockDecompressor {
abstract class SpdyHeaderBlockDecoder {
static SpdyHeaderBlockDecompressor newInstance(int version) {
return new SpdyHeaderBlockZlibDecompressor(version);
static SpdyHeaderBlockDecoder newInstance(int version, int maxHeaderSize) {
return new SpdyHeaderBlockZlibDecoder(version, maxHeaderSize);
}
abstract void setInput(ByteBuf compressed);
abstract int decode(ByteBuf decompressed) throws Exception;
abstract void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception;
abstract void reset();
abstract void end();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -18,21 +18,20 @@ package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf;
import io.netty.util.internal.PlatformDependent;
abstract class SpdyHeaderBlockCompressor {
abstract class SpdyHeaderBlockEncoder {
static SpdyHeaderBlockCompressor newInstance(
static SpdyHeaderBlockEncoder newInstance(
int version, int compressionLevel, int windowBits, int memLevel) {
if (PlatformDependent.javaVersion() >= 7) {
return new SpdyHeaderBlockZlibCompressor(
return new SpdyHeaderBlockZlibEncoder(
version, compressionLevel);
} else {
return new SpdyHeaderBlockJZlibCompressor(
return new SpdyHeaderBlockJZlibEncoder(
version, compressionLevel, windowBits, memLevel);
}
}
abstract void setInput(ByteBuf decompressed);
abstract void encode(ByteBuf compressed);
abstract ByteBuf encode(SpdyHeadersFrame frame) throws Exception;
abstract void end();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -20,18 +20,18 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import com.jcraft.jzlib.Deflater;
import com.jcraft.jzlib.JZlib;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.compression.CompressionException;
class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
class SpdyHeaderBlockJZlibEncoder extends SpdyHeaderBlockRawEncoder {
private final Deflater z = new Deflater();
public SpdyHeaderBlockJZlibCompressor(
private boolean finished;
public SpdyHeaderBlockJZlibEncoder(
int version, int compressionLevel, int windowBits, int memLevel) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
super(version);
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
@ -63,8 +63,7 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
}
}
@Override
public void setInput(ByteBuf decompressed) {
private void setInput(ByteBuf decompressed) {
byte[] in = new byte[decompressed.readableBytes()];
decompressed.readBytes(in);
z.next_in = in;
@ -72,8 +71,7 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
z.avail_in = in.length;
}
@Override
public void encode(ByteBuf compressed) {
private void encode(ByteBuf compressed) {
try {
byte[] out = new byte[(int) Math.ceil(z.next_in.length * 1.001) + 12];
z.next_out = out;
@ -99,7 +97,32 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
}
@Override
public void end() {
public synchronized ByteBuf encode(SpdyHeadersFrame frame) throws Exception {
if (frame == null) {
throw new IllegalArgumentException("frame");
}
if (finished) {
throw new IllegalAccessException("compressor closed");
}
ByteBuf decompressed = super.encode(frame);
if (decompressed.readableBytes() == 0) {
return Unpooled.EMPTY_BUFFER;
}
ByteBuf compressed = Unpooled.buffer();
setInput(decompressed);
encode(compressed);
return compressed;
}
@Override
public synchronized void end() {
if (finished) {
return;
}
finished = true;
z.deflateEnd();
z.next_in = null;
z.next_out = null;

View File

@ -0,0 +1,199 @@
/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.TooLongFrameException;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
private final int version;
private final int maxHeaderSize;
private final int lengthFieldSize;
// Header block decoding fields
private int headerSize;
private int numHeaders;
public SpdyHeaderBlockRawDecoder(int version, int maxHeaderSize) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
this.version = version;
this.maxHeaderSize = maxHeaderSize;
lengthFieldSize = version < 3 ? 2 : 4;
reset();
}
private int readLengthField(ByteBuf buffer) {
int length;
if (version < 3) {
length = getUnsignedShort(buffer, buffer.readerIndex());
buffer.skipBytes(2);
} else {
length = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
}
return length;
}
@Override
void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception {
if (encoded == null) {
throw new NullPointerException("encoded");
}
if (frame == null) {
throw new NullPointerException("frame");
}
if (numHeaders == -1) {
// Read number of Name/Value pairs
if (encoded.readableBytes() < lengthFieldSize) {
return;
}
numHeaders = readLengthField(encoded);
if (numHeaders < 0) {
frame.setInvalid();
return;
}
}
while (numHeaders > 0) {
int headerSize = this.headerSize;
encoded.markReaderIndex();
// Try to read length of name
if (encoded.readableBytes() < lengthFieldSize) {
encoded.resetReaderIndex();
encoded.discardReadBytes();
return;
}
int nameLength = readLengthField(encoded);
// Recipients of a zero-length name must issue a stream error
if (nameLength <= 0) {
frame.setInvalid();
return;
}
headerSize += nameLength;
if (headerSize > maxHeaderSize) {
throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize);
}
// Try to read name
if (encoded.readableBytes() < nameLength) {
encoded.resetReaderIndex();
encoded.discardReadBytes();
return;
}
byte[] nameBytes = new byte[nameLength];
encoded.readBytes(nameBytes);
String name = new String(nameBytes, "UTF-8");
// Check for identically named headers
if (frame.headers().contains(name)) {
frame.setInvalid();
return;
}
// Try to read length of value
if (encoded.readableBytes() < lengthFieldSize) {
encoded.resetReaderIndex();
encoded.discardReadBytes();
return;
}
int valueLength = readLengthField(encoded);
// Recipients of illegal value fields must issue a stream error
if (valueLength < 0) {
frame.setInvalid();
return;
}
// SPDY/3 allows zero-length (empty) header values
if (valueLength == 0) {
if (version < 3) {
frame.setInvalid();
return;
} else {
frame.headers().add(name, "");
numHeaders --;
this.headerSize = headerSize;
continue;
}
}
headerSize += valueLength;
if (headerSize > maxHeaderSize) {
throw new TooLongFrameException(
"Header block exceeds " + maxHeaderSize);
}
// Try to read value
if (encoded.readableBytes() < valueLength) {
encoded.resetReaderIndex();
encoded.discardReadBytes();
return;
}
byte[] valueBytes = new byte[valueLength];
encoded.readBytes(valueBytes);
// Add Name/Value pair to headers
int index = 0;
int offset = 0;
while (index < valueLength) {
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
index ++;
}
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
// Received multiple, in-sequence NULL characters
// Recipients of illegal value fields must issue a stream error
frame.setInvalid();
return;
}
String value = new String(valueBytes, offset, index - offset, "UTF-8");
try {
frame.headers().add(name, value);
} catch (IllegalArgumentException e) {
// Name contains NULL or non-ascii characters
frame.setInvalid();
return;
}
index ++;
offset = index;
}
numHeaders --;
this.headerSize = headerSize;
}
}
@Override
void reset() {
// Initialize header block decoding fields
headerSize = 0;
numHeaders = -1;
}
@Override
void end() {
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.spdy;
import java.nio.ByteOrder;
import java.util.Set;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
public class SpdyHeaderBlockRawEncoder extends SpdyHeaderBlockEncoder {
private final int version;
public SpdyHeaderBlockRawEncoder(int version) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unknown version: " + version);
}
this.version = version;
}
private void setLengthField(ByteBuf buffer, int writerIndex, int length) {
if (version < 3) {
buffer.setShort(writerIndex, length);
} else {
buffer.setInt(writerIndex, length);
}
}
private void writeLengthField(ByteBuf buffer, int length) {
if (version < 3) {
buffer.writeShort(length);
} else {
buffer.writeInt(length);
}
}
@Override
public ByteBuf encode(SpdyHeadersFrame frame) throws Exception {
Set<String> names = frame.headers().names();
int numHeaders = names.size();
if (numHeaders == 0) {
return Unpooled.EMPTY_BUFFER;
}
if (numHeaders > SPDY_MAX_NV_LENGTH) {
throw new IllegalArgumentException(
"header block contains too many headers");
}
ByteBuf headerBlock = Unpooled.buffer();
writeLengthField(headerBlock, numHeaders);
for (String name: names) {
byte[] nameBytes = name.getBytes("UTF-8");
writeLengthField(headerBlock, nameBytes.length);
headerBlock.writeBytes(nameBytes);
int savedIndex = headerBlock.writerIndex();
int valueLength = 0;
writeLengthField(headerBlock, valueLength);
for (String value: frame.headers().getAll(name)) {
byte[] valueBytes = value.getBytes("UTF-8");
if (valueBytes.length > 0) {
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
valueLength += valueBytes.length + 1;
}
}
if (valueLength == 0) {
if (version < 3) {
throw new IllegalArgumentException(
"header value cannot be empty: " + name);
}
} else {
valueLength --;
}
if (valueLength > SPDY_MAX_NV_LENGTH) {
throw new IllegalArgumentException(
"header exceeds allowable length: " + name);
}
if (valueLength > 0) {
setLengthField(headerBlock, savedIndex, valueLength);
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
}
}
return headerBlock;
}
@Override
void end() {
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -16,34 +16,46 @@
package io.netty.handler.codec.spdy;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
private final int version;
private final byte[] out = new byte[8192];
private final Inflater decompressor = new Inflater();
public SpdyHeaderBlockZlibDecompressor(int version) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
private ByteBuf decompressed;
public SpdyHeaderBlockZlibDecoder(int version, int maxHeaderSize) {
super(version, maxHeaderSize);
this.version = version;
}
@Override
public void setInput(ByteBuf compressed) {
void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception {
setInput(encoded);
int numBytes;
do {
numBytes = decompress(frame);
} while (!decompressed.readable() && numBytes > 0);
}
private void setInput(ByteBuf compressed) {
byte[] in = new byte[compressed.readableBytes()];
compressed.readBytes(in);
decompressor.setInput(in);
}
@Override
public int decode(ByteBuf decompressed) throws Exception {
private int decompress(SpdyHeadersFrame frame) throws Exception {
if (decompressed == null) {
decompressed = decompressed = Unpooled.buffer(8192);
}
try {
int numBytes = decompressor.inflate(out);
if (numBytes == 0 && decompressor.needsDictionary()) {
@ -54,7 +66,10 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
}
numBytes = decompressor.inflate(out);
}
decompressed.writeBytes(out, 0, numBytes);
if (frame != null) {
decompressed.writeBytes(out, 0, numBytes);
super.decode(decompressed, frame);
}
return numBytes;
} catch (DataFormatException e) {
throw new SpdyProtocolException(
@ -62,8 +77,16 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
}
}
@Override
public void reset() {
decompressed = null;
super.reset();
}
@Override
public void end() {
decompressed = null;
decompressor.end();
super.end();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
@ -16,20 +16,21 @@
package io.netty.handler.codec.spdy;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.zip.Deflater;
class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
class SpdyHeaderBlockZlibEncoder extends SpdyHeaderBlockRawEncoder {
private final byte[] out = new byte[8192];
private final Deflater compressor;
public SpdyHeaderBlockZlibCompressor(int version, int compressionLevel) {
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
throw new IllegalArgumentException(
"unsupported version: " + version);
}
private boolean finished;
public SpdyHeaderBlockZlibEncoder(int version, int compressionLevel) {
super(version);
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
@ -42,15 +43,13 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
}
}
@Override
public void setInput(ByteBuf decompressed) {
private void setInput(ByteBuf decompressed) {
byte[] in = new byte[decompressed.readableBytes()];
decompressed.readBytes(in);
compressor.setInput(in);
}
@Override
public void encode(ByteBuf compressed) {
private void encode(ByteBuf compressed) {
int numBytes = out.length;
while (numBytes == out.length) {
numBytes = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH);
@ -58,8 +57,33 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
}
}
@Override
public synchronized ByteBuf encode(SpdyHeadersFrame frame) throws Exception {
if (frame == null) {
throw new IllegalArgumentException("frame");
}
if (finished) {
throw new IllegalAccessException("compressor closed");
}
ByteBuf decompressed = super.encode(frame);
if (decompressed.readableBytes() == 0) {
return Unpooled.EMPTY_BUFFER;
}
ByteBuf compressed = Unpooled.buffer();
setInput(decompressed);
encode(compressed);
return compressed;
}
@Override
public void end() {
if (finished) {
return;
}
finished = true;
compressor.end();
super.end();
}
}