HTTP2: Guard against empty DATA frames (without end_of_stream flag) set (#9461)
Motivation: It is possible for a remote peer to flood the server / client with empty DATA frames (without end_of_stream flag) set and so cause high CPU usage without the possibility to ever hit a limit. We need to guard against this. See CVE-2019-9518 Modifications: - Add a new config option to AbstractHttp2ConnectionBuilder and sub-classes which allows to set the max number of consecutive empty DATA frames (without end_of_stream flag). After this limit is hit we will close the connection. A limit of 10 is used by default. - Add unit tests Result: Guards against CVE-2019-9518
This commit is contained in:
parent
c6c679597f
commit
6283a78e4f
@ -108,6 +108,7 @@ public abstract class AbstractHttp2ConnectionHandlerBuilder<T extends Http2Conne
|
|||||||
private boolean autoAckSettingsFrame = true;
|
private boolean autoAckSettingsFrame = true;
|
||||||
private boolean autoAckPingFrame = true;
|
private boolean autoAckPingFrame = true;
|
||||||
private int maxQueuedControlFrames = Http2CodecUtil.DEFAULT_MAX_QUEUED_CONTROL_FRAMES;
|
private int maxQueuedControlFrames = Http2CodecUtil.DEFAULT_MAX_QUEUED_CONTROL_FRAMES;
|
||||||
|
private int maxConsecutiveEmptyFrames = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link Http2Settings} to use for the initial connection settings exchange.
|
* Sets the {@link Http2Settings} to use for the initial connection settings exchange.
|
||||||
@ -407,6 +408,31 @@ public abstract class AbstractHttp2ConnectionHandlerBuilder<T extends Http2Conne
|
|||||||
return promisedRequestVerifier;
|
return promisedRequestVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed before
|
||||||
|
* the connection is closed. This allows to protected against the remote peer flooding us with such frames and
|
||||||
|
* so use up a lot of CPU. There is no valid use-case for empty DATA frames without end_of_stream flag.
|
||||||
|
*
|
||||||
|
* {@code 0} means no protection is in place.
|
||||||
|
*/
|
||||||
|
protected int decoderEnforceMaxConsecutiveEmptyDataFrames() {
|
||||||
|
return maxConsecutiveEmptyFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed before
|
||||||
|
* the connection is closed. This allows to protected against the remote peer flooding us with such frames and
|
||||||
|
* so use up a lot of CPU. There is no valid use-case for empty DATA frames without end_of_stream flag.
|
||||||
|
*
|
||||||
|
* {@code 0} means no protection should be applied.
|
||||||
|
*/
|
||||||
|
protected B decoderEnforceMaxConsecutiveEmptyDataFrames(int maxConsecutiveEmptyFrames) {
|
||||||
|
enforceNonCodecConstraints("maxConsecutiveEmptyFrames");
|
||||||
|
this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositiveOrZero(
|
||||||
|
maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames");
|
||||||
|
return self();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if settings frame should automatically be acknowledged and applied.
|
* Determine if settings frame should automatically be acknowledged and applied.
|
||||||
* @return this.
|
* @return this.
|
||||||
@ -515,6 +541,10 @@ public abstract class AbstractHttp2ConnectionHandlerBuilder<T extends Http2Conne
|
|||||||
}
|
}
|
||||||
|
|
||||||
private T buildFromCodec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) {
|
private T buildFromCodec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) {
|
||||||
|
int maxConsecutiveEmptyDataFrames = decoderEnforceMaxConsecutiveEmptyDataFrames();
|
||||||
|
if (maxConsecutiveEmptyDataFrames > 0) {
|
||||||
|
decoder = new Http2EmptyDataFrameConnectionDecoder(decoder, maxConsecutiveEmptyDataFrames);
|
||||||
|
}
|
||||||
final T handler;
|
final T handler;
|
||||||
try {
|
try {
|
||||||
// Call the abstract build method
|
// Call the abstract build method
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.http2;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce a limit on the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed
|
||||||
|
* before the connection will be closed.
|
||||||
|
*/
|
||||||
|
final class Http2EmptyDataFrameConnectionDecoder extends DecoratingHttp2ConnectionDecoder {
|
||||||
|
|
||||||
|
private final int maxConsecutiveEmptyFrames;
|
||||||
|
|
||||||
|
Http2EmptyDataFrameConnectionDecoder(Http2ConnectionDecoder delegate, int maxConsecutiveEmptyFrames) {
|
||||||
|
super(delegate);
|
||||||
|
this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositive(
|
||||||
|
maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void frameListener(Http2FrameListener listener) {
|
||||||
|
if (listener != null) {
|
||||||
|
super.frameListener(new Http2EmptyDataFrameListener(listener, maxConsecutiveEmptyFrames));
|
||||||
|
} else {
|
||||||
|
super.frameListener(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.http2;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce a limit on the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed
|
||||||
|
* before the connection will be closed.
|
||||||
|
*/
|
||||||
|
final class Http2EmptyDataFrameListener extends Http2FrameListenerDecorator {
|
||||||
|
private final int maxConsecutiveEmptyFrames;
|
||||||
|
|
||||||
|
private boolean violationDetected;
|
||||||
|
private int emptyDataFrames;
|
||||||
|
|
||||||
|
Http2EmptyDataFrameListener(Http2FrameListener listener, int maxConsecutiveEmptyFrames) {
|
||||||
|
super(listener);
|
||||||
|
this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositive(
|
||||||
|
maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
|
||||||
|
throws Http2Exception {
|
||||||
|
if (endOfStream || data.isReadable()) {
|
||||||
|
emptyDataFrames = 0;
|
||||||
|
} else if (emptyDataFrames++ == maxConsecutiveEmptyFrames && !violationDetected) {
|
||||||
|
violationDetected = true;
|
||||||
|
throw Http2Exception.connectionError(Http2Error.ENHANCE_YOUR_CALM,
|
||||||
|
"Maximum number %d of empty data frames without end_of_stream flag received",
|
||||||
|
maxConsecutiveEmptyFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onDataRead(ctx, streamId, data, padding, endOfStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
|
||||||
|
int padding, boolean endStream) throws Http2Exception {
|
||||||
|
emptyDataFrames = 0;
|
||||||
|
super.onHeadersRead(ctx, streamId, headers, padding, endStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
|
||||||
|
short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception {
|
||||||
|
emptyDataFrames = 0;
|
||||||
|
super.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream);
|
||||||
|
}
|
||||||
|
}
|
@ -167,6 +167,16 @@ public class Http2FrameCodecBuilder extends
|
|||||||
return super.decoupleCloseAndGoAway(decoupleCloseAndGoAway);
|
return super.decoupleCloseAndGoAway(decoupleCloseAndGoAway);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int decoderEnforceMaxConsecutiveEmptyDataFrames() {
|
||||||
|
return super.decoderEnforceMaxConsecutiveEmptyDataFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Http2FrameCodecBuilder decoderEnforceMaxConsecutiveEmptyDataFrames(int maxConsecutiveEmptyFrames) {
|
||||||
|
return super.decoderEnforceMaxConsecutiveEmptyDataFrames(maxConsecutiveEmptyFrames);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a {@link Http2FrameCodec} object.
|
* Build a {@link Http2FrameCodec} object.
|
||||||
*/
|
*/
|
||||||
@ -192,7 +202,10 @@ public class Http2FrameCodecBuilder extends
|
|||||||
}
|
}
|
||||||
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader,
|
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader,
|
||||||
promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame());
|
promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame());
|
||||||
|
int maxConsecutiveEmptyDataFrames = decoderEnforceMaxConsecutiveEmptyDataFrames();
|
||||||
|
if (maxConsecutiveEmptyDataFrames > 0) {
|
||||||
|
decoder = new Http2EmptyDataFrameConnectionDecoder(decoder, maxConsecutiveEmptyDataFrames);
|
||||||
|
}
|
||||||
return build(decoder, encoder, initialSettings());
|
return build(decoder, encoder, initialSettings());
|
||||||
}
|
}
|
||||||
return super.build();
|
return super.build();
|
||||||
|
@ -196,6 +196,16 @@ public class Http2MultiplexCodecBuilder
|
|||||||
return super.decoupleCloseAndGoAway(decoupleCloseAndGoAway);
|
return super.decoupleCloseAndGoAway(decoupleCloseAndGoAway);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int decoderEnforceMaxConsecutiveEmptyDataFrames() {
|
||||||
|
return super.decoderEnforceMaxConsecutiveEmptyDataFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Http2MultiplexCodecBuilder decoderEnforceMaxConsecutiveEmptyDataFrames(int maxConsecutiveEmptyFrames) {
|
||||||
|
return super.decoderEnforceMaxConsecutiveEmptyDataFrames(maxConsecutiveEmptyFrames);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Http2MultiplexCodec build() {
|
public Http2MultiplexCodec build() {
|
||||||
Http2FrameWriter frameWriter = this.frameWriter;
|
Http2FrameWriter frameWriter = this.frameWriter;
|
||||||
@ -219,6 +229,11 @@ public class Http2MultiplexCodecBuilder
|
|||||||
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader,
|
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader,
|
||||||
promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame());
|
promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame());
|
||||||
|
|
||||||
|
int maxConsecutiveEmptyDataFrames = decoderEnforceMaxConsecutiveEmptyDataFrames();
|
||||||
|
if (maxConsecutiveEmptyDataFrames > 0) {
|
||||||
|
decoder = new Http2EmptyDataFrameConnectionDecoder(decoder, maxConsecutiveEmptyDataFrames);
|
||||||
|
}
|
||||||
|
|
||||||
return build(decoder, encoder, initialSettings());
|
return build(decoder, encoder, initialSettings());
|
||||||
}
|
}
|
||||||
return super.build();
|
return super.build();
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.http2;
|
||||||
|
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class Http2EmptyDataFrameConnectionDecoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecoration() {
|
||||||
|
Http2ConnectionDecoder delegate = mock(Http2ConnectionDecoder.class);
|
||||||
|
final ArgumentCaptor<Http2FrameListener> listenerArgumentCaptor =
|
||||||
|
ArgumentCaptor.forClass(Http2FrameListener.class);
|
||||||
|
when(delegate.frameListener()).then(new Answer<Http2FrameListener>() {
|
||||||
|
@Override
|
||||||
|
public Http2FrameListener answer(InvocationOnMock invocationOnMock) {
|
||||||
|
return listenerArgumentCaptor.getValue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Http2FrameListener listener = mock(Http2FrameListener.class);
|
||||||
|
Http2EmptyDataFrameConnectionDecoder decoder = new Http2EmptyDataFrameConnectionDecoder(delegate, 2);
|
||||||
|
decoder.frameListener(listener);
|
||||||
|
verify(delegate).frameListener(listenerArgumentCaptor.capture());
|
||||||
|
|
||||||
|
assertThat(decoder.frameListener(), CoreMatchers.instanceOf(Http2EmptyDataFrameListener.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecorationWithNull() {
|
||||||
|
Http2ConnectionDecoder delegate = mock(Http2ConnectionDecoder.class);
|
||||||
|
|
||||||
|
Http2EmptyDataFrameConnectionDecoder decoder = new Http2EmptyDataFrameConnectionDecoder(delegate, 2);
|
||||||
|
decoder.frameListener(null);
|
||||||
|
assertNull(decoder.frameListener());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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.http2;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.any;
|
||||||
|
import static org.mockito.Mockito.eq;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
|
||||||
|
public class Http2EmptyDataFrameListenerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Http2FrameListener frameListener;
|
||||||
|
@Mock
|
||||||
|
private ChannelHandlerContext ctx;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ByteBuf nonEmpty;
|
||||||
|
|
||||||
|
private Http2EmptyDataFrameListener listener;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
initMocks(this);
|
||||||
|
when(nonEmpty.isReadable()).thenReturn(true);
|
||||||
|
listener = new Http2EmptyDataFrameListener(frameListener, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyDataFrames() throws Http2Exception {
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
fail();
|
||||||
|
} catch (Http2Exception expected) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
verify(frameListener, times(2)).onDataRead(eq(ctx), eq(1), any(ByteBuf.class), eq(0), eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyDataFramesWithNonEmptyInBetween() throws Http2Exception {
|
||||||
|
Http2EmptyDataFrameListener listener = new Http2EmptyDataFrameListener(frameListener, 2);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, nonEmpty, 0, false);
|
||||||
|
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
fail();
|
||||||
|
} catch (Http2Exception expected) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
verify(frameListener, times(4)).onDataRead(eq(ctx), eq(1), any(ByteBuf.class), eq(0), eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyDataFramesWithEndOfStreamInBetween() throws Http2Exception {
|
||||||
|
Http2EmptyDataFrameListener listener = new Http2EmptyDataFrameListener(frameListener, 2);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, true);
|
||||||
|
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
fail();
|
||||||
|
} catch (Http2Exception expected) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
verify(frameListener, times(1)).onDataRead(eq(ctx), eq(1), any(ByteBuf.class), eq(0), eq(true));
|
||||||
|
verify(frameListener, times(3)).onDataRead(eq(ctx), eq(1), any(ByteBuf.class), eq(0), eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyDataFramesWithHeaderFrameInBetween() throws Http2Exception {
|
||||||
|
Http2EmptyDataFrameListener listener = new Http2EmptyDataFrameListener(frameListener, 2);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onHeadersRead(ctx, 1, EmptyHttp2Headers.INSTANCE, 0, true);
|
||||||
|
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
fail();
|
||||||
|
} catch (Http2Exception expected) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(frameListener, times(1)).onHeadersRead(eq(ctx), eq(1), eq(EmptyHttp2Headers.INSTANCE), eq(0), eq(true));
|
||||||
|
verify(frameListener, times(3)).onDataRead(eq(ctx), eq(1), any(ByteBuf.class), eq(0), eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyDataFramesWithHeaderFrameInBetween2() throws Http2Exception {
|
||||||
|
Http2EmptyDataFrameListener listener = new Http2EmptyDataFrameListener(frameListener, 2);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onHeadersRead(ctx, 1, EmptyHttp2Headers.INSTANCE, 0, (short) 0, false, 0, true);
|
||||||
|
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
listener.onDataRead(ctx, 1, Unpooled.EMPTY_BUFFER, 0, false);
|
||||||
|
fail();
|
||||||
|
} catch (Http2Exception expected) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(frameListener, times(1)).onHeadersRead(eq(ctx), eq(1),
|
||||||
|
eq(EmptyHttp2Headers.INSTANCE), eq(0), eq((short) 0), eq(false), eq(0), eq(true));
|
||||||
|
verify(frameListener, times(3)).onDataRead(eq(ctx), eq(1), any(ByteBuf.class), eq(0), eq(false));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user