#1 - quicr module

This commit is contained in:
Martin Slachta
2026-07-22 17:34:44 +02:00
parent a04f0dc262
commit e6dd954ded
149 changed files with 3997 additions and 2126 deletions
+452
View File
@@ -0,0 +1,452 @@
#include "catch2/catch_test_macros.hpp"
#include "Address.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
#include "protocol/quicr/QuicrConnectionListener.hpp"
#include "protocol/quicr/QuicrEncoder.hpp"
#include "protocol/quicr/QuicrReliability.hpp"
#include <barrier>
#include <span>
using namespace tw::net::quicr;
TEST_CASE("Client begins with Hello datagram", "[quicr2]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
connection.send_initial_hello();
// should contain only the hello frame
REQUIRE(connection.has_next_datagram() == true);
auto buffer = connection.pop_datagram();
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
REQUIRE(header.type == QuicrPacketType::Initial);
REQUIRE(header.destination_id == connection.peer_id());
REQUIRE(header.local_id == connection.self_id());
REQUIRE(header.frames.size() == 1);
REQUIRE(header.frames[0].type == FrameType::Hello);
REQUIRE(connection.has_next_datagram() == false);
}
TEST_CASE("Client wants to resend the Hello", "[quicr2]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
connection.send_initial_hello();
// should contain only the hello frame
REQUIRE(connection.has_next_datagram() == true);
auto buffer = connection.pop_datagram();
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
REQUIRE(connection.has_next_datagram() == false);
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
REQUIRE(connection.has_next_datagram() == true);
buffer = connection.pop_datagram();
header = QuicrDecoder::decode_packet(buffer);
REQUIRE(header.type == QuicrPacketType::Initial);
REQUIRE(header.destination_id == connection.peer_id());
REQUIRE(header.local_id == connection.self_id());
REQUIRE(header.frames.size() == 1);
REQUIRE(header.frames[0].type == FrameType::Hello);
REQUIRE(connection.has_next_datagram() == false);
}
TEST_CASE("Closed connection will setup connection IDs after Hello", "[quicr2]") {
}
TEST_CASE("Connection reacts to Hello with ACK & Hello", "[quicr2]") {
QuicrConnection client(0, 0, tw::net::Address({}), nullptr);
client.send_initial_hello();
auto hello = client.pop_datagram();
QuicrConnection server(0, 0, tw::net::Address({}), nullptr);
server.process_datagram(hello);
REQUIRE(server.has_next_datagram() == true);
auto dgram = server.pop_datagram();
QuicrPacket packet = QuicrDecoder::decode_packet(dgram);
REQUIRE(packet.type == QuicrPacketType::Initial);
REQUIRE(packet.destination_id == server.peer_id());
REQUIRE(packet.local_id == server.self_id());
REQUIRE(packet.frames.size() == 2);
REQUIRE(std::any_of(packet.frames.begin(), packet.frames.end(), [](const QuicrFrame& f) { return f.type == FrameType::Ack; }));
REQUIRE(std::any_of(packet.frames.begin(), packet.frames.end(), [](const QuicrFrame& f) { return f.type == FrameType::Hello; }));
}
TEST_CASE("Both connections have correct IDs after Initial exchange", "[quicr2]") {
QuicrConnection client(0, 0, tw::net::Address({}), nullptr);
client.send_initial_hello();
auto client_hello = client.pop_datagram();
QuicrConnection server(0, 0, tw::net::Address({}), nullptr);
server.process_datagram(client_hello);
auto server_hello = server.pop_datagram();
client.process_datagram(server_hello);
REQUIRE(client.self_id() == server.peer_id());
REQUIRE(client.peer_id() == server.self_id());
}
TEST_CASE("When client receives ACK, it won't send the packet again", "[quicr2]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
connection.send_initial_hello();
// should contain only the hello frame
REQUIRE(connection.has_next_datagram() == true);
auto buffer = connection.pop_datagram();
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
REQUIRE(connection.has_next_datagram() == false);
std::vector<std::byte> target(1200);
size_t offset = 0;
std::vector<uint32_t> acks = { header.packet_number.value() };
QuicrFrame hello_frame = QuicrFrame::make_hello();
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection);
encoder
.encode_ack_frame(acks);
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
REQUIRE(connection.has_next_datagram() == false);
}
TEST_CASE("Connection don't send ACK when packet has no reliable frames", "[quicr3]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
auto buffer = connection.pop_datagram();
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
std::vector<std::byte> target(1200);
size_t offset = 0;
std::string message = "Hello world";
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection);
encoder
.encode_stream_frame(std::as_writable_bytes(std::span(message)), false);
REQUIRE(connection.has_next_datagram() == false);
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
REQUIRE(connection.has_next_datagram() == false);
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
REQUIRE(connection.has_next_datagram() == false);
}
TEST_CASE("Connection sends ACK when the packet has reliable frames", "[quicr3]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
auto buffer = connection.pop_datagram();
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
std::vector<std::byte> target(1200);
size_t offset = 0;
std::string message = "Hello world";
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection);
encoder
.encode_stream_frame(std::as_writable_bytes(std::span(message)), true);
REQUIRE(connection.has_next_datagram() == false);
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
REQUIRE(connection.has_next_datagram() == true);
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
REQUIRE(connection.has_next_datagram() == true);
}
TEST_CASE("Connection applies to ACK to all packets that sent the frame", "[quicr2]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
connection.send_initial_hello();
auto dgram1 = connection.pop_datagram();
auto packet1 = QuicrDecoder::decode_packet(dgram1);
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
auto dgram2 = connection.pop_datagram();
auto packet2 = QuicrDecoder::decode_packet(dgram2);
REQUIRE(packet1.packet_number.value() != packet2.packet_number.value());
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
std::vector<std::byte> target(1200);
size_t offset = 0;
QuicrConnection connection2(0, 0, tw::net::Address({}), nullptr);
std::vector<uint32_t> acks = { packet1.packet_number.value() };
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection2);
encoder
.encode_ack_frame(acks);
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
REQUIRE(!connection.has_next_datagram());
}
TEST_CASE("Connection can be established", "[quicr2]") {
std::barrier create_sync_point(2);
std::barrier send_sync_point(2);
std::barrier client_send_sync_point(2);
std::string mesg = "Hello world";
std::string client_msg = "Client hello";
std::thread server_thread([&]() {
auto endpoint_r = QuicrEndpoint::create();
REQUIRE(endpoint_r);
auto endpoint = std::move(endpoint_r.value());
REQUIRE(endpoint->bind(6971));
auto listener_r = QuicrConnectionListener::listen(endpoint.get());
REQUIRE(listener_r);
auto listener = std::move(listener_r.value());
create_sync_point.arrive_and_wait();
QuicrConnection* connection = nullptr;
// wait for connection
while(connection == nullptr) {
endpoint->poll();
connection = listener->listen();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
endpoint->poll();
spdlog::info("Connection established with peer id: 0x{:x}", connection->peer_id());
// write whole message
auto bytes = std::as_writable_bytes(std::span(mesg.begin(), mesg.end()));
auto r = connection->send_message(bytes, true);
if(!r) {
spdlog::error("Failed to write to connection");
}
REQUIRE(r);
endpoint->poll();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
send_sync_point.arrive_and_wait();
client_send_sync_point.arrive_and_wait();
endpoint->poll();
});
std::thread client_thread([&]() {
create_sync_point.arrive_and_wait();
auto endpoint_r = QuicrEndpoint::create();
REQUIRE(endpoint_r);
auto endpoint = std::move(*endpoint_r);
auto connection_result = endpoint->connect(tw::net::Address {"127.0.0.1", 6971}); // QuicrConnection::connect(Address{"127.0.0.1", 6970});
REQUIRE(connection_result);
auto conn = std::move(*connection_result);
spdlog::info("Client ID: {}", conn->self_id());
while(conn->state() != QuicrConnectionState::Established) {
endpoint->poll();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
endpoint->poll();
spdlog::info("Connection established");
send_sync_point.arrive_and_wait();
endpoint->poll();
std::string buffer(1024, '\0');
spdlog::info("Waiting to receive message from server...");
auto r = conn->read_into(std::as_writable_bytes(std::span(buffer.data(), buffer.size())));
if(!r) {
spdlog::error("Failed to read from connection: {}", r.error().message());
}
spdlog::info("Received: [{}], {}", r.value(), buffer.substr(0, r.value()));
REQUIRE(buffer.substr(0, r.value()) == mesg);
auto bytes = std::as_writable_bytes(std::span(client_msg.begin(), client_msg.end()));
conn->send_message(bytes, true);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
client_send_sync_point.arrive_and_wait();
});
client_thread.join();
server_thread.join();
}
TEST_CASE("Send large datagram", "[quicr2]") {
std::barrier create_sync_point(2);
std::barrier send_sync_point(2);
std::barrier client_send_sync_point(2);
std::string mesg = std::string(2000, 'a');
std::string client_msg = "Client hello";
std::thread server_thread([&]() {
auto endpoint_r = QuicrEndpoint::create();
REQUIRE(endpoint_r);
auto endpoint = std::move(endpoint_r.value());
REQUIRE(endpoint->bind(6970));
auto listener_r = QuicrConnectionListener::listen(endpoint.get());
REQUIRE(listener_r);
auto listener = std::move(listener_r.value());
create_sync_point.arrive_and_wait();
QuicrConnection* connection = nullptr;
// wait for connection
while(connection == nullptr) {
endpoint->poll();
connection = listener->listen();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
endpoint->poll();
spdlog::info("Connection established with peer id: 0x{:x}", connection->peer_id());
// write whole message
auto bytes = std::as_writable_bytes(std::span(mesg.begin(), mesg.end()));
auto r = connection->send_message(bytes, true);
if(!r) {
spdlog::error("Failed to write to connection");
}
REQUIRE(r);
endpoint->poll();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
send_sync_point.arrive_and_wait();
client_send_sync_point.arrive_and_wait();
endpoint->poll();
});
std::thread client_thread([&]() {
create_sync_point.arrive_and_wait();
auto endpoint_r = QuicrEndpoint::create();
REQUIRE(endpoint_r);
auto endpoint = std::move(*endpoint_r);
spdlog::info("Connecting");
auto connection_result = endpoint->connect(tw::net::Address {"127.0.0.1", 6970}); // QuicrConnection::connect(Address{"127.0.0.1", 6970});
if(!connection_result) {
spdlog::error("Failed to connect to server: {}", connection_result.error().message());
}
auto conn = std::move(*connection_result);
spdlog::info("Client ID: {}", conn->self_id());
while(conn->state() != QuicrConnectionState::Established) {
endpoint->poll();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
endpoint->poll();
spdlog::info("Connection established");
send_sync_point.arrive_and_wait();
endpoint->poll();
std::string buffer(64 * 1024, '\0');
spdlog::info("Waiting to receive message from server...");
auto r = conn->read_into(std::as_writable_bytes(std::span(buffer.data(), buffer.size())));
if(!r) {
spdlog::error("Failed to read from connection: {}", r.error().message());
}
spdlog::info("Received: [{}], {}", r.value(), buffer.substr(0, r.value()));
REQUIRE(buffer.substr(0, r.value()) == mesg);
auto bytes = std::as_writable_bytes(std::span(client_msg.begin(), client_msg.end()));
conn->send_message(bytes, true);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
client_send_sync_point.arrive_and_wait();
});
client_thread.join();
server_thread.join();
}
TEST_CASE("Sending message through closed connection returns error", "[quicr2]") {
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
REQUIRE(connection.state() == QuicrConnectionState::Closed);
std::string mesg = "Hello world";
auto bytes = std::as_writable_bytes(std::span(mesg.begin(), mesg.end()));
auto send_r = connection.send_message(bytes, true);
REQUIRE(!send_r);
REQUIRE(send_r.error().type() == QuicrErrorType::ConnectionClosed);
}
TEST_CASE("Frame can close the connection", "[quicr3]") {
}