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  
22  import java.util.List;
23  
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 MessageToMessageEncoder<ByteBuf> {
53  
54      private final int lengthFieldLength;
55      private final boolean lengthIncludesLengthFieldLength;
56      private final int lengthAdjustment;
57  
58      /**
59       * Creates a new instance.
60       *
61       * @param lengthFieldLength the length of the prepended length field.
62       *                          Only 1, 2, 3, 4, and 8 are allowed.
63       *
64       * @throws IllegalArgumentException
65       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
66       */
67      public LengthFieldPrepender(int lengthFieldLength) {
68          this(lengthFieldLength, false);
69      }
70  
71      /**
72       * Creates a new instance.
73       *
74       * @param lengthFieldLength the length of the prepended length field.
75       *                          Only 1, 2, 3, 4, and 8 are allowed.
76       * @param lengthIncludesLengthFieldLength
77       *                          if {@code true}, the length of the prepended
78       *                          length field is added to the value of the
79       *                          prepended length field.
80       *
81       * @throws IllegalArgumentException
82       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
83       */
84      public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
85          this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
86      }
87  
88      /**
89       * Creates a new instance.
90       *
91       * @param lengthFieldLength the length of the prepended length field.
92       *                          Only 1, 2, 3, 4, and 8 are allowed.
93       * @param lengthAdjustment  the compensation value to add to the value
94       *                          of the length field
95       *
96       * @throws IllegalArgumentException
97       *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
98       */
99      public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment) {
100         this(lengthFieldLength, lengthAdjustment, false);
101     }
102 
103     /**
104      * Creates a new instance.
105      *
106      * @param lengthFieldLength the length of the prepended length field.
107      *                          Only 1, 2, 3, 4, and 8 are allowed.
108      * @param lengthAdjustment  the compensation value to add to the value
109      *                          of the length field
110      * @param lengthIncludesLengthFieldLength
111      *                          if {@code true}, the length of the prepended
112      *                          length field is added to the value of the
113      *                          prepended length field.
114      *
115      * @throws IllegalArgumentException
116      *         if {@code lengthFieldLength} is not 1, 2, 3, 4, or 8
117      */
118     public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
119         if (lengthFieldLength != 1 && lengthFieldLength != 2 &&
120             lengthFieldLength != 3 && lengthFieldLength != 4 &&
121             lengthFieldLength != 8) {
122             throw new IllegalArgumentException(
123                     "lengthFieldLength must be either 1, 2, 3, 4, or 8: " +
124                     lengthFieldLength);
125         }
126 
127         this.lengthFieldLength = lengthFieldLength;
128         this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
129         this.lengthAdjustment = lengthAdjustment;
130     }
131 
132     @Override
133     protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
134         int length = msg.readableBytes() + lengthAdjustment;
135         if (lengthIncludesLengthFieldLength) {
136             length += lengthFieldLength;
137         }
138 
139         if (length < 0) {
140             throw new IllegalArgumentException(
141                     "Adjusted frame length (" + length + ") is less than zero");
142         }
143 
144         switch (lengthFieldLength) {
145         case 1:
146             if (length >= 256) {
147                 throw new IllegalArgumentException(
148                         "length does not fit into a byte: " + length);
149             }
150             out.add(ctx.alloc().buffer(1).writeByte((byte) length));
151             break;
152         case 2:
153             if (length >= 65536) {
154                 throw new IllegalArgumentException(
155                         "length does not fit into a short integer: " + length);
156             }
157             out.add(ctx.alloc().buffer(2).writeShort((short) length));
158             break;
159         case 3:
160             if (length >= 16777216) {
161                 throw new IllegalArgumentException(
162                         "length does not fit into a medium integer: " + length);
163             }
164             out.add(ctx.alloc().buffer(3).writeMedium(length));
165             break;
166         case 4:
167             out.add(ctx.alloc().buffer(4).writeInt(length));
168             break;
169         case 8:
170             out.add(ctx.alloc().buffer(8).writeLong(length));
171             break;
172         default:
173             throw new Error("should not reach here");
174         }
175         out.add(msg.retain());
176     }
177 }