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    *   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 }