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