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 }