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 org.jboss.netty.handler.codec.frame;
17
18 import org.jboss.netty.buffer.ChannelBuffer;
19 import org.jboss.netty.channel.Channel;
20 import org.jboss.netty.channel.ChannelHandlerContext;
21 import org.jboss.netty.channel.Channels;
22
23 /**
24 * A decoder that splits the received {@link ChannelBuffer}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}{@code (}{@link Delimiters#lineDelimiter() Delimiters.lineDelimiter()}{@code )}
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 *
59 * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful
60 */
61 public class DelimiterBasedFrameDecoder extends FrameDecoder {
62
63 private final ChannelBuffer[] delimiters;
64 private final int maxFrameLength;
65 private final boolean stripDelimiter;
66 private final boolean failFast;
67 private boolean discardingTooLongFrame;
68 private int tooLongFrameLength;
69 /** Set only when decoding with "\n" and "\r\n" as the delimiter. */
70 private final LineBasedFrameDecoder lineBasedDecoder;
71
72 /**
73 * Creates a new instance.
74 *
75 * @param maxFrameLength the maximum length of the decoded frame.
76 * A {@link TooLongFrameException} is thrown if
77 * the length of the frame exceeds this value.
78 * @param delimiter the delimiter
79 */
80 public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer delimiter) {
81 this(maxFrameLength, true, delimiter);
82 }
83
84 /**
85 * Creates a new instance.
86 *
87 * @param maxFrameLength the maximum length of the decoded frame.
88 * A {@link TooLongFrameException} is thrown if
89 * the length of the frame exceeds this value.
90 * @param stripDelimiter whether the decoded frame should strip out the
91 * delimiter or not
92 * @param delimiter the delimiter
93 */
94 public DelimiterBasedFrameDecoder(
95 int maxFrameLength, boolean stripDelimiter, ChannelBuffer delimiter) {
96 this(maxFrameLength, stripDelimiter, false, delimiter);
97 }
98
99 /**
100 * Creates a new instance.
101 *
102 * @param maxFrameLength the maximum length of the decoded frame.
103 * A {@link TooLongFrameException} is thrown if
104 * the length of the frame exceeds this value.
105 * @param stripDelimiter whether the decoded frame should strip out the
106 * delimiter or not
107 * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is
108 * thrown as soon as the decoder notices the length of the
109 * frame will exceed <tt>maxFrameLength</tt> regardless of
110 * whether the entire frame has been read.
111 * If <tt>false</tt>, a {@link TooLongFrameException} is
112 * thrown after the entire frame that exceeds
113 * <tt>maxFrameLength</tt> has been read.
114 * @param delimiter the delimiter
115 */
116 public DelimiterBasedFrameDecoder(
117 int maxFrameLength, boolean stripDelimiter, boolean failFast,
118 ChannelBuffer delimiter) {
119 this(maxFrameLength, stripDelimiter, failFast, new ChannelBuffer[] {
120 delimiter.slice(
121 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, ChannelBuffer... 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, ChannelBuffer... delimiters) {
148 this(maxFrameLength, stripDelimiter, false, 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, ChannelBuffer... delimiters) {
170 validateMaxFrameLength(maxFrameLength);
171 if (delimiters == null) {
172 throw new NullPointerException("delimiters");
173 }
174 if (delimiters.length == 0) {
175 throw new IllegalArgumentException("empty delimiters");
176 }
177
178 if (isLineBased(delimiters) && !isSubclass()) {
179 lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
180 this.delimiters = null;
181 } else {
182 this.delimiters = new ChannelBuffer[delimiters.length];
183 for (int i = 0; i < delimiters.length; i ++) {
184 ChannelBuffer d = delimiters[i];
185 validateDelimiter(d);
186 this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
187 }
188 lineBasedDecoder = null;
189 }
190 this.maxFrameLength = maxFrameLength;
191 this.stripDelimiter = stripDelimiter;
192 this.failFast = failFast;
193 }
194
195 /** Returns true if the delimiters are "\n" and "\r\n". */
196 private static boolean isLineBased(final ChannelBuffer[] delimiters) {
197 if (delimiters.length != 2) {
198 return false;
199 }
200 ChannelBuffer a = delimiters[0];
201 ChannelBuffer b = delimiters[1];
202 if (a.capacity() < b.capacity()) {
203 a = delimiters[1];
204 b = delimiters[0];
205 }
206 return a.capacity() == 2 && b.capacity() == 1
207 && a.getByte(0) == '\r' && a.getByte(1) == '\n'
208 && b.getByte(0) == '\n';
209 }
210
211 /**
212 * Return {@code true} if the current instance is a subclass of DelimiterBasedFrameDecoder
213 */
214 private boolean isSubclass() {
215 return getClass() != DelimiterBasedFrameDecoder.class;
216 }
217
218 @Override
219 protected Object decode(
220 ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
221 if (lineBasedDecoder != null) {
222 return lineBasedDecoder.decode(ctx, channel, buffer);
223 }
224 // Try all delimiters and choose the delimiter which yields the shortest frame.
225 int minFrameLength = Integer.MAX_VALUE;
226 ChannelBuffer minDelim = null;
227 for (ChannelBuffer delim: delimiters) {
228 int frameLength = indexOf(buffer, delim);
229 if (frameLength >= 0 && frameLength < minFrameLength) {
230 minFrameLength = frameLength;
231 minDelim = delim;
232 }
233 }
234
235 if (minDelim != null) {
236 int minDelimLength = minDelim.capacity();
237 ChannelBuffer frame;
238
239 if (discardingTooLongFrame) {
240 // We've just finished discarding a very large frame.
241 // Go back to the initial state.
242 discardingTooLongFrame = false;
243 buffer.skipBytes(minFrameLength + minDelimLength);
244
245 int tooLongFrameLength = this.tooLongFrameLength;
246 this.tooLongFrameLength = 0;
247 if (!failFast) {
248 fail(ctx, tooLongFrameLength);
249 }
250 return null;
251 }
252
253 if (minFrameLength > maxFrameLength) {
254 // Discard read frame.
255 buffer.skipBytes(minFrameLength + minDelimLength);
256 fail(ctx, minFrameLength);
257 return null;
258 }
259
260 if (stripDelimiter) {
261 frame = extractFrame(buffer, buffer.readerIndex(), minFrameLength);
262 } else {
263 frame = extractFrame(buffer, buffer.readerIndex(), minFrameLength + minDelimLength);
264 }
265 buffer.skipBytes(minFrameLength + minDelimLength);
266
267 return frame;
268 } else {
269 if (!discardingTooLongFrame) {
270 if (buffer.readableBytes() > maxFrameLength) {
271 // Discard the content of the buffer until a delimiter is found.
272 tooLongFrameLength = buffer.readableBytes();
273 buffer.skipBytes(buffer.readableBytes());
274 discardingTooLongFrame = true;
275 if (failFast) {
276 fail(ctx, tooLongFrameLength);
277 }
278 }
279 } else {
280 // Still discarding the buffer since a delimiter is not found.
281 tooLongFrameLength += buffer.readableBytes();
282 buffer.skipBytes(buffer.readableBytes());
283 }
284 return null;
285 }
286 }
287
288 private void fail(ChannelHandlerContext ctx, long frameLength) {
289 if (frameLength > 0) {
290 Channels.fireExceptionCaught(
291 ctx.getChannel(),
292 new TooLongFrameException(
293 "frame length exceeds " + maxFrameLength +
294 ": " + frameLength + " - discarded"));
295 } else {
296 Channels.fireExceptionCaught(
297 ctx.getChannel(),
298 new TooLongFrameException(
299 "frame length exceeds " + maxFrameLength +
300 " - discarding"));
301 }
302 }
303
304 /**
305 * Returns the number of bytes between the readerIndex of the haystack and
306 * the first needle found in the haystack. -1 is returned if no needle is
307 * found in the haystack.
308 */
309 private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
310 for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
311 int haystackIndex = i;
312 int needleIndex;
313 for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
314 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
315 break;
316 } else {
317 haystackIndex ++;
318 if (haystackIndex == haystack.writerIndex() &&
319 needleIndex != needle.capacity() - 1) {
320 return -1;
321 }
322 }
323 }
324
325 if (needleIndex == needle.capacity()) {
326 // Found the needle from the haystack!
327 return i - haystack.readerIndex();
328 }
329 }
330 return -1;
331 }
332
333 private static void validateDelimiter(ChannelBuffer delimiter) {
334 if (delimiter == null) {
335 throw new NullPointerException("delimiter");
336 }
337 if (!delimiter.readable()) {
338 throw new IllegalArgumentException("empty delimiter");
339 }
340 }
341
342 private static void validateMaxFrameLength(int maxFrameLength) {
343 if (maxFrameLength <= 0) {
344 throw new IllegalArgumentException(
345 "maxFrameLength must be a positive integer: " +
346 maxFrameLength);
347 }
348 }
349 }