/* * 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 com.google.common.primitives.Bytes; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.TooLongFrameException; import io.netty.util.CharsetUtil; import io.netty.util.internal.ThreadLocalRandom; import org.junit.Assert; import org.junit.Test; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; public class TestDelimitedLengthFieldBasedFrameDecoder { private static List<List<Byte>> getRandomByteSlices(byte[] bytes) { List<Byte> byteList = Bytes.asList(bytes); int numSlices = nextInt(2, 10); List<Integer> sliceIndexes = new ArrayList<Integer>(); for (int i = 1; i <= numSlices; i++) { sliceIndexes.add(nextInt(0, bytes.length)); } Collections.sort(sliceIndexes); List<List<Byte>> slices = new LinkedList<List<Byte>>(); int byteInd = 0; for (int sliceIndex : sliceIndexes) { slices.add(byteList.subList(byteInd, sliceIndex)); byteInd = sliceIndex; } slices.add(byteList.subList(byteInd, byteList.size())); return slices; } /** * Generates a new pseudo-random integer within the specific range. * * This is essentially the same method that is present in Apache commons-lang. It is simply copied here to avoid * bringing in a new dependency * * @param startInclusive the lowest value that can be generated * @param endExclusive * @return a pseurandom number in [startInclusive, endExclusive) */ private static int nextInt(final int startInclusive, final int endExclusive) { if (startInclusive == endExclusive) { return startInclusive; } return startInclusive + ThreadLocalRandom.current().nextInt(endExclusive - startInclusive); } @Test public void testDelimitedLengths() throws Exception { Charset charset = CharsetUtil.ISO_8859_1; EmbeddedChannel ch = getTestChannel(charset, 100, 0, false); String v1 = "a"; String v2 = "abcdefghij"; String v3 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrsxt" + "uvwxyz"; writeStringAndAssert(ch, v1, charset, false, false); writeStringAndAssert(ch, v2, charset, false, false); writeStringAndAssert(ch, v3, charset, false, true); writeStringAndAssert(ch, v1, charset, true, false); writeStringAndAssert(ch, v2, charset, true, false); writeStringAndAssert(ch, v3, charset, true, true); writeStringAndAssert(ch, v1, charset, false, false); ch.close(); } @Test public void testMaxFrameLengthOverflow() throws Exception { Charset charset = CharsetUtil.ISO_8859_1; // maxFrameLength plus adjustment would overflow an int final long numBytes = Integer.MAX_VALUE - 1; final int lengthAdjustment = 10; EmbeddedChannel ch = getTestChannel(charset, (int) numBytes, lengthAdjustment, true); //this is a bad frame, but will still test the overflow condition String longString = String.valueOf(numBytes) + " abcd"; try { ch.writeInbound(Unpooled.copiedBuffer(longString, charset)); Assert.fail("TooLongFrameException should have been thrown"); } catch (TooLongFrameException ignored) { //ignored } Assert.assertNull(ch.readInbound()); ch.close(); } private EmbeddedChannel getTestChannel( Charset charset, int maxFrameLength, int lengthAdjustment, boolean failFast ) { return new EmbeddedChannel(new DelimitedLengthFieldBasedFrameDecoder( maxFrameLength, lengthAdjustment, failFast, Unpooled.copiedBuffer(" ", charset), charset, true )); } private void writeStringAndAssert( EmbeddedChannel channel, String value, Charset charset, boolean randomlyPartition, boolean expectFrameTooLarge ) { String frame = makeFrame(value, charset); try { if (randomlyPartition) { for (List<Byte> chunk : getRandomByteSlices(frame.getBytes())) { channel.writeInbound(Unpooled.copiedBuffer(Bytes.toArray(chunk))); } } else { channel.writeInbound(Unpooled.copiedBuffer(frame, charset)); } } catch (TooLongFrameException e) { if (!expectFrameTooLarge) { Assert.fail("TooLongFrameException unexpectedly thrown"); } else { Assert.assertNull(channel.readInbound()); } } if (!expectFrameTooLarge) { ByteBuf in = (ByteBuf) channel.readInbound(); Assert.assertEquals(value, in.toString(charset)); in.release(); } } private String makeFrame(String value, Charset charset) { int byteLength = value.getBytes(charset).length; return byteLength + " " + value; } }