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 }