SPDY: refactor frame codec implementation

Motivation:

Currently, the SPDY frame encoding and decoding code is based upon
the ChannelHandler abstraction. This requires maintaining multiple
versions for 3.x and 4.x (and possibly 5.x moving forward).

Modifications:

The SPDY frame encoding and decoding code is separated from the
ChannelHandler and SpdyFrame abstractions. Also test coverage is
improved.

Result:

SpdyFrameCodec now implements the ChannelHandler abstraction and is
responsible for creating and handling SpdyFrame objects.
This commit is contained in:
Jeff Pinner 2014-04-13 13:55:29 -07:00 committed by Norman Maurer
parent 15d11289b0
commit 7808b9926d
16 changed files with 3058 additions and 1109 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 The Netty Project
* Copyright 2014 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
@ -15,12 +15,38 @@
*/
package io.netty.handler.codec.spdy;
import io.netty.channel.ChannelHandlerAppender;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import java.net.SocketAddress;
import java.util.List;
/**
* A combination of {@link SpdyFrameDecoder} and {@link SpdyFrameEncoder}.
* A {@link ChannelHandler} that encodes and decodes SPDY Frames.
*/
public final class SpdyFrameCodec extends ChannelHandlerAppender {
public final class SpdyFrameCodec extends ByteToMessageDecoder
implements SpdyFrameDecoderDelegate, ChannelOutboundHandler {
private static final SpdyProtocolException INVALID_FRAME =
new SpdyProtocolException("Received invalid frame");
private final SpdyFrameDecoder spdyFrameDecoder;
private final SpdyFrameEncoder spdyFrameEncoder;
private final SpdyHeaderBlockDecoder spdyHeaderBlockDecoder;
private final SpdyHeaderBlockEncoder spdyHeaderBlockEncoder;
private SpdyHeadersFrame spdyHeadersFrame;
private SpdySettingsFrame spdySettingsFrame;
private ChannelHandlerContext ctx;
/**
* Creates a new instance with the specified {@code version} and
* the default decoder and encoder options
@ -38,8 +64,289 @@ public final class SpdyFrameCodec extends ChannelHandlerAppender {
public SpdyFrameCodec(
SpdyVersion version, int maxChunkSize, int maxHeaderSize,
int compressionLevel, int windowBits, int memLevel) {
super(
new SpdyFrameDecoder(version, maxChunkSize, maxHeaderSize),
new SpdyFrameEncoder(version, compressionLevel, windowBits, memLevel));
this(version, maxChunkSize,
SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize),
SpdyHeaderBlockEncoder.newInstance(version, compressionLevel, windowBits, memLevel));
}
protected SpdyFrameCodec(SpdyVersion version, int maxChunkSize,
SpdyHeaderBlockDecoder spdyHeaderBlockDecoder, SpdyHeaderBlockEncoder spdyHeaderBlockEncoder) {
spdyFrameDecoder = new SpdyFrameDecoder(version, this, maxChunkSize);
spdyFrameEncoder = new SpdyFrameEncoder(version);
this.spdyHeaderBlockDecoder = spdyHeaderBlockDecoder;
this.spdyHeaderBlockEncoder = spdyHeaderBlockEncoder;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
this.ctx = ctx;
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
spdyHeaderBlockDecoder.end();
spdyHeaderBlockEncoder.end();
}
});
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
spdyFrameDecoder.decode(in);
}
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise);
}
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
ctx.disconnect(promise);
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
ctx.close(promise);
}
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
ctx.deregister(promise);
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.read();
}
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf frame;
if (msg instanceof SpdyDataFrame) {
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
frame = spdyFrameEncoder.encodeDataFrame(
ctx.alloc(),
spdyDataFrame.getStreamId(),
spdyDataFrame.isLast(),
spdyDataFrame.content()
);
spdyDataFrame.release();
ctx.write(frame, promise);
} else if (msg instanceof SpdySynStreamFrame) {
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(spdySynStreamFrame);
try {
frame = spdyFrameEncoder.encodeSynStreamFrame(
ctx.alloc(),
spdySynStreamFrame.getStreamId(),
spdySynStreamFrame.getAssociatedToStreamId(),
spdySynStreamFrame.getPriority(),
spdySynStreamFrame.isLast(),
spdySynStreamFrame.isUnidirectional(),
headerBlock
);
} finally {
headerBlock.release();
}
ctx.write(frame, promise);
} else if (msg instanceof SpdySynReplyFrame) {
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(spdySynReplyFrame);
try {
frame = spdyFrameEncoder.encodeSynReplyFrame(
ctx.alloc(),
spdySynReplyFrame.getStreamId(),
spdySynReplyFrame.isLast(),
headerBlock
);
} finally {
headerBlock.release();
}
ctx.write(frame, promise);
} else if (msg instanceof SpdyRstStreamFrame) {
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
frame = spdyFrameEncoder.encodeRstStreamFrame(
ctx.alloc(),
spdyRstStreamFrame.getStreamId(),
spdyRstStreamFrame.getStatus().getCode()
);
ctx.write(frame, promise);
} else if (msg instanceof SpdySettingsFrame) {
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
frame = spdyFrameEncoder.encodeSettingsFrame(
ctx.alloc(),
spdySettingsFrame
);
ctx.write(frame, promise);
} else if (msg instanceof SpdyPingFrame) {
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
frame = spdyFrameEncoder.encodePingFrame(
ctx.alloc(),
spdyPingFrame.getId()
);
ctx.write(frame, promise);
} else if (msg instanceof SpdyGoAwayFrame) {
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
frame = spdyFrameEncoder.encodeGoAwayFrame(
ctx.alloc(),
spdyGoAwayFrame.getLastGoodStreamId(),
spdyGoAwayFrame.getStatus().getCode()
);
ctx.write(frame, promise);
} else if (msg instanceof SpdyHeadersFrame) {
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
ByteBuf headerBlock = spdyHeaderBlockEncoder.encode(spdyHeadersFrame);
try {
frame = spdyFrameEncoder.encodeHeadersFrame(
ctx.alloc(),
spdyHeadersFrame.getStreamId(),
spdyHeadersFrame.isLast(),
headerBlock
);
} finally {
headerBlock.release();
}
ctx.write(frame, promise);
} else if (msg instanceof SpdyWindowUpdateFrame) {
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
frame = spdyFrameEncoder.encodeWindowUpdateFrame(
ctx.alloc(),
spdyWindowUpdateFrame.getStreamId(),
spdyWindowUpdateFrame.getDeltaWindowSize()
);
ctx.write(frame, promise);
} else {
throw new UnsupportedMessageTypeException(msg);
}
}
@Override
public void readDataFrame(int streamId, boolean last, ByteBuf data) {
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
spdyDataFrame.setLast(last);
ctx.fireChannelRead(spdyDataFrame);
}
@Override
public void readSynStreamFrame(
int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional) {
SpdySynStreamFrame spdySynStreamFrame = new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
spdySynStreamFrame.setLast(last);
spdySynStreamFrame.setUnidirectional(unidirectional);
spdyHeadersFrame = spdySynStreamFrame;
}
@Override
public void readSynReplyFrame(int streamId, boolean last) {
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
spdySynReplyFrame.setLast(last);
spdyHeadersFrame = spdySynReplyFrame;
}
@Override
public void readRstStreamFrame(int streamId, int statusCode) {
SpdyRstStreamFrame spdyRstStreamFrame = new DefaultSpdyRstStreamFrame(streamId, statusCode);
ctx.fireChannelRead(spdyRstStreamFrame);
}
@Override
public void readSettingsFrame(boolean clearPersisted) {
spdySettingsFrame = new DefaultSpdySettingsFrame();
spdySettingsFrame.setClearPreviouslyPersistedSettings(clearPersisted);
}
@Override
public void readSetting(int id, int value, boolean persistValue, boolean persisted) {
spdySettingsFrame.setValue(id, value, persistValue, persisted);
}
@Override
public void readSettingsEnd() {
Object frame = spdySettingsFrame;
spdySettingsFrame = null;
ctx.fireChannelRead(frame);
}
@Override
public void readPingFrame(int id) {
SpdyPingFrame spdyPingFrame = new DefaultSpdyPingFrame(id);
ctx.fireChannelRead(spdyPingFrame);
}
@Override
public void readGoAwayFrame(int lastGoodStreamId, int statusCode) {
SpdyGoAwayFrame spdyGoAwayFrame = new DefaultSpdyGoAwayFrame(lastGoodStreamId, statusCode);
ctx.fireChannelRead(spdyGoAwayFrame);
}
@Override
public void readHeadersFrame(int streamId, boolean last) {
spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
spdyHeadersFrame.setLast(last);
}
@Override
public void readWindowUpdateFrame(int streamId, int deltaWindowSize) {
SpdyWindowUpdateFrame spdyWindowUpdateFrame = new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
ctx.fireChannelRead(spdyWindowUpdateFrame);
}
@Override
public void readHeaderBlock(ByteBuf headerBlock) {
try {
spdyHeaderBlockDecoder.decode(headerBlock, spdyHeadersFrame);
} catch (Exception e) {
ctx.fireExceptionCaught(e);
}
}
@Override
public void readHeaderBlockEnd() {
Object frame = null;
try {
spdyHeaderBlockDecoder.endHeaderBlock(spdyHeadersFrame);
frame = spdyHeadersFrame;
spdyHeadersFrame = null;
} catch (Exception e) {
ctx.fireExceptionCaught(e);
}
if (frame != null) {
ctx.fireChannelRead(frame);
}
}
@Override
public void readFrameError(String message) {
ctx.fireExceptionCaught(INVALID_FRAME);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 The Netty Project
* Copyright 2014 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
@ -39,511 +39,427 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedInt;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedMedium;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.getUnsignedShort;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
import io.netty.buffer.Unpooled;
/**
* Decodes {@link ByteBuf}s into SPDY Frames.
*/
public class SpdyFrameDecoder extends ByteToMessageDecoder {
private static final SpdyProtocolException INVALID_FRAME =
new SpdyProtocolException("Received invalid frame");
public class SpdyFrameDecoder {
private final int spdyVersion;
private final int maxChunkSize;
private final SpdyHeaderBlockDecoder headerBlockDecoder;
private final SpdyFrameDecoderDelegate delegate;
private State state;
private SpdySettingsFrame spdySettingsFrame;
private SpdyHeadersFrame spdyHeadersFrame;
// SPDY common header fields
private byte flags;
private int length;
private int version;
private int type;
private int streamId;
private int numSettings;
private enum State {
READ_COMMON_HEADER,
READ_CONTROL_FRAME,
READ_SETTINGS_FRAME,
READ_HEADER_BLOCK_FRAME,
READ_HEADER_BLOCK,
READ_DATA_FRAME,
READ_SYN_STREAM_FRAME,
READ_SYN_REPLY_FRAME,
READ_RST_STREAM_FRAME,
READ_SETTINGS_FRAME,
READ_SETTING,
READ_PING_FRAME,
READ_GOAWAY_FRAME,
READ_HEADERS_FRAME,
READ_WINDOW_UPDATE_FRAME,
READ_HEADER_BLOCK,
DISCARD_FRAME,
FRAME_ERROR
}
/**
* Creates a new instance with the specified {@code version} and the default
* {@code maxChunkSize (8192)} and {@code maxHeaderSize (16384)}.
* Creates a new instance with the specified {@code version}
* and the default {@code maxChunkSize (8192)}.
*/
public SpdyFrameDecoder(SpdyVersion version) {
this(version, 8192, 16384);
public SpdyFrameDecoder(SpdyVersion spdyVersion, SpdyFrameDecoderDelegate delegate) {
this(spdyVersion, delegate, 8192);
}
/**
* Creates a new instance with the specified parameters.
*/
public SpdyFrameDecoder(SpdyVersion version, int maxChunkSize, int maxHeaderSize) {
this(version, maxChunkSize, SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize));
}
protected SpdyFrameDecoder(
SpdyVersion version, int maxChunkSize, SpdyHeaderBlockDecoder headerBlockDecoder) {
if (version == null) {
throw new NullPointerException("version");
public SpdyFrameDecoder(SpdyVersion spdyVersion, SpdyFrameDecoderDelegate delegate, int maxChunkSize) {
if (spdyVersion == null) {
throw new NullPointerException("spdyVersion");
}
if (delegate == null) {
throw new NullPointerException("delegate");
}
if (maxChunkSize <= 0) {
throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " + maxChunkSize);
}
spdyVersion = version.getVersion();
this.spdyVersion = spdyVersion.getVersion();
this.delegate = delegate;
this.maxChunkSize = maxChunkSize;
this.headerBlockDecoder = headerBlockDecoder;
state = State.READ_COMMON_HEADER;
}
@Override
public void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
try {
decode(ctx, in, out);
} finally {
headerBlockDecoder.end();
}
}
public void decode(ByteBuf buffer) {
boolean last;
int statusCode;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
switch(state) {
case READ_COMMON_HEADER:
state = readCommonHeader(buffer);
if (state == State.FRAME_ERROR) {
if (version != spdyVersion) {
fireProtocolException(ctx, "Unsupported version: " + version);
} else {
fireInvalidFrameException(ctx);
}
}
// FrameDecoders must consume data when producing frames
// All length 0 frames must be generated now
if (length == 0) {
if (state == State.READ_DATA_FRAME) {
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId);
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
state = State.READ_COMMON_HEADER;
out.add(spdyDataFrame);
return;
}
// There are no length 0 control frames
state = State.READ_COMMON_HEADER;
}
return;
case READ_CONTROL_FRAME:
try {
Object frame = readControlFrame(buffer);
if (frame != null) {
state = State.READ_COMMON_HEADER;
out.add(frame);
}
return;
} catch (IllegalArgumentException e) {
state = State.FRAME_ERROR;
fireInvalidFrameException(ctx);
}
return;
case READ_SETTINGS_FRAME:
if (spdySettingsFrame == null) {
// Validate frame length against number of entries
if (buffer.readableBytes() < 4) {
return;
}
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;
fireInvalidFrameException(ctx);
return;
}
spdySettingsFrame = new DefaultSpdySettingsFrame();
boolean clear = (flags & SPDY_SETTINGS_CLEAR) != 0;
spdySettingsFrame.setClearPreviouslyPersistedSettings(clear);
}
int readableEntries = Math.min(buffer.readableBytes() >> 3, length >> 3);
for (int i = 0; i < readableEntries; i ++) {
byte ID_flags = buffer.getByte(buffer.readerIndex());
int ID = getUnsignedMedium(buffer, buffer.readerIndex() + 1);
int value = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
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);
}
}
length -= 8 * readableEntries;
if (length == 0) {
state = State.READ_COMMON_HEADER;
Object frame = spdySettingsFrame;
spdySettingsFrame = null;
out.add(frame);
return;
}
return;
case READ_HEADER_BLOCK_FRAME:
try {
spdyHeadersFrame = readHeaderBlockFrame(buffer);
if (spdyHeadersFrame != null) {
if (length == 0) {
state = State.READ_COMMON_HEADER;
Object frame = spdyHeadersFrame;
spdyHeadersFrame = null;
out.add(frame);
while (true) {
switch(state) {
case READ_COMMON_HEADER:
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
return;
}
state = State.READ_HEADER_BLOCK;
}
return;
} catch (IllegalArgumentException e) {
state = State.FRAME_ERROR;
fireInvalidFrameException(ctx);
return;
}
case READ_HEADER_BLOCK:
int compressedBytes = Math.min(buffer.readableBytes(), length);
ByteBuf compressed = buffer.slice(buffer.readerIndex(), compressedBytes);
int frameOffset = buffer.readerIndex();
int flagsOffset = frameOffset + SPDY_HEADER_FLAGS_OFFSET;
int lengthOffset = frameOffset + SPDY_HEADER_LENGTH_OFFSET;
buffer.skipBytes(SPDY_HEADER_SIZE);
try {
headerBlockDecoder.decode(compressed, spdyHeadersFrame);
} catch (Exception e) {
state = State.FRAME_ERROR;
spdyHeadersFrame = null;
ctx.fireExceptionCaught(e);
return;
}
boolean control = (buffer.getByte(frameOffset) & 0x80) != 0;
int readBytes = compressedBytes - compressed.readableBytes();
buffer.skipBytes(readBytes);
length -= readBytes;
int version;
int type;
if (control) {
// Decode control frame common header
version = getUnsignedShort(buffer, frameOffset) & 0x7FFF;
type = getUnsignedShort(buffer, frameOffset + SPDY_HEADER_TYPE_OFFSET);
streamId = 0; // Default to session Stream-ID
} else {
// Decode data frame common header
version = spdyVersion; // Default to expected version
type = SPDY_DATA_FRAME;
streamId = getUnsignedInt(buffer, frameOffset);
}
if (spdyHeadersFrame != null &&
(spdyHeadersFrame.isInvalid() || spdyHeadersFrame.isTruncated())) {
flags = buffer.getByte(flagsOffset);
length = getUnsignedMedium(buffer, lengthOffset);
// Check version first then validity
if (version != spdyVersion) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SPDY Version");
} else if (!isValidFrameHeader(streamId, type, flags, length)) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid Frame Error");
} else {
state = getNextState(type, length);
}
break;
case READ_DATA_FRAME:
if (length == 0) {
state = State.READ_COMMON_HEADER;
delegate.readDataFrame(streamId, hasFlag(flags, SPDY_DATA_FLAG_FIN), Unpooled.buffer(0));
break;
}
// 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;
}
ByteBuf data = buffer.alloc().buffer(dataLength);
data.writeBytes(buffer, dataLength);
length -= dataLength;
if (length == 0) {
state = State.READ_COMMON_HEADER;
}
last = length == 0 && hasFlag(flags, SPDY_DATA_FLAG_FIN);
delegate.readDataFrame(streamId, last, data);
break;
case READ_SYN_STREAM_FRAME:
if (buffer.readableBytes() < 10) {
return;
}
int offset = buffer.readerIndex();
streamId = getUnsignedInt(buffer, offset);
int associatedToStreamId = getUnsignedInt(buffer, offset + 4);
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
last = hasFlag(flags, SPDY_FLAG_FIN);
boolean unidirectional = hasFlag(flags, SPDY_FLAG_UNIDIRECTIONAL);
buffer.skipBytes(10);
length -= 10;
if (streamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SYN_STREAM Frame");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readSynStreamFrame(streamId, associatedToStreamId, priority, last, unidirectional);
}
break;
case READ_SYN_REPLY_FRAME:
if (buffer.readableBytes() < 4) {
return;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
last = hasFlag(flags, SPDY_FLAG_FIN);
buffer.skipBytes(4);
length -= 4;
if (streamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SYN_REPLY Frame");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readSynReplyFrame(streamId, last);
}
break;
case READ_RST_STREAM_FRAME:
if (buffer.readableBytes() < 8) {
return;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
if (streamId == 0 || statusCode == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid RST_STREAM Frame");
} else {
state = State.READ_COMMON_HEADER;
delegate.readRstStreamFrame(streamId, statusCode);
}
break;
case READ_SETTINGS_FRAME:
if (buffer.readableBytes() < 4) {
return;
}
boolean clear = hasFlag(flags, SPDY_SETTINGS_CLEAR);
numSettings = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
length -= 4;
// Validate frame length against number of entries. Each ID/Value entry is 8 bytes.
if ((length & 0x07) != 0 || length >> 3 != numSettings) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid SETTINGS Frame");
} else {
state = State.READ_SETTING;
delegate.readSettingsFrame(clear);
}
break;
case READ_SETTING:
if (numSettings == 0) {
state = State.READ_COMMON_HEADER;
delegate.readSettingsEnd();
break;
}
if (buffer.readableBytes() < 8) {
return;
}
byte settingsFlags = buffer.getByte(buffer.readerIndex());
int id = getUnsignedMedium(buffer, buffer.readerIndex() + 1);
int value = getSignedInt(buffer, buffer.readerIndex() + 4);
boolean persistValue = hasFlag(settingsFlags, SPDY_SETTINGS_PERSIST_VALUE);
boolean persisted = hasFlag(settingsFlags, SPDY_SETTINGS_PERSISTED);
buffer.skipBytes(8);
--numSettings;
delegate.readSetting(id, value, persistValue, persisted);
break;
case READ_PING_FRAME:
if (buffer.readableBytes() < 4) {
return;
}
int pingId = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
Object frame = spdyHeadersFrame;
spdyHeadersFrame = null;
if (length == 0) {
headerBlockDecoder.reset();
state = State.READ_COMMON_HEADER;
}
out.add(frame);
return;
delegate.readPingFrame(pingId);
break;
case READ_GOAWAY_FRAME:
if (buffer.readableBytes() < 8) {
return;
}
int lastGoodStreamId = getUnsignedInt(buffer, buffer.readerIndex());
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
state = State.READ_COMMON_HEADER;
delegate.readGoAwayFrame(lastGoodStreamId, statusCode);
break;
case READ_HEADERS_FRAME:
if (buffer.readableBytes() < 4) {
return;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
last = hasFlag(flags, SPDY_FLAG_FIN);
buffer.skipBytes(4);
length -= 4;
if (streamId == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid HEADERS Frame");
} else {
state = State.READ_HEADER_BLOCK;
delegate.readHeadersFrame(streamId, last);
}
break;
case READ_WINDOW_UPDATE_FRAME:
if (buffer.readableBytes() < 8) {
return;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
if (deltaWindowSize == 0) {
state = State.FRAME_ERROR;
delegate.readFrameError("Invalid WINDOW_UPDATE Frame");
} else {
state = State.READ_COMMON_HEADER;
delegate.readWindowUpdateFrame(streamId, deltaWindowSize);
}
break;
case READ_HEADER_BLOCK:
if (length == 0) {
state = State.READ_COMMON_HEADER;
delegate.readHeaderBlockEnd();
break;
}
if (!buffer.isReadable()) {
return;
}
int compressedBytes = Math.min(buffer.readableBytes(), length);
ByteBuf headerBlock = buffer.alloc().buffer(compressedBytes);
headerBlock.writeBytes(buffer, compressedBytes);
length -= compressedBytes;
delegate.readHeaderBlock(headerBlock);
break;
case DISCARD_FRAME:
int numBytes = Math.min(buffer.readableBytes(), length);
buffer.skipBytes(numBytes);
length -= numBytes;
if (length == 0) {
state = State.READ_COMMON_HEADER;
break;
}
return;
case FRAME_ERROR:
buffer.skipBytes(buffer.readableBytes());
return;
default:
throw new Error("Shouldn't reach here.");
}
if (length == 0) {
Object frame = spdyHeadersFrame;
spdyHeadersFrame = null;
headerBlockDecoder.reset();
state = State.READ_COMMON_HEADER;
if (frame != null) {
out.add(frame);
}
}
return;
case READ_DATA_FRAME:
if (streamId == 0) {
state = State.FRAME_ERROR;
fireProtocolException(ctx, "Received invalid data frame");
return;
}
// 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;
}
ByteBuf data = ctx.alloc().buffer(dataLength);
data.writeBytes(buffer, dataLength);
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
length -= dataLength;
if (length == 0) {
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
state = State.READ_COMMON_HEADER;
}
out.add(spdyDataFrame);
return;
case DISCARD_FRAME:
int numBytes = Math.min(buffer.readableBytes(), length);
buffer.skipBytes(numBytes);
length -= numBytes;
if (length == 0) {
state = State.READ_COMMON_HEADER;
}
return;
case FRAME_ERROR:
buffer.skipBytes(buffer.readableBytes());
return;
default:
throw new Error("Shouldn't reach here.");
}
}
private State readCommonHeader(ByteBuf buffer) {
// Wait until entire header is readable
if (buffer.readableBytes() < SPDY_HEADER_SIZE) {
return State.READ_COMMON_HEADER;
}
private static boolean hasFlag(byte flags, byte flag) {
return (flags & flag) != 0;
}
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);
streamId = 0;
} else {
// Decode data frame common header
version = spdyVersion; // Default to expected version
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) {
private static State getNextState(int type, int length) {
switch (type) {
case SPDY_DATA_FRAME:
nextState = State.READ_DATA_FRAME;
break;
return State.READ_DATA_FRAME;
case SPDY_SYN_STREAM_FRAME:
return State.READ_SYN_STREAM_FRAME;
case SPDY_SYN_REPLY_FRAME:
case SPDY_HEADERS_FRAME:
nextState = State.READ_HEADER_BLOCK_FRAME;
break;
return State.READ_SYN_REPLY_FRAME;
case SPDY_RST_STREAM_FRAME:
return State.READ_RST_STREAM_FRAME;
case SPDY_SETTINGS_FRAME:
nextState = State.READ_SETTINGS_FRAME;
break;
return State.READ_SETTINGS_FRAME;
case SPDY_PING_FRAME:
return State.READ_PING_FRAME;
case SPDY_GOAWAY_FRAME:
return State.READ_GOAWAY_FRAME;
case SPDY_HEADERS_FRAME:
return State.READ_HEADERS_FRAME;
case SPDY_WINDOW_UPDATE_FRAME:
return State.READ_WINDOW_UPDATE_FRAME;
default:
nextState = State.READ_CONTROL_FRAME;
}
} else if (length != 0) {
nextState = State.DISCARD_FRAME;
} else {
nextState = State.READ_COMMON_HEADER;
if (length != 0) {
return State.DISCARD_FRAME;
} else {
return State.READ_COMMON_HEADER;
}
}
return nextState;
}
private Object readControlFrame(ByteBuf buffer) {
int streamId;
int statusCode;
private static boolean isValidFrameHeader(int streamId, int type, byte flags, int length) {
switch (type) {
case SPDY_RST_STREAM_FRAME:
if (buffer.readableBytes() < 8) {
return null;
}
case SPDY_DATA_FRAME:
return streamId != 0;
streamId = getUnsignedInt(buffer, buffer.readerIndex());
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
case SPDY_SYN_STREAM_FRAME:
return length >= 10;
return new DefaultSpdyRstStreamFrame(streamId, statusCode);
case SPDY_SYN_REPLY_FRAME:
return length >= 4;
case SPDY_PING_FRAME:
if (buffer.readableBytes() < 4) {
return null;
}
case SPDY_RST_STREAM_FRAME:
return flags == 0 && length == 8;
int ID = getSignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
case SPDY_SETTINGS_FRAME:
return length >= 4;
return new DefaultSpdyPingFrame(ID);
case SPDY_PING_FRAME:
return length == 4;
case SPDY_GOAWAY_FRAME:
if (buffer.readableBytes() < 8) {
return null;
}
case SPDY_GOAWAY_FRAME:
return length == 8;
int lastGoodStreamId = getUnsignedInt(buffer, buffer.readerIndex());
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
case SPDY_HEADERS_FRAME:
return length >= 4;
return new DefaultSpdyGoAwayFrame(lastGoodStreamId, statusCode);
case SPDY_WINDOW_UPDATE_FRAME:
return length == 8;
case SPDY_WINDOW_UPDATE_FRAME:
if (buffer.readableBytes() < 8) {
return null;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
buffer.skipBytes(8);
return new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
default:
throw new Error("Shouldn't reach here.");
default:
return true;
}
}
private SpdyHeadersFrame readHeaderBlockFrame(ByteBuf buffer) {
int streamId;
switch (type) {
case SPDY_SYN_STREAM_FRAME:
if (buffer.readableBytes() < 10) {
return null;
}
int offset = buffer.readerIndex();
streamId = getUnsignedInt(buffer, offset);
int associatedToStreamId = getUnsignedInt(buffer, offset + 4);
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
buffer.skipBytes(10);
length -= 10;
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() < 4) {
return null;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
length -= 4;
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
return spdySynReplyFrame;
case SPDY_HEADERS_FRAME:
if (buffer.readableBytes() < 4) {
return null;
}
streamId = getUnsignedInt(buffer, buffer.readerIndex());
buffer.skipBytes(4);
length -= 4;
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
return spdyHeadersFrame;
default:
throw new Error("Shouldn't reach here.");
}
}
private boolean isValidFrameHeader() {
switch (type) {
case SPDY_DATA_FRAME:
return streamId != 0;
case SPDY_SYN_STREAM_FRAME:
return length >= 10;
case SPDY_SYN_REPLY_FRAME:
return length >= 4;
case SPDY_RST_STREAM_FRAME:
return flags == 0 && length == 8;
case SPDY_SETTINGS_FRAME:
return length >= 4;
case SPDY_PING_FRAME:
return length == 4;
case SPDY_GOAWAY_FRAME:
return length == 8;
case SPDY_HEADERS_FRAME:
return length >= 4;
case SPDY_WINDOW_UPDATE_FRAME:
return length == 8;
default:
return true;
}
}
private boolean willGenerateFrame() {
switch (type) {
case SPDY_DATA_FRAME:
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:
case SPDY_WINDOW_UPDATE_FRAME:
return true;
default:
return false;
}
}
private static void fireInvalidFrameException(ChannelHandlerContext ctx) {
ctx.fireExceptionCaught(INVALID_FRAME);
}
private static void fireProtocolException(ChannelHandlerContext ctx, String message) {
ctx.fireExceptionCaught(new SpdyProtocolException(message));
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright 2014 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;
/**
* Callback interface for {@link SpdyFrameDecoder}.
*/
public interface SpdyFrameDecoderDelegate {
/**
* Called when a DATA frame is received.
*/
void readDataFrame(int streamId, boolean last, ByteBuf data);
/**
* Called when a SYN_STREAM frame is received.
* The Name/Value Header Block is not included. See readHeaderBlock().
*/
void readSynStreamFrame(
int streamId, int associatedToStreamId, byte priority, boolean last, boolean unidirectional);
/**
* Called when a SYN_REPLY frame is received.
* The Name/Value Header Block is not included. See readHeaderBlock().
*/
void readSynReplyFrame(int streamId, boolean last);
/**
* Called when a RST_STREAM frame is received.
*/
void readRstStreamFrame(int streamId, int statusCode);
/**
* Called when a SETTINGS frame is received.
* Settings are not included. See readSetting().
*/
void readSettingsFrame(boolean clearPersisted);
/**
* Called when an individual setting within a SETTINGS frame is received.
*/
void readSetting(int id, int value, boolean persistValue, boolean persisted);
/**
* Called when the entire SETTINGS frame has been received.
*/
void readSettingsEnd();
/**
* Called when a PING frame is received.
*/
void readPingFrame(int id);
/**
* Called when a GOAWAY frame is received.
*/
void readGoAwayFrame(int lastGoodStreamId, int statusCode);
/**
* Called when a HEADERS frame is received.
* The Name/Value Header Block is not included. See readHeaderBlock().
*/
void readHeadersFrame(int streamId, boolean last);
/**
* Called when a WINDOW_UPDATE frame is received.
*/
void readWindowUpdateFrame(int streamId, int deltaWindowSize);
/**
* Called when the header block within a SYN_STREAM, SYN_REPLY, or HEADERS frame is received.
*/
void readHeaderBlock(ByteBuf headerBlock);
/**
* Called when an entire header block has been received.
*/
void readHeaderBlockEnd();
/**
* Called when an unrecoverable session error has occurred.
*/
void readFrameError(String message);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 The Netty Project
* Copyright 2014 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,12 +16,9 @@
package io.netty.handler.codec.spdy;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.buffer.ByteBufAllocator;
import java.nio.ByteOrder;
import java.util.Set;
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
@ -29,188 +26,138 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
/**
* Encodes a SPDY Frame into a {@link ByteBuf}.
*/
public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
public class SpdyFrameEncoder {
private final int version;
private final SpdyHeaderBlockEncoder headerBlockEncoder;
/**
* Creates a new instance with the specified {@code version} and the
* default {@code compressionLevel (6)}, {@code windowBits (15)},
* and {@code memLevel (8)}.
* Creates a new instance with the specified {@code spdyVersion}.
*/
public SpdyFrameEncoder(SpdyVersion version) {
this(version, 6, 15, 8);
}
/**
* Creates a new instance with the specified parameters.
*/
public SpdyFrameEncoder(SpdyVersion version, int compressionLevel, int windowBits, int memLevel) {
this(version, SpdyHeaderBlockEncoder.newInstance(
version, compressionLevel, windowBits, memLevel));
}
protected SpdyFrameEncoder(SpdyVersion version, SpdyHeaderBlockEncoder headerBlockEncoder) {
if (version == null) {
throw new NullPointerException("version");
public SpdyFrameEncoder(SpdyVersion spdyVersion) {
if (spdyVersion == null) {
throw new NullPointerException("spdyVersion");
}
this.version = version.getVersion();
this.headerBlockEncoder = headerBlockEncoder;
version = spdyVersion.getVersion();
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
headerBlockEncoder.end();
}
});
private void writeControlFrameHeader(ByteBuf buffer, int type, byte flags, int length) {
buffer.writeShort(version | 0x8000);
buffer.writeShort(type);
buffer.writeByte(flags);
buffer.writeMedium(length);
}
@Override
protected void encode(ChannelHandlerContext ctx, SpdyFrame msg, ByteBuf out) throws Exception {
if (msg instanceof SpdyDataFrame) {
public ByteBuf encodeDataFrame(ByteBufAllocator allocator, int streamId, boolean last, ByteBuf data) {
byte flags = last ? SPDY_DATA_FLAG_FIN : 0;
int length = data.readableBytes();
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
frame.writeInt(streamId & 0x7FFFFFFF);
frame.writeByte(flags);
frame.writeMedium(length);
frame.writeBytes(data, data.readerIndex(), length);
return frame;
}
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
ByteBuf data = spdyDataFrame.content();
byte flags = spdyDataFrame.isLast() ? SPDY_DATA_FLAG_FIN : 0;
out.ensureWritable(SPDY_HEADER_SIZE + data.readableBytes());
out.writeInt(spdyDataFrame.getStreamId() & 0x7FFFFFFF);
out.writeByte(flags);
out.writeMedium(data.readableBytes());
out.writeBytes(data, data.readerIndex(), data.readableBytes());
public ByteBuf encodeSynStreamFrame(ByteBufAllocator allocator, int streamId, int associatedToStreamId,
byte priority, boolean last, boolean unidirectional, ByteBuf headerBlock) {
int headerBlockLength = headerBlock.readableBytes();
byte flags = last ? SPDY_FLAG_FIN : 0;
if (unidirectional) {
flags |= SPDY_FLAG_UNIDIRECTIONAL;
}
int length = 10 + headerBlockLength;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_SYN_STREAM_FRAME, flags, length);
frame.writeInt(streamId);
frame.writeInt(associatedToStreamId);
frame.writeShort((priority & 0xFF) << 13);
frame.writeBytes(headerBlock, headerBlock.readerIndex(), headerBlockLength);
return frame;
}
} else if (msg instanceof SpdySynStreamFrame) {
public ByteBuf encodeSynReplyFrame(ByteBufAllocator allocator, int streamId, boolean last, ByteBuf headerBlock) {
int headerBlockLength = headerBlock.readableBytes();
byte flags = last ? SPDY_FLAG_FIN : 0;
int length = 4 + headerBlockLength;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_SYN_REPLY_FRAME, flags, length);
frame.writeInt(streamId);
frame.writeBytes(headerBlock, headerBlock.readerIndex(), headerBlockLength);
return frame;
}
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
ByteBuf data = headerBlockEncoder.encode(spdySynStreamFrame);
try {
byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
if (spdySynStreamFrame.isUnidirectional()) {
flags |= SPDY_FLAG_UNIDIRECTIONAL;
}
int headerBlockLength = data.readableBytes();
int length = 10 + headerBlockLength;
out.ensureWritable(SPDY_HEADER_SIZE + length);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_SYN_STREAM_FRAME);
out.writeByte(flags);
out.writeMedium(length);
out.writeInt(spdySynStreamFrame.getStreamId());
out.writeInt(spdySynStreamFrame.getAssociatedToStreamId());
out.writeShort((spdySynStreamFrame.getPriority() & 0xFF) << 13);
out.writeBytes(data, data.readerIndex(), headerBlockLength);
} finally {
data.release();
}
public ByteBuf encodeRstStreamFrame(ByteBufAllocator allocator, int streamId, int statusCode) {
byte flags = 0;
int length = 8;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_RST_STREAM_FRAME, flags, length);
frame.writeInt(streamId);
frame.writeInt(statusCode);
return frame;
}
} else if (msg instanceof SpdySynReplyFrame) {
public ByteBuf encodeSettingsFrame(ByteBufAllocator allocator, SpdySettingsFrame spdySettingsFrame) {
Set<Integer> ids = spdySettingsFrame.getIds();
int numSettings = ids.size();
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
ByteBuf data = headerBlockEncoder.encode(spdySynReplyFrame);
try {
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
int headerBlockLength = data.readableBytes();
int length = 4 + headerBlockLength;
out.ensureWritable(SPDY_HEADER_SIZE + length);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_SYN_REPLY_FRAME);
out.writeByte(flags);
out.writeMedium(length);
out.writeInt(spdySynReplyFrame.getStreamId());
out.writeBytes(data, data.readerIndex(), headerBlockLength);
} finally {
data.release();
}
} else if (msg instanceof SpdyRstStreamFrame) {
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
out.ensureWritable(SPDY_HEADER_SIZE + 8);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_RST_STREAM_FRAME);
out.writeInt(8);
out.writeInt(spdyRstStreamFrame.getStreamId());
out.writeInt(spdyRstStreamFrame.getStatus().getCode());
} else if (msg instanceof SpdySettingsFrame) {
SpdySettingsFrame spdySettingsFrame = (SpdySettingsFrame) msg;
byte flags = spdySettingsFrame.clearPreviouslyPersistedSettings() ?
byte flags = spdySettingsFrame.clearPreviouslyPersistedSettings() ?
SPDY_SETTINGS_CLEAR : 0;
Set<Integer> IDs = spdySettingsFrame.getIds();
int numEntries = IDs.size();
int length = 4 + numEntries * 8;
out.ensureWritable(SPDY_HEADER_SIZE + length);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_SETTINGS_FRAME);
out.writeByte(flags);
out.writeMedium(length);
out.writeInt(numEntries);
for (Integer id: IDs) {
byte ID_flags = 0;
if (spdySettingsFrame.isPersistValue(id)) {
ID_flags |= SPDY_SETTINGS_PERSIST_VALUE;
}
if (spdySettingsFrame.isPersisted(id)) {
ID_flags |= SPDY_SETTINGS_PERSISTED;
}
out.writeByte(ID_flags);
out.writeMedium(id);
out.writeInt(spdySettingsFrame.getValue(id));
int length = 4 + 8 * numSettings;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_SETTINGS_FRAME, flags, length);
frame.writeInt(numSettings);
for (Integer id : ids) {
flags = 0;
if (spdySettingsFrame.isPersistValue(id)) {
flags |= SPDY_SETTINGS_PERSIST_VALUE;
}
} else if (msg instanceof SpdyPingFrame) {
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
out.ensureWritable(SPDY_HEADER_SIZE + 4);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_PING_FRAME);
out.writeInt(4);
out.writeInt(spdyPingFrame.getId());
} else if (msg instanceof SpdyGoAwayFrame) {
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
out.ensureWritable(SPDY_HEADER_SIZE + 8);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_GOAWAY_FRAME);
out.writeInt(8);
out.writeInt(spdyGoAwayFrame.getLastGoodStreamId());
out.writeInt(spdyGoAwayFrame.getStatus().getCode());
} else if (msg instanceof SpdyHeadersFrame) {
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
ByteBuf data = headerBlockEncoder.encode(spdyHeadersFrame);
try {
byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
int headerBlockLength = data.readableBytes();
int length = 4 + headerBlockLength;
out.ensureWritable(SPDY_HEADER_SIZE + length);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_HEADERS_FRAME);
out.writeByte(flags);
out.writeMedium(length);
out.writeInt(spdyHeadersFrame.getStreamId());
out.writeBytes(data, data.readerIndex(), headerBlockLength);
} finally {
data.release();
if (spdySettingsFrame.isPersisted(id)) {
flags |= SPDY_SETTINGS_PERSISTED;
}
} else if (msg instanceof SpdyWindowUpdateFrame) {
SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg;
out.ensureWritable(SPDY_HEADER_SIZE + 8);
out.writeShort(version | 0x8000);
out.writeShort(SPDY_WINDOW_UPDATE_FRAME);
out.writeInt(8);
out.writeInt(spdyWindowUpdateFrame.getStreamId());
out.writeInt(spdyWindowUpdateFrame.getDeltaWindowSize());
} else {
throw new UnsupportedMessageTypeException(msg);
frame.writeByte(flags);
frame.writeMedium(id);
frame.writeInt(spdySettingsFrame.getValue(id));
}
return frame;
}
public ByteBuf encodePingFrame(ByteBufAllocator allocator, int id) {
byte flags = 0;
int length = 4;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_PING_FRAME, flags, length);
frame.writeInt(id);
return frame;
}
public ByteBuf encodeGoAwayFrame(ByteBufAllocator allocator, int lastGoodStreamId, int statusCode) {
byte flags = 0;
int length = 8;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_GOAWAY_FRAME, flags, length);
frame.writeInt(lastGoodStreamId);
frame.writeInt(statusCode);
return frame;
}
public ByteBuf encodeHeadersFrame(ByteBufAllocator allocator, int streamId, boolean last, ByteBuf headerBlock) {
int headerBlockLength = headerBlock.readableBytes();
byte flags = last ? SPDY_FLAG_FIN : 0;
int length = 4 + headerBlockLength;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_HEADERS_FRAME, flags, length);
frame.writeInt(streamId);
frame.writeBytes(headerBlock, headerBlock.readerIndex(), headerBlockLength);
return frame;
}
public ByteBuf encodeWindowUpdateFrame(ByteBufAllocator allocator, int streamId, int deltaWindowSize) {
byte flags = 0;
int length = 8;
ByteBuf frame = allocator.ioBuffer(SPDY_HEADER_SIZE + length).order(ByteOrder.BIG_ENDIAN);
writeControlFrameHeader(frame, SPDY_WINDOW_UPDATE_FRAME, flags, length);
frame.writeInt(streamId);
frame.writeInt(deltaWindowSize);
return frame;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 The Netty Project
* Copyright 2014 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
@ -19,11 +19,24 @@ import io.netty.buffer.ByteBuf;
abstract class SpdyHeaderBlockDecoder {
static SpdyHeaderBlockDecoder newInstance(SpdyVersion version, int maxHeaderSize) {
return new SpdyHeaderBlockZlibDecoder(version, maxHeaderSize);
static SpdyHeaderBlockDecoder newInstance(SpdyVersion spdyVersion, int maxHeaderSize) {
return new SpdyHeaderBlockZlibDecoder(spdyVersion, maxHeaderSize);
}
abstract void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception;
abstract void reset();
/**
* Decodes a SPDY Header Block, adding the Name/Value pairs to the given Headers frame.
* If the header block is malformed, the Headers frame will be marked as invalid.
* A stream error with status code PROTOCOL_ERROR must be issued in response to an invalid frame.
*
* @param headerBlock the HeaderBlock to decode
* @param frame the Headers frame that receives the Name/Value pairs
* @throws Exception If the header block is malformed in a way that prevents any future
* decoding of any other header blocks, an exception will be thrown.
* A session error with status code PROTOCOL_ERROR must be issued.
*/
abstract void decode(ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception;
abstract void endHeaderBlock(SpdyHeadersFrame frame) throws Exception;
abstract void end();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 The Netty Project
* Copyright 2014 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
@ -23,20 +23,33 @@ public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
private static final int LENGTH_FIELD_SIZE = 4;
private final int version;
private final int maxHeaderSize;
// Header block decoding fields
private State state;
private int headerSize;
private int numHeaders = -1;
private int numHeaders;
private int length;
private String name;
private enum State {
READ_NUM_HEADERS,
READ_NAME_LENGTH,
READ_NAME,
SKIP_NAME,
READ_VALUE_LENGTH,
READ_VALUE,
SKIP_VALUE,
END_HEADER_BLOCK,
ERROR
}
public SpdyHeaderBlockRawDecoder(SpdyVersion spdyVersion, int maxHeaderSize) {
if (spdyVersion == null) {
throw new NullPointerException("spdyVersion");
}
version = spdyVersion.getVersion();
this.maxHeaderSize = maxHeaderSize;
this.state = State.READ_NUM_HEADERS;
}
private static int readLengthField(ByteBuf buffer) {
@ -46,133 +59,218 @@ public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
}
@Override
void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception {
if (encoded == null) {
throw new NullPointerException("encoded");
void decode(ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
if (headerBlock == null) {
throw new NullPointerException("headerBlock");
}
if (frame == null) {
throw new NullPointerException("frame");
}
if (numHeaders == -1) {
// Read number of Name/Value pairs
if (encoded.readableBytes() < LENGTH_FIELD_SIZE) {
return;
}
numHeaders = readLengthField(encoded);
if (numHeaders < 0) {
frame.setInvalid();
return;
}
}
int skipLength;
while (headerBlock.isReadable()) {
switch(state) {
case READ_NUM_HEADERS:
if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
return;
}
while (numHeaders > 0) {
int headerSize = this.headerSize;
encoded.markReaderIndex();
numHeaders = readLengthField(headerBlock);
// Try to read length of name
if (encoded.readableBytes() < LENGTH_FIELD_SIZE) {
encoded.resetReaderIndex();
return;
}
int nameLength = readLengthField(encoded);
if (numHeaders < 0) {
state = State.ERROR;
frame.setInvalid();
} else if (numHeaders == 0) {
state = State.END_HEADER_BLOCK;
} else {
state = State.READ_NAME_LENGTH;
}
break;
// Recipients of a zero-length name must issue a stream error
if (nameLength <= 0) {
frame.setInvalid();
return;
}
headerSize += nameLength;
if (headerSize > maxHeaderSize) {
frame.setTruncated();
return;
}
case READ_NAME_LENGTH:
if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
return;
}
// Try to read name
if (encoded.readableBytes() < nameLength) {
encoded.resetReaderIndex();
return;
}
byte[] nameBytes = new byte[nameLength];
encoded.readBytes(nameBytes);
String name = new String(nameBytes, "UTF-8");
length = readLengthField(headerBlock);
// Check for identically named headers
if (frame.headers().contains(name)) {
frame.setInvalid();
return;
}
// Recipients of a zero-length name must issue a stream error
if (length <= 0) {
state = State.ERROR;
frame.setInvalid();
} else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) {
headerSize = maxHeaderSize + 1;
state = State.SKIP_NAME;
frame.setTruncated();
} else {
headerSize += length;
state = State.READ_NAME;
}
break;
// Try to read length of value
if (encoded.readableBytes() < LENGTH_FIELD_SIZE) {
encoded.resetReaderIndex();
return;
}
int valueLength = readLengthField(encoded);
case READ_NAME:
if (headerBlock.readableBytes() < length) {
return;
}
// Recipients of illegal value fields must issue a stream error
if (valueLength < 0) {
frame.setInvalid();
return;
}
byte[] nameBytes = new byte[length];
headerBlock.readBytes(nameBytes);
name = new String(nameBytes, "UTF-8");
// SPDY/3 allows zero-length (empty) header values
if (valueLength == 0) {
frame.headers().add(name, "");
numHeaders --;
this.headerSize = headerSize;
continue;
}
// Check for identically named headers
if (frame.headers().contains(name)) {
state = State.ERROR;
frame.setInvalid();
} else {
state = State.READ_VALUE_LENGTH;
}
break;
headerSize += valueLength;
if (headerSize > maxHeaderSize) {
frame.setTruncated();
return;
}
case SKIP_NAME:
skipLength = Math.min(headerBlock.readableBytes(), length);
headerBlock.skipBytes(skipLength);
length -= skipLength;
// Try to read value
if (encoded.readableBytes() < valueLength) {
encoded.resetReaderIndex();
return;
}
byte[] valueBytes = new byte[valueLength];
encoded.readBytes(valueBytes);
if (length == 0) {
state = State.READ_VALUE_LENGTH;
}
break;
case READ_VALUE_LENGTH:
if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
return;
}
length = readLengthField(headerBlock);
// 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");
if (length < 0) {
state = State.ERROR;
frame.setInvalid();
} else if (length == 0) {
if (!frame.isTruncated()) {
// SPDY/3 allows zero-length (empty) header values
frame.headers().add(name, "");
}
try {
frame.headers().add(name, value);
} catch (IllegalArgumentException e) {
// Name contains NULL or non-ascii characters
name = null;
if (--numHeaders == 0) {
state = State.END_HEADER_BLOCK;
} else {
state = State.READ_NAME_LENGTH;
}
} else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) {
headerSize = maxHeaderSize + 1;
name = null;
state = State.SKIP_VALUE;
frame.setTruncated();
} else {
headerSize += length;
state = State.READ_VALUE;
}
break;
case READ_VALUE:
if (headerBlock.readableBytes() < length) {
return;
}
byte[] valueBytes = new byte[length];
headerBlock.readBytes(valueBytes);
// Add Name/Value pair to headers
int index = 0;
int offset = 0;
// Value must not start with a NULL character
if (valueBytes[0] == (byte) 0) {
state = State.ERROR;
frame.setInvalid();
break;
}
while (index < length) {
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
index ++;
}
if (index < valueBytes.length) {
// Received NULL character
if (index + 1 == valueBytes.length || valueBytes[index + 1] == (byte) 0) {
// Value field ended with a NULL character or
// received multiple, in-sequence NULL characters.
// Recipients of illegal value fields must issue a stream error
state = State.ERROR;
frame.setInvalid();
break;
}
}
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
state = State.ERROR;
frame.setInvalid();
break;
}
index ++;
offset = index;
}
name = null;
// If we broke out of the add header loop, break here
if (state == State.ERROR) {
break;
}
if (--numHeaders == 0) {
state = State.END_HEADER_BLOCK;
} else {
state = State.READ_NAME_LENGTH;
}
break;
case SKIP_VALUE:
skipLength = Math.min(headerBlock.readableBytes(), length);
headerBlock.skipBytes(skipLength);
length -= skipLength;
if (length == 0) {
if (--numHeaders == 0) {
state = State.END_HEADER_BLOCK;
} else {
state = State.READ_NAME_LENGTH;
}
}
break;
case END_HEADER_BLOCK:
state = State.ERROR;
frame.setInvalid();
break;
case ERROR:
headerBlock.skipBytes(headerBlock.readableBytes());
return;
}
index ++;
offset = index;
default:
throw new Error("Shouldn't reach here.");
}
numHeaders --;
this.headerSize = headerSize;
}
}
@Override
void reset() {
void endHeaderBlock(SpdyHeadersFrame frame) throws Exception {
if (state != State.END_HEADER_BLOCK) {
frame.setInvalid();
}
// Initialize header block decoding fields
headerSize = 0;
numHeaders = -1;
name = null;
state = State.READ_NUM_HEADERS;
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2013 The Netty Project
* Copyright 2014 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
@ -26,6 +26,8 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
final class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
private static final int DEFAULT_BUFFER_CAPACITY = 4096;
private static final SpdyProtocolException INVALID_HEADER_BLOCK =
new SpdyProtocolException("Invalid Header Block");
private final Inflater decompressor = new Inflater();
@ -36,19 +38,22 @@ final class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
}
@Override
void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception {
int len = setInput(encoded);
void decode(ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
int len = setInput(headerBlock);
int numBytes;
do {
numBytes = decompress(encoded.alloc(), frame);
numBytes = decompress(headerBlock.alloc(), frame);
} while (numBytes > 0);
// z_stream has an internal 64-bit hold buffer
// it is always capable of consuming the entire input
if (decompressor.getRemaining() != 0) {
throw new SpdyProtocolException("client sent extra data beyond headers");
// we reached the end of the deflate stream
throw INVALID_HEADER_BLOCK;
}
encoded.skipBytes(len);
headerBlock.skipBytes(len);
}
private int setInput(ByteBuf compressed) {
@ -72,7 +77,11 @@ final class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
try {
int numBytes = decompressor.inflate(out, off, decompressed.writableBytes());
if (numBytes == 0 && decompressor.needsDictionary()) {
decompressor.setDictionary(SPDY_DICT);
try {
decompressor.setDictionary(SPDY_DICT);
} catch (IllegalArgumentException e) {
throw INVALID_HEADER_BLOCK;
}
numBytes = decompressor.inflate(out, off, decompressed.writableBytes());
}
if (frame != null) {
@ -95,16 +104,16 @@ final class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
}
@Override
void reset() {
void endHeaderBlock(SpdyHeadersFrame frame) throws Exception {
super.endHeaderBlock(frame);
releaseBuffer();
super.reset();
}
@Override
public void end() {
super.end();
releaseBuffer();
decompressor.end();
super.end();
}
private void releaseBuffer() {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2014 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
@ -129,8 +129,7 @@ public abstract class SpdyOrHttpChooser extends ByteToMessageDecoder {
*/
protected void addSpdyHandlers(ChannelHandlerContext ctx, SpdyVersion version) {
ChannelPipeline pipeline = ctx.pipeline();
pipeline.addLast("spdyDecoder", new SpdyFrameDecoder(version));
pipeline.addLast("spdyEncoder", new SpdyFrameEncoder(version));
pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(version));
pipeline.addLast("spdySessionHandler", new SpdySessionHandler(version, true));
pipeline.addLast("spdyHttpEncoder", new SpdyHttpEncoder(version));
pipeline.addLast("spdyHttpDecoder", new SpdyHttpDecoder(version, maxSpdyContentLength));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012 The Netty Project
* Copyright 2014 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
@ -15,10 +15,6 @@
*/
package io.netty.handler.codec.spdy;
/**
* An {@link Exception} which is thrown when the received frame cannot
* be decoded by the {@link SpdyFrameDecoder}.
*/
public class SpdyProtocolException extends Exception {
private static final long serialVersionUID = 7870000537743847264L;

View File

@ -43,6 +43,7 @@ public class SpdySessionHandler
private static final int DEFAULT_WINDOW_SIZE = 64 * 1024; // 64 KB default initial window size
private int initialSendWindowSize = DEFAULT_WINDOW_SIZE;
private int initialReceiveWindowSize = DEFAULT_WINDOW_SIZE;
private volatile int initialSessionReceiveWindowSize = DEFAULT_WINDOW_SIZE;
private final SpdySession spdySession = new SpdySession(initialSendWindowSize, initialReceiveWindowSize);
private int lastGoodStreamId;
@ -80,6 +81,19 @@ public class SpdySessionHandler
minorVersion = version.getMinorVersion();
}
public void setSessionReceiveWindowSize(int sessionReceiveWindowSize) {
if (sessionReceiveWindowSize < 0) {
throw new IllegalArgumentException("sessionReceiveWindowSize");
}
// This will not send a window update frame immediately.
// If this value increases the allowed receive window size,
// a WINDOW_UPDATE frame will be sent when only half of the
// session window size remains during data frame processing.
// If this value decreases the allowed receive window size,
// the window will be reduced as data frames are processed.
initialSessionReceiveWindowSize = sessionReceiveWindowSize;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof SpdyDataFrame) {
@ -120,8 +134,8 @@ public class SpdySessionHandler
}
// Send a WINDOW_UPDATE frame if less than half the session window size remains
if (newSessionWindowSize <= initialReceiveWindowSize / 2) {
int sessionDeltaWindowSize = initialReceiveWindowSize - newSessionWindowSize;
if (newSessionWindowSize <= initialSessionReceiveWindowSize / 2) {
int sessionDeltaWindowSize = initialSessionReceiveWindowSize - newSessionWindowSize;
spdySession.updateReceiveWindowSize(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);
SpdyWindowUpdateFrame spdyWindowUpdateFrame =
new DefaultSpdyWindowUpdateFrame(SPDY_SESSION_STREAM_ID, sessionDeltaWindowSize);

View File

@ -0,0 +1,485 @@
/*
* Copyright 2014 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.buffer.Unpooled;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SpdyHeaderBlockRawDecoderTest {
private static final int maxHeaderSize = 16;
private static final String name = "name";
private static final String value = "value";
private static final byte[] nameBytes = name.getBytes();
private static final byte[] valueBytes = value.getBytes();
private SpdyHeaderBlockRawDecoder decoder;
private SpdyHeadersFrame frame;
@Before
public void setUp() {
decoder = new SpdyHeaderBlockRawDecoder(SpdyVersion.SPDY_3_1, maxHeaderSize);
frame = new DefaultSpdyHeadersFrame(1);
}
@Test
public void testEmptyHeaderBlock() throws Exception {
ByteBuf headerBlock = Unpooled.EMPTY_BUFFER;
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testZeroNameValuePairs() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(4);
headerBlock.writeInt(0);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testNegativeNameValuePairs() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(4);
headerBlock.writeInt(-1);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testOneNameValuePair() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(21);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testMissingNameLength() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(4);
headerBlock.writeInt(1);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testZeroNameLength() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(8);
headerBlock.writeInt(1);
headerBlock.writeInt(0);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testNegativeNameLength() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(8);
headerBlock.writeInt(1);
headerBlock.writeInt(-1);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testMissingName() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(8);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testIllegalNameOnlyNull() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(18);
headerBlock.writeInt(1);
headerBlock.writeInt(1);
headerBlock.writeByte(0);
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testMissingValueLength() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(12);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testZeroValueLength() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(16);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(0);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals("", frame.headers().get(name));
}
@Test
public void testNegativeValueLength() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(16);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(-1);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testMissingValue() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(16);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testIllegalValueOnlyNull() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(17);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(1);
headerBlock.writeByte(0);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testIllegalValueStartsWithNull() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(22);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(6);
headerBlock.writeByte(0);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testIllegalValueEndsWithNull() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(22);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(6);
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testMultipleValues() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(27);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(11);
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(2, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().getAll(name).get(0));
assertEquals(value, frame.headers().getAll(name).get(1));
}
@Test
public void testMultipleValuesEndsWithNull() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(28);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(12);
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testIllegalValueMultipleNulls() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(28);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(12);
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
headerBlock.writeByte(0);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testMissingNextNameValuePair() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(21);
headerBlock.writeInt(2);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testMultipleNames() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(38);
headerBlock.writeInt(2);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testExtraData() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(22);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0);
decoder.decode(headerBlock, frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testMultipleDecodes() throws Exception {
ByteBuf numHeaders = Unpooled.buffer(4);
numHeaders.writeInt(1);
ByteBuf nameBlock = Unpooled.buffer(8);
nameBlock.writeInt(4);
nameBlock.writeBytes(nameBytes);
ByteBuf valueBlock = Unpooled.buffer(9);
valueBlock.writeInt(5);
valueBlock.writeBytes(valueBytes);
decoder.decode(numHeaders, frame);
decoder.decode(nameBlock, frame);
decoder.decode(valueBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(numHeaders.isReadable());
assertFalse(nameBlock.isReadable());
assertFalse(valueBlock.isReadable());
assertFalse(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testContinueAfterInvalidHeaders() throws Exception {
ByteBuf numHeaders = Unpooled.buffer(4);
numHeaders.writeInt(1);
ByteBuf nameBlock = Unpooled.buffer(8);
nameBlock.writeInt(4);
nameBlock.writeBytes(nameBytes);
ByteBuf valueBlock = Unpooled.buffer(9);
valueBlock.writeInt(5);
valueBlock.writeBytes(valueBytes);
decoder.decode(numHeaders, frame);
decoder.decode(nameBlock, frame);
frame.setInvalid();
decoder.decode(valueBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(numHeaders.isReadable());
assertFalse(nameBlock.isReadable());
assertFalse(valueBlock.isReadable());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testTruncatedHeaderName() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(maxHeaderSize + 18);
headerBlock.writeInt(1);
headerBlock.writeInt(maxHeaderSize + 1);
for (int i = 0; i < maxHeaderSize + 1; i++) {
headerBlock.writeByte('a');
}
headerBlock.writeInt(5);
headerBlock.writeBytes(valueBytes);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isTruncated());
assertFalse(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
@Test
public void testTruncatedHeaderValue() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(maxHeaderSize + 13);
headerBlock.writeInt(1);
headerBlock.writeInt(4);
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(13);
for (int i = 0; i < maxHeaderSize - 3; i++) {
headerBlock.writeByte('a');
}
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertTrue(frame.isTruncated());
assertFalse(frame.isInvalid());
assertEquals(0, frame.headers().names().size());
}
}

View File

@ -0,0 +1,203 @@
/*
* Copyright 2014 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.buffer.Unpooled;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SpdyHeaderBlockZlibDecoderTest {
// zlib header indicating 32K window size fastest deflate algorithm with SPDY dictionary
private static final byte[] zlibHeader = {0x78, 0x3f, (byte) 0xe3, (byte) 0xc6, (byte) 0xa7, (byte) 0xc2};
private static final byte[] zlibSyncFlush = {0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff};
private static final int maxHeaderSize = 8192;
private static final String name = "name";
private static final String value = "value";
private static final byte[] nameBytes = name.getBytes();
private static final byte[] valueBytes = value.getBytes();
private SpdyHeaderBlockZlibDecoder decoder;
private SpdyHeadersFrame frame;
@Before
public void setUp() {
decoder = new SpdyHeaderBlockZlibDecoder(SpdyVersion.SPDY_3_1, maxHeaderSize);
frame = new DefaultSpdyHeadersFrame(1);
}
@Test
public void testHeaderBlock() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(37);
headerBlock.writeBytes(zlibHeader);
headerBlock.writeByte(0); // Non-compressed block
headerBlock.writeByte(0x15); // little-endian length (21)
headerBlock.writeByte(0x00); // little-endian length (21)
headerBlock.writeByte(0xea); // one's compliment of length
headerBlock.writeByte(0xff); // one's compliment of length
headerBlock.writeInt(1); // number of Name/Value pairs
headerBlock.writeInt(4); // length of name
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5); // length of value
headerBlock.writeBytes(valueBytes);
headerBlock.writeBytes(zlibSyncFlush);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testHeaderBlockMultipleDecodes() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(37);
headerBlock.writeBytes(zlibHeader);
headerBlock.writeByte(0); // Non-compressed block
headerBlock.writeByte(0x15); // little-endian length (21)
headerBlock.writeByte(0x00); // little-endian length (21)
headerBlock.writeByte(0xea); // one's compliment of length
headerBlock.writeByte(0xff); // one's compliment of length
headerBlock.writeInt(1); // number of Name/Value pairs
headerBlock.writeInt(4); // length of name
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5); // length of value
headerBlock.writeBytes(valueBytes);
headerBlock.writeBytes(zlibSyncFlush);
int readableBytes = headerBlock.readableBytes();
for (int i = 0; i < readableBytes; i++) {
ByteBuf headerBlockSegment = headerBlock.slice(i, 1);
decoder.decode(headerBlockSegment, frame);
}
decoder.endHeaderBlock(frame);
assertFalse(frame.isInvalid());
assertEquals(1, frame.headers().names().size());
assertTrue(frame.headers().contains(name));
assertEquals(1, frame.headers().getAll(name).size());
assertEquals(value, frame.headers().get(name));
}
@Test
public void testLargeHeaderName() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(8220);
headerBlock.writeBytes(zlibHeader);
headerBlock.writeByte(0); // Non-compressed block
headerBlock.writeByte(0x0c); // little-endian length (8204)
headerBlock.writeByte(0x20); // little-endian length (8204)
headerBlock.writeByte(0xf3); // one's compliment of length
headerBlock.writeByte(0xdf); // one's compliment of length
headerBlock.writeInt(1); // number of Name/Value pairs
headerBlock.writeInt(8192); // length of name
for (int i = 0; i < 8192; i++) {
headerBlock.writeByte('n');
}
headerBlock.writeInt(0); // length of value
headerBlock.writeBytes(zlibSyncFlush);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertFalse(frame.isTruncated());
assertEquals(1, frame.headers().names().size());
}
@Test
public void testLargeHeaderValue() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(8220);
headerBlock.writeBytes(zlibHeader);
headerBlock.writeByte(0); // Non-compressed block
headerBlock.writeByte(0x0c); // little-endian length (8204)
headerBlock.writeByte(0x20); // little-endian length (8204)
headerBlock.writeByte(0xf3); // one's compliment of length
headerBlock.writeByte(0xdf); // one's compliment of length
headerBlock.writeInt(1); // number of Name/Value pairs
headerBlock.writeInt(1); // length of name
headerBlock.writeByte('n');
headerBlock.writeInt(8191); // length of value
for (int i = 0; i < 8191; i++) {
headerBlock.writeByte('v');
}
headerBlock.writeBytes(zlibSyncFlush);
decoder.decode(headerBlock, frame);
decoder.endHeaderBlock(frame);
assertFalse(headerBlock.isReadable());
assertFalse(frame.isInvalid());
assertFalse(frame.isTruncated());
assertEquals(1, frame.headers().names().size());
assertEquals(8191, frame.headers().get("n").length());
}
@Test(expected = SpdyProtocolException.class)
public void testHeaderBlockExtraData() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(37);
headerBlock.writeBytes(zlibHeader);
headerBlock.writeByte(0); // Non-compressed block
headerBlock.writeByte(0x15); // little-endian length (21)
headerBlock.writeByte(0x00); // little-endian length (21)
headerBlock.writeByte(0xea); // one's compliment of length
headerBlock.writeByte(0xff); // one's compliment of length
headerBlock.writeInt(1); // number of Name/Value pairs
headerBlock.writeInt(4); // length of name
headerBlock.writeBytes(nameBytes);
headerBlock.writeInt(5); // length of value
headerBlock.writeBytes(valueBytes);
headerBlock.writeByte(0x19); // adler-32 checksum
headerBlock.writeByte(0xa5); // adler-32 checksum
headerBlock.writeByte(0x03); // adler-32 checksum
headerBlock.writeByte(0xc9); // adler-32 checksum
headerBlock.writeByte(0); // Data following zlib stream
decoder.decode(headerBlock, frame);
}
@Test(expected = SpdyProtocolException.class)
public void testHeaderBlockInvalidDictionary() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(7);
headerBlock.writeByte(0x78);
headerBlock.writeByte(0x3f);
headerBlock.writeByte(0x01); // Unknown dictionary
headerBlock.writeByte(0x02); // Unknown dictionary
headerBlock.writeByte(0x03); // Unknown dictionary
headerBlock.writeByte(0x04); // Unknown dictionary
headerBlock.writeByte(0); // Non-compressed block
decoder.decode(headerBlock, frame);
}
@Test(expected = SpdyProtocolException.class)
public void testHeaderBlockInvalidDeflateBlock() throws Exception {
ByteBuf headerBlock = Unpooled.buffer(11);
headerBlock.writeBytes(zlibHeader);
headerBlock.writeByte(0); // Non-compressed block
headerBlock.writeByte(0x00); // little-endian length (0)
headerBlock.writeByte(0x00); // little-endian length (0)
headerBlock.writeByte(0x00); // invalid one's compliment
headerBlock.writeByte(0x00); // invalid one's compliment
decoder.decode(headerBlock, frame);
}
}

View File

@ -43,8 +43,8 @@ public class SpdySessionHandlerTest {
assertNotNull(msg);
assertTrue(msg instanceof SpdyDataFrame);
SpdyDataFrame spdyDataFrame = (SpdyDataFrame) msg;
assertEquals(spdyDataFrame.getStreamId(), streamId);
assertEquals(spdyDataFrame.isLast(), last);
assertEquals(streamId, spdyDataFrame.getStreamId());
assertEquals(last, spdyDataFrame.isLast());
}
private static void assertSynReply(Object msg, int streamId, boolean last, SpdyHeaders headers) {
@ -57,30 +57,30 @@ public class SpdySessionHandlerTest {
assertNotNull(msg);
assertTrue(msg instanceof SpdyRstStreamFrame);
SpdyRstStreamFrame spdyRstStreamFrame = (SpdyRstStreamFrame) msg;
assertEquals(spdyRstStreamFrame.getStreamId(), streamId);
assertEquals(spdyRstStreamFrame.getStatus(), status);
assertEquals(streamId, spdyRstStreamFrame.getStreamId());
assertEquals(status, spdyRstStreamFrame.getStatus());
}
private static void assertPing(Object msg, int id) {
assertNotNull(msg);
assertTrue(msg instanceof SpdyPingFrame);
SpdyPingFrame spdyPingFrame = (SpdyPingFrame) msg;
assertEquals(spdyPingFrame.getId(), id);
assertEquals(id, spdyPingFrame.getId());
}
private static void assertGoAway(Object msg, int lastGoodStreamId) {
assertNotNull(msg);
assertTrue(msg instanceof SpdyGoAwayFrame);
SpdyGoAwayFrame spdyGoAwayFrame = (SpdyGoAwayFrame) msg;
assertEquals(spdyGoAwayFrame.getLastGoodStreamId(), lastGoodStreamId);
assertEquals(lastGoodStreamId, spdyGoAwayFrame.getLastGoodStreamId());
}
private static void assertHeaders(Object msg, int streamId, boolean last, SpdyHeaders headers) {
assertNotNull(msg);
assertTrue(msg instanceof SpdyHeadersFrame);
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
assertEquals(spdyHeadersFrame.getStreamId(), streamId);
assertEquals(spdyHeadersFrame.isLast(), last);
assertEquals(streamId, spdyHeadersFrame.getStreamId());
assertEquals(last, spdyHeadersFrame.isLast());
for (String name: headers.names()) {
List<String> expectedValues = headers.getAll(name);
List<String> receivedValues = spdyHeadersFrame.headers().getAll(name);

View File

@ -19,8 +19,7 @@ import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.example.securechat.SecureChatSslContextFactory;
import io.netty.handler.codec.spdy.SpdyFrameDecoder;
import io.netty.handler.codec.spdy.SpdyFrameEncoder;
import io.netty.handler.codec.spdy.SpdyFrameCodec;
import io.netty.handler.codec.spdy.SpdyHttpDecoder;
import io.netty.handler.codec.spdy.SpdyHttpEncoder;
import io.netty.handler.codec.spdy.SpdySessionHandler;
@ -52,8 +51,7 @@ public class SpdyClientInitializer extends ChannelInitializer<SocketChannel> {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("ssl", new SslHandler(engine));
pipeline.addLast("spdyEncoder", new SpdyFrameEncoder(SPDY_3_1));
pipeline.addLast("spdyDecoder", new SpdyFrameDecoder(SPDY_3_1));
pipeline.addLast("spdyFrameCodec", new SpdyFrameCodec(SPDY_3_1));
pipeline.addLast("spdyFrameLogger", new SpdyFrameLogger(INFO));
pipeline.addLast("spdySessionHandler", new SpdySessionHandler(SPDY_3_1, false));
pipeline.addLast("spdyHttpEncoder", new SpdyHttpEncoder(SPDY_3_1));

View File

@ -25,8 +25,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.spdy.SpdyFrameDecoder;
import io.netty.handler.codec.spdy.SpdyFrameEncoder;
import io.netty.handler.codec.spdy.SpdyFrameCodec;
import io.netty.handler.codec.spdy.SpdyVersion;
import io.netty.util.NetUtil;
import org.junit.Test;
@ -184,8 +183,7 @@ public class SocketSpdyEchoTest extends AbstractSocketTest {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(
new SpdyFrameDecoder(version),
new SpdyFrameEncoder(version),
new SpdyFrameCodec(version),
sh);
}
});