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  import java.util.List;
25  
26  
27  /**
28   * An encoder that prepends the length of the message.  The length value is
29   * prepended as a binary form.
30   * <p>
31   * For example, <tt>{@link LengthFieldPrepender}(2)</tt> will encode the
32   * following 12-bytes string:
33   * <pre>
34   * +----------------+
35   * | "HELLO, WORLD" |
36   * +----------------+
37   * </pre>
38   * into the following:
39   * <pre>
40   * +--------+----------------+
41   * + 0x000C | "HELLO, WORLD" |
42   * +--------+----------------+
43   * </pre>
44   * If you turned on the {@code lengthIncludesLengthFieldLength} flag in the
45   * constructor, the encoded data would look like the following
46   * (12 (original data) + 2 (prepended data) = 14 (0xE)):
47   * <pre>
48   * +--------+----------------+
49   * + 0x000E | "HELLO, WORLD" |
50   * +--------+----------------+
51   * </pre>
52   */
53  @Sharable
54  public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {
55  
56      private final ByteOrder byteOrder;
57      private final int lengthFieldLength;
58      private final boolean lengthIncludesLengthFieldLength;
59      private final int lengthAdjustment;
60  
61      /**
62       * Creates a new instance.
63       *
64       * @param lengthFieldLength the length of the prepended length field.
65       *                          Only 1, 2, 3, 4, and 8 are allowed.
66       *
67       * @throws IllegalArgumentException
68       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
69       */
70      public LengthFieldPrepender(int lengthFieldLength) {
71          this(lengthFieldLength, false);
72      }
73  
74      /**
75       * Creates a new instance.
76       *
77       * @param lengthFieldLength the length of the prepended length field.
78       *                          Only 1, 2, 3, 4, and 8 are allowed.
79       * @param lengthIncludesLengthFieldLength
80       *                          if {@code true}, the length of the prepended
81       *                          length field is added to the value of the
82       *                          prepended length field.
83       *
84       * @throws IllegalArgumentException
85       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
86       */
87      public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
88          this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
89      }
90  
91      /**
92       * Creates a new instance.
93       *
94       * @param lengthFieldLength the length of the prepended length field.
95       *                          Only 1, 2, 3, 4, and 8 are allowed.
96       * @param lengthAdjustment  the compensation value to add to the value
97       *                          of the length field
98       *
99       * @throws IllegalArgumentException
100      *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
101      */
102     public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment) {
103         this(lengthFieldLength, lengthAdjustment, false);
104     }
105 
106     /**
107      * Creates a new instance.
108      *
109      * @param lengthFieldLength the length of the prepended length field.
110      *                          Only 1, 2, 3, 4, and 8 are allowed.
111      * @param lengthAdjustment  the compensation value to add to the value
112      *                          of the length field
113      * @param lengthIncludesLengthFieldLength
114      *                          if {@code true}, the length of the prepended
115      *                          length field is added to the value of the
116      *                          prepended length field.
117      *
118      * @throws IllegalArgumentException
119      *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
120      */
121     public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
122         this(ByteOrder.BIG_ENDIAN, lengthFieldLength, lengthAdjustment, lengthIncludesLengthFieldLength);
123     }
124 
125     /**
126      * Creates a new instance.
127      *
128      * @param byteOrder         the {@link ByteOrder} of the length field
129      * @param lengthFieldLength the length of the prepended length field.
130      *                          Only 1, 2, 3, 4, and 8 are allowed.
131      * @param lengthAdjustment  the compensation value to add to the value
132      *                          of the length field
133      * @param lengthIncludesLengthFieldLength
134      *                          if {@code true}, the length of the prepended
135      *                          length field is added to the value of the
136      *                          prepended length field.
137      *
138      * @throws IllegalArgumentException
139      *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
140      */
141     public LengthFieldPrepender(
142             ByteOrder byteOrder, int lengthFieldLength,
143             int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
144         if (lengthFieldLength != 1 && lengthFieldLength != 2 &&
145             lengthFieldLength != 3 && lengthFieldLength != 4 &&
146             lengthFieldLength != 8) {
147             throw new IllegalArgumentException(
148                     "lengthFieldLength must be either 1, 2, 3, 4, or 8: " +
149                     lengthFieldLength);
150         }
151         ObjectUtil.checkNotNull(byteOrder, "byteOrder");
152 
153         this.byteOrder = byteOrder;
154         this.lengthFieldLength = lengthFieldLength;
155         this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
156         this.lengthAdjustment = lengthAdjustment;
157     }
158 
159     @Override
160     protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
161         int length = msg.readableBytes() + lengthAdjustment;
162         if (lengthIncludesLengthFieldLength) {
163             length += lengthFieldLength;
164         }
165 
166         if (length < 0) {
167             throw new IllegalArgumentException(
168                     "Adjusted frame length (" + length + ") is less than zero");
169         }
170 
171         switch (lengthFieldLength) {
172         case 1:
173             if (length >= 256) {
174                 throw new IllegalArgumentException(
175                         "length does not fit into a byte: " + length);
176             }
177             out.add(ctx.alloc().buffer(1).order(byteOrder).writeByte((byte) length));
178             break;
179         case 2:
180             if (length >= 65536) {
181                 throw new IllegalArgumentException(
182                         "length does not fit into a short integer: " + length);
183             }
184             out.add(ctx.alloc().buffer(2).order(byteOrder).writeShort((short) length));
185             break;
186         case 3:
187             if (length >= 16777216) {
188                 throw new IllegalArgumentException(
189                         "length does not fit into a medium integer: " + length);
190             }
191             out.add(ctx.alloc().buffer(3).order(byteOrder).writeMedium(length));
192             break;
193         case 4:
194             out.add(ctx.alloc().buffer(4).order(byteOrder).writeInt(length));
195             break;
196         case 8:
197             out.add(ctx.alloc().buffer(8).order(byteOrder).writeLong(length));
198             break;
199         default:
200             throw new Error("should not reach here");
201         }
202         out.add(msg.retain());
203     }
204 }