|
30 | 30 | import java.io.IOException;
|
31 | 31 | import java.nio.ByteBuffer;
|
32 | 32 | import java.util.ArrayList;
|
| 33 | +import java.util.Arrays; |
33 | 34 | import java.util.List;
|
| 35 | +import java.util.concurrent.locks.Lock; |
34 | 36 |
|
35 | 37 | import org.apache.hc.core5.function.Supplier;
|
36 | 38 | import org.apache.hc.core5.http.Header;
|
|
44 | 46 | import org.apache.hc.core5.http.nio.command.ExecutableCommand;
|
45 | 47 | import org.apache.hc.core5.http.protocol.HttpProcessor;
|
46 | 48 | import org.apache.hc.core5.http2.H2ConnectionException;
|
| 49 | +import org.apache.hc.core5.http2.H2Error; |
| 50 | +import org.apache.hc.core5.http2.H2StreamResetException; |
47 | 51 | import org.apache.hc.core5.http2.WritableByteChannelMock;
|
48 | 52 | import org.apache.hc.core5.http2.config.H2Config;
|
49 | 53 | import org.apache.hc.core5.http2.frame.DefaultFrameFactory;
|
@@ -72,17 +76,22 @@ class TestAbstractH2StreamMultiplexer {
|
72 | 76 | @Mock
|
73 | 77 | ProtocolIOSession protocolIOSession;
|
74 | 78 | @Mock
|
| 79 | + Lock lock; |
| 80 | + @Mock |
75 | 81 | HttpProcessor httpProcessor;
|
76 | 82 | @Mock
|
77 | 83 | H2StreamListener h2StreamListener;
|
78 | 84 | @Mock
|
79 | 85 | H2StreamHandler streamHandler;
|
80 | 86 | @Captor
|
81 | 87 | ArgumentCaptor<List<Header>> headersCaptor;
|
| 88 | + @Captor |
| 89 | + ArgumentCaptor<Exception> exceptionCaptor; |
82 | 90 |
|
83 | 91 | @BeforeEach
|
84 | 92 | void prepareMocks() {
|
85 | 93 | MockitoAnnotations.openMocks(this);
|
| 94 | + Mockito.when(protocolIOSession.getLock()).thenReturn(lock); |
86 | 95 | }
|
87 | 96 |
|
88 | 97 | static class H2StreamMultiplexerImpl extends AbstractH2StreamMultiplexer {
|
@@ -270,5 +279,264 @@ void testInputHeaderContinuationFrame() throws IOException, HttpException {
|
270 | 279 | Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(false));
|
271 | 280 | Assertions.assertFalse(headersCaptor.getValue().isEmpty());
|
272 | 281 | }
|
| 282 | + |
| 283 | + @Test |
| 284 | + void testZeroIncrement() throws Exception { |
| 285 | + final H2Config h2Config = H2Config.custom() |
| 286 | + .build(); |
| 287 | + |
| 288 | + final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( |
| 289 | + protocolIOSession, |
| 290 | + FRAME_FACTORY, |
| 291 | + StreamIdGenerator.EVEN, |
| 292 | + httpProcessor, |
| 293 | + CharCodingConfig.DEFAULT, |
| 294 | + h2Config, |
| 295 | + h2StreamListener, |
| 296 | + () -> streamHandler); |
| 297 | + |
| 298 | + final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); |
| 299 | + final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), |
| 300 | + CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); |
| 301 | + |
| 302 | + final List<Header> headers = Arrays.asList( |
| 303 | + new BasicHeader(":method", "GET"), |
| 304 | + new BasicHeader(":scheme", "http"), |
| 305 | + new BasicHeader(":path", "/"), |
| 306 | + new BasicHeader(":authority", "www.example.com")); |
| 307 | + encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); |
| 308 | + |
| 309 | + final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); |
| 310 | + final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); |
| 311 | + |
| 312 | + final RawFrame headerFrame = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 313 | + outBuffer.write(headerFrame, writableChannel); |
| 314 | + |
| 315 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 316 | + |
| 317 | + Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); |
| 318 | + Assertions.assertFalse(headersCaptor.getValue().isEmpty()); |
| 319 | + |
| 320 | + writableChannel.reset(); |
| 321 | + final ByteBuffer payload = ByteBuffer.allocate(4); |
| 322 | + payload.putInt(0); |
| 323 | + payload.flip(); |
| 324 | + final RawFrame incrementFrame = new RawFrame(FrameType.WINDOW_UPDATE.getValue(), 0, 1, payload); |
| 325 | + outBuffer.write(incrementFrame, writableChannel); |
| 326 | + |
| 327 | + final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> |
| 328 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); |
| 329 | + Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(exception.getCode())); |
| 330 | + } |
| 331 | + |
| 332 | + @Test |
| 333 | + void testIncrementOverflow() throws Exception { |
| 334 | + final H2Config h2Config = H2Config.custom() |
| 335 | + .build(); |
| 336 | + |
| 337 | + final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( |
| 338 | + protocolIOSession, |
| 339 | + FRAME_FACTORY, |
| 340 | + StreamIdGenerator.EVEN, |
| 341 | + httpProcessor, |
| 342 | + CharCodingConfig.DEFAULT, |
| 343 | + h2Config, |
| 344 | + h2StreamListener, |
| 345 | + () -> streamHandler); |
| 346 | + |
| 347 | + final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); |
| 348 | + final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), |
| 349 | + CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); |
| 350 | + |
| 351 | + final List<Header> headers = Arrays.asList( |
| 352 | + new BasicHeader(":method", "GET"), |
| 353 | + new BasicHeader(":scheme", "http"), |
| 354 | + new BasicHeader(":path", "/"), |
| 355 | + new BasicHeader(":authority", "www.example.com")); |
| 356 | + encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); |
| 357 | + |
| 358 | + final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); |
| 359 | + final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); |
| 360 | + |
| 361 | + final RawFrame headerFrame = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 362 | + outBuffer.write(headerFrame, writableChannel); |
| 363 | + |
| 364 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 365 | + |
| 366 | + Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); |
| 367 | + Assertions.assertFalse(headersCaptor.getValue().isEmpty()); |
| 368 | + |
| 369 | + writableChannel.reset(); |
| 370 | + final RawFrame incrementFrame1 = FRAME_FACTORY.createWindowUpdate(1, 100); |
| 371 | + outBuffer.write(incrementFrame1, writableChannel); |
| 372 | + |
| 373 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 374 | + |
| 375 | + writableChannel.reset(); |
| 376 | + final RawFrame incrementFrame2 = FRAME_FACTORY.createWindowUpdate(1, 0x7fffffff - 50); |
| 377 | + outBuffer.write(incrementFrame2, writableChannel); |
| 378 | + final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> |
| 379 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); |
| 380 | + Assertions.assertEquals(H2Error.FLOW_CONTROL_ERROR, H2Error.getByCode(exception.getCode())); |
| 381 | + } |
| 382 | + |
| 383 | + @Test |
| 384 | + void testHeadersAfterEndOfStream() throws Exception { |
| 385 | + final H2Config h2Config = H2Config.custom() |
| 386 | + .build(); |
| 387 | + |
| 388 | + final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( |
| 389 | + protocolIOSession, |
| 390 | + FRAME_FACTORY, |
| 391 | + StreamIdGenerator.EVEN, |
| 392 | + httpProcessor, |
| 393 | + CharCodingConfig.DEFAULT, |
| 394 | + h2Config, |
| 395 | + h2StreamListener, |
| 396 | + () -> streamHandler); |
| 397 | + |
| 398 | + final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); |
| 399 | + final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), |
| 400 | + CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); |
| 401 | + |
| 402 | + final List<Header> headers = Arrays.asList( |
| 403 | + new BasicHeader(":method", "GET"), |
| 404 | + new BasicHeader(":scheme", "http"), |
| 405 | + new BasicHeader(":path", "/"), |
| 406 | + new BasicHeader(":authority", "www.example.com")); |
| 407 | + encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); |
| 408 | + |
| 409 | + final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); |
| 410 | + final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); |
| 411 | + |
| 412 | + final RawFrame headerFrame1 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 413 | + outBuffer.write(headerFrame1, writableChannel); |
| 414 | + |
| 415 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 416 | + |
| 417 | + Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); |
| 418 | + Assertions.assertFalse(headersCaptor.getValue().isEmpty()); |
| 419 | + |
| 420 | + writableChannel.reset(); |
| 421 | + final RawFrame headerFrame2 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 422 | + outBuffer.write(headerFrame2, writableChannel); |
| 423 | + |
| 424 | + // Treat the first occurrence as a stream error |
| 425 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 426 | + Mockito.verify(streamHandler).failed(exceptionCaptor.capture()); |
| 427 | + Assertions.assertInstanceOf(H2StreamResetException.class, exceptionCaptor.getValue()); |
| 428 | + |
| 429 | + writableChannel.reset(); |
| 430 | + final RawFrame headerFrame3 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 431 | + outBuffer.write(headerFrame3, writableChannel); |
| 432 | + |
| 433 | + // Treat subsequent occurrences as a connection-wide protocol error |
| 434 | + final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> |
| 435 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); |
| 436 | + Assertions.assertEquals(H2Error.STREAM_CLOSED, H2Error.getByCode(exception.getCode())); |
| 437 | + } |
| 438 | + |
| 439 | + @Test |
| 440 | + void testDataAfterEndOfStream() throws Exception { |
| 441 | + final H2Config h2Config = H2Config.custom() |
| 442 | + .build(); |
| 443 | + |
| 444 | + final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( |
| 445 | + protocolIOSession, |
| 446 | + FRAME_FACTORY, |
| 447 | + StreamIdGenerator.EVEN, |
| 448 | + httpProcessor, |
| 449 | + CharCodingConfig.DEFAULT, |
| 450 | + h2Config, |
| 451 | + h2StreamListener, |
| 452 | + () -> streamHandler); |
| 453 | + |
| 454 | + final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); |
| 455 | + final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), |
| 456 | + CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); |
| 457 | + |
| 458 | + final List<Header> headers = Arrays.asList( |
| 459 | + new BasicHeader(":method", "GET"), |
| 460 | + new BasicHeader(":scheme", "http"), |
| 461 | + new BasicHeader(":path", "/"), |
| 462 | + new BasicHeader(":authority", "www.example.com")); |
| 463 | + encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); |
| 464 | + |
| 465 | + final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); |
| 466 | + final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); |
| 467 | + |
| 468 | + final RawFrame headerFrame1 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 469 | + outBuffer.write(headerFrame1, writableChannel); |
| 470 | + |
| 471 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 472 | + |
| 473 | + Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); |
| 474 | + Assertions.assertFalse(headersCaptor.getValue().isEmpty()); |
| 475 | + |
| 476 | + writableChannel.reset(); |
| 477 | + final RawFrame dataFrame1 = FRAME_FACTORY.createData(1, ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}), true); |
| 478 | + outBuffer.write(dataFrame1, writableChannel); |
| 479 | + |
| 480 | + // Treat the first occurrence as a stream error |
| 481 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 482 | + Mockito.verify(streamHandler).failed(exceptionCaptor.capture()); |
| 483 | + Assertions.assertInstanceOf(H2StreamResetException.class, exceptionCaptor.getValue()); |
| 484 | + |
| 485 | + writableChannel.reset(); |
| 486 | + final RawFrame dataFrame2 = FRAME_FACTORY.createData(1, ByteBuffer.wrap(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}), true); |
| 487 | + outBuffer.write(dataFrame2, writableChannel); |
| 488 | + |
| 489 | + // Treat subsequent occurrences as a connection-wide protocol error |
| 490 | + final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> |
| 491 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); |
| 492 | + Assertions.assertEquals(H2Error.STREAM_CLOSED, H2Error.getByCode(exception.getCode())); |
| 493 | + } |
| 494 | + |
| 495 | + @Test |
| 496 | + void testContinuationAfterEndOfStream() throws Exception { |
| 497 | + final H2Config h2Config = H2Config.custom() |
| 498 | + .build(); |
| 499 | + |
| 500 | + final AbstractH2StreamMultiplexer streamMultiplexer = new H2StreamMultiplexerImpl( |
| 501 | + protocolIOSession, |
| 502 | + FRAME_FACTORY, |
| 503 | + StreamIdGenerator.EVEN, |
| 504 | + httpProcessor, |
| 505 | + CharCodingConfig.DEFAULT, |
| 506 | + h2Config, |
| 507 | + h2StreamListener, |
| 508 | + () -> streamHandler); |
| 509 | + |
| 510 | + final ByteArrayBuffer headerBuf = new ByteArrayBuffer(200); |
| 511 | + final HPackEncoder encoder = new HPackEncoder(h2Config.getHeaderTableSize(), |
| 512 | + CharCodingSupport.createEncoder(CharCodingConfig.DEFAULT)); |
| 513 | + |
| 514 | + final List<Header> headers = Arrays.asList( |
| 515 | + new BasicHeader(":method", "GET"), |
| 516 | + new BasicHeader(":scheme", "http"), |
| 517 | + new BasicHeader(":path", "/"), |
| 518 | + new BasicHeader(":authority", "www.example.com")); |
| 519 | + encoder.encodeHeaders(headerBuf, headers, h2Config.isCompressionEnabled()); |
| 520 | + |
| 521 | + final WritableByteChannelMock writableChannel = new WritableByteChannelMock(1024); |
| 522 | + final FrameOutputBuffer outBuffer = new FrameOutputBuffer(16 * 1024); |
| 523 | + |
| 524 | + final RawFrame headerFrame1 = FRAME_FACTORY.createHeaders(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true, true); |
| 525 | + outBuffer.write(headerFrame1, writableChannel); |
| 526 | + |
| 527 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray())); |
| 528 | + |
| 529 | + Mockito.verify(streamHandler).consumeHeader(headersCaptor.capture(), ArgumentMatchers.eq(true)); |
| 530 | + Assertions.assertFalse(headersCaptor.getValue().isEmpty()); |
| 531 | + |
| 532 | + writableChannel.reset(); |
| 533 | + final RawFrame continuationFrame = FRAME_FACTORY.createContinuation(1, ByteBuffer.wrap(headerBuf.array(), 0, headerBuf.length()), true); |
| 534 | + outBuffer.write(continuationFrame, writableChannel); |
| 535 | + |
| 536 | + final H2ConnectionException exception = Assertions.assertThrows(H2ConnectionException.class, () -> |
| 537 | + streamMultiplexer.onInput(ByteBuffer.wrap(writableChannel.toByteArray()))); |
| 538 | + Assertions.assertEquals(H2Error.PROTOCOL_ERROR, H2Error.getByCode(exception.getCode())); |
| 539 | + } |
| 540 | + |
273 | 541 | }
|
274 | 542 |
|
0 commit comments