Add support for byte order to LengthFieldPrepender

Motivation:

While the LengthFieldBasedFrameDecoder supports a byte order the LengthFieldPrepender does not.
That means that I can simply add a LengthFieldBasedFrameDecoder with ByteOrder.LITTLE_ENDIAN to my pipeline
but have to write my own Encoder to write length fields in little endian byte order.

Modifications:

Added a constructor that takes a byte order and all other parameters.
All other constructors delegate to this one with ByteOrder.BIG_ENDIAN.
LengthFieldPrepender.encode() uses this byte order to write the length field.

Result:

LengthFieldPrepender will write the length field in the defined byte order.
This commit is contained in:
Robert.Panzer 2015-03-09 11:32:40 +01:00 committed by Norman Maurer
parent c7827dc16a
commit 18443efeab
2 changed files with 55 additions and 5 deletions

View File

@ -18,7 +18,9 @@ package io.netty.handler.codec;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;
import java.nio.ByteOrder;
import java.util.List; import java.util.List;
@ -51,6 +53,7 @@ import java.util.List;
@Sharable @Sharable
public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> { public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {
private final ByteOrder byteOrder;
private final int lengthFieldLength; private final int lengthFieldLength;
private final boolean lengthIncludesLengthFieldLength; private final boolean lengthIncludesLengthFieldLength;
private final int lengthAdjustment; private final int lengthAdjustment;
@ -116,6 +119,28 @@ public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {
* if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8 * if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
*/ */
public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) { public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
this(ByteOrder.BIG_ENDIAN, lengthFieldLength, lengthAdjustment, lengthIncludesLengthFieldLength);
}
/**
* Creates a new instance.
*
* @param byteOrder the {@link ByteOrder} of the length field
* @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(
ByteOrder byteOrder, int lengthFieldLength,
int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
if (lengthFieldLength != 1 && lengthFieldLength != 2 && if (lengthFieldLength != 1 && lengthFieldLength != 2 &&
lengthFieldLength != 3 && lengthFieldLength != 4 && lengthFieldLength != 3 && lengthFieldLength != 4 &&
lengthFieldLength != 8) { lengthFieldLength != 8) {
@ -123,7 +148,9 @@ public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {
"lengthFieldLength must be either 1, 2, 3, 4, or 8: " + "lengthFieldLength must be either 1, 2, 3, 4, or 8: " +
lengthFieldLength); lengthFieldLength);
} }
ObjectUtil.checkNotNull(byteOrder, "byteOrder");
this.byteOrder = byteOrder;
this.lengthFieldLength = lengthFieldLength; this.lengthFieldLength = lengthFieldLength;
this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength; this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
this.lengthAdjustment = lengthAdjustment; this.lengthAdjustment = lengthAdjustment;
@ -147,27 +174,27 @@ public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"length does not fit into a byte: " + length); "length does not fit into a byte: " + length);
} }
out.add(ctx.alloc().buffer(1).writeByte((byte) length)); out.add(ctx.alloc().buffer(1).order(byteOrder).writeByte((byte) length));
break; break;
case 2: case 2:
if (length >= 65536) { if (length >= 65536) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"length does not fit into a short integer: " + length); "length does not fit into a short integer: " + length);
} }
out.add(ctx.alloc().buffer(2).writeShort((short) length)); out.add(ctx.alloc().buffer(2).order(byteOrder).writeShort((short) length));
break; break;
case 3: case 3:
if (length >= 16777216) { if (length >= 16777216) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"length does not fit into a medium integer: " + length); "length does not fit into a medium integer: " + length);
} }
out.add(ctx.alloc().buffer(3).writeMedium(length)); out.add(ctx.alloc().buffer(3).order(byteOrder).writeMedium(length));
break; break;
case 4: case 4:
out.add(ctx.alloc().buffer(4).writeInt(length)); out.add(ctx.alloc().buffer(4).order(byteOrder).writeInt(length));
break; break;
case 8: case 8:
out.add(ctx.alloc().buffer(8).writeLong(length)); out.add(ctx.alloc().buffer(8).order(byteOrder).writeLong(length));
break; break;
default: default:
throw new Error("should not reach here"); throw new Error("should not reach here");

View File

@ -24,6 +24,8 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static io.netty.buffer.Unpooled.*; import static io.netty.buffer.Unpooled.*;
import java.nio.ByteOrder;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class LengthFieldPrependerTest { public class LengthFieldPrependerTest {
@ -87,4 +89,25 @@ public class LengthFieldPrependerTest {
// Expected // Expected
} }
} }
@Test
public void testPrependLengthInLittleEndian() throws Exception {
final EmbeddedChannel ch = new EmbeddedChannel(new LengthFieldPrepender(ByteOrder.LITTLE_ENDIAN, 4, 0, false));
ch.writeOutbound(msg);
ByteBuf buf = ch.readOutbound();
assertEquals(4, buf.readableBytes());
byte[] writtenBytes = new byte[buf.readableBytes()];
buf.getBytes(0, writtenBytes);
assertEquals(1, writtenBytes[0]);
assertEquals(0, writtenBytes[1]);
assertEquals(0, writtenBytes[2]);
assertEquals(0, writtenBytes[3]);
buf.release();
buf = ch.readOutbound();
assertSame(buf, msg);
buf.release();
assertFalse("The channel must have been completely read", ch.finish());
}
} }