#1 - quicr module
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
project(tw_quicr_tests)
|
||||
|
||||
# set(CMAKE_CXX_CLANG_TIDY "/usr/bin/clang-tidy;-checks=*")
|
||||
|
||||
file(GLOB FILES
|
||||
src/*.cpp
|
||||
)
|
||||
|
||||
add_library(tw_quicr_lib STATIC ${FILES})
|
||||
|
||||
target_include_directories(tw_quicr_lib
|
||||
PUBLIC
|
||||
${PROJECT_SOURCE_DIR}/src/
|
||||
)
|
||||
|
||||
target_link_libraries(tw_server_lib
|
||||
PUBLIC
|
||||
tl::expected
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
add_subdirectory(./tests/)
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user