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.api.Buffer;
19  import io.netty5.channel.ChannelHandlerContext;
20  
21  import static io.netty5.util.internal.ObjectUtil.checkPositive;
22  import static java.util.Objects.requireNonNull;
23  
24  /**
25   * A decoder that splits the received {@link Buffer}s by one or more
26   * delimiters.  It is particularly useful for decoding the frames which ends
27   * with a delimiter such as {@link Delimiters#nulDelimiter() NUL} or
28   * {@linkplain Delimiters#lineDelimiter() newline characters}.
29   *
30   * <h3>Predefined delimiters</h3>
31   * <p>
32   * {@link Delimiters} defines frequently used delimiters for convenience' sake.
33   *
34   * <h3>Specifying more than one delimiter</h3>
35   * <p>
36   * {@link DelimiterBasedFrameDecoder} allows you to specify more than one
37   * delimiter.  If more than one delimiter is found in the buffer, it chooses
38   * the delimiter which produces the shortest frame.  For example, if you have
39   * the following data in the buffer:
40   * <pre>
41   * +--------------+
42   * | ABC\nDEF\r\n |
43   * +--------------+
44   * </pre>
45   * a {@link DelimiterBasedFrameDecoder}({@link Delimiters#lineDelimiter() Delimiters.lineDelimiter()})
46   * will choose {@code '\n'} as the first delimiter and produce two frames:
47   * <pre>
48   * +-----+-----+
49   * | ABC | DEF |
50   * +-----+-----+
51   * </pre>
52   * rather than incorrectly choosing {@code '\r\n'} as the first delimiter:
53   * <pre>
54   * +----------+
55   * | ABC\nDEF |
56   * +----------+
57   * </pre>
58   */
59  public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
60  
61      private final Buffer[] delimiters;
62      private final int maxFrameLength;
63      private final boolean stripDelimiter;
64      private final boolean failFast;
65      private boolean discardingTooLongFrame;
66      private int tooLongFrameLength;
67      /** Set only when decoding with "\n" and "\r\n" as the delimiter.  */
68      private final LineBasedFrameDecoder lineBasedDecoder;
69  
70      /**
71       * Creates a new instance.
72       *
73       * @param maxFrameLength  the maximum length of the decoded frame.
74       *                        A {@link TooLongFrameException} is thrown if
75       *                        the length of the frame exceeds this value.
76       * @param delimiters  the delimiters
77       */
78      public DelimiterBasedFrameDecoder(int maxFrameLength, Buffer... delimiters) {
79          this(maxFrameLength, true, delimiters);
80      }
81  
82      /**
83       * Creates a new instance.
84       *
85       * @param maxFrameLength  the maximum length of the decoded frame.
86       *                        A {@link TooLongFrameException} is thrown if
87       *                        the length of the frame exceeds this value.
88       * @param stripDelimiter  whether the decoded frame should strip out the
89       *                        delimiter or not
90       * @param delimiters  the delimiters
91       */
92      public DelimiterBasedFrameDecoder(
93              int maxFrameLength, boolean stripDelimiter, Buffer... delimiters) {
94          this(maxFrameLength, stripDelimiter, true, delimiters);
95      }
96  
97      /**
98       * Creates a new instance.
99       *
100      * @param maxFrameLength  the maximum length of the decoded frame.
101      *                        A {@link TooLongFrameException} is thrown if
102      *                        the length of the frame exceeds this value.
103      * @param stripDelimiter  whether the decoded frame should strip out the
104      *                        delimiter or not
105      * @param failFast  If <tt>true</tt>, a {@link TooLongFrameException} is
106      *                  thrown as soon as the decoder notices the length of the
107      *                  frame will exceed <tt>maxFrameLength</tt> regardless of
108      *                  whether the entire frame has been read.
109      *                  If <tt>false</tt>, a {@link TooLongFrameException} is
110      *                  thrown after the entire frame that exceeds
111      *                  <tt>maxFrameLength</tt> has been read.
112      * @param delimiters  the delimiters
113      */
114     public DelimiterBasedFrameDecoder(
115             int maxFrameLength, boolean stripDelimiter, boolean failFast, Buffer... delimiters) {
116         validateMaxFrameLength(maxFrameLength);
117         requireNonNull(delimiters, "delimiters");
118         if (delimiters.length == 0) {
119             throw new IllegalArgumentException("empty delimiters");
120         }
121 
122         if (isLineBased(delimiters) && !isSubclass()) {
123             lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
124             this.delimiters = null;
125             closeDelimiters(delimiters);
126         } else {
127             this.delimiters = new Buffer[delimiters.length];
128             RuntimeException re = null;
129             try {
130                 for (int i = 0; i < delimiters.length; i++) {
131                     Buffer d = delimiters[i];
132                     validateDelimiter(d);
133                     this.delimiters[i] = d.copy(true);
134                 }
135             } catch (IllegalArgumentException e) {
136                 re = e;
137                 try {
138                     closeDelimiters(this.delimiters);
139                 } catch (RuntimeException e1) {
140                     re.addSuppressed(e1);
141                 }
142             } finally {
143                 try {
144                     closeDelimiters(delimiters);
145                 } catch (RuntimeException e) {
146                     if (re == null) {
147                         re = e;
148                     } else {
149                         re.addSuppressed(e);
150                     }
151                 }
152             }
153             if (re != null) {
154                 throw re;
155             }
156             lineBasedDecoder = null;
157         }
158         this.maxFrameLength = maxFrameLength;
159         this.stripDelimiter = stripDelimiter;
160         this.failFast = failFast;
161     }
162 
163     /** Returns true if the delimiters are "\n" and "\r\n".  */
164     private static boolean isLineBased(final Buffer[] delimiters) {
165         if (delimiters.length != 2) {
166             return false;
167         }
168         Buffer a = delimiters[0];
169         Buffer b = delimiters[1];
170         if (a.capacity() < b.capacity()) {
171             a = delimiters[1];
172             b = delimiters[0];
173         }
174         return a.capacity() == 2 && b.capacity() == 1
175                 && a.getByte(0) == '\r' && a.getByte(1) == '\n'
176                 && b.getByte(0) == '\n';
177     }
178 
179     /**
180      * Return {@code true} if the current instance is a subclass of DelimiterBasedFrameDecoder
181      */
182     private boolean isSubclass() {
183         return getClass() != DelimiterBasedFrameDecoder.class;
184     }
185 
186     @Override
187     protected final void decode(ChannelHandlerContext ctx, Buffer in) throws Exception {
188         Object decoded = decode0(ctx, in);
189         if (decoded != null) {
190             ctx.fireChannelRead(decoded);
191         }
192     }
193 
194     /**
195      * Create a frame out of the {@link Buffer} and return it.
196      *
197      * @param   ctx             the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
198      * @param   buffer          the {@link Buffer} from which to read data
199      * @return  frame           the {@link Buffer} which represent the frame or {@code null} if no frame could
200      *                          be created.
201      */
202     protected Object decode0(ChannelHandlerContext ctx, Buffer buffer) {
203         if (lineBasedDecoder != null) {
204             return lineBasedDecoder.decode0(ctx, buffer);
205         }
206         // Try all delimiters and choose the delimiter which yields the shortest frame.
207         int minFrameLength = Integer.MAX_VALUE;
208         Buffer minDelim = null;
209         for (Buffer delim: delimiters) {
210             int frameLength = indexOf(buffer, delim);
211             if (frameLength >= 0 && frameLength < minFrameLength) {
212                 minFrameLength = frameLength;
213                 minDelim = delim;
214             }
215         }
216 
217         if (minDelim != null) {
218             int minDelimLength = minDelim.capacity();
219             Buffer frame;
220 
221             if (discardingTooLongFrame) {
222                 // We've just finished discarding a very large frame.
223                 // Go back to the initial state.
224                 discardingTooLongFrame = false;
225                 buffer.skipReadableBytes(minFrameLength + minDelimLength);
226 
227                 int tooLongFrameLength = this.tooLongFrameLength;
228                 this.tooLongFrameLength = 0;
229                 if (!failFast) {
230                     fail(tooLongFrameLength);
231                 }
232                 return null;
233             }
234 
235             if (minFrameLength > maxFrameLength) {
236                 // Discard read frame.
237                 buffer.skipReadableBytes(minFrameLength + minDelimLength);
238                 fail(minFrameLength);
239                 return null;
240             }
241 
242             if (stripDelimiter) {
243                 frame = buffer.readSplit(minFrameLength);
244                 buffer.skipReadableBytes(minDelimLength);
245             } else {
246                 frame = buffer.readSplit(minFrameLength + minDelimLength);
247             }
248 
249             return frame;
250         } else {
251             if (!discardingTooLongFrame) {
252                 if (buffer.readableBytes() > maxFrameLength) {
253                     // Discard the content of the buffer until a delimiter is found.
254                     tooLongFrameLength = buffer.readableBytes();
255                     buffer.skipReadableBytes(buffer.readableBytes());
256                     discardingTooLongFrame = true;
257                     if (failFast) {
258                         fail(tooLongFrameLength);
259                     }
260                 }
261             } else {
262                 // Still discarding the buffer since a delimiter is not found.
263                 tooLongFrameLength += buffer.readableBytes();
264                 buffer.skipReadableBytes(buffer.readableBytes());
265             }
266             return null;
267         }
268     }
269 
270     @Override
271     protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
272         if (delimiters != null) {
273             closeDelimiters(delimiters);
274         }
275         super.handlerRemoved0(ctx);
276     }
277 
278     private static void closeDelimiters(Buffer[] delimiters) {
279         RuntimeException re = null;
280         for (Buffer delimiter : delimiters) {
281             try {
282                 if (delimiter != null) {
283                     delimiter.close();
284                 }
285             } catch (RuntimeException e) {
286                 if (re == null) {
287                     re = e;
288                 } else {
289                     re.addSuppressed(e);
290                 }
291             }
292         }
293         if (re != null) {
294             throw re;
295         }
296     }
297 
298     private void fail(long frameLength) {
299         if (frameLength > 0) {
300             throw new TooLongFrameException(
301                             "frame length exceeds " + maxFrameLength +
302                             ": " + frameLength + " - discarded");
303         } else {
304             throw new TooLongFrameException(
305                             "frame length exceeds " + maxFrameLength +
306                             " - discarding");
307         }
308     }
309 
310     /**
311      * Returns the number of bytes between the readerIndex of the haystack and
312      * the first needle found in the haystack.  -1 is returned if no needle is
313      * found in the haystack.
314      */
315     private static int indexOf(Buffer haystack, Buffer needle) {
316         for (int i = haystack.readerOffset(); i < haystack.writerOffset(); i ++) {
317             int haystackIndex = i;
318             int needleIndex;
319             for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
320                 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
321                     break;
322                 } else {
323                     haystackIndex ++;
324                     if (haystackIndex == haystack.writerOffset() &&
325                         needleIndex != needle.capacity() - 1) {
326                         return -1;
327                     }
328                 }
329             }
330 
331             if (needleIndex == needle.capacity()) {
332                 // Found the needle from the haystack!
333                 return i - haystack.readerOffset();
334             }
335         }
336         return -1;
337     }
338 
339     private static void validateDelimiter(Buffer delimiter) {
340         requireNonNull(delimiter, "delimiter");
341         if (delimiter.readableBytes() == 0) {
342             throw new IllegalArgumentException("empty delimiter");
343         }
344     }
345 
346     private static void validateMaxFrameLength(int maxFrameLength) {
347         checkPositive(maxFrameLength, "maxFrameLength");
348     }
349 }