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