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 * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful
59 */
60 public class DelimiterBasedFrameDecoder extends FrameDecoder {
61
62 private final ChannelBuffer[] delimiters;
63 private final int maxFrameLength;
64 private final boolean stripDelimiter;
65 private final boolean failFast;
66 private boolean discardingTooLongFrame;
67 private int tooLongFrameLength;
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, ChannelBuffer 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, ChannelBuffer delimiter) {
93 this(maxFrameLength, stripDelimiter, false, 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 ChannelBuffer delimiter) {
116 validateMaxFrameLength(maxFrameLength);
117 validateDelimiter(delimiter);
118 delimiters = new ChannelBuffer[] {
119 delimiter.slice(
120 delimiter.readerIndex(), delimiter.readableBytes())
121 };
122 this.maxFrameLength = maxFrameLength;
123 this.stripDelimiter = stripDelimiter;
124 this.failFast = failFast;
125 }
126
127 /**
128 * Creates a new instance.
129 *
130 * @param maxFrameLength the maximum length of the decoded frame.
131 * A {@link TooLongFrameException} is thrown if
132 * the length of the frame exceeds this value.
133 * @param delimiters the delimiters
134 */
135 public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer... delimiters) {
136 this(maxFrameLength, true, delimiters);
137 }
138
139 /**
140 * Creates a new instance.
141 *
142 * @param maxFrameLength the maximum length of the decoded frame.
143 * A {@link TooLongFrameException} is thrown if
144 * the length of the frame exceeds this value.
145 * @param stripDelimiter whether the decoded frame should strip out the
146 * delimiter or not
147 * @param delimiters the delimiters
148 */
149 public DelimiterBasedFrameDecoder(
150 int maxFrameLength, boolean stripDelimiter, ChannelBuffer... delimiters) {
151 this(maxFrameLength, stripDelimiter, false, delimiters);
152 }
153
154 /**
155 * Creates a new instance.
156 *
157 * @param maxFrameLength the maximum length of the decoded frame.
158 * A {@link TooLongFrameException} is thrown if
159 * the length of the frame exceeds this value.
160 * @param stripDelimiter whether the decoded frame should strip out the
161 * delimiter or not
162 * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is
163 * thrown as soon as the decoder notices the length of the
164 * frame will exceed <tt>maxFrameLength</tt> regardless of
165 * whether the entire frame has been read.
166 * If <tt>false</tt>, a {@link TooLongFrameException} is
167 * thrown after the entire frame that exceeds
168 * <tt>maxFrameLength</tt> has been read.
169 * @param delimiters the delimiters
170 */
171 public DelimiterBasedFrameDecoder(
172 int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) {
173 validateMaxFrameLength(maxFrameLength);
174 if (delimiters == null) {
175 throw new NullPointerException("delimiters");
176 }
177 if (delimiters.length == 0) {
178 throw new IllegalArgumentException("empty delimiters");
179 }
180 this.delimiters = new ChannelBuffer[delimiters.length];
181 for (int i = 0; i < delimiters.length; i ++) {
182 ChannelBuffer d = delimiters[i];
183 validateDelimiter(d);
184 this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
185 }
186 this.maxFrameLength = maxFrameLength;
187 this.stripDelimiter = stripDelimiter;
188 this.failFast = failFast;
189 }
190
191 @Override
192 protected Object decode(
193 ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
194 // Try all delimiters and choose the delimiter which yields the shortest frame.
195 int minFrameLength = Integer.MAX_VALUE;
196 ChannelBuffer minDelim = null;
197 for (ChannelBuffer delim: delimiters) {
198 int frameLength = indexOf(buffer, delim);
199 if (frameLength >= 0 && frameLength < minFrameLength) {
200 minFrameLength = frameLength;
201 minDelim = delim;
202 }
203 }
204
205 if (minDelim != null) {
206 int minDelimLength = minDelim.capacity();
207 ChannelBuffer frame;
208
209 if (discardingTooLongFrame) {
210 // We've just finished discarding a very large frame.
211 // Go back to the initial state.
212 discardingTooLongFrame = false;
213 buffer.skipBytes(minFrameLength + minDelimLength);
214
215 int tooLongFrameLength = this.tooLongFrameLength;
216 this.tooLongFrameLength = 0;
217 if (!failFast) {
218 fail(ctx, tooLongFrameLength);
219 }
220 return null;
221 }
222
223 if (minFrameLength > maxFrameLength) {
224 // Discard read frame.
225 buffer.skipBytes(minFrameLength + minDelimLength);
226 fail(ctx, minFrameLength);
227 return null;
228 }
229
230 if (stripDelimiter) {
231 frame = buffer.readBytes(minFrameLength);
232 buffer.skipBytes(minDelimLength);
233 } else {
234 frame = buffer.readBytes(minFrameLength + minDelimLength);
235 }
236
237 return frame;
238 } else {
239 if (!discardingTooLongFrame) {
240 if (buffer.readableBytes() > maxFrameLength) {
241 // Discard the content of the buffer until a delimiter is found.
242 tooLongFrameLength = buffer.readableBytes();
243 buffer.skipBytes(buffer.readableBytes());
244 discardingTooLongFrame = true;
245 if (failFast) {
246 fail(ctx, tooLongFrameLength);
247 }
248 }
249 } else {
250 // Still discarding the buffer since a delimiter is not found.
251 tooLongFrameLength += buffer.readableBytes();
252 buffer.skipBytes(buffer.readableBytes());
253 }
254 return null;
255 }
256 }
257
258 private void fail(ChannelHandlerContext ctx, long frameLength) {
259 if (frameLength > 0) {
260 Channels.fireExceptionCaught(
261 ctx.getChannel(),
262 new TooLongFrameException(
263 "frame length exceeds " + maxFrameLength +
264 ": " + frameLength + " - discarded"));
265 } else {
266 Channels.fireExceptionCaught(
267 ctx.getChannel(),
268 new TooLongFrameException(
269 "frame length exceeds " + maxFrameLength +
270 " - discarding"));
271 }
272 }
273
274 /**
275 * Returns the number of bytes between the readerIndex of the haystack and
276 * the first needle found in the haystack. -1 is returned if no needle is
277 * found in the haystack.
278 */
279 private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) {
280 for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
281 int haystackIndex = i;
282 int needleIndex;
283 for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
284 if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
285 break;
286 } else {
287 haystackIndex ++;
288 if (haystackIndex == haystack.writerIndex() &&
289 needleIndex != needle.capacity() - 1) {
290 return -1;
291 }
292 }
293 }
294
295 if (needleIndex == needle.capacity()) {
296 // Found the needle from the haystack!
297 return i - haystack.readerIndex();
298 }
299 }
300 return -1;
301 }
302
303 private static void validateDelimiter(ChannelBuffer delimiter) {
304 if (delimiter == null) {
305 throw new NullPointerException("delimiter");
306 }
307 if (!delimiter.readable()) {
308 throw new IllegalArgumentException("empty delimiter");
309 }
310 }
311
312 private static void validateMaxFrameLength(int maxFrameLength) {
313 if (maxFrameLength <= 0) {
314 throw new IllegalArgumentException(
315 "maxFrameLength must be a positive integer: " +
316 maxFrameLength);
317 }
318 }
319 }