netty5/codec-redis/src/test/java/io/netty/handler/codec/redis/RedisDecoderTest.java
Scott Mitchell 742ee76424 RedisDecoder infinite loop
Motivation:
RedisDecoder can get into an infinite loop while decoding bulk strings if the final \r and \n to indicate the end of content are split on ByteBuf boundaries.

Modifications:
- We should break out of the decode loop if remainingBulkLength is 0 and we don't have enough data to read EOL

Result:
No more infinite loop in RedisDecoder#decodeBulkStringContent.
2017-05-30 08:53:36 -07:00

283 lines
10 KiB
Java

/*
* Copyright 2016 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 io.netty.handler.codec.redis;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ReferenceCountUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import static io.netty.handler.codec.redis.RedisCodecTestUtil.byteBufOf;
import static io.netty.handler.codec.redis.RedisCodecTestUtil.bytesOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Verifies the correct functionality of the {@link RedisDecoder} and {@link RedisArrayAggregator}.
*/
public class RedisDecoderTest {
private EmbeddedChannel channel;
@Before
public void setup() throws Exception {
channel = new EmbeddedChannel(
new RedisDecoder(),
new RedisBulkStringAggregator(),
new RedisArrayAggregator());
}
@After
public void teardown() throws Exception {
assertFalse(channel.finish());
}
@Test
public void splitEOLDoesNotInfiniteLoop() throws Exception {
assertFalse(channel.writeInbound(byteBufOf("$6\r\nfoobar\r")));
assertTrue(channel.writeInbound(byteBufOf("\n")));
RedisMessage msg = channel.readInbound();
assertTrue(msg instanceof FullBulkStringRedisMessage);
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeSimpleString() {
assertFalse(channel.writeInbound(byteBufOf("+")));
assertFalse(channel.writeInbound(byteBufOf("O")));
assertFalse(channel.writeInbound(byteBufOf("K")));
assertTrue(channel.writeInbound(byteBufOf("\r\n")));
SimpleStringRedisMessage msg = channel.readInbound();
assertThat(msg.content(), is("OK"));
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeTwoSimpleStrings() {
assertFalse(channel.writeInbound(byteBufOf("+")));
assertFalse(channel.writeInbound(byteBufOf("O")));
assertFalse(channel.writeInbound(byteBufOf("K")));
assertTrue(channel.writeInbound(byteBufOf("\r\n+SEC")));
assertTrue(channel.writeInbound(byteBufOf("OND\r\n")));
SimpleStringRedisMessage msg1 = channel.readInbound();
assertThat(msg1.content(), is("OK"));
ReferenceCountUtil.release(msg1);
SimpleStringRedisMessage msg2 = channel.readInbound();
assertThat(msg2.content(), is("SECOND"));
ReferenceCountUtil.release(msg2);
}
@Test
public void shouldDecodeError() {
String content = "ERROR sample message";
assertFalse(channel.writeInbound(byteBufOf("-")));
assertFalse(channel.writeInbound(byteBufOf(content)));
assertFalse(channel.writeInbound(byteBufOf("\r")));
assertTrue(channel.writeInbound(byteBufOf("\n")));
ErrorRedisMessage msg = channel.readInbound();
assertThat(msg.content(), is(content));
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeInteger() {
long value = 1234L;
byte[] content = bytesOf(value);
assertFalse(channel.writeInbound(byteBufOf(":")));
assertFalse(channel.writeInbound(byteBufOf(content)));
assertTrue(channel.writeInbound(byteBufOf("\r\n")));
IntegerRedisMessage msg = channel.readInbound();
assertThat(msg.value(), is(value));
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeBulkString() {
String buf1 = "bulk\nst";
String buf2 = "ring\ntest\n1234";
byte[] content = bytesOf(buf1 + buf2);
assertFalse(channel.writeInbound(byteBufOf("$")));
assertFalse(channel.writeInbound(byteBufOf(Integer.toString(content.length))));
assertFalse(channel.writeInbound(byteBufOf("\r\n")));
assertFalse(channel.writeInbound(byteBufOf(buf1)));
assertFalse(channel.writeInbound(byteBufOf(buf2)));
assertTrue(channel.writeInbound(byteBufOf("\r\n")));
FullBulkStringRedisMessage msg = channel.readInbound();
assertThat(bytesOf(msg.content()), is(content));
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeEmptyBulkString() {
byte[] content = bytesOf("");
assertFalse(channel.writeInbound(byteBufOf("$")));
assertFalse(channel.writeInbound(byteBufOf(Integer.toString(content.length))));
assertFalse(channel.writeInbound(byteBufOf("\r\n")));
assertFalse(channel.writeInbound(byteBufOf(content)));
assertTrue(channel.writeInbound(byteBufOf("\r\n")));
FullBulkStringRedisMessage msg = channel.readInbound();
assertThat(bytesOf(msg.content()), is(content));
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeNullBulkString() {
assertFalse(channel.writeInbound(byteBufOf("$")));
assertFalse(channel.writeInbound(byteBufOf(Integer.toString(-1))));
assertTrue(channel.writeInbound(byteBufOf("\r\n")));
assertTrue(channel.writeInbound(byteBufOf("$")));
assertTrue(channel.writeInbound(byteBufOf(Integer.toString(-1))));
assertTrue(channel.writeInbound(byteBufOf("\r\n")));
FullBulkStringRedisMessage msg1 = channel.readInbound();
assertThat(msg1.isNull(), is(true));
ReferenceCountUtil.release(msg1);
FullBulkStringRedisMessage msg2 = channel.readInbound();
assertThat(msg2.isNull(), is(true));
ReferenceCountUtil.release(msg2);
FullBulkStringRedisMessage msg3 = channel.readInbound();
assertThat(msg3, is(nullValue()));
}
@Test
public void shouldDecodeSimpleArray() throws Exception {
assertFalse(channel.writeInbound(byteBufOf("*3\r\n")));
assertFalse(channel.writeInbound(byteBufOf(":1234\r\n")));
assertFalse(channel.writeInbound(byteBufOf("+sim")));
assertFalse(channel.writeInbound(byteBufOf("ple\r\n-err")));
assertTrue(channel.writeInbound(byteBufOf("or\r\n")));
ArrayRedisMessage msg = channel.readInbound();
List<RedisMessage> children = msg.children();
assertThat(msg.children().size(), is(equalTo(3)));
assertThat(children.get(0), instanceOf(IntegerRedisMessage.class));
assertThat(((IntegerRedisMessage) children.get(0)).value(), is(1234L));
assertThat(children.get(1), instanceOf(SimpleStringRedisMessage.class));
assertThat(((SimpleStringRedisMessage) children.get(1)).content(), is("simple"));
assertThat(children.get(2), instanceOf(ErrorRedisMessage.class));
assertThat(((ErrorRedisMessage) children.get(2)).content(), is("error"));
ReferenceCountUtil.release(msg);
}
@Test
public void shouldDecodeNestedArray() throws Exception {
ByteBuf buf = Unpooled.buffer();
buf.writeBytes(byteBufOf("*2\r\n"));
buf.writeBytes(byteBufOf("*3\r\n:1\r\n:2\r\n:3\r\n"));
buf.writeBytes(byteBufOf("*2\r\n+Foo\r\n-Bar\r\n"));
assertTrue(channel.writeInbound(buf));
ArrayRedisMessage msg = channel.readInbound();
List<RedisMessage> children = msg.children();
assertThat(msg.children().size(), is(2));
ArrayRedisMessage intArray = (ArrayRedisMessage) children.get(0);
ArrayRedisMessage strArray = (ArrayRedisMessage) children.get(1);
assertThat(intArray.children().size(), is(3));
assertThat(((IntegerRedisMessage) intArray.children().get(0)).value(), is(1L));
assertThat(((IntegerRedisMessage) intArray.children().get(1)).value(), is(2L));
assertThat(((IntegerRedisMessage) intArray.children().get(2)).value(), is(3L));
assertThat(strArray.children().size(), is(2));
assertThat(((SimpleStringRedisMessage) strArray.children().get(0)).content(), is("Foo"));
assertThat(((ErrorRedisMessage) strArray.children().get(1)).content(), is("Bar"));
ReferenceCountUtil.release(msg);
}
@Test(expected = IllegalReferenceCountException.class)
public void shouldErrorOnDoubleReleaseArrayReferenceCounted() throws Exception {
ByteBuf buf = Unpooled.buffer();
buf.writeBytes(byteBufOf("*2\r\n"));
buf.writeBytes(byteBufOf("*3\r\n:1\r\n:2\r\n:3\r\n"));
buf.writeBytes(byteBufOf("*2\r\n+Foo\r\n-Bar\r\n"));
assertTrue(channel.writeInbound(buf));
ArrayRedisMessage msg = channel.readInbound();
ReferenceCountUtil.release(msg);
ReferenceCountUtil.release(msg);
}
@Test(expected = IllegalReferenceCountException.class)
public void shouldErrorOnReleaseArrayChildReferenceCounted() throws Exception {
ByteBuf buf = Unpooled.buffer();
buf.writeBytes(byteBufOf("*2\r\n"));
buf.writeBytes(byteBufOf("*3\r\n:1\r\n:2\r\n:3\r\n"));
buf.writeBytes(byteBufOf("$3\r\nFoo\r\n"));
assertTrue(channel.writeInbound(buf));
ArrayRedisMessage msg = channel.readInbound();
List<RedisMessage> children = msg.children();
ReferenceCountUtil.release(msg);
ReferenceCountUtil.release(children.get(1));
}
@Test(expected = IllegalReferenceCountException.class)
public void shouldErrorOnReleasecontentOfArrayChildReferenceCounted() throws Exception {
ByteBuf buf = Unpooled.buffer();
buf.writeBytes(byteBufOf("*2\r\n"));
buf.writeBytes(byteBufOf("$3\r\nFoo\r\n$3\r\nBar\r\n"));
assertTrue(channel.writeInbound(buf));
ArrayRedisMessage msg = channel.readInbound();
List<RedisMessage> children = msg.children();
ByteBuf childBuf = ((FullBulkStringRedisMessage) children.get(0)).content();
ReferenceCountUtil.release(msg);
ReferenceCountUtil.release(childBuf);
}
}