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:
parent
15d11289b0
commit
7808b9926d
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user