diff --git a/codec/src/main/java/io/netty/handler/codec/LengthFieldPrepender.java b/codec/src/main/java/io/netty/handler/codec/LengthFieldPrepender.java index 9094522106..a2cca5b0ee 100644 --- a/codec/src/main/java/io/netty/handler/codec/LengthFieldPrepender.java +++ b/codec/src/main/java/io/netty/handler/codec/LengthFieldPrepender.java @@ -51,6 +51,7 @@ public class LengthFieldPrepender extends MessageToByteEncoder { private final int lengthFieldLength; private final boolean lengthIncludesLengthFieldLength; + private final int lengthAdjustment; /** * Creates a new instance. @@ -79,6 +80,40 @@ public class LengthFieldPrepender extends MessageToByteEncoder { * if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8 */ public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) { + this(lengthFieldLength, 0, lengthIncludesLengthFieldLength); + } + + /** + * Creates a new instance. + * + * @param lengthFieldLength the length of the prepended length field. + * Only 1, 2, 3, 4, and 8 are allowed. + * @param lengthAdjustment the compensation value to add to the value + * of the length field + * + * @throws IllegalArgumentException + * if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8 + */ + public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment) { + this(lengthFieldLength, lengthAdjustment, false); + } + + /** + * Creates a new instance. + * + * @param lengthFieldLength the length of the prepended length field. + * Only 1, 2, 3, 4, and 8 are allowed. + * @param lengthAdjustment the compensation value to add to the value + * of the length field + * @param lengthIncludesLengthFieldLength + * if {@code true}, the length of the prepended + * length field is added to the value of the + * prepended length field. + * + * @throws IllegalArgumentException + * if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8 + */ + public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) { if (lengthFieldLength != 1 && lengthFieldLength != 2 && lengthFieldLength != 3 && lengthFieldLength != 4 && lengthFieldLength != 8) { @@ -89,6 +124,7 @@ public class LengthFieldPrepender extends MessageToByteEncoder { this.lengthFieldLength = lengthFieldLength; this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength; + this.lengthAdjustment = lengthAdjustment; } @Override @@ -96,8 +132,15 @@ public class LengthFieldPrepender extends MessageToByteEncoder { ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { - int length = lengthIncludesLengthFieldLength? - msg.readableBytes() + lengthFieldLength : msg.readableBytes(); + int length = (lengthIncludesLengthFieldLength? + msg.readableBytes() + lengthFieldLength : msg.readableBytes()) + + lengthAdjustment; + + if (length < 0) { + throw new IllegalArgumentException( + "Adjusted frame length (" + length + ") is less than zero"); + } + switch (lengthFieldLength) { case 1: if (length >= 256) { diff --git a/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldPrependerTest.java b/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldPrependerTest.java new file mode 100644 index 0000000000..ff6530cbd1 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/frame/LengthFieldPrependerTest.java @@ -0,0 +1,72 @@ +/* + * 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.frame; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.IncompleteFlushException; +import io.netty.channel.embedded.EmbeddedByteChannel; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.util.CharsetUtil; +import org.junit.Before; +import org.junit.Test; + +import static io.netty.buffer.Unpooled.copiedBuffer; +import static io.netty.buffer.Unpooled.wrappedBuffer; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class LengthFieldPrependerTest { + + private ByteBuf msg; + + @Before + public void setUp() throws Exception { + msg = copiedBuffer("A", CharsetUtil.ISO_8859_1); + } + + @Test + public void testPrependLength() throws Exception { + final EmbeddedByteChannel ch = new EmbeddedByteChannel(new LengthFieldPrepender(4)); + ch.writeOutbound(msg); + assertThat(ch.readOutbound(), is(wrappedBuffer(new byte[]{0, 0, 0, 1, 'A'}))); + } + + @Test + public void testPrependLengthIncludesLengthFieldLength() throws Exception { + final EmbeddedByteChannel ch = new EmbeddedByteChannel(new LengthFieldPrepender(4, true)); + ch.writeOutbound(msg); + assertThat(ch.readOutbound(), is(wrappedBuffer(new byte[]{0, 0, 0, 5, 'A'}))); + } + + @Test + public void testPrependAdjustedLength() throws Exception { + final EmbeddedByteChannel ch = new EmbeddedByteChannel(new LengthFieldPrepender(4, -1)); + ch.writeOutbound(msg); + assertThat(ch.readOutbound(), is(wrappedBuffer(new byte[]{0, 0, 0, 0, 'A'}))); + } + + @Test + public void testAdjustedLengthLessThanZero() throws Exception { + final EmbeddedByteChannel ch = new EmbeddedByteChannel(new LengthFieldPrepender(4, -2)); + try { + ch.writeOutbound(msg); + fail(IncompleteFlushException.class.getSimpleName() + " must be raised."); + } catch (IncompleteFlushException e) { + // Expected + } + } +}