453 lines
14 KiB
C++
453 lines
14 KiB
C++
#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]") {
|
|
|
|
}
|