| /** @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 <cstdio> | |
| #include "proxy/http3/Http3Frame.h" | |
| #include "proxy/http3/Http3FrameDispatcher.h" | |
| #include "proxy/http3/Http3SettingsHandler.h" | |
| TEST_CASE("Http3Frame Type", "[http3]") | |
| { | |
| CHECK(Http3Frame::type(reinterpret_cast<const uint8_t *>("\x00\x00"), 2) == Http3FrameType::DATA); | |
| // Undefined range | |
| CHECK(Http3Frame::type(reinterpret_cast<const uint8_t *>("\x0f\x00"), 2) == Http3FrameType::UNKNOWN); | |
| CHECK(Http3Frame::type(reinterpret_cast<const uint8_t *>("\xff\xff\xff\xff\xff\xff\xff\x00"), 9) == Http3FrameType::UNKNOWN); | |
| } | |
| TEST_CASE("Load DATA Frame", "[http3]") | |
| { | |
| SECTION("Normal") | |
| { | |
| uint8_t buf1[] = { | |
| 0x00, // Type | |
| 0x04, // Length | |
| 0x11, 0x22, 0x33, 0x44, // Payload | |
| }; | |
| MIOBuffer *input = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input->write(buf1, sizeof(buf1)); | |
| IOBufferReader *input_reader = input->alloc_reader(); | |
| std::shared_ptr<Http3Frame> frame1 = Http3FrameFactory::create(*input_reader); | |
| frame1->update(); | |
| CHECK(frame1->type() == Http3FrameType::DATA); | |
| CHECK(frame1->length() == 4); | |
| std::shared_ptr<Http3DataFrame> data_frame = std::dynamic_pointer_cast<Http3DataFrame>(frame1); | |
| CHECK(data_frame); | |
| CHECK(data_frame->payload_length() == 4); | |
| IOBufferReader *data_reader = data_frame->data(); | |
| CHECK(data_reader->read_avail() == 4); | |
| CHECK(memcmp(data_reader->start(), "\x11\x22\x33\x44", 4) == 0); | |
| // ~Http3Frame deallocates the reader through its MIOBuffer, so release the | |
| // frames before freeing that buffer. | |
| data_frame.reset(); | |
| frame1.reset(); | |
| free_MIOBuffer(input); | |
| } | |
| } | |
| TEST_CASE("Store DATA Frame", "[http3]") | |
| { | |
| SECTION("Normal") | |
| { | |
| uint8_t buf[32] = {0}; | |
| size_t len; | |
| uint8_t expected1[] = { | |
| 0x00, // Type | |
| 0x04, // Length | |
| 0x11, 0x22, 0x33, 0x44, // Payload | |
| }; | |
| uint8_t raw1[] = "\x11\x22\x33\x44"; | |
| MIOBuffer *payload1 = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); | |
| payload1->set(raw1, 4); | |
| IOBufferReader *payload1_reader = payload1->alloc_reader(); | |
| Http3DataFrame data_frame(*payload1_reader, 4); | |
| free_MIOBuffer(payload1); | |
| CHECK(data_frame.length() == 4); | |
| auto ibb = data_frame.to_io_buffer_block(); | |
| IOBufferReader reader; | |
| reader.block = ibb.get(); | |
| len = reader.read_avail(); | |
| reader.read(buf, sizeof(buf)); | |
| CHECK(len == 6); | |
| CHECK(memcmp(buf, expected1, len) == 0); | |
| } | |
| } | |
| TEST_CASE("Store HEADERS Frame", "[http3]") | |
| { | |
| SECTION("From reader, via factory") | |
| { | |
| uint8_t buf[32] = {0}; | |
| size_t len; | |
| uint8_t expected1[] = { | |
| 0x01, // Type | |
| 0x04, // Length | |
| 0x11, 0x22, 0x33, 0x44, // Payload | |
| }; | |
| uint8_t raw1[] = "\x11\x22\x33\x44"; | |
| MIOBuffer *header_block = new_MIOBuffer(BUFFER_SIZE_INDEX_8K); | |
| header_block->set(raw1, 4); | |
| IOBufferReader *header_block_reader = header_block->alloc_reader(); | |
| Http3HeadersFrameUPtr frame = Http3FrameFactory::create_headers_frame(header_block_reader, 4); | |
| CHECK(frame->length() == 4); | |
| // The factory must consume the caller's reader (needed so a header block sent across | |
| // multiple generate_frame() calls advances instead of re-reading the same bytes). | |
| CHECK(header_block_reader->read_avail() == 0); | |
| // The frame must still serialize correctly even though the original reader was already | |
| // consumed above -- it holds its own independent clone of the reader. | |
| auto ibb = frame->to_io_buffer_block(); | |
| IOBufferReader reader; | |
| reader.block = ibb.get(); | |
| len = reader.read_avail(); | |
| reader.read(buf, sizeof(buf)); | |
| CHECK(len == 6); | |
| CHECK(memcmp(buf, expected1, len) == 0); | |
| // The frame holds a reader cloned from header_block; it must be torn down before the | |
| // source MIOBuffer is freed, or its destructor dealloc()s a reader into freed memory. | |
| frame.reset(); | |
| free_MIOBuffer(header_block); | |
| } | |
| } | |
| TEST_CASE("Load SETTINGS Frame", "[http3]") | |
| { | |
| SECTION("Normal") | |
| { | |
| uint8_t buf[] = { | |
| 0x04, // Type | |
| 0x08, // Length | |
| 0x06, // Identifier | |
| 0x44, 0x00, // Value | |
| 0x09, // Identifier | |
| 0x0f, // Value | |
| 0x4a, 0xba, // Identifier | |
| 0x00, // Value | |
| }; | |
| MIOBuffer *input = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input->write(buf, sizeof(buf)); | |
| IOBufferReader *input_reader = input->alloc_reader(); | |
| std::shared_ptr<Http3Frame> frame = Http3FrameFactory::create(*input_reader); | |
| frame->update(); | |
| CHECK(frame->type() == Http3FrameType::SETTINGS); | |
| CHECK(frame->length() == sizeof(buf) - 2); | |
| std::shared_ptr<Http3SettingsFrame> settings_frame = std::dynamic_pointer_cast<Http3SettingsFrame>(frame); | |
| CHECK(settings_frame); | |
| CHECK(settings_frame->is_valid()); | |
| CHECK(settings_frame->get(Http3SettingsId::MAX_FIELD_SECTION_SIZE) == 0x0400); | |
| CHECK(settings_frame->get(Http3SettingsId::NUM_PLACEHOLDERS) == 0x0f); | |
| // ~Http3Frame deallocates the reader through its MIOBuffer, so release the | |
| // frames before freeing that buffer. | |
| settings_frame.reset(); | |
| frame.reset(); | |
| free_MIOBuffer(input); | |
| } | |
| } | |
| // A SETTINGS identifier is a full QUIC variable-length integer, and an | |
| // identifier the implementation does not understand must be ignored | |
| // (RFC 9114, Section 7.2.4). Comparing a narrowed copy of the decoded | |
| // identifier against the known-ID set lets an unknown identifier alias a | |
| // known one. | |
| // | |
| // Identifiers of the form 0x1f * N + 0x21 are reserved to exercise that | |
| // requirement, and endpoints are expected to send one (RFC 9114, Section | |
| // 7.2.4.1). 0x1f * 33824 + 0x21 == 0x100001, which shares its low 16 bits | |
| // with HEADER_TABLE_SIZE (0x01). | |
| TEST_CASE("Load SETTINGS Frame ignores reserved identifier", "[http3]") | |
| { | |
| uint8_t buf[] = { | |
| 0x04, // Type | |
| 0x05, // Length | |
| 0x80, 0x10, 0x00, 0x01, // Identifier: QUIC varint encoding of 0x100001 | |
| 0x2a, // Value | |
| }; | |
| MIOBuffer *input = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input->write(buf, sizeof(buf)); | |
| IOBufferReader *input_reader = input->alloc_reader(); | |
| std::shared_ptr<Http3Frame> frame = Http3FrameFactory::create(*input_reader); | |
| frame->update(); | |
| REQUIRE(frame->type() == Http3FrameType::SETTINGS); | |
| std::shared_ptr<Http3SettingsFrame> settings_frame = std::dynamic_pointer_cast<Http3SettingsFrame>(frame); | |
| REQUIRE(settings_frame); | |
| REQUIRE(settings_frame->is_valid()); | |
| CHECK_FALSE(settings_frame->contains(Http3SettingsId::HEADER_TABLE_SIZE)); | |
| // ~Http3Frame deallocates the reader through its MIOBuffer, so release the | |
| // frames before freeing that buffer. | |
| settings_frame.reset(); | |
| frame.reset(); | |
| free_MIOBuffer(input); | |
| } | |
| TEST_CASE("Store SETTINGS Frame", "[http3]") | |
| { | |
| SECTION("Normal") | |
| { | |
| uint8_t expected[] = { | |
| 0x04, // Type | |
| 0x08, // Length | |
| 0x06, // Identifier | |
| 0x44, 0x00, // Value | |
| 0x09, // Identifier | |
| 0x0f, // Value | |
| 0x4a, 0x0a, // Identifier | |
| 0x00, // Value | |
| }; | |
| Http3SettingsFrame settings_frame; | |
| settings_frame.set(Http3SettingsId::MAX_FIELD_SECTION_SIZE, 0x0400); | |
| settings_frame.set(Http3SettingsId::NUM_PLACEHOLDERS, 0x0f); | |
| uint8_t buf[32] = {0}; | |
| size_t len; | |
| auto ibb = settings_frame.to_io_buffer_block(); | |
| IOBufferReader reader; | |
| reader.block = ibb.get(); | |
| len = reader.read_avail(); | |
| reader.read(buf, sizeof(buf)); | |
| CHECK(len == sizeof(expected)); | |
| CHECK(memcmp(buf, expected, len) == 0); | |
| } | |
| SECTION("Normal from Client") | |
| { | |
| uint8_t expected[] = { | |
| 0x04, // Type | |
| 0x06, // Length | |
| 0x06, // Identifier | |
| 0x44, 0x00, // Value | |
| 0x4a, 0x0a, // Identifier | |
| 0x00, // Value | |
| }; | |
| Http3SettingsFrame settings_frame; | |
| settings_frame.set(Http3SettingsId::MAX_FIELD_SECTION_SIZE, 0x0400); | |
| uint8_t buf[32] = {0}; | |
| size_t len; | |
| auto ibb = settings_frame.to_io_buffer_block(); | |
| IOBufferReader reader; | |
| reader.block = ibb.get(); | |
| len = reader.read_avail(); | |
| reader.read(buf, sizeof(buf)); | |
| CHECK(len == sizeof(expected)); | |
| CHECK(memcmp(buf, expected, len) == 0); | |
| } | |
| } | |
| TEST_CASE("Http3FrameFactory Create Unknown Frame", "[http3]") | |
| { | |
| uint8_t buf1[] = { | |
| 0x0f, // Type | |
| 0x00, // Length | |
| }; | |
| MIOBuffer *input = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input->write(buf1, sizeof(buf1)); | |
| IOBufferReader *input_reader = input->alloc_reader(); | |
| std::shared_ptr<const Http3Frame> frame1 = Http3FrameFactory::create(*input_reader); | |
| CHECK(frame1); | |
| CHECK(frame1->type() == Http3FrameType::UNKNOWN); | |
| CHECK(frame1->length() == 0); | |
| free_MIOBuffer(input); | |
| } | |
| TEST_CASE("Http3FrameFactory Fast Create Frame", "[http3]") | |
| { | |
| Http3FrameFactory factory; | |
| uint8_t buf1[] = { | |
| 0x00, // Type | |
| 0x04, // Length | |
| 0x11, 0x22, 0x33, 0x44, // Payload | |
| }; | |
| uint8_t buf2[] = { | |
| 0x00, // Type | |
| 0x04, // Length | |
| 0xaa, 0xbb, 0xcc, 0xdd, // Payload | |
| }; | |
| MIOBuffer *input1 = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input1->write(buf1, sizeof(buf1)); | |
| IOBufferReader *input_reader1 = input1->alloc_reader(); | |
| MIOBuffer *input2 = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input2->write(buf2, sizeof(buf2)); | |
| IOBufferReader *input_reader2 = input2->alloc_reader(); | |
| std::shared_ptr<const Http3Frame> frame1 = factory.fast_create(*input_reader1); | |
| CHECK(frame1 != nullptr); | |
| std::shared_ptr<const Http3DataFrame> data_frame1 = std::dynamic_pointer_cast<const Http3DataFrame>(frame1); | |
| CHECK(data_frame1 != nullptr); | |
| CHECK(memcmp(data_frame1->data()->start(), buf1 + 2, 4) == 0); | |
| std::shared_ptr<const Http3Frame> frame2 = factory.fast_create(*input_reader2); | |
| CHECK(frame2 != nullptr); | |
| std::shared_ptr<const Http3DataFrame> data_frame2 = std::dynamic_pointer_cast<const Http3DataFrame>(frame2); | |
| CHECK(data_frame2 != nullptr); | |
| CHECK(memcmp(data_frame2->data()->start(), buf2 + 2, 4) == 0); | |
| CHECK(frame1 == frame2); | |
| free_MIOBuffer(input1); | |
| free_MIOBuffer(input2); | |
| } | |
| TEST_CASE("Http3FrameFactory Fast Create Unknown Frame", "[http3]") | |
| { | |
| Http3FrameFactory factory; | |
| uint8_t buf1[] = { | |
| 0x0f, // Type | |
| }; | |
| MIOBuffer *input = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input->write(buf1, sizeof(buf1)); | |
| IOBufferReader *input_reader = input->alloc_reader(); | |
| std::shared_ptr<const Http3Frame> frame1 = factory.fast_create(*input_reader); | |
| CHECK(frame1); | |
| CHECK(frame1->type() == Http3FrameType::UNKNOWN); | |
| free_MIOBuffer(input); | |
| } | |
| TEST_CASE("SETTINGS frame handler", "[http3]") | |
| { | |
| uint8_t buf1[] = { | |
| 0x04, // Type | |
| 0x08, // Length | |
| 0x06, // Identifier | |
| 0x44, 0x00, // Value | |
| 0x09, // Identifier | |
| 0x0f, // Value | |
| 0x4a, 0x0a, // Identifier | |
| 0x00, // Value | |
| }; | |
| MIOBuffer *input = new_MIOBuffer(BUFFER_SIZE_INDEX_128); | |
| input->write(buf1, sizeof(buf1)); | |
| IOBufferReader *input_reader = input->alloc_reader(); | |
| Http3SettingsHandler handler = Http3SettingsHandler(nullptr); | |
| Http3SettingsFrame invalid_frame = Http3SettingsFrame(*input_reader, 1); | |
| Http3ErrorUPtr error = Http3ErrorUPtr(nullptr); | |
| invalid_frame.update(); | |
| CHECK(invalid_frame.is_valid() == false); | |
| std::shared_ptr<const Http3Frame> invalid_frame_ptr = std::make_shared<const Http3SettingsFrame>(invalid_frame); | |
| error = handler.handle_frame(invalid_frame_ptr); | |
| REQUIRE(error); | |
| CHECK(error->code == Http3ErrorCode::H3_EXCESSIVE_LOAD); | |
| input->reset(); | |
| input->write(buf1, sizeof(buf1)); | |
| Http3SettingsFrame valid_frame = Http3SettingsFrame(*input_reader, 3); | |
| CHECK(valid_frame.is_valid() == true); | |
| std::shared_ptr<const Http3Frame> valid_frame_ptr = std::make_shared<const Http3SettingsFrame>(valid_frame); | |
| error = handler.handle_frame(valid_frame_ptr); | |
| CHECK(error == nullptr); | |
| free_MIOBuffer(input); | |
| } |