/* * Copyright 2017 StreamSets Inc. * * Licensed under the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 com.streamsets.pipeline.lib.parser.net; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.serialization.ObjectDecoder; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.util.List; public class DelimitedLengthFieldBasedFrameDecoder extends ByteToMessageDecoder { private final int maxFrameLength; private final int lengthAdjustment; private final boolean failFast; private final Charset lengthFieldCharset; private final ByteBuf delimiter; private final boolean trimLengthString; private boolean discardingTooLongFrame; private long tooLongFrameLength; private long bytesToDiscard; private boolean consumingLength = true; private long frameLength = 0; public DelimitedLengthFieldBasedFrameDecoder( int maxFrameLength, int lengthAdjustment, boolean failFast, ByteBuf delimiter, Charset lengthFieldCharset, boolean trimLengthString ) { if (delimiter == null) { throw new NullPointerException("delimiter"); } if (!delimiter.isReadable()) { throw new IllegalArgumentException("empty delimiter"); } this.delimiter = delimiter.slice(delimiter.readerIndex(), delimiter.readableBytes()); this.lengthFieldCharset = lengthFieldCharset; this.maxFrameLength = maxFrameLength; this.lengthAdjustment = lengthAdjustment; this.failFast = failFast; this.trimLengthString = trimLengthString; } @Override protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { Object decoded = decode(ctx, in); if (decoded != null) { out.add(decoded); } } /** * Create a frame out of the {@link ByteBuf} and return it. * * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to * @param in the {@link ByteBuf} from which to read data * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could * be created. */ protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { if (discardingTooLongFrame) { long bytesToDiscard = this.bytesToDiscard; int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes()); in.skipBytes(localBytesToDiscard); bytesToDiscard -= localBytesToDiscard; this.bytesToDiscard = bytesToDiscard; failIfNecessary(false); return null; } if (consumingLength) { int delimIndex = indexOf(in, delimiter); if (delimIndex < 0) { return null; } final String lengthStr = in.toString(in.readerIndex(), delimIndex, lengthFieldCharset); try { frameLength = Long.parseLong(trimLengthString ? lengthStr.trim() : lengthStr); } catch (NumberFormatException e) { throw new CorruptedFrameException( String.format( "Invalid length field decoded (in %s charset): %s", lengthFieldCharset.name(), lengthStr ), e ); } if (frameLength < 0) { throw new CorruptedFrameException("negative pre-adjustment length field: " + frameLength); } frameLength += lengthAdjustment; //consume length field and delimiter bytes in.skipBytes(delimIndex + delimiter.capacity()); //consume delimiter bytes consumingLength = false; } if (frameLength > maxFrameLength) { long discard = frameLength - in.readableBytes(); tooLongFrameLength = frameLength; if (discard < 0) { // buffer contains more bytes then the frameLength so we can discard all now in.skipBytes((int) frameLength); } else { // Enter the discard mode and discard everything received so far. discardingTooLongFrame = true; consumingLength = true; bytesToDiscard = discard; in.skipBytes(in.readableBytes()); } failIfNecessary(true); return null; } // never overflows because it's less than maxFrameLength int frameLengthInt = (int) frameLength; if (in.readableBytes() < frameLengthInt) { // need more bytes available to read actual frame return null; } // the frame is now entirely present, reset state vars consumingLength = true; frameLength = 0; // extract frame int readerIndex = in.readerIndex(); int actualFrameLength = frameLengthInt;// - initialBytesToStrip; ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength); in.readerIndex(readerIndex + actualFrameLength); return frame; } private void failIfNecessary(boolean firstDetectionOfTooLongFrame) { if (bytesToDiscard == 0) { // Reset to the initial state and tell the handlers that // the frame was too large. long prevTooLongFrameLength = tooLongFrameLength; tooLongFrameLength = 0; discardingTooLongFrame = false; consumingLength = true; frameLength = 0; if (!failFast || firstDetectionOfTooLongFrame) { fail(prevTooLongFrameLength); } } else { // Keep discarding and notify handlers if necessary. if (failFast && firstDetectionOfTooLongFrame) { fail(tooLongFrameLength); } } } /** * Extract the sub-region of the specified buffer. * <p> * If you are sure that the frame and its content are not accessed after * the current {@link #decode(ChannelHandlerContext, ByteBuf)} * call returns, you can even avoid memory copy by returning the sliced * sub-region (i.e. <tt>return buffer.slice(index, length)</tt>). * It's often useful when you convert the extracted frame into an object. * Refer to the source code of {@link ObjectDecoder} to see how this method * is overridden to avoid memory copy. */ protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) { return buffer.slice(index, length).retain(); } private void fail(long frameLength) { if (frameLength > 0) { throw new TooLongFrameException("Adjusted frame length exceeds " + maxFrameLength + ": " + frameLength + " - " + "discarded"); } else { throw new TooLongFrameException("Adjusted 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(ByteBuf haystack, ByteBuf 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; } }