View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelHandler.Sharable;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.util.internal.ObjectUtil;
22  
23  import java.nio.ByteOrder;
24  
25  /**
26   * An encoder that prepends the length of the message.  The length value is
27   * prepended as a binary form.
28   * <p>
29   * For example, <tt>{@link LengthFieldPrepender}(2)</tt> will encode the
30   * following 12-bytes string:
31   * <pre>
32   * +----------------+
33   * | "HELLO, WORLD" |
34   * +----------------+
35   * </pre>
36   * into the following:
37   * <pre>
38   * +--------+----------------+
39   * + 0x000C | "HELLO, WORLD" |
40   * +--------+----------------+
41   * </pre>
42   * If you turned on the {@code lengthIncludesLengthFieldLength} flag in the
43   * constructor, the encoded data would look like the following
44   * (12 (original data) + 2 (prepended data) = 14 (0xE)):
45   * <pre>
46   * +--------+----------------+
47   * + 0x000E | "HELLO, WORLD" |
48   * +--------+----------------+
49   * </pre>
50   */
51  @Sharable
52  public class LengthFieldPrepender extends MessageToByteEncoder<ByteBuf> {
53  
54      private final ByteOrder byteOrder;
55      private final int lengthFieldLength;
56      private final boolean lengthIncludesLengthFieldLength;
57      private final int lengthAdjustment;
58  
59      /**
60       * Creates a new instance.
61       *
62       * @param lengthFieldLength the length of the prepended length field.
63       *                          Only 1, 2, 3, 4, and 8 are allowed.
64       *
65       * @throws IllegalArgumentException
66       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
67       */
68      public LengthFieldPrepender(int lengthFieldLength) {
69          this(lengthFieldLength, false);
70      }
71  
72      /**
73       * Creates a new instance.
74       *
75       * @param lengthFieldLength the length of the prepended length field.
76       *                          Only 1, 2, 3, 4, and 8 are allowed.
77       * @param lengthIncludesLengthFieldLength
78       *                          if {@code true}, the length of the prepended
79       *                          length field is added to the value of the
80       *                          prepended length field.
81       *
82       * @throws IllegalArgumentException
83       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
84       */
85      public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
86          this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
87      }
88  
89      /**
90       * Creates a new instance.
91       *
92       * @param lengthFieldLength the length of the prepended length field.
93       *                          Only 1, 2, 3, 4, and 8 are allowed.
94       * @param lengthAdjustment  the compensation value to add to the value
95       *                          of the length field
96       *
97       * @throws IllegalArgumentException
98       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
99       */
100     public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment) {
101         this(lengthFieldLength, lengthAdjustment, false);
102     }
103 
104     /**
105      * Creates a new instance.
106      *
107      * @param lengthFieldLength the length of the prepended length field.
108      *                          Only 1, 2, 3, 4, and 8 are allowed.
109      * @param lengthAdjustment  the compensation value to add to the value
110      *                          of the length field
111      * @param lengthIncludesLengthFieldLength
112      *                          if {@code true}, the length of the prepended
113      *                          length field is added to the value of the
114      *                          prepended length field.
115      *
116      * @throws IllegalArgumentException
117      *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
118      */
119     public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
120         this(ByteOrder.BIG_ENDIAN, lengthFieldLength, lengthAdjustment, lengthIncludesLengthFieldLength);
121     }
122 
123     /**
124      * Creates a new instance.
125      *
126      * @param byteOrder         the {@link ByteOrder} of the length field
127      * @param lengthFieldLength the length of the prepended length field.
128      *                          Only 1, 2, 3, 4, and 8 are allowed.
129      * @param lengthAdjustment  the compensation value to add to the value
130      *                          of the length field
131      * @param lengthIncludesLengthFieldLength
132      *                          if {@code true}, the length of the prepended
133      *                          length field is added to the value of the
134      *                          prepended length field.
135      *
136      * @throws IllegalArgumentException
137      *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
138      */
139     public LengthFieldPrepender(
140             ByteOrder byteOrder, int lengthFieldLength,
141             int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
142         if (lengthFieldLength != 1 && lengthFieldLength != 2 &&
143             lengthFieldLength != 3 && lengthFieldLength != 4 &&
144             lengthFieldLength != 8) {
145             throw new IllegalArgumentException(
146                     "lengthFieldLength must be either 1, 2, 3, 4, or 8: " +
147                     lengthFieldLength);
148         }
149         ObjectUtil.checkNotNull(byteOrder, "byteOrder");
150 
151         this.byteOrder = byteOrder;
152         this.lengthFieldLength = lengthFieldLength;
153         this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
154         this.lengthAdjustment = lengthAdjustment;
155     }
156 
157     @Override
158     protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
159         int length = msg.readableBytes() + lengthAdjustment;
160         if (lengthIncludesLengthFieldLength) {
161             length += lengthFieldLength;
162         }
163 
164         if (length < 0) {
165             throw new IllegalArgumentException(
166                     "Adjusted frame length (" + length + ") is less than zero");
167         }
168 
169         switch (lengthFieldLength) {
170         case 1:
171             if (length >= 256) {
172                 throw new IllegalArgumentException(
173                         "length does not fit into a byte: " + length);
174             }
175             out.writeByte((byte) length);
176             break;
177         case 2:
178             if (length >= 65536) {
179                 throw new IllegalArgumentException(
180                         "length does not fit into a short integer: " + length);
181             }
182             out.writeShort((short) length);
183             break;
184         case 3:
185             if (length >= 16777216) {
186                 throw new IllegalArgumentException(
187                         "length does not fit into a medium integer: " + length);
188             }
189             out.writeMedium(length);
190             break;
191         case 4:
192             out.writeInt(length);
193             break;
194         case 8:
195             out.writeLong(length);
196             break;
197         default:
198             throw new Error("should not reach here");
199         }
200 
201         out.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
202     }
203 
204     @Override
205     protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
206         return super.allocateBuffer(ctx, msg, preferDirect).order(byteOrder);
207     }
208 }