// This file is part of OpenTSDB.
// Copyright (C) 2012 The OpenTSDB Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at your
// option) any later version. This program is distributed in the hope that it
// will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details. You should have received a copy
// of the GNU Lesser General Public License along with this program. If not,
// see <http://www.gnu.org/licenses/>.
package net.opentsdb.tsd;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.jboss.netty.handler.codec.frame.TooLongFrameException;
/**
* Decodes telnet-style frames delimited by new-lines.
* <p>
* Both "\n" and "\r\n" are handled.
* <p>
* This decoder is stateful and is thus <strong>NOT</strong> shareable.
*/
final class LineBasedFrameDecoder extends FrameDecoder {
/** Maximum length of a frame we're willing to decode. */
private final int max_length;
/** True if we're discarding input because we're already over max_length. */
private boolean discarding;
/**
* Creates a new decoder.
* @param max_length Maximum length of a frame we're willing to decode.
* If a frame is longer than that, a {@link TooLongFrameException} will
* be fired on the channel causing it.
*/
public LineBasedFrameDecoder(final int max_length) {
this.max_length = max_length;
}
@Override
protected Object decode(final ChannelHandlerContext ctx,
final Channel channel,
final ChannelBuffer buffer) throws Exception {
final int eol = findEndOfLine(buffer);
if (eol != -1) {
final ChannelBuffer frame;
final int length = eol - buffer.readerIndex();
assert length >= 0: "WTF? length=" + length;
if (discarding) {
frame = null;
buffer.skipBytes(length);
} else {
frame = buffer.readBytes(length);
}
final byte delim = buffer.readByte();
if (delim == '\r') {
buffer.skipBytes(1); // Skip the \n.
}
return frame;
}
final int buffered = buffer.readableBytes();
if (!discarding && buffered > max_length) {
discarding = true;
Channels.fireExceptionCaught(ctx.getChannel(),
new TooLongFrameException("Frame length exceeds " + max_length + " ("
+ buffered + " bytes buffered already)"));
}
if (discarding) {
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
/**
* Returns the index in the buffer of the end of line found.
* Returns -1 if no end of line was found in the buffer.
*/
private static int findEndOfLine(final ChannelBuffer buffer) {
final int n = buffer.writerIndex();
for (int i = buffer.readerIndex(); i < n; i ++) {
final byte b = buffer.getByte(i);
if (b == '\n') {
return i;
} else if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
return i; // \r\n
}
}
return -1; // Not found.
}
}