/* * Copyright 2011 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package org.jboss.netty.handler.codec.frame; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.Channels; /** * A decoder that splits the received {@link ChannelBuffer}s by one or more * delimiters. It is particularly useful for decoding the frames which ends * with a delimiter such as {@link Delimiters#nulDelimiter() NUL} or * {@linkplain Delimiters#lineDelimiter() newline characters}. * * <h3>Predefined delimiters</h3> * <p> * {@link Delimiters} defines frequently used delimiters for convenience' sake. * * <h3>Specifying more than one delimiter</h3> * <p> * {@link DelimiterBasedFrameDecoder} allows you to specify more than one * delimiter. If more than one delimiter is found in the buffer, it chooses * the delimiter which produces the shortest frame. For example, if you have * the following data in the buffer: * <pre> * +--------------+ * | ABC\nDEF\r\n | * +--------------+ * </pre> * a {@link DelimiterBasedFrameDecoder}{@code (}{@link Delimiters#lineDelimiter() Delimiters.lineDelimiter()}{@code )} * will choose {@code '\n'} as the first delimiter and produce two frames: * <pre> * +-----+-----+ * | ABC | DEF | * +-----+-----+ * </pre> * rather than incorrectly choosing {@code '\r\n'} as the first delimiter: * <pre> * +----------+ * | ABC\nDEF | * +----------+ * </pre> * * @apiviz.uses org.jboss.netty.handler.codec.frame.Delimiters - - useful */ public class DelimiterBasedFrameDecoder extends FrameDecoder { private final ChannelBuffer[] delimiters; private final int maxFrameLength; private final boolean stripDelimiter; private final boolean failFast; private boolean discardingTooLongFrame; private int tooLongFrameLength; /** * Creates a new instance. * * @param maxFrameLength the maximum length of the decoded frame. * A {@link TooLongFrameException} is thrown if * the length of the frame exceeds this value. * @param delimiter the delimiter */ public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer delimiter) { this(maxFrameLength, true, delimiter); } /** * Creates a new instance. * * @param maxFrameLength the maximum length of the decoded frame. * A {@link TooLongFrameException} is thrown if * the length of the frame exceeds this value. * @param stripDelimiter whether the decoded frame should strip out the * delimiter or not * @param delimiter the delimiter */ public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, ChannelBuffer delimiter) { this(maxFrameLength, stripDelimiter, false, delimiter); } /** * Creates a new instance. * * @param maxFrameLength the maximum length of the decoded frame. * A {@link TooLongFrameException} is thrown if * the length of the frame exceeds this value. * @param stripDelimiter whether the decoded frame should strip out the * delimiter or not * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is * thrown as soon as the decoder notices the length of the * frame will exceed <tt>maxFrameLength</tt> regardless of * whether the entire frame has been read. * If <tt>false</tt>, a {@link TooLongFrameException} is * thrown after the entire frame that exceeds * <tt>maxFrameLength</tt> has been read. * @param delimiter the delimiter */ public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer delimiter) { validateMaxFrameLength(maxFrameLength); validateDelimiter(delimiter); delimiters = new ChannelBuffer[] { delimiter.slice( delimiter.readerIndex(), delimiter.readableBytes()) }; this.maxFrameLength = maxFrameLength; this.stripDelimiter = stripDelimiter; this.failFast = failFast; } /** * Creates a new instance. * * @param maxFrameLength the maximum length of the decoded frame. * A {@link TooLongFrameException} is thrown if * the length of the frame exceeds this value. * @param delimiters the delimiters */ public DelimiterBasedFrameDecoder(int maxFrameLength, ChannelBuffer... delimiters) { this(maxFrameLength, true, delimiters); } /** * Creates a new instance. * * @param maxFrameLength the maximum length of the decoded frame. * A {@link TooLongFrameException} is thrown if * the length of the frame exceeds this value. * @param stripDelimiter whether the decoded frame should strip out the * delimiter or not * @param delimiters the delimiters */ public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, ChannelBuffer... delimiters) { this(maxFrameLength, stripDelimiter, false, delimiters); } /** * Creates a new instance. * * @param maxFrameLength the maximum length of the decoded frame. * A {@link TooLongFrameException} is thrown if * the length of the frame exceeds this value. * @param stripDelimiter whether the decoded frame should strip out the * delimiter or not * @param failFast If <tt>true</tt>, a {@link TooLongFrameException} is * thrown as soon as the decoder notices the length of the * frame will exceed <tt>maxFrameLength</tt> regardless of * whether the entire frame has been read. * If <tt>false</tt>, a {@link TooLongFrameException} is * thrown after the entire frame that exceeds * <tt>maxFrameLength</tt> has been read. * @param delimiters the delimiters */ public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, boolean failFast, ChannelBuffer... delimiters) { validateMaxFrameLength(maxFrameLength); if (delimiters == null) { throw new NullPointerException("delimiters"); } if (delimiters.length == 0) { throw new IllegalArgumentException("empty delimiters"); } this.delimiters = new ChannelBuffer[delimiters.length]; for (int i = 0; i < delimiters.length; i ++) { ChannelBuffer d = delimiters[i]; validateDelimiter(d); this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes()); } this.maxFrameLength = maxFrameLength; this.stripDelimiter = stripDelimiter; this.failFast = failFast; } @Override protected Object decode( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { // Try all delimiters and choose the delimiter which yields the shortest frame. int minFrameLength = Integer.MAX_VALUE; ChannelBuffer minDelim = null; for (ChannelBuffer delim: delimiters) { int frameLength = indexOf(buffer, delim); if (frameLength >= 0 && frameLength < minFrameLength) { minFrameLength = frameLength; minDelim = delim; } } if (minDelim != null) { int minDelimLength = minDelim.capacity(); ChannelBuffer frame; if (discardingTooLongFrame) { // We've just finished discarding a very large frame. // Go back to the initial state. discardingTooLongFrame = false; buffer.skipBytes(minFrameLength + minDelimLength); int tooLongFrameLength = this.tooLongFrameLength; this.tooLongFrameLength = 0; if (!failFast) { fail(ctx, tooLongFrameLength); } return null; } if (minFrameLength > maxFrameLength) { // Discard read frame. buffer.skipBytes(minFrameLength + minDelimLength); fail(ctx, minFrameLength); return null; } if (stripDelimiter) { frame = buffer.readBytes(minFrameLength); buffer.skipBytes(minDelimLength); } else { frame = buffer.readBytes(minFrameLength + minDelimLength); } return frame; } else { if (!discardingTooLongFrame) { if (buffer.readableBytes() > maxFrameLength) { // Discard the content of the buffer until a delimiter is found. tooLongFrameLength = buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); discardingTooLongFrame = true; if (failFast) { fail(ctx, tooLongFrameLength); } } } else { // Still discarding the buffer since a delimiter is not found. tooLongFrameLength += buffer.readableBytes(); buffer.skipBytes(buffer.readableBytes()); } return null; } } private void fail(ChannelHandlerContext ctx, long frameLength) { if (frameLength > 0) { Channels.fireExceptionCaught( ctx.getChannel(), new TooLongFrameException( "frame length exceeds " + maxFrameLength + ": " + frameLength + " - discarded")); } else { Channels.fireExceptionCaught( ctx.getChannel(), new TooLongFrameException( "frame length exceeds " + maxFrameLength + " - discarding")); } } /** * Returns the number of bytes between the readerIndex of the haystack and * the first needle found in the haystack. -1 is returned if no needle is * found in the haystack. */ private static int indexOf(ChannelBuffer haystack, ChannelBuffer needle) { for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) { int haystackIndex = i; int needleIndex; for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) { if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) { break; } else { haystackIndex ++; if (haystackIndex == haystack.writerIndex() && needleIndex != needle.capacity() - 1) { return -1; } } } if (needleIndex == needle.capacity()) { // Found the needle from the haystack! return i - haystack.readerIndex(); } } return -1; } private static void validateDelimiter(ChannelBuffer delimiter) { if (delimiter == null) { throw new NullPointerException("delimiter"); } if (!delimiter.readable()) { throw new IllegalArgumentException("empty delimiter"); } } private static void validateMaxFrameLength(int maxFrameLength) { if (maxFrameLength <= 0) { throw new IllegalArgumentException( "maxFrameLength must be a positive integer: " + maxFrameLength); } } }