| /** @file |
| * |
| * A brief file description |
| * |
| * @section license License |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF 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. |
| */ |
| |
| #include <catch2/catch_test_macros.hpp> |
| |
| #include "proxy/http3/Http3FrameDispatcher.h" |
| #include "proxy/http3/Http3ProtocolEnforcer.h" |
| #include "Mock.h" |
| |
| TEST_CASE("Http3FrameHandler dispatch", "[http3]") |
| { |
| Http3FrameDispatcher http3FrameDispatcher; |
| Http3MockFrameHandler handler; |
| Http3ProtocolEnforcer enforcer; |
| http3FrameDispatcher.add_handler(&handler); |
| http3FrameDispatcher.add_handler(&enforcer); |
| |
| MIOBuffer *buf = new_MIOBuffer(BUFFER_SIZE_INDEX_512); |
| IOBufferReader *reader = buf->alloc_reader(); |
| uint64_t nread = 0; |
| Http3ErrorUPtr error = Http3ErrorUPtr(nullptr); |
| |
| SECTION("Test good case") |
| { |
| uint8_t input[] = {// 1st frame (HEADERS) |
| 0x01, 0x04, 0x11, 0x22, 0x33, 0x44, |
| // 2nd frame (DATA) |
| 0x00, 0x04, 0xaa, 0xbb, 0xcc, 0xdd, |
| // 3rd frame (incomplete) |
| 0xff}; |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread); |
| CHECK(!error); |
| CHECK(handler.total_frame_received == 1); |
| CHECK(nread == 12); |
| } |
| |
| SECTION("Test good case with a multibyte frame type encoding") |
| { |
| uint8_t input[] = {// 1st frame (HEADERS) |
| 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x11, 0x22, 0x33, 0x44, |
| // 2nd frame (DATA) |
| 0x00, 0x04, 0xaa, 0xbb, 0xcc, 0xdd, |
| // 3rd frame (incomplete) |
| 0xff}; |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| SECTION("Write everything at once") |
| { |
| buf->write(input, sizeof(input)); |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread); |
| CHECK(!error); |
| CHECK(handler.total_frame_received == 1); |
| CHECK(nread == 19); |
| } |
| |
| SECTION("Write one byte at a time") |
| { |
| int total_nread{}; |
| for (uint8_t *it{input}; it < input + sizeof(input); ++it) { |
| buf->write(it, 1); |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread); |
| total_nread += nread; |
| CHECK(!error); |
| } |
| CHECK(handler.total_frame_received == 5); |
| CHECK(total_nread == 19); |
| } |
| } |
| |
| free_MIOBuffer(buf); |
| } |
| |
| TEST_CASE("control stream tests", "[http3]") |
| { |
| Http3FrameDispatcher http3FrameDispatcher; |
| Http3ProtocolEnforcer enforcer; |
| Http3MockFrameHandler handler; |
| |
| http3FrameDispatcher.add_handler(&enforcer); |
| http3FrameDispatcher.add_handler(&handler); |
| |
| MIOBuffer *buf = new_MIOBuffer(BUFFER_SIZE_INDEX_512); |
| IOBufferReader *reader = buf->alloc_reader(); |
| uint64_t nread = 0; |
| Http3ErrorUPtr error = Http3ErrorUPtr(nullptr); |
| |
| SECTION("Only one SETTINGS frame is allowed per the control stream") |
| { |
| uint8_t input[] = { |
| 0x04, // Type |
| 0x08, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| 0x09, // Identifier |
| 0x0f, // Value |
| 0x4a, 0x0a, // Identifier |
| 0x00, // Value |
| 0x04, // Type |
| 0x08, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| 0x09, // Identifier |
| 0x0f, // Value |
| 0x4a, 0x0a, // Identifier |
| 0x00, // Value |
| }; |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::CONTROL, *reader, nread); |
| REQUIRE(error); |
| CHECK(error->code == Http3ErrorCode::H3_FRAME_UNEXPECTED); |
| CHECK(handler.total_frame_received == 1); |
| CHECK(nread == sizeof(input)); |
| } |
| |
| SECTION("first frame of the control stream must be SETTINGS frame") |
| { |
| uint8_t input[] = { |
| 0x0d, // Type |
| 0x01, // Length |
| 0x01, // Push ID |
| 0x04, // Type |
| 0x08, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| 0x09, // Identifier |
| 0x0f, // Value |
| 0x4a, 0x0a, // Identifier |
| 0x00, // Value |
| }; |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::CONTROL, *reader, nread); |
| REQUIRE(error); |
| CHECK(error->code == Http3ErrorCode::H3_MISSING_SETTINGS); |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 3); |
| } |
| |
| SECTION("DATA frame is not allowed on control stream") |
| { |
| uint8_t input[] = {0x04, // Type |
| 0x08, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| 0x09, // Identifier |
| 0x0f, // Value |
| 0x4a, 0x0a, // Identifier |
| 0x00, // Value |
| 0x00, // Type |
| 0x04, // Length |
| 0x11, 0x22, 0x33, 0x44}; |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::CONTROL, *reader, nread); |
| REQUIRE(error); |
| CHECK(error->code == Http3ErrorCode::H3_FRAME_UNEXPECTED); |
| CHECK(handler.total_frame_received == 1); |
| CHECK(nread == sizeof(input)); |
| } |
| |
| SECTION("HEADERS frame is not allowed on control stream") |
| { |
| uint8_t input[] = {0x04, // Type |
| 0x08, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| 0x09, // Identifier |
| 0x0f, // Value |
| 0x4a, 0x0a, // Identifier |
| 0x00, // Value |
| 0x01, // Type |
| 0x04, // Length |
| 0x11, 0x22, 0x33, 0x44}; |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::CONTROL, *reader, nread); |
| REQUIRE(error); |
| CHECK(error->code == Http3ErrorCode::H3_FRAME_UNEXPECTED); |
| CHECK(handler.total_frame_received == 1); |
| CHECK(nread == sizeof(input)); |
| } |
| |
| SECTION("RESERVED frame is not allowed on control stream") |
| { |
| uint8_t input[] = {0x04, // Type |
| 0x08, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| 0x09, // Identifier |
| 0x0f, // Value |
| 0x4a, 0x0a, // Identifier |
| 0x00, // Value |
| 0x06, // Type |
| 0x04, // Length |
| 0x11, 0x22, 0x33, 0x44}; |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::CONTROL, *reader, nread); |
| REQUIRE(error); |
| CHECK(error->code == Http3ErrorCode::H3_FRAME_UNEXPECTED); |
| CHECK(handler.total_frame_received == 1); |
| CHECK(nread == sizeof(input)); |
| } |
| |
| SECTION("padding should not be interpreted as a DATA frame", "[http3]") |
| { |
| uint8_t input[] = { |
| 0x40, 0x04, // Type |
| 0x03, // Length |
| 0x06, // Identifier |
| 0x44, 0x00, // Value |
| }; |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| int total_nread{}; |
| for (uint8_t *it{input}; it < input + sizeof(input); ++it) { |
| buf->write(it, 1); |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::CONTROL, *reader, nread); |
| total_nread += nread; |
| CHECK(!error); |
| } |
| |
| CHECK(handler.total_frame_received == 1); |
| CHECK(total_nread == 6); |
| } |
| |
| free_MIOBuffer(buf); |
| } |
| |
| TEST_CASE("ignore unknown frames", "[http3]") |
| { |
| SECTION("ignore unkown frame") |
| { |
| uint8_t input[] = { |
| 0x0f // Type |
| }; |
| |
| Http3FrameDispatcher http3FrameDispatcher; |
| |
| MIOBuffer *buf = new_MIOBuffer(BUFFER_SIZE_INDEX_512); |
| IOBufferReader *reader = buf->alloc_reader(); |
| uint64_t nread = 0; |
| Http3ErrorUPtr error = Http3ErrorUPtr(nullptr); |
| |
| buf->write(input, sizeof(input)); |
| |
| CHECK(nread == 0); |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread); |
| CHECK(!error); |
| CHECK(nread == 0); |
| free_MIOBuffer(buf); |
| } |
| } |
| |
| TEST_CASE("Reserved frame type not allowed", "[http3]") |
| { |
| SECTION("Reject reserved frame type in non control stream") |
| { |
| uint8_t input[] = {// 1st frame (HEADERS) |
| 0x01, 0x04, 0x11, 0x22, 0x33, 0x44, |
| // 2nd frame (DATA) |
| 0x06, 0x04, 0xaa, 0xbb, 0xcc, 0xdd, |
| // 3rd frame (incomplete) |
| 0xff}; |
| |
| Http3FrameDispatcher http3FrameDispatcher; |
| Http3MockFrameHandler handler; |
| Http3ProtocolEnforcer enforcer; |
| http3FrameDispatcher.add_handler(&handler); |
| http3FrameDispatcher.add_handler(&enforcer); |
| |
| MIOBuffer *buf = new_MIOBuffer(BUFFER_SIZE_INDEX_512); |
| IOBufferReader *reader = buf->alloc_reader(); |
| uint64_t nread = 0; |
| Http3ErrorUPtr error = Http3ErrorUPtr(nullptr); |
| |
| buf->write(input, sizeof(input)); |
| |
| // Initial state |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 0); |
| |
| error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread); |
| REQUIRE(error); |
| CHECK(error->code == Http3ErrorCode::H3_FRAME_UNEXPECTED); |
| CHECK(handler.total_frame_received == 0); |
| CHECK(nread == 12); |
| free_MIOBuffer(buf); |
| } |
| } |