This commit is contained in:
Martin Slachta
2026-07-18 14:31:15 +02:00
commit a04f0dc262
3343 changed files with 1140208 additions and 0 deletions
@@ -0,0 +1,12 @@
#include "bytebuffer/ByteBuffer.hpp"
#include "bytebuffer/ByteBufferDecoder.hpp"
#include "catch2/catch_test_macros.hpp"
struct DatagramHeader {
uint32_t type;
uint64_t secret_key;
};
TEST_CASE("Byte buffer codec test", "[byte_buffer]") {
}
+51
View File
@@ -0,0 +1,51 @@
#include "TestGenerators.hpp"
#include "bytebuffer/ByteBuffer.hpp"
#include "bytebuffer/ByteBufferDecoder.hpp"
#include "catch2/catch_test_macros.hpp"
TEST_CASE("Byte buffer insert", "[byte_buffer]") {
std::vector<std::byte> bytes = generate_sequence(8);
std::vector<std::byte> storage(12);
tw::net::RingByteBuffer byte_buffer(storage, false);
REQUIRE(byte_buffer.remaining_write() == 12);
REQUIRE(byte_buffer.remaining_read() == 0);
size_t written = byte_buffer.write_bytes(bytes);
REQUIRE(byte_buffer.remaining_write() == 4);
REQUIRE(byte_buffer.remaining_read() == 8);
REQUIRE(written == 8);
}
TEST_CASE("Byte buffer pop", "[byte_buffer]") {
std::vector<std::byte> bytes = generate_sequence(8);
std::vector<std::byte> storage(12);
tw::net::RingByteBuffer byte_buffer(storage, false);
size_t written = byte_buffer.write_bytes(bytes);
std::vector<std::byte> popped(6);
size_t read = byte_buffer.pop_bytes(popped.data(), 6);
REQUIRE(read == 6);
REQUIRE(popped[0] == std::byte{1});
REQUIRE(popped[1] == std::byte{2});
REQUIRE(popped[2] == std::byte{3});
REQUIRE(popped[3] == std::byte{4});
REQUIRE(popped[4] == std::byte{5});
REQUIRE(popped[5] == std::byte{6});
REQUIRE(byte_buffer.remaining_read() == 2);
REQUIRE(byte_buffer.remaining_write() == 10);
}
TEST_CASE("Byte buffer decoder", "[byte_buffer]") {
std::vector<std::byte> bytes = generate_sequence(8);
std::vector<std::byte> storage(12);
tw::net::RingByteBuffer byte_buffer(storage, false);
size_t written = byte_buffer.write_bytes(bytes);
}
+59
View File
@@ -0,0 +1,59 @@
project(tw_network_tests)
set(LIBS
tw::network
)
file(GLOB FILES
./*.cpp
./quicr/QuicrBasicTests.cpp
./quicr/QuicrEndpointTests.cpp
)
add_library(${PROJECT_NAME}_sources OBJECT ${FILES})
target_link_libraries(${PROJECT_NAME}_sources
Catch2::Catch2WithMain
${LIBS}
tl::expected
)
add_executable(${PROJECT_NAME})
add_executable(QuicrOverloadTest ./quicr/QuicrOverloadTests.cpp)
add_executable(QuicrBenchmarks ./quicr/QuicrBenchmarks.cpp)
target_link_libraries(QuicrBenchmarks
PRIVATE
${LIBS}
${PROJECT_NAME}_sources
Tracy::TracyClient
Catch2::Catch2WithMain
tl::expected
EnTT::EnTT
)
target_link_libraries(QuicrOverloadTest
PRIVATE
${LIBS}
${PROJECT_NAME}_sources
Tracy::TracyClient
TracyClient
Catch2::Catch2WithMain
tl::expected
EnTT::EnTT
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
${LIBS}
${PROJECT_NAME}_sources
Catch2::Catch2WithMain
tl::expected
EnTT::EnTT
)
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
include(CTest)
include(Catch)
catch_discover_tests(${PROJECT_NAME})
@@ -0,0 +1,29 @@
#include "TestGenerators.hpp"
#include "bytebuffer/ByteBufferDecoder.hpp"
#include "catch2/catch_test_macros.hpp"
#include "frames/FrameCodec.hpp"
#include "protocol/quicr/QuicrFrameType.hpp"
using namespace tw::net;
TEST_CASE("Encode stream frame", "[frame_encoder]") {
// std::vector<std::byte> bytes = generate_sequence(8);
// Frame frame(quicr::FrameType::StreamBase, bytes);
// ByteBuffer byte_buffer(16);
// frame::FrameCodec::encode(byte_buffer, frame);
// REQUIRE(byte_buffer.remaining_read() == 2 * sizeof(uint32_t) + 8);
// auto frame_type = ByteBufferDecoder<uint32_t>::decode(byte_buffer, 0);
// REQUIRE(frame_type);
// REQUIRE(*frame_type == (uint32_t)quicr::FrameType::StreamBase);
// auto length = ByteBufferDecoder<uint32_t>::decode(byte_buffer, 4);
// REQUIRE(length);
// REQUIRE(*length == 8);
}
+46
View File
@@ -0,0 +1,46 @@
#include <catch2/catch_test_macros.hpp>
// #include "io/Read.hpp"
// #include "messenger/Messenger.hpp"
// class MockReader : public Read<std::byte> {
// size_t m_cursor;
// std::string m_content;
// public:
// MockReader(const std::string& content) :
// m_cursor(0),
// m_content(content) {
// }
// size_t read(std::span<std::byte> data) override {
// size_t read_len = std::min(data.size(), m_content.size() - m_cursor);
// if(read_len == 0) {
// return 0;
// }
// std::copy(m_content.begin() + m_cursor, m_content.begin() + m_cursor + read_len, data.begin());
// m_cursor += read_len;
// return read_len;
// }
// };
// class MockWriter : public Write<std::byte> {
// public:
// MockWriter() {
// }
// size_t write(std::span<const std::byte> data) override {
// }
// };
// TEST_CASE("Test01", "[Messenger_Test]") {
// MockReader reader("0Hello, World!");
// MockWriter writer;
// tw::net::Messenger messenger(&writer, &reader);
// REQUIRE(messenger.peek() == '0');
// }
+259
View File
@@ -0,0 +1,259 @@
#include <catch2/catch_test_macros.hpp>
#include <print>
#include "Serializers.hpp"
struct TestMessage1 {
uint32_t a, b, c, d;
};
template<>
class tw::net::Serializer<TestMessage1> {
public:
static bool serialize(Serialization& buffer, TestMessage1& mesg) {
buffer.serialize(&mesg.a);
buffer.serialize(&mesg.b);
buffer.serialize(&mesg.c);
buffer.serialize(&mesg.d);
return true;
}
};
// TEST_CASE("Test01", "[serializer]") {
// TestMessage1 mesg = {
// .a = 10,
// .b = 100,
// .c = 3,
// .d = 0
// };
//
// tw::net::ByteBuffer buffer(1024);
//
// buffer.write(mesg);
//
// REQUIRE(mesg.a == 10);
// REQUIRE(mesg.b == 100);
// REQUIRE(mesg.c == 3);
// REQUIRE(mesg.d == 0);
//
// TestMessage1 mesg2 = {};
//
// REQUIRE(mesg2.a == 0);
// REQUIRE(mesg2.b == 0);
// REQUIRE(mesg2.c == 0);
// REQUIRE(mesg2.d == 0);
//
// buffer.read(mesg2);
//
// REQUIRE(mesg.a == mesg2.a);
// REQUIRE(mesg.b == mesg2.b);
// REQUIRE(mesg.c == mesg2.c);
// REQUIRE(mesg.d == mesg2.d);
// }
//
// TEST_CASE("ByteBufferRemainingTest", "[serializer]") {
// TestMessage1 mesg = {
// .a = 10,
// .b = 100,
// .c = 3,
// .d = 0
// };
//
// tw::net::ByteBuffer buffer(4 * sizeof(uint32_t) + 10);
// buffer.write(mesg);
//
// REQUIRE(buffer.remaining() == 10);
// }
//
// TEST_CASE("ByteBufferOverflowsTest1", "[serializer]") {
// TestMessage1 mesg = {
// .a = 10,
// .b = 100,
// .c = 3,
// .d = 0
// };
//
// tw::net::ByteBuffer buffer(2);
// buffer.write(mesg);
//
// // nothing should be written
// REQUIRE(buffer.remaining() == 2);
// }
//
// TEST_CASE("TestMultipleMessagesOnePacket", "[serializer]") {
// TestMessage1 mesg = {
// .a = 10,
// .b = 100,
// .c = 3,
// .d = 0
// };
//
// tw::net::ByteBuffer buffer(1024);
//
// buffer.write(mesg);
// buffer.write(mesg);
//
// REQUIRE(mesg.a == 10);
// REQUIRE(mesg.b == 100);
// REQUIRE(mesg.c == 3);
// REQUIRE(mesg.d == 0);
//
// TestMessage1 mesg2 = {};
//
// REQUIRE(mesg2.a == 0);
// REQUIRE(mesg2.b == 0);
// REQUIRE(mesg2.c == 0);
// REQUIRE(mesg2.d == 0);
//
// buffer.read(mesg2);
// REQUIRE(buffer.remaining() == sizeof(TestMessage1));
// TestMessage1 mesg3 = {};
// buffer.read(mesg3);
//
// REQUIRE(mesg.a == mesg2.a); REQUIRE(mesg.a == mesg3.a);
// REQUIRE(mesg.b == mesg2.b); REQUIRE(mesg.b == mesg3.b);
// REQUIRE(mesg.c == mesg2.c); REQUIRE(mesg.c == mesg3.c);
// REQUIRE(mesg.d == mesg2.d); REQUIRE(mesg.d == mesg3.d);
// }
//
// TEST_CASE("SerializationTest", "[serialization]") {
// TestMessage1 mesg = {
// .a = 10,
// .b = 100,
// .c = 3,
// .d = 0
// };
//
// tw::net::ByteBuffer buffer(1024);
// buffer.write(mesg);
// char *ptr = (char*)&mesg.a;
// }
//
// #define MAX_MESG_SIZE 256
//
// struct TestMessageString {
// uint32_t mesg_size;
// char mesg[MAX_MESG_SIZE];
// };
//
// template<>
// class tw::net::Serializer<TestMessageString> {
// public:
// static bool serialize(ByteBuffer& buffer, TestMessageString& mesg) {
// buffer.serialize(&mesg.mesg_size);
// buffer.bytes(mesg.mesg, mesg.mesg_size);
// return true;
// }
// };
//
// TEST_CASE("StringTest", "[serializer]") {
// TestMessageString mesg = {
// .mesg_size = sizeof("Hello World") - 1,
// .mesg = "Hello World"
// };
//
// tw::net::ByteBuffer buffer(1024);
//
// buffer.write(mesg);
//
// TestMessageString mesg2 = {};
//
// buffer.read(mesg2);
// mesg2.mesg[mesg2.mesg_size] = 0;
//
// REQUIRE(!strcmp(mesg2.mesg, "Hello World"));
// }
//
//
// #define MAX_ELEMENTS 10
//
// struct TestScalableMessageElement {
// int32_t a, b, c, d;
// };
//
//
// template<>
// class tw::net::Serializer<TestScalableMessageElement> {
// public:
// static bool serialize(ByteBuffer& buffer, TestScalableMessageElement& mesg) {
// if(buffer.remaining() < 4 * sizeof(uint32_t)) {
// return false;
// }
//
// buffer.serialize(&mesg.a);
// buffer.serialize(&mesg.b);
// buffer.serialize(&mesg.c);
// buffer.serialize(&mesg.d);
//
// return true;
// }
// };
//
// struct TestScalableMessage {
// uint32_t num_elements;
// TestScalableMessageElement elements[MAX_ELEMENTS];
// };
//
// template<>
// class tw::net::Serializer<TestScalableMessage> {
// public:
// static bool serialize(ByteBuffer& buffer, TestScalableMessage& mesg) {
// buffer.serialize(&mesg.num_elements);
// int i = 0;
// for(i = 0; i < mesg.num_elements; i++) {
// bool is_there = true;
// buffer.serialize(&is_there);
//
// if(!buffer.serialize(mesg.elements[i])) {
// break;
// }
// }
//
// return true;
// }
// };
//
// TEST_CASE("ByteBufferOverflowsTest2", "[serializer]") {
// TestScalableMessage mesg = { };
//
// for(int i = 0; i < 10; i++) {
// mesg.elements[i] = {
// .a = 1 * i,
// .b = 2,
// .c = 3 * i,
// .d = 4
// };
// mesg.num_elements++;
// }
//
// tw::net::ByteBuffer buffer(20);
//
// buffer.write(mesg);
// }
//
//
// #include <glm/glm.hpp>
//
//
// template<>
// class tw::net::Serializer<glm::vec3> final {
// public:
// static bool serialize(ByteBuffer& buffer, glm::vec3& value) {
// buffer.serialize(&value.x);
// buffer.serialize(&value.y);
// buffer.serialize(&value.z);
// return true;
// }
// };
//
// TEST_CASE("Vec3Serialize", "[socket]") {
// tw::net::ByteBuffer buffer(20);
//
// glm::vec3 vec = glm::vec3(10.0f, 5.0f, 3.14f);
// buffer.write(vec);
//
// glm::vec3 vec2 = {};
// buffer.read(vec2);
//
// REQUIRE(vec == vec2);
// }
+13
View File
@@ -0,0 +1,13 @@
#include "TestGenerators.hpp"
std::vector<std::byte> generate_sequence(const size_t size) {
assert(size < UINT8_MAX);
std::vector<std::byte> result(size);
for(size_t i = 0; i < size; i++) {
result[i] = (std::byte)(i + 1);
}
return result;
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <vector>
std::vector<std::byte> generate_sequence(const size_t size);
+71
View File
@@ -0,0 +1,71 @@
#include <barrier>
#include <catch2/catch_test_macros.hpp>
#include <span>
#include <iostream>
#include "UdpStream.hpp"
#include "Address.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
#include "protocol/quicr/QuicrConnectionListener.hpp"
#include "protocol/quicr/QuicrEndpoint.hpp"
TEST_CASE("Start two sockets and send message", "[udp]") {
std::barrier create_sync_point(2);
std::barrier send_sync_point(2);
const std::string message = "Hello, server!";
std::thread server_thread([&]() {
auto r = tw::net::UdpStream::bind(tw::net::Address {"127.0.0.1", 6969});
if(!r.has_value()) {
std::cout << ("Failed to bind UDP stream: {}") << r.error().message() << std::endl;
}
auto flag_result = r->set_non_blocking();
auto server = std::move(r.value());
create_sync_point.arrive_and_wait();
send_sync_point.arrive_and_wait();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::vector<std::byte> buffer(1024);
tw::net::Address from {{}, 0};
auto read_result = server.read_into(std::span{buffer.data(), buffer.size()}, &from);
if(!read_result.has_value()) {
spdlog::error("Failed to read from UDP stream: {}", read_result.error().message());
}
// convert the buffer to a string
std::string mesg(buffer.data(), buffer.data() + read_result.value());
REQUIRE(mesg == message);
});
std::thread client_thread([&]() {
create_sync_point.arrive_and_wait();
auto connect_result = tw::net::UdpStream::to(tw::net::Address{"127.0.0.1", 6969});
if(!connect_result.has_value()) {
spdlog::error("Failed to bind UDP stream: {}", connect_result.error().mesg());
}
auto client = std::move(connect_result.value());
std::string mesg = message;
auto write_result = client.write(std::as_writable_bytes(std::span(mesg.begin(), mesg.end())));
if(!write_result.has_value() && write_result.value() == mesg.length()) {
spdlog::error("Failed to write to UDP stream: {}", write_result.error().message());
}
send_sync_point.arrive_and_wait();
});
server_thread.join();
client_thread.join();
}
using namespace tw::net;
using namespace tw::net::quicr;
@@ -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]") {
}
@@ -0,0 +1,346 @@
#include "TcpListener.hpp"
#include "bytebuffer/ByteBufferReader.hpp"
#include "bytebuffer/ByteBufferWriter.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
#include "protocol/quicr/QuicrConnectionListener.hpp"
#include "protocol/quicr/QuicrEndpoint.hpp"
#include "io/Read.hpp"
#include "io/Write.hpp"
#include <iostream>
#include <fstream>
#include <chrono>
#include <ratio>
#include <tracy/Tracy.hpp>
#define PORT 6970
#define FRAMES_PER_SECOND 60
#define SECONDS_OF_TESTING 10
void server_func(std::atomic<bool>& is_done, tw::net::Write<std::byte>* writer, tw::net::Read<std::byte>* reader) {
double value = 0.0f;
std::vector<std::byte> inbound_buffer(1200);
size_t inbound_length = 0;
std::vector<std::byte> outbound_buffer(1200);
while(!is_done) {
auto read_r = reader->read_into(std::span(inbound_buffer).subspan(inbound_length));
inbound_length += *read_r;
uint32_t frame_number = 0;
auto decoder = tw::net::ByteBufferReader(std::span(inbound_buffer).subspan(0, inbound_length));
while(decoder.remaining()) {
auto read_r = decoder.pop_bytes(&frame_number);
if(!read_r) {
break;
}
double velocity = 0.0f;
read_r = decoder.pop_bytes(&velocity);
if(!read_r) {
break;
}
value += velocity;
// encode response
tw::net::ByteBufferWriter encoder((std::span<std::byte>(outbound_buffer)));
encoder.write_bytes(&frame_number);
encoder.write_bytes(&value);
auto write_r = writer->write(std::span(outbound_buffer).subspan(0, encoder.length()));
if(!write_r) {
break;
}
}
// move bytes back
memcpy(inbound_buffer.data(), inbound_buffer.data() + decoder.position(), decoder.remaining());
}
}
void client_func(std::atomic<bool>& is_done, tw::net::Write<std::byte>* writer, tw::net::Read<std::byte>* reader) {
std::vector<std::byte> outbound_buffer(1200);
std::vector<std::byte> inbound_buffer(1200);
uint32_t frame_number = 0;
while(!is_done) {
tw::net::ByteBufferWriter writer(outbound_buffer);
writer.write_bytes(&frame_number);
double random = std::sin(frame_number);
writer.write_bytes(&random);
// writer.write_bytes();
}
}
double derivation_func(uint32_t frame_number) {
return std::sin((double)frame_number / 25.0f);
}
void test_quic() {
std::atomic<bool> client_is_done = false;
std::thread server_thread([&]() {
auto server_endpoint = tw::net::quicr::QuicrEndpoint::create().value();
assert(server_endpoint->bind(PORT));
auto listener_r = tw::net::quicr::QuicrConnectionListener::listen(server_endpoint.get());
auto listener = std::move(listener_r.value());
tw::net::quicr::QuicrConnection* connection = nullptr;
while(connection == nullptr) {
server_endpoint->poll();
connection = listener->listen();
}
uint32_t frame_number = 0;
std::vector<std::byte> buffer(1200);
std::vector<std::byte> outbound_buffer(1200);
double value = 0.0f;
while(true) {
if(client_is_done) {
break;
}
server_endpoint->poll();
auto read_r = connection->read_into(buffer);
if(read_r.has_value() && *read_r > 0) {
ZoneScopedN("Server read");
tw::net::ByteBufferReader reader((std::span<std::byte>(buffer).subspan(0, read_r.value())));
uint32_t frame_number = 0;
reader.pop_bytes(&frame_number);
double velocity = 0;
reader.pop_bytes(&velocity);
value += velocity;
}
tw::net::ByteBufferWriter writer(outbound_buffer);
writer.write_bytes(&frame_number);
writer.write_bytes(&value);
auto send_r = connection->send_message(std::span(outbound_buffer).subspan(0, writer.length()), false);
assert(send_r.has_value());
server_endpoint->poll();
frame_number++;
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
});
std::thread client_thread([&client_is_done]() {
auto client_endpoint = tw::net::quicr::QuicrEndpoint::create().value();
auto connection = client_endpoint->connect({"127.0.0.1", PORT}).value();
while(connection->state() != tw::net::quicr::Established) {
client_endpoint->poll();
}
std::vector<std::byte> outbound_buffer(1200);
std::vector<std::byte> inbound_buffer(1200);
std::map<uint32_t, std::chrono::steady_clock::time_point> sent_at;
int32_t countdown = FRAMES_PER_SECOND * SECONDS_OF_TESTING;
std::ofstream quicr_csv("quicr.csv");
std::ofstream quicr_integration_csv("quicr_integration.csv");
uint32_t frame_number = 0;
double position = 0;
while(true) {
if(countdown <= 0) {
client_is_done.store(true);
break;
}
client_endpoint->poll();
tw::net::ByteBufferWriter writer(outbound_buffer);
writer.write_bytes(&frame_number);
double random = derivation_func(frame_number);
writer.write_bytes(&random);
auto send_r = connection->send_message(std::span(outbound_buffer).subspan(0, writer.length()), false);
assert(send_r.has_value());
sent_at.emplace(frame_number, std::chrono::steady_clock::now());
auto read_r = connection->read_into(std::span<std::byte>(inbound_buffer));
if(read_r.has_value() && *read_r > 0) {
tw::net::ByteBufferReader reader(std::span<std::byte>(inbound_buffer).subspan(0, read_r.value()));
uint32_t _frame_number = 0;
reader.pop_bytes(&_frame_number);
if(!sent_at.contains(_frame_number)) {
spdlog::warn("Frame {} not sent", _frame_number);
continue;
}
reader.pop_bytes(&position);
auto rtt = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - sent_at[_frame_number]).count();
spdlog::info("Frame {} received after {}ms", _frame_number, rtt);
sent_at.erase(_frame_number);
quicr_csv << _frame_number << "," << rtt << "," << position << std::endl;
countdown--;
}
quicr_integration_csv << frame_number << "," << position << std::endl;
client_endpoint->poll();
std::this_thread::sleep_for(std::chrono::milliseconds(16));
frame_number++;
}
});
server_thread.join();
client_thread.join();
}
void test_tcp() {
std::atomic<bool> client_is_done(false);
std::thread server_thread([&]() {
tw::net::Address address {"127.0.0.1", PORT};
auto server_listener = tw::net::TcpListener::listen(address, PORT).value();
std::optional<tw::net::TcpStream> stream;
while(true) {
auto stream_r = server_listener.listen();
if(stream_r) {
stream = std::move(*stream_r);
break;
}
}
auto non_blocking_r = stream->set_non_blocking();
std::vector<std::byte> buffer(1200);
std::vector<std::byte> outbound_buffer(1200);
uint32_t frame_number = 0;
int32_t countdown = FRAMES_PER_SECOND * SECONDS_OF_TESTING;
double value = 0.0f;
while(!client_is_done) {
auto read_r = stream->read_into(buffer);
if(read_r.has_value() && *read_r > 0) {
tw::net::ByteBufferReader reader((std::span<std::byte>(buffer).subspan(0, read_r.value())));
while(reader.remaining() > 0) {
uint32_t _frame_number = 0;
reader.pop_bytes(&_frame_number);
double velocity = 0;
reader.pop_bytes(&velocity);
value += velocity;
countdown--;
}
}
tw::net::ByteBufferWriter writer(outbound_buffer);
writer.write_bytes(&frame_number);
writer.write_bytes(&value);
auto send_r = stream->write(std::span(outbound_buffer).subspan(0, writer.length()));
assert(send_r.has_value());
frame_number++;
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
});
std::thread client_thread([&client_is_done]() {
auto client_stream = tw::net::TcpStream::connect({"127.0.0.1", PORT}).value();
auto non_blocking_r = client_stream.set_non_blocking();
std::vector<std::byte> outbound_buffer(1200);
std::vector<std::byte> inbound_buffer(1200);
std::map<uint32_t, std::chrono::steady_clock::time_point> sent_at;
int32_t countdown = FRAMES_PER_SECOND * SECONDS_OF_TESTING;
uint32_t frame_number = 0;
// open file tcp.csv
std::ofstream tcp_csv("tcp.csv");
std::ofstream tcp_integration_csv("tcp_integration.csv");
double position = 0.0f;
while(true) {
if(countdown <= 0) {
client_is_done.store(true);
break;
}
tw::net::ByteBufferWriter writer(outbound_buffer);
writer.write_bytes(&frame_number);
double random = derivation_func(frame_number);
writer.write_bytes(&random);
auto send_r = client_stream.write(std::span(outbound_buffer).subspan(0, writer.length()));
assert(send_r.has_value());
sent_at.emplace(frame_number, std::chrono::steady_clock::now());
frame_number++;
auto read_r = client_stream.read_into(std::span<std::byte>(inbound_buffer));
if(read_r.has_value() && *read_r > 0) {
tw::net::ByteBufferReader reader(std::span<std::byte>(inbound_buffer).subspan(0, read_r.value()));
while(reader.remaining() > 0) {
uint32_t _frame_number = 0;
reader.pop_bytes(&_frame_number);
reader.pop_bytes(&position);
auto rtt = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - sent_at[_frame_number]).count();
spdlog::info("Frame {} received after {}ms", _frame_number, rtt);
tcp_csv << _frame_number << "," << rtt << "," << position << std::endl;
countdown--;
}
}
tcp_integration_csv << frame_number << "," << position << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
tcp_csv.close();
});
server_thread.join();
client_thread.join();
}
int main() {
test_quic();
test_tcp();
return 0;
}
@@ -0,0 +1,38 @@
#include <catch2/catch_test_macros.hpp>
#include "protocol/quicr/QuicrEndpoint.hpp"
using namespace tw::net;
using namespace tw::net::quicr;
TEST_CASE("Endpoint registers new connection with correct ID", "[quicr2]") {
auto endpoint_r = QuicrEndpoint::create();
REQUIRE(endpoint_r);
auto& server_endpoint = *endpoint_r.value();
REQUIRE(server_endpoint.bind(6972));
auto client_endpoint_r = QuicrEndpoint::create();
REQUIRE(client_endpoint_r);
auto& client_endpoint = *client_endpoint_r.value();
auto connect_r = client_endpoint.connect(Address{"127.0.0.1", 6972});
REQUIRE(connect_r);
QuicrConnection& connection = *connect_r.value();
REQUIRE(connection.self_id() != 0);
REQUIRE(connection.peer_id() != 0);
connection.send_initial_hello();
client_endpoint.poll();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
server_endpoint.poll();
auto clients = server_endpoint.clients();
REQUIRE(clients.size() == 2);
REQUIRE(((clients[0].first == connection.peer_id()) || (clients[1].first == connection.peer_id())));
REQUIRE(clients[0].second->peer_id() == connection.self_id());
REQUIRE(clients[1].second->peer_id() == connection.self_id());
}
@@ -0,0 +1,122 @@
/**
* Testing overloading the listener and how much can it handle.
*/
#include <span>
#include <unordered_map>
#include <tracy/Tracy.hpp>
#include "Address.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
#include "protocol/quicr/QuicrEndpoint.hpp"
#include "protocol/quicr/QuicrConnectionListener.hpp"
using namespace tw::net;
using namespace tw::net::quicr;
std::atomic<bool> is_stopped(false);
void got_signal(int) {
is_stopped.store(true);
}
void register_signal_handler() {
struct sigaction sa;
memset( &sa, 0, sizeof(sa) );
sa.sa_handler = got_signal;
sigfillset(&sa.sa_mask);
sigaction(SIGINT,&sa,NULL);
}
int main() {
register_signal_handler();
// spdlog::set_pattern("[%H:%M:%S] [thread %t] %v");
const int NUM_CONNECTIONS = 500;
std::thread server_thread([&]() {
auto server_endpoint_r = QuicrEndpoint::create();
assert(server_endpoint_r);
auto server_endpoint = std::move(*server_endpoint_r);
assert(server_endpoint->bind(8100));
auto listen_r = QuicrConnectionListener::listen(server_endpoint.get());
assert(listen_r);
auto listen = std::move(listen_r.value());
struct ConnectionTestSession {
QuicrConnection *connection;
bool is_answered;
std::vector<std::byte> buffer;
ConnectionTestSession(QuicrConnection *connection)
: connection(connection), is_answered(false),
buffer(1024 * 16) {}
};
std::unordered_map<Address, ConnectionTestSession*> connections;
uint32_t answered_count = 0;
uint32_t num_connections = 0;
while(answered_count < NUM_CONNECTIONS) {
if(is_stopped) break;
server_endpoint->poll();
auto new_connection = listen->listen();
if(new_connection) {
connections[new_connection->address()] = new ConnectionTestSession(new_connection);
num_connections++;
spdlog::warn("Num connections: {}", num_connections);
}
for(auto& connection : connections) {
// assert(!connection.second->is_answered);
auto read_r = connection.second->connection->read_into(connection.second->buffer);
assert(read_r);
if(connection.second->is_answered) {
continue;
}
std::string mesg(connection.second->buffer.begin(), connection.second->buffer.begin() + *read_r);
std::transform(mesg.begin(), mesg.end(), mesg.begin(), ::toupper);
connection.second->connection->send_message(std::as_writable_bytes(std::span(mesg)), true);
connection.second->is_answered = true;
answered_count++;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
spdlog::warn("DONE: got all answers");
});
std::vector<std::unique_ptr<QuicrEndpoint>> endpoints(NUM_CONNECTIONS);
std::vector<QuicrConnection*> connections(NUM_CONNECTIONS);
std::vector<bool> established_counts(NUM_CONNECTIONS, false);
for(int i = 0; i < NUM_CONNECTIONS; i++) {
endpoints[i] = QuicrEndpoint::create().value();
connections[i] = endpoints[i]->connect(Address{"127.0.0.1", 8100}).value();
}
std::atomic<uint32_t> established_count(0);
spdlog::info("Starting overload test with {} connections", NUM_CONNECTIONS);
while(!is_stopped && established_count.load() < NUM_CONNECTIONS) {
for(int i = 0; i < NUM_CONNECTIONS; i++) {
{
endpoints[i]->poll();
}
if(!established_counts[i] && connections[i]->state() == QuicrConnectionState::Established) {
established_counts[i] = true;
established_count++;
}
}
}
server_thread.join();
return 0;
}