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