View Javadoc
1   /*
2    * Copyright 2022 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  
24  import static io.netty5.util.internal.ObjectUtil.checkPositive;
25  import static io.netty5.util.internal.ObjectUtil.checkPositiveOrZero;
26  import static java.util.Objects.requireNonNull;
27  
28  /**
29   * A decoder that splits the received {@link Buffer}s dynamically by the
30   * value of the length field in the message.  It is particularly useful when you
31   * decode a binary message which has an integer header field that represents the
32   * length of the message body or the whole message.
33   * <p>
34   * {@link LengthFieldBasedFrameDecoder} has many configuration parameters so
35   * that it can decode any message with a length field, which is often seen in
36   * proprietary client-server protocols. Here are some example that will give
37   * you the basic idea on which option does what.
38   *
39   * <h3>2 bytes length field at offset 0, do not strip header</h3>
40   * <p>
41   * The value of the length field in this example is <tt>12 (0x0C)</tt> which
42   * represents the length of "HELLO, WORLD".  By default, the decoder assumes
43   * that the length field represents the number of the bytes that follows the
44   * length field.  Therefore, it can be decoded with the simplistic parameter
45   * combination.
46   * <pre>
47   * <b>lengthFieldOffset</b>   = <b>0</b>
48   * <b>lengthFieldLength</b>   = <b>2</b>
49   * lengthAdjustment    = 0
50   * initialBytesToStrip = 0 (= do not strip header)
51   *
52   * BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
53   * +--------+----------------+      +--------+----------------+
54   * | Length | Actual Content |----->| Length | Actual Content |
55   * | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
56   * +--------+----------------+      +--------+----------------+
57   * </pre>
58   *
59   * <h3>2 bytes length field at offset 0, strip header</h3>
60   * <p>
61   * Because we can get the length of the content by calling
62   * {@link Buffer#readableBytes()}, you might want to strip the length
63   * field by specifying <tt>initialBytesToStrip</tt>.  In this example, we
64   * specified <tt>2</tt>, that is same with the length of the length field, to
65   * strip the first two bytes.
66   * <pre>
67   * lengthFieldOffset   = 0
68   * lengthFieldLength   = 2
69   * lengthAdjustment    = 0
70   * <b>initialBytesToStrip</b> = <b>2</b> (= the length of the Length field)
71   *
72   * BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
73   * +--------+----------------+      +----------------+
74   * | Length | Actual Content |----->| Actual Content |
75   * | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
76   * +--------+----------------+      +----------------+
77   * </pre>
78   *
79   * <h3>2 bytes length field at offset 0, do not strip header, the length field
80   * represents the length of the whole message</h3>
81   * <p>
82   * In most cases, the length field represents the length of the message body
83   * only, as shown in the previous examples.  However, in some protocols, the
84   * length field represents the length of the whole message, including the
85   * message header.  In such a case, we specify a non-zero
86   * <tt>lengthAdjustment</tt>.  Because the length value in this example message
87   * is always greater than the body length by <tt>2</tt>, we specify <tt>-2</tt>
88   * as <tt>lengthAdjustment</tt> for compensation.
89   * <pre>
90   * lengthFieldOffset   =  0
91   * lengthFieldLength   =  2
92   * <b>lengthAdjustment</b>    = <b>-2</b> (= the length of the Length field)
93   * initialBytesToStrip =  0
94   *
95   * BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
96   * +--------+----------------+      +--------+----------------+
97   * | Length | Actual Content |----->| Length | Actual Content |
98   * | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
99   * +--------+----------------+      +--------+----------------+
100  * </pre>
101  *
102  * <h3>3 bytes length field at the end of 5 bytes header, do not strip header</h3>
103  * <p>
104  * The following message is a simple variation of the first example.  An extra
105  * header value is prepended to the message.  <tt>lengthAdjustment</tt> is zero
106  * again because the decoder always takes the length of the prepended data into
107  * account during frame length calculation.
108  * <pre>
109  * <b>lengthFieldOffset</b>   = <b>2</b> (= the length of Header 1)
110  * <b>lengthFieldLength</b>   = <b>3</b>
111  * lengthAdjustment    = 0
112  * initialBytesToStrip = 0
113  *
114  * BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
115  * +----------+----------+----------------+      +----------+----------+----------------+
116  * | Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
117  * |  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
118  * +----------+----------+----------------+      +----------+----------+----------------+
119  * </pre>
120  *
121  * <h3>3 bytes length field at the beginning of 5 bytes header, do not strip header</h3>
122  * <p>
123  * This is an advanced example that shows the case where there is an extra
124  * header between the length field and the message body.  You have to specify a
125  * positive <tt>lengthAdjustment</tt> so that the decoder counts the extra
126  * header into the frame length calculation.
127  * <pre>
128  * lengthFieldOffset   = 0
129  * lengthFieldLength   = 3
130  * <b>lengthAdjustment</b>    = <b>2</b> (= the length of Header 1)
131  * initialBytesToStrip = 0
132  *
133  * BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
134  * +----------+----------+----------------+      +----------+----------+----------------+
135  * |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
136  * | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
137  * +----------+----------+----------------+      +----------+----------+----------------+
138  * </pre>
139  *
140  * <h3>2 bytes length field at offset 1 in the middle of 4 bytes header,
141  * strip the first header field and the length field</h3>
142  * <p>
143  * This is a combination of all the examples above.  There are the prepended
144  * header before the length field and the extra header after the length field.
145  * The prepended header affects the <tt>lengthFieldOffset</tt> and the extra
146  * header affects the <tt>lengthAdjustment</tt>.  We also specified a non-zero
147  * <tt>initialBytesToStrip</tt> to strip the length field and the prepended
148  * header from the frame.  If you don't want to strip the prepended header, you
149  * could specify <tt>0</tt> for <tt>initialBytesToSkip</tt>.
150  * <pre>
151  * lengthFieldOffset   = 1 (= the length of HDR1)
152  * lengthFieldLength   = 2
153  * <b>lengthAdjustment</b>    = <b>1</b> (= the length of HDR2)
154  * <b>initialBytesToStrip</b> = <b>3</b> (= the length of HDR1 + LEN)
155  *
156  * BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
157  * +------+--------+------+----------------+      +------+----------------+
158  * | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
159  * | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
160  * +------+--------+------+----------------+      +------+----------------+
161  * </pre>
162  *
163  * <h3>2 bytes length field at offset 1 in the middle of 4 bytes header,
164  * strip the first header field and the length field, the length field
165  * represents the length of the whole message</h3>
166  * <p>
167  * Let's give another twist to the previous example.  The only difference from
168  * the previous example is that the length field represents the length of the
169  * whole message instead of the message body, just like the third example.
170  * We have to count the length of HDR1 and Length into <tt>lengthAdjustment</tt>.
171  * Please note that we don't need to take the length of HDR2 into account
172  * because the length field already includes the whole header length.
173  * <pre>
174  * lengthFieldOffset   =  1
175  * lengthFieldLength   =  2
176  * <b>lengthAdjustment</b>    = <b>-3</b> (= the length of HDR1 + LEN, negative)
177  * <b>initialBytesToStrip</b> = <b> 3</b>
178  *
179  * BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
180  * +------+--------+------+----------------+      +------+----------------+
181  * | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
182  * | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
183  * +------+--------+------+----------------+      +------+----------------+
184  * </pre>
185  *
186  * @see LengthFieldPrepender
187  */
188 public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder { // TODO rename
189 
190     private final ByteOrder byteOrder;
191     private final int maxFrameLength;
192     private final int lengthFieldOffset;
193     private final int lengthFieldLength;
194     private final int lengthFieldEndOffset;
195     private final int lengthAdjustment;
196     private final int initialBytesToStrip;
197     private final boolean failFast;
198 
199     private long tooLongFrameLength;
200     private long bytesToDiscard;
201     private int currentFrameLength = -1;
202 
203     /**
204      * Creates a new instance.
205      *
206      * @param maxFrameLength    the maximum length of the frame.  If the length of the frame is
207      *                          greater than this value, {@link TooLongFrameException} will be
208      *                          thrown.
209      * @param lengthFieldOffset the offset of the length field
210      * @param lengthFieldLength the length of the length field
211      */
212     public LengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
213         this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
214     }
215 
216     /**
217      * Creates a new instance.
218      *
219      * @param maxFrameLength      the maximum length of the frame.  If the length of the frame is
220      *                            greater than this value, {@link TooLongFrameException} will be
221      *                            thrown.
222      * @param lengthFieldOffset   the offset of the length field
223      * @param lengthFieldLength   the length of the length field
224      * @param lengthAdjustment    the compensation value to add to the value of the length field
225      * @param initialBytesToStrip the number of first bytes to strip out from the decoded frame
226      */
227     public LengthFieldBasedFrameDecoder(
228             int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
229             int lengthAdjustment, int initialBytesToStrip) {
230         this(maxFrameLength,
231              lengthFieldOffset, lengthFieldLength, lengthAdjustment,
232              initialBytesToStrip, true);
233     }
234 
235     /**
236      * Creates a new instance.
237      *
238      * @param maxFrameLength      the maximum length of the frame.  If the length of the frame is
239      *                            greater than this value, {@link TooLongFrameException} will be
240      *                            thrown.
241      * @param lengthFieldOffset   the offset of the length field
242      * @param lengthFieldLength   the length of the length field
243      * @param lengthAdjustment    the compensation value to add to the value of the length field
244      * @param initialBytesToStrip the number of first bytes to strip out from the decoded frame
245      * @param failFast            If <tt>true</tt>, a {@link TooLongFrameException} is thrown as
246      *                            soon as the decoder notices the length of the frame will exceed
247      *                            <tt>maxFrameLength</tt> regardless of whether the entire frame
248      *                            has been read.  If <tt>false</tt>, a {@link TooLongFrameException}
249      *                            is thrown after the entire frame that exceeds <tt>maxFrameLength</tt>
250      *                            has been read.
251      */
252     public LengthFieldBasedFrameDecoder(
253             int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
254             int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
255         this(ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
256              lengthAdjustment, initialBytesToStrip, failFast);
257     }
258 
259     /**
260      * Creates a new instance.
261      *
262      * @param byteOrder           the {@link ByteOrder} of the length field
263      * @param maxFrameLength      the maximum length of the frame.  If the length of the frame is
264      *                            greater than this value, {@link TooLongFrameException} will be
265      *                            thrown.
266      * @param lengthFieldOffset   the offset of the length field
267      * @param lengthFieldLength   the length of the length field
268      * @param lengthAdjustment    the compensation value to add to the value of the length field
269      * @param initialBytesToStrip the number of first bytes to strip out from the decoded frame
270      * @param failFast            If <tt>true</tt>, a {@link TooLongFrameException} is thrown as
271      *                            soon as the decoder notices the length of the frame will exceed
272      *                            <tt>maxFrameLength</tt> regardless of whether the entire frame
273      *                            has been read.  If <tt>false</tt>, a {@link TooLongFrameException}
274      *                            is thrown after the entire frame that exceeds <tt>maxFrameLength</tt>
275      *                            has been read.
276      */
277     public LengthFieldBasedFrameDecoder(
278             ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
279             int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
280         requireNonNull(byteOrder, "byteOrder");
281         checkPositive(maxFrameLength, "maxFrameLength");
282         checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");
283         checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");
284 
285         if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {
286             throw new IllegalArgumentException(
287                     "maxFrameLength (" + maxFrameLength + ") " +
288                     "must be equal to or greater than " +
289                     "lengthFieldOffset (" + lengthFieldOffset + ") + " +
290                     "lengthFieldLength (" + lengthFieldLength + ").");
291         }
292 
293         this.byteOrder = byteOrder;
294         this.maxFrameLength = maxFrameLength;
295         this.lengthFieldOffset = lengthFieldOffset;
296         this.lengthFieldLength = lengthFieldLength;
297         this.lengthAdjustment = lengthAdjustment;
298         lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
299         this.initialBytesToStrip = initialBytesToStrip;
300         this.failFast = failFast;
301     }
302 
303     @Override
304     protected final void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {
305         final Object decoded = decode0(ctx, in);
306         if (decoded != null) {
307             ctx.fireChannelRead(decoded);
308         }
309     }
310 
311     private void discardTooLongFrame(Buffer in) {
312         final int bytesToDiscardNow = (int) Math.min(bytesToDiscard, in.readableBytes());
313         in.skipReadableBytes(bytesToDiscardNow);
314 
315         bytesToDiscard -= bytesToDiscardNow;
316 
317         failOnLengthExceededIfNecessary(false);
318     }
319 
320     private static void failOnNegativeLengthField(Buffer buffer, long frameLength, int lengthFieldEndOffset) {
321         buffer.skipReadableBytes(lengthFieldEndOffset);
322         throw new CorruptedFrameException("negative pre-adjustment length field: " + frameLength);
323     }
324 
325     private static void failOnFrameLengthLessThanLengthFieldEndOffset(
326             Buffer buffer, long frameLength, int lengthFieldEndOffset) {
327         buffer.skipReadableBytes(lengthFieldEndOffset);
328         throw new CorruptedFrameException(
329                 "Adjusted frame length (" + frameLength + ") is less " +
330                 "than lengthFieldEndOffset: " + lengthFieldEndOffset);
331     }
332 
333     private void handleFrameLengthExceeded(Buffer buffer, long frameLength) {
334         final long discard = frameLength - buffer.readableBytes();
335         tooLongFrameLength = frameLength;
336 
337         if (discard < 0) {
338             // buffer contains more bytes then the frameLength so we can discard all now
339             buffer.skipReadableBytes((int) frameLength);
340         } else {
341             // Enter the discard mode and discard everything received so far.
342             bytesToDiscard = discard;
343             buffer.skipReadableBytes(buffer.readableBytes());
344         }
345         failOnLengthExceededIfNecessary(true);
346     }
347 
348     private static void failOnFrameLengthLessThanInitialBytesToStrip(
349             Buffer buffer, int frameLength, int initialBytesToStrip) {
350         buffer.skipReadableBytes(frameLength);
351         throw new CorruptedFrameException(
352                 "Adjusted frame length (" + frameLength + ") is less " +
353                 "than initialBytesToStrip: " + initialBytesToStrip);
354     }
355 
356     /**
357      * Create a frame out of the {@link Buffer} and return it.
358      *
359      * @param ctx    the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
360      * @param buffer the {@link Buffer} from which to read data
361      * @return the {@link Buffer} which represent the frame or {@code null} if no frame could be created.
362      */
363     protected Object decode0(ChannelHandlerContext ctx, Buffer buffer) throws Exception {
364         if (currentFrameLength == -1) { // new frame
365             if (bytesToDiscard > 0) {
366                 discardTooLongFrame(buffer);
367             }
368 
369             if (buffer.readableBytes() < lengthFieldEndOffset) {
370                 return null;
371             }
372 
373             final int actualLengthFieldOffset = buffer.readerOffset() + lengthFieldOffset;
374             long frameLength = getUnadjustedFrameLength(buffer, actualLengthFieldOffset, lengthFieldLength, byteOrder);
375 
376             if (frameLength < 0) {
377                 failOnNegativeLengthField(buffer, frameLength, lengthFieldEndOffset);
378             }
379 
380             frameLength += lengthAdjustment + lengthFieldEndOffset;
381 
382             if (frameLength < lengthFieldEndOffset) {
383                 failOnFrameLengthLessThanLengthFieldEndOffset(buffer, frameLength, lengthFieldEndOffset);
384             }
385 
386             if (frameLength > maxFrameLength) {
387                 handleFrameLengthExceeded(buffer, frameLength);
388                 return null;
389             }
390 
391             // never overflows because it's less than maxFrameLength
392             currentFrameLength = (int) frameLength;
393         }
394 
395         if (buffer.readableBytes() < currentFrameLength) { // frameLengthInt exist, just check buf
396             return null;
397         }
398 
399         if (initialBytesToStrip > currentFrameLength) {
400             failOnFrameLengthLessThanInitialBytesToStrip(buffer, currentFrameLength, initialBytesToStrip);
401         }
402 
403         buffer.skipReadableBytes(initialBytesToStrip);
404 
405         // extract frame
406         final Buffer frame = extractFrame(ctx, buffer, currentFrameLength - initialBytesToStrip);
407         currentFrameLength = -1; // start processing the next frame
408         return frame;
409     }
410 
411     /**
412      * Decodes the specified region of the buffer into an unadjusted frame length.  The default implementation is
413      * capable of decoding the specified region into an unsigned 8/16/24/32/64 bit integer.  Override this method to
414      * decode the length field encoded differently.  Note that this method must not modify the state of the specified
415      * buffer (e.g. {@code readerOffset}, {@code writerOffset}, and the content of the buffer.)
416      *
417      * @throws DecoderException if failed to decode the specified region
418      */
419     protected long getUnadjustedFrameLength(Buffer buffer, int offset, int length, ByteOrder byteOrder) {
420         final boolean reverseBytes = byteOrder == ByteOrder.LITTLE_ENDIAN;
421 
422         switch (length) {
423         case 1:
424             return buffer.getUnsignedByte(offset);
425         case 2:
426             final int shortLength = buffer.getUnsignedShort(offset);
427             return reverseBytes? BufferUtil.reverseUnsignedShort(shortLength) : shortLength;
428         case 3:
429             final int mediumLength = buffer.getUnsignedMedium(offset);
430             return reverseBytes? BufferUtil.reverseUnsignedMedium(mediumLength) : mediumLength;
431         case 4:
432             final long intLength = buffer.getUnsignedInt(offset);
433             return reverseBytes? BufferUtil.reverseUnsignedInt(intLength) : intLength;
434         case 8:
435             final long longLength = buffer.getLong(offset);
436             return reverseBytes? Long.reverseBytes(longLength) : longLength;
437         default:
438             throw new DecoderException("unsupported lengthFieldLength: " + length + " (expected: 1, 2, 3, 4, or 8)");
439         }
440     }
441 
442     private void failOnLengthExceededIfNecessary(boolean firstDetectionOfTooLongFrame) {
443         if (bytesToDiscard == 0) {
444             // Reset to the initial state and tell the handlers that
445             // the frame was too large.
446             final long tooLongFrameLength = this.tooLongFrameLength;
447             this.tooLongFrameLength = 0;
448 
449             if (!failFast || firstDetectionOfTooLongFrame) {
450                 failOnLengthExceeded(tooLongFrameLength);
451             }
452         } else {
453             // Keep discarding and notify handlers if necessary.
454             if (failFast && firstDetectionOfTooLongFrame) {
455                 failOnLengthExceeded(tooLongFrameLength);
456             }
457         }
458     }
459 
460     private void failOnLengthExceeded(long frameLength) {
461         if (frameLength > 0) {
462             throw new TooLongFrameException(
463                     "Adjusted frame length exceeds " + maxFrameLength + ": " + frameLength + " - discarded");
464         } else {
465             throw new TooLongFrameException("Adjusted frame length exceeds " + maxFrameLength + " - discarding");
466         }
467     }
468 
469     /**
470      * Extract the sub-region of the specified buffer.  This method must modify the state of the buffer to continue
471      * after the frame (e.g. by splitting the buffer or by increasing {@code readerOffset}).
472      */
473     protected Buffer extractFrame(@SuppressWarnings("unused") ChannelHandlerContext ctx, Buffer buffer, int length) {
474         return buffer.readSplit(length);
475     }
476 
477 }