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.netty5.handler.codec;
17
18 import io.netty5.buffer.BufferUtil;
19 import io.netty5.buffer.api.Buffer;
20 import io.netty5.channel.ChannelHandlerContext;
21
22 import java.nio.ByteOrder;
23 import java.util.List;
24
25 import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero;
26 import static java.util.Objects.requireNonNull;
27
28 /**
29 * An encoder that prepends the length of the message. The length value is
30 * prepended as a binary form.
31 * <p>
32 * For example, <tt>{@link LengthFieldPrepender}(2)</tt> will encode the
33 * following 12-bytes string:
34 * <pre>
35 * +----------------+
36 * | "HELLO, WORLD" |
37 * +----------------+
38 * </pre>
39 * into the following:
40 * <pre>
41 * +--------+----------------+
42 * + 0x000C | "HELLO, WORLD" |
43 * +--------+----------------+
44 * </pre>
45 * If you turned on the {@code lengthIncludesLengthFieldLength} flag in the
46 * constructor, the encoded data would look like the following
47 * (12 (original data) + 2 (prepended data) = 14 (0xE)):
48 * <pre>
49 * +--------+----------------+
50 * + 0x000E | "HELLO, WORLD" |
51 * +--------+----------------+
52 * </pre>
53 */
54 public class LengthFieldPrepender extends MessageToMessageEncoder<Buffer> {
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 supported by default.
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 supported by default.
76 * @param lengthIncludesLengthFieldLength if {@code true}, the length of the prepended
77 * length field is added to the value of the
78 * prepended length field.
79 */
80 public LengthFieldPrepender(int lengthFieldLength, boolean lengthIncludesLengthFieldLength) {
81 this(lengthFieldLength, 0, lengthIncludesLengthFieldLength);
82 }
83
84 /**
85 * Creates a new instance.
86 *
87 * @param lengthFieldLength the length of the prepended length field.
88 * Only 1, 2, 3, 4, and 8 are supported by default.
89 * @param lengthAdjustment the compensation value to add to the value
90 * of the length field
91 */
92 public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment) {
93 this(lengthFieldLength, lengthAdjustment, false);
94 }
95
96 /**
97 * Creates a new instance.
98 *
99 * @param lengthFieldLength the length of the prepended length field.
100 * Only 1, 2, 3, 4, and 8 are supported by default.
101 * @param lengthAdjustment the compensation value to add to the value
102 * of the length field
103 * @param lengthIncludesLengthFieldLength if {@code true}, the length of the prepended
104 * length field is added to the value of the
105 * prepended length field.
106 */
107 public LengthFieldPrepender(int lengthFieldLength, int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
108 this(ByteOrder.BIG_ENDIAN, lengthFieldLength, lengthAdjustment, lengthIncludesLengthFieldLength);
109 }
110
111 /**
112 * Creates a new instance.
113 *
114 * @param byteOrder the {@link ByteOrder} of the length field
115 * @param lengthFieldLength the length of the prepended length field.
116 * Only 1, 2, 3, 4, and 8 are supported by default.
117 * @param lengthAdjustment the compensation value to add to the value
118 * of the length field
119 * @param lengthIncludesLengthFieldLength if {@code true}, the length of the prepended
120 * length field is added to the value of the
121 * prepended length field.
122 */
123 public LengthFieldPrepender(
124 ByteOrder byteOrder, int lengthFieldLength,
125 int lengthAdjustment, boolean lengthIncludesLengthFieldLength) {
126 requireNonNull(byteOrder, "byteOrder");
127
128 this.byteOrder = byteOrder;
129 this.lengthFieldLength = lengthFieldLength;
130 this.lengthIncludesLengthFieldLength = lengthIncludesLengthFieldLength;
131 this.lengthAdjustment = lengthAdjustment;
132 }
133
134 @Override
135 public boolean isSharable() {
136 return true;
137 }
138
139 @Override
140 protected void encode(ChannelHandlerContext ctx, Buffer buffer, List<Object> out) throws Exception {
141 int length = buffer.readableBytes() + lengthAdjustment;
142 if (lengthIncludesLengthFieldLength) {
143 length += lengthFieldLength;
144 }
145
146 checkPositiveOrZero(length, "length");
147
148 out.add(getLengthFieldBuffer(ctx, length, lengthFieldLength, byteOrder));
149 out.add(buffer.split());
150 }
151
152 /**
153 * Encodes the length into a buffer which will be prepended as the length field. The default implementation is
154 * capable of encoding the length into an 8/16/24/32/64 bit integer. Override this method to encode the length
155 * field differently.
156 *
157 * @param ctx the {@link ChannelHandlerContext} which this {@link LengthFieldPrepender} belongs to
158 * @param length the length which should be encoded in the length field
159 * @param lengthFieldLength the length of the prepended length field
160 * @param byteOrder the {@link ByteOrder} of the length field
161 * @return A buffer containing the encoded length
162 * @throws EncoderException if failed to encode the length
163 */
164 protected Buffer getLengthFieldBuffer(
165 ChannelHandlerContext ctx, int length, int lengthFieldLength, ByteOrder byteOrder) {
166 final boolean reverseBytes = byteOrder == ByteOrder.LITTLE_ENDIAN;
167
168 switch (lengthFieldLength) {
169 case 1:
170 if (length >= 256) {
171 throw new IllegalArgumentException("length does not fit into a byte: " + length);
172 }
173 return ctx.bufferAllocator().allocate(lengthFieldLength).writeByte((byte) length);
174 case 2:
175 if (length >= 65536) {
176 throw new IllegalArgumentException("length does not fit into a short integer: " + length);
177 }
178 return ctx.bufferAllocator().allocate(lengthFieldLength)
179 .writeShort(reverseBytes ? Short.reverseBytes((short) length) : (short) length);
180 case 3:
181 if (length >= 16777216) {
182 throw new IllegalArgumentException("length does not fit into a medium integer: " + length);
183 }
184 return ctx.bufferAllocator().allocate(lengthFieldLength)
185 .writeMedium(reverseBytes ? BufferUtil.reverseMedium(length) : length);
186 case 4:
187 return ctx.bufferAllocator().allocate(lengthFieldLength)
188 .writeInt(reverseBytes ? Integer.reverseBytes(length) : length);
189 case 8:
190 return ctx.bufferAllocator().allocate(lengthFieldLength)
191 .writeLong(reverseBytes ? Long.reverseBytes(length) : length);
192 default:
193 throw new EncoderException(
194 "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
195 }
196 }
197
198 }