diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java new file mode 100644 index 0000000000..5093213d6c --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyWindowUpdateFrame.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012 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.util.internal.StringUtil; + +/** + * The default {@link SpdyWindowUpdateFrame} implementation. + */ +public class DefaultSpdyWindowUpdateFrame implements SpdyWindowUpdateFrame { + + private int streamID; + private int deltaWindowSize; + + /** + * Creates a new instance. + * + * @param streamID the Stream-ID of this frame + * @param deltaWindowSize the Delta-Window-Size of this frame + */ + public DefaultSpdyWindowUpdateFrame(int streamID, int deltaWindowSize) { + setStreamID(streamID); + setDeltaWindowSize(deltaWindowSize); + } + + public int getStreamID() { + return streamID; + } + + public void setStreamID(int streamID) { + if (streamID <= 0) { + throw new IllegalArgumentException( + "Stream-ID must be positive: " + streamID); + } + this.streamID = streamID; + } + + public int getDeltaWindowSize() { + return deltaWindowSize; + } + + public void setDeltaWindowSize(int deltaWindowSize) { + if (deltaWindowSize <= 0) { + throw new IllegalArgumentException( + "Delta-Window-Size must be positive: " + + deltaWindowSize); + } + this.deltaWindowSize = deltaWindowSize; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append(StringUtil.NEWLINE); + buf.append("--> Stream-ID = "); + buf.append(streamID); + buf.append(StringUtil.NEWLINE); + buf.append("--> Delta-Window-Size = "); + buf.append(deltaWindowSize); + return buf.toString(); + } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java index d2a5a04657..016aac0cc4 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameDecoder.java @@ -115,6 +115,26 @@ public class SpdyFrameDecoder extends FrameDecoder { fireInvalidControlFrameException(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) { + if (streamID == 0) { + state = State.FRAME_ERROR; + fireProtocolException(ctx, "Received invalid data frame"); + return null; + } + + SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID); + spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0); + state = State.READ_COMMON_HEADER; + return spdyDataFrame; + } + // There are no length 0 control frames + state = State.READ_COMMON_HEADER; + } + return null; case READ_CONTROL_FRAME: @@ -343,13 +363,14 @@ public class SpdyFrameDecoder extends FrameDecoder { } private Object readControlFrame(ChannelBuffer buffer) { + int streamID; switch (type) { case SPDY_RST_STREAM_FRAME: if (buffer.readableBytes() < 8) { return null; } - int streamID = getUnsignedInt(buffer, buffer.readerIndex()); + streamID = getUnsignedInt(buffer, buffer.readerIndex()); int statusCode = getSignedInt(buffer, buffer.readerIndex() + 4); buffer.skipBytes(8); @@ -375,6 +396,17 @@ public class SpdyFrameDecoder extends FrameDecoder { return new DefaultSpdyGoAwayFrame(lastGoodStreamID); + 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."); } @@ -622,6 +654,8 @@ public class SpdyFrameDecoder extends FrameDecoder { return length == 4 || length >= 8; case SPDY_WINDOW_UPDATE_FRAME: + return length == 8; + default: return true; } @@ -636,10 +670,10 @@ public class SpdyFrameDecoder extends FrameDecoder { case SPDY_PING_FRAME: case SPDY_GOAWAY_FRAME: case SPDY_HEADERS_FRAME: + case SPDY_WINDOW_UPDATE_FRAME: return true; case SPDY_NOOP_FRAME: - case SPDY_WINDOW_UPDATE_FRAME: default: return false; } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java index 39619dfbb4..309c3551e3 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyFrameEncoder.java @@ -231,6 +231,18 @@ public class SpdyFrameEncoder extends OneToOneEncoder { frame.writeShort(0); } return ChannelBuffers.wrappedBuffer(frame, data); + + } else if (msg instanceof SpdyWindowUpdateFrame) { + + SpdyWindowUpdateFrame spdyWindowUpdateFrame = (SpdyWindowUpdateFrame) msg; + ChannelBuffer frame = ChannelBuffers.buffer( + ByteOrder.BIG_ENDIAN, SPDY_HEADER_SIZE + 8); + frame.writeShort(SPDY_VERSION | 0x8000); + frame.writeShort(SPDY_WINDOW_UPDATE_FRAME); + frame.writeInt(8); + frame.writeInt(spdyWindowUpdateFrame.getStreamID()); + frame.writeInt(spdyWindowUpdateFrame.getDeltaWindowSize()); + return frame; } // Unknown message type diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java new file mode 100644 index 0000000000..18f9712987 --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyWindowUpdateFrame.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 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.handler.codec.spdy; + +/** + * A SPDY Protocol WINDOW_UPDATE Control Frame + */ +public interface SpdyWindowUpdateFrame { + + /** + * Returns the Stream-ID of this frame. + */ + int getStreamID(); + + /** + * Sets the Stream-ID of this frame. The Stream-ID must be positive. + */ + void setStreamID(int streamID); + + /** + * Returns the Delta-Window-Size of this frame. + */ + int getDeltaWindowSize(); + + /** + * Sets the Delta-Window-Size of this frame. + * The Delta-Window-Size must be positive. + */ + void setDeltaWindowSize(int deltaWindowSize); +} diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java index 03caefc4f6..3e08e7d89f 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/AbstractSocketSpdyEchoTest.java @@ -47,7 +47,7 @@ import org.junit.Test; public abstract class AbstractSocketSpdyEchoTest { private static final Random random = new Random(); - static final ChannelBuffer frames = ChannelBuffers.buffer(1160); + static final ChannelBuffer frames = ChannelBuffers.buffer(1176); static final int ignoredBytes = 20; private static ExecutorService executor; @@ -68,7 +68,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeInt(0); // SPDY Data Frame - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeByte(0x01); frames.writeMedium(1024); for (int i = 0; i < 256; i ++) { @@ -81,7 +81,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeShort(1); frames.writeByte(0x03); frames.writeMedium(12); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() & 0x7FFFFFFF); frames.writeShort(0x8000); frames.writeShort(0); @@ -92,7 +92,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeShort(2); frames.writeByte(0x01); frames.writeMedium(8); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(0); // SPDY RST_STREAM Frame @@ -100,7 +100,7 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeByte(2); frames.writeShort(3); frames.writeInt(8); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); frames.writeInt(random.nextInt() | 0x01); // SPDY SETTINGS Frame @@ -133,7 +133,15 @@ public abstract class AbstractSocketSpdyEchoTest { frames.writeByte(2); frames.writeShort(8); frames.writeInt(4); - frames.writeInt(random.nextInt() & 0x7FFFFFFF); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + + // SPDY WINDOW_UPDATE Frame + frames.writeByte(0x80); + frames.writeByte(2); + frames.writeShort(9); + frames.writeInt(8); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); + frames.writeInt(random.nextInt() & 0x7FFFFFFF | 0x01); } @BeforeClass