Skip to content

Reject empty fragments #903

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
import io.netty.util.ReferenceCountUtil;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
import io.rsocket.frame.*;
import io.rsocket.frame.FragmentationCodec;
import io.rsocket.frame.FrameHeaderCodec;
import io.rsocket.frame.FrameType;
import io.rsocket.frame.PayloadFrameCodec;
import io.rsocket.frame.RequestChannelFrameCodec;
import io.rsocket.frame.RequestFireAndForgetFrameCodec;
import io.rsocket.frame.RequestResponseFrameCodec;
import io.rsocket.frame.RequestStreamFrameCodec;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -221,31 +228,34 @@ void handleFollowsFlag(ByteBuf frame, int streamId, FrameType frameType) {
putHeader(streamId, header);
}

ByteBuf metadata = null;
if (FrameHeaderCodec.hasMetadata(frame)) {
CompositeByteBuf metadata = getMetadata(streamId);
switch (frameType) {
case REQUEST_FNF:
metadata.addComponents(true, RequestFireAndForgetFrameCodec.metadata(frame).retain());
metadata = RequestFireAndForgetFrameCodec.metadata(frame);
break;
case REQUEST_STREAM:
metadata.addComponents(true, RequestStreamFrameCodec.metadata(frame).retain());
metadata = RequestStreamFrameCodec.metadata(frame);
break;
case REQUEST_RESPONSE:
metadata.addComponents(true, RequestResponseFrameCodec.metadata(frame).retain());
metadata = RequestResponseFrameCodec.metadata(frame);
break;
case REQUEST_CHANNEL:
metadata.addComponents(true, RequestChannelFrameCodec.metadata(frame).retain());
metadata = RequestChannelFrameCodec.metadata(frame);
break;
// Payload and synthetic types
case PAYLOAD:
case NEXT:
case NEXT_COMPLETE:
case COMPLETE:
metadata.addComponents(true, PayloadFrameCodec.metadata(frame).retain());
metadata = PayloadFrameCodec.metadata(frame);
break;
default:
throw new IllegalStateException("unsupported fragment type");
}
if (metadata != null) {
getMetadata(streamId).addComponents(true, metadata.retain());
}
}

ByteBuf data;
Expand Down Expand Up @@ -276,6 +286,10 @@ void handleFollowsFlag(ByteBuf frame, int streamId, FrameType frameType) {

getData(streamId).addComponents(true, data);
frame.release();

if ((metadata != null && metadata.readableBytes() == 0) && data.readableBytes() == 0) {
throw new IllegalStateException("Empty frame.");
}
}

void reassembleFrame(ByteBuf frame, SynchronousSink<ByteBuf> sink) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ public void cancelBeforeAssembling() {
Assert.assertFalse(reassembler.data.containsKey(1));
}

@ParameterizedTest(name = "throws error if reassembling payload size exist {0}")
@ParameterizedTest(name = "throws error if reassembling payload size exceeds {0}")
@ValueSource(ints = {64, 1024, 2048, 4096})
public void errorTooBigPayload(int maxFrameLength) {
List<ByteBuf> byteBufs =
Expand Down Expand Up @@ -478,6 +478,24 @@ public void errorTooBigPayload(int maxFrameLength) {
.isExactlyInstanceOf(IllegalStateException.class);
}

@DisplayName("throws error on empty fragment")
@Test
public void errorEmptyFrame() {
List<ByteBuf> byteBufs =
Arrays.asList(
RequestResponseFrameCodec.encode(
allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER),
PayloadFrameCodec.encode(
allocator, 1, true, false, true, Unpooled.EMPTY_BUFFER, Unpooled.EMPTY_BUFFER));

FrameReassembler reassembler = new FrameReassembler(allocator, Integer.MAX_VALUE);

Assertions.assertThatThrownBy(
Flux.fromIterable(byteBufs).handle(reassembler::reassembleFrame)::blockLast)
.hasMessage("Empty frame.")
.isExactlyInstanceOf(IllegalStateException.class);
}

@DisplayName("dispose should clean up maps")
@Test
public void dispose() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ void reassembleNonFragmentableFrame() {
.verifyComplete();
}

@ParameterizedTest(name = "throws error if reassembling payload size exist {0}")
@ParameterizedTest(name = "throws error if reassembling payload size exceeds {0}")
@ValueSource(ints = {64, 1024, 2048, 4096})
public void errorTooBigPayload(int maxFrameLength) {
List<ByteBuf> byteBufs =
Expand Down Expand Up @@ -331,4 +331,37 @@ public void errorTooBigPayload(int maxFrameLength) {

allocator.assertHasNoLeaks();
}

@DisplayName("throws error on empty fragment")
@Test
public void errorEmptyFrame() {
List<ByteBuf> byteBufs =
Arrays.asList(
RequestResponseFrameCodec.encode(
allocator, 1, true, Unpooled.wrappedBuffer(metadata), Unpooled.EMPTY_BUFFER),
PayloadFrameCodec.encode(
allocator, 1, true, false, true, Unpooled.EMPTY_BUFFER, Unpooled.EMPTY_BUFFER));

MonoProcessor<Void> onClose = MonoProcessor.create();

when(delegate.receive())
.thenReturn(
Flux.fromIterable(byteBufs)
.doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release));
when(delegate.onClose()).thenReturn(onClose);
when(delegate.alloc()).thenReturn(allocator);

new ReassemblyDuplexConnection(delegate, Integer.MAX_VALUE)
.receive()
.doFinally(__ -> onClose.onComplete())
.as(StepVerifier::create)
.expectErrorSatisfies(
t ->
Assertions.assertThat(t)
.hasMessage("Empty frame.")
.isExactlyInstanceOf(IllegalStateException.class))
.verify(Duration.ofSeconds(1));

allocator.assertHasNoLeaks();
}
}