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.
283 lines
10 KiB
Java
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);
|
|
}
|
|
}
|