#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
+34
View File
@@ -0,0 +1,34 @@
project(tw_quicr)
# set(CMAKE_CXX_CLANG_TIDY "/usr/bin/clang-tidy;-checks=*")
file(GLOB FILES
src/*.cpp
)
file(GLOB HEADERS
include/*.hpp
)
add_library(${PROJECT_NAME} OBJECT ${FILES})
add_library(tw::quicr ALIAS ${PROJECT_NAME})
target_sources(${PROJECT_NAME}
PUBLIC FILE_SET HEADERS
BASE_DIRS include
FILES ${HEADERS})
target_include_directories(${PROJECT_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include/
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
tw::io
tl::expected
Tracy::TracyClient
TracyClient
)
# add_subdirectory(./tests/)
@@ -0,0 +1,182 @@
#pragma once
#include <arpa/inet.h>
#include <spdlog/spdlog.h>
#include <string>
#include <cstring>
#include <optional>
#include <sys/socket.h>
#include <format>
namespace tw::net::quicr {
/**
* IP Address
*/
struct QuicrAddress {
private:
sockaddr_storage m_storage {};
public:
QuicrAddress(const std::optional<std::string>& address, int port) {
std::memset((char*)&this->m_storage, 0, sizeof(this->m_storage));
auto& addr = reinterpret_cast<sockaddr_in&>(m_storage);
addr.sin_family = AF_INET;
addr.sin_port = htons(static_cast<uint16_t>(port));
addr.sin_addr.s_addr = address.has_value()
? inet_addr(address->c_str())
: INADDR_ANY;
}
QuicrAddress(sockaddr_storage& storage)
: m_storage(storage)
{ }
QuicrAddress(sockaddr_storage&& storage)
: m_storage(storage)
{ }
/** Return a const pointer suitable for connect / sendto / bind. */
const struct sockaddr* sockaddr() const {
return reinterpret_cast<const struct sockaddr*>(&m_storage);
}
/** Return a mutable pointer suitable for recvfrom / accept. */
struct sockaddr* sockaddr_mut() {
return reinterpret_cast<struct sockaddr*>(&m_storage);
}
/** Return the size of the active address (depends on family). */
socklen_t socklen() const {
switch (m_storage.ss_family) {
case AF_INET: return sizeof(sockaddr_in);
case AF_INET6: return sizeof(sockaddr_in6);
default: return sizeof(sockaddr_storage);
}
}
/** Mutable reference to the raw storage — useful when you need to pass
* a sockaddr_storage* to recvfrom together with a socklen_t. */
sockaddr_storage& storage() { return m_storage; }
const sockaddr_storage& storage() const { return m_storage; }
sa_family_t family() const { return m_storage.ss_family; }
/** Returns the raw network-order IPv4 address, or 0 if not AF_INET. */
uint32_t ipv4_addr_raw() const {
if (m_storage.ss_family == AF_INET) {
return reinterpret_cast<const sockaddr_in&>(m_storage).sin_addr.s_addr;
}
return 0;
}
/** Returns the raw network-order port (any family). */
uint16_t port_raw() const {
switch (m_storage.ss_family) {
case AF_INET:
return reinterpret_cast<const sockaddr_in&>(m_storage).sin_port;
case AF_INET6:
return reinterpret_cast<const sockaddr_in6&>(m_storage).sin6_port;
default:
return 0;
}
}
uint16_t port() const {
switch (m_storage.ss_family) {
case AF_INET:
return ntohs(reinterpret_cast<const sockaddr_in&>(m_storage).sin_port);
case AF_INET6:
return ntohs(reinterpret_cast<const sockaddr_in6&>(m_storage).sin6_port);
default:
return 0;
}
}
/** Return the IP portion only (no port). */
std::string ip_string() const {
char buf[INET6_ADDRSTRLEN]{};
switch (m_storage.ss_family) {
case AF_INET: {
const auto& v4 = reinterpret_cast<const sockaddr_in&>(m_storage);
inet_ntop(AF_INET, &v4.sin_addr, buf, sizeof(buf));
break;
}
case AF_INET6: {
const auto& v6 = reinterpret_cast<const sockaddr_in6&>(m_storage);
inet_ntop(AF_INET6, &v6.sin6_addr, buf, sizeof(buf));
break;
}
default:
return "<unknown>";
}
return std::string(buf);
}
/** Human-readable "ip:port" (or "[ip]:port" for IPv6). */
std::string to_string() const {
if (m_storage.ss_family == AF_INET6) {
return std::format("[{}]:{}", ip_string(), port());
}
return std::format("{}:{}", ip_string(), port());
}
bool operator==(const QuicrAddress& other) const {
if (m_storage.ss_family != other.m_storage.ss_family) return false;
switch (m_storage.ss_family) {
case AF_INET: {
const auto& a = reinterpret_cast<const sockaddr_in&>(m_storage);
const auto& b = reinterpret_cast<const sockaddr_in&>(other.m_storage);
return a.sin_port == b.sin_port
&& a.sin_addr.s_addr == b.sin_addr.s_addr;
}
case AF_INET6: {
const auto& a = reinterpret_cast<const sockaddr_in6&>(m_storage);
const auto& b = reinterpret_cast<const sockaddr_in6&>(other.m_storage);
return a.sin6_port == b.sin6_port
&& std::memcmp(&a.sin6_addr, &b.sin6_addr, sizeof(in6_addr)) == 0;
}
default:
return std::memcmp(&m_storage, &other.m_storage, sizeof(m_storage)) == 0;
}
}
bool operator!=(const QuicrAddress& other) const { return !(*this == other); }
/**
* Retained for source compatibility. Prefer operator==.
*/
bool equals(const QuicrAddress& other) const { return *this == other; }
};
}
template<>
struct std::hash<tw::net::quicr::QuicrAddress> {
std::size_t operator()(const tw::net::quicr::QuicrAddress& addr) const noexcept {
// FNV-style combine of family + port + address bytes
std::size_t h = std::hash<uint16_t>{}(addr.family());
h ^= std::hash<uint16_t>{}(addr.port_raw()) + 0x9e3779b9 + (h << 6) + (h >> 2);
switch (addr.family()) {
case AF_INET:
h ^= std::hash<uint32_t>{}(addr.ipv4_addr_raw()) + 0x9e3779b9 + (h << 6) + (h >> 2);
break;
case AF_INET6: {
const auto& s = reinterpret_cast<const sockaddr_in6&>(addr.storage());
const auto* bytes = reinterpret_cast<const uint8_t*>(&s.sin6_addr);
for (int i = 0; i < 16; ++i) {
h ^= std::hash<uint8_t>{}(bytes[i]) + 0x9e3779b9 + (h << 6) + (h >> 2);
}
break;
}
default:
break;
}
return h;
}
};
@@ -0,0 +1,218 @@
#pragma once
#include <cstddef>
#include <chrono>
#include <deque>
#include <sys/socket.h>
#include <tl/expected.hpp>
#include "quicr/QuicrAddress.hpp"
#include "quicr/QuicrError.hpp"
#include "bytebuffer/ByteBuffer.hpp"
#include "quicr/QuicrConnectionIdGenerator.hpp"
#include "quicr/QuicrEndpoint.hpp"
#include "quicr/QuicrPacket.hpp"
#include "quicr/QuicrReliability.hpp"
namespace tw::net::quicr {
const int TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS = 5000;
const int TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS = 500;
/**
* Overwriting ring buffer;
*/
template<typename T>
class Ring {
std::vector<T> m_buffer;
size_t m_head = 0;
size_t m_tail = 0;
public:
const T pop() {
T value = m_buffer[m_tail];
m_tail = (m_tail + 1) % m_buffer.size();
return value;
}
void push_back(T value) {
m_head = (m_head + 1) % m_buffer.size();
if(m_tail == m_head) {
m_tail += 1;
}
m_buffer[m_head] = value;
}
private:
};
class UdpConnectionStreamPayloadQueue {
std::vector<std::byte> m_buffer;
Ring<uint32_t> m_payload_ends;
std::span<std::byte> pop() {
return std::span(m_buffer.data(), m_payload_ends.pop());
}
};
enum QuicrConnectionState {
Closed,
SentHello,
ReceivedHello,
Established
};
constexpr uint64_t STREAM_FLAG_FIN = 0x01;
constexpr uint64_t STREAM_FLAG_LEN = 0x02;
constexpr uint64_t STREAM_FLAG_OFF = 0x04;
class QuicrEndpoint;
/**
* Established QUICr connection.
*/
class QuicrConnection {
static constexpr int PROTOCOL_VERSION = 1;
static constexpr int MAX_HELLO_RETRIES = 5;
static constexpr int HELLO_RETRY_INTERVAL_MS = 200;
using Clock = std::chrono::steady_clock;
QuicrAddress m_peer_address;
QuicrEndpoint* m_endpoint;
QuicrReliabilityUnit* m_reliability_unit;
uint32_t m_packet_number = 1;
uint64_t m_self_id;
uint64_t m_peer_id;
Clock::time_point m_last_heartbeat_sent;
Clock::time_point m_last_heartbeat_received;
QuicrConnectionState m_state;
std::vector<std::byte> m_recv_buffer;
std::deque<std::vector<std::byte>> m_messages;
std::deque<std::vector<std::byte>> m_outbound_messages;
std::vector<QuicrFrame> m_outbound_frames;
std::vector<uint32_t> m_hello_packets;
/**
* Builds and writes next datagram.
*/
tl::expected<size_t, QuicrError> write_datagram(std::span<std::byte> data);
public:
QuicrConnection(uint64_t self_id, uint64_t peer_id, QuicrAddress peer_address, QuicrEndpoint* endpoint) :
m_peer_address{peer_address},
m_endpoint{endpoint},
m_self_id{generate_id()},
m_peer_id{generate_id()},
m_state(QuicrConnectionState::Closed),
m_last_heartbeat_received(Clock::now()),
m_recv_buffer(64 * 1024),
m_reliability_unit(new QuicrReliabilityUnit(this))
{ }
// static tl::expected<QuicrConnection, NetworkError> connect(const Address& address);
constexpr QuicrAddress address() {
return m_peer_address;
}
constexpr const uint64_t& self_id() const {
return m_self_id;
}
constexpr const uint64_t& peer_id() const {
return m_peer_id;
}
constexpr QuicrConnectionState state() {
return m_state;
}
void set_peer_id(uint64_t peer_id) {
m_peer_id = peer_id;
}
bool is_timed_out() const {
return m_last_heartbeat_received < std::chrono::steady_clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2);
}
tl::expected<void, QuicrError> send_keep_alive();
void send_initial_hello();
/**
* Schedules one stream frame to be sent.
*/
tl::expected<void, QuicrError>
send_message(std::span<std::byte> data, bool is_reliable);
/*
* Processes stream frame and appends message to the queue.
*/
bool process_stream_frame(uint64_t type, std::span<const std::byte> dgram, size_t& offset);
/**
* Hello frame
* - Protocol version
* - self connection ID
*/
void send_hello();
bool process_hello(const QuicrPacket& packet, const QuicrFrame& frame);
bool process_hello_fin(const QuicrPacket& packet, const QuicrFrame& frame);
/**
* Hello ACK frame:
* - Protocol version
* - self connection ID
* - echoed peer ID
*/
void send_hello_ack_frame();
bool process_hello_ack_frame(std::span<const std::byte> dgram, size_t& off);
/*
* Handshake Done Frame
* - Protocol version
* - self connection ID
* - echoed peer ID
*/
void send_handshake_done();
bool process_handshake_done(std::span<const std::byte> dgram, size_t& off);
bool process_ack_frame(const QuicrPacket& packet, const QuicrFrame& frame);
void process_datagram(std::span<std::byte> dgram);
tl::expected<size_t, QuicrError> read_into(std::span<std::byte> target);
void on_tick(std::chrono::steady_clock::time_point now);
bool has_next_datagram();
std::vector<std::byte> pop_datagram();
size_t flush() {
return 0;
}
void encode_next_packet(RingByteBuffer& target);
};
}
@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
#include <random>
namespace tw::net::quicr {
static uint64_t generate_id() {
static std::mt19937_64 rng(std::random_device{}());
return rng() & 0x3FFFFFFFFFFFFFFF;
}
}
@@ -0,0 +1,62 @@
#pragma once
#include "quicr/QuicrError.hpp"
#include "tl/expected.hpp"
#include <memory>
#include <sys/socket.h>
#include <deque>
namespace tw::net::quicr {
class QuicrConnection;
class QuicrEndpoint;
class QuicrConnectionListener {
std::deque<QuicrConnection*> m_listened_connections;
QuicrConnectionListener(QuicrEndpoint* endpoint);
public:
QuicrConnectionListener(const QuicrConnectionListener&) = delete;
QuicrConnectionListener& operator=(const QuicrConnectionListener&) = delete;
QuicrConnectionListener(QuicrConnectionListener&&) = delete;
QuicrConnectionListener& operator=(QuicrConnectionListener&&) = delete;
static tl::expected<std::unique_ptr<QuicrConnectionListener>, QuicrError>
listen(QuicrEndpoint* endpoint);
QuicrConnection* listen();
void on_new_connection(QuicrConnection* connection) {
m_listened_connections.push_back(connection);
}
/**
* Receives single datagram.
*/
// tl::expected<size_t, QuicrError> recv_into(std::span<std::byte> buffer, Address* from) {
// struct sockaddr_storage sockaddr_from;
// socklen_t from_length = sizeof( sockaddr_from );
// int result = ::recvfrom(m_socket_fd, (char*)m_input_buffer.data(), m_input_buffer.size(), 0, (struct sockaddr*) &sockaddr_from, &from_length );
// *from = Address(sockaddr_from);
// return result;
// }
// sends single datagram to the given address
// void send_to(const Address& address, std::span<const std::byte> data) {
// size_t r = ::sendto(m_stream.socket_fd(), data.data(), data.size(),
// MSG_NOSIGNAL | MSG_DONTWAIT,
// address.sockaddr(), address.socklen());
// if(r <= 0) {
// spdlog::error("Failed to send datagram to {}: {}", address.to_string(), strerror(errno));
// }
// }
};
}
@@ -0,0 +1,10 @@
#pragma once
namespace tw::quicr {
enum QuicrConnectionState {
AwaitingHello = 0,
AwaitingHelloAck = 1,
Established = 2,
TimedOut = 3
};
}
@@ -0,0 +1,77 @@
#pragma once
#include "bytebuffer/ByteBuffer.hpp"
#include "bytebuffer/ByteBufferReader.hpp"
#include "bytebuffer/ByteBufferWriter.hpp"
#include "quicr/QuicrFrame.hpp"
#include "quicr/QuicrPacket.hpp"
#include <cstddef>
namespace tw::net::quicr {
class QuicrConnection;
class QuicrEncoder {
public:
static size_t encode_frame(RingByteBuffer& target, QuicrFrame& frame);
};
class QuicrDecoder {
public:
static QuicrPacket decode_packet_header(std::span<std::byte> data, size_t& offset);
static QuicrPacket decode_packet(std::span<std::byte> data);
};
template<typename T>
class QuicrFrameCodec {
public:
static size_t encode(ByteBufferWriter& writer, T& frame);
static T decode(ByteBufferReader& reader);
};
/*
* Encodes a QUICr objects into datagram byte vector.
*/
class QuicrPacketEncoder {
public:
QuicrPacketEncoder(std::span<std::byte> target, size_t& offset,
QuicrPacketType type, std::optional<uint32_t> packet_number,
QuicrConnection& connection);
QuicrPacketEncoder& encode_stream_frame(std::span<std::byte> data, bool is_reliable);
QuicrPacketEncoder& encode_ack_frame(std::vector<uint32_t>& acked_packets);
QuicrPacketEncoder& encode_frame(QuicrFrame& frame);
constexpr size_t size() const {
return m_writer.length();
}
private:
void write_length(size_t value) {
m_target[size_val_offset] = static_cast<std::byte>(value >> 24);
m_target[size_val_offset + 1] = static_cast<std::byte>(value >> 16);
m_target[size_val_offset + 2] = static_cast<std::byte>(value >> 8);
m_target[size_val_offset + 3] = static_cast<std::byte>(value);
}
void set_as_reliable() {
m_target[is_reliable_val_offset] = static_cast<std::byte>(1);
}
std::span<std::byte> m_target;
ByteBufferWriter m_writer;
size_t size_val_offset;
size_t is_reliable_val_offset;
size_t& m_offset;
QuicrPacketType m_type;
QuicrConnection& m_connection;
};
}
@@ -0,0 +1,72 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <unistd.h>
#include <tl/expected.hpp>
#include "quicr/QuicrAddress.hpp"
#include "quicr/QuicrError.hpp"
#include "quicr/QuicrConnection.hpp"
namespace tw::net::quicr {
class QuicrConnection;
class QuicrConnectionListener;
class QuicrEndpoint {
int32_t m_socket_fd;
std::unordered_map<uint64_t, std::shared_ptr<QuicrConnection>> m_connections;
std::vector<std::byte> m_inbound_buffer;
QuicrConnectionListener* m_new_connection_handler;
void process_datagram(std::span<std::byte> datagram, QuicrAddress from);
QuicrEndpoint(int socket_fd);
public:
QuicrEndpoint(const QuicrEndpoint&) = delete;
QuicrEndpoint& operator=(const QuicrEndpoint&) = delete;
QuicrEndpoint(QuicrEndpoint&&) = delete;
QuicrEndpoint& operator=(QuicrEndpoint&&) = delete;
~QuicrEndpoint() {
::close(m_socket_fd);
m_socket_fd = -1;
}
std::vector<std::pair<uint64_t, std::shared_ptr<QuicrConnection>>> clients() const {
std::vector<std::pair<uint64_t, std::shared_ptr<QuicrConnection>>> result;
for (const auto& [id, connection] : m_connections) {
result.emplace_back(id, connection);
}
return result;
}
static tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> create();
/**
* Creates the QUICr endpoint and binds it to a port.
*/
static tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> create_and_bind(int16_t port);
void assign_listener(QuicrConnectionListener* listener) {
m_new_connection_handler = listener;
}
tl::expected<void, QuicrError> bind(int port);
tl::expected<QuicrConnection*, QuicrError> connect(QuicrAddress address);
tl::expected<size_t, QuicrError> send_to(std::span<std::byte> data, QuicrAddress to);
tl::expected<size_t, QuicrError> read_from_into(std::span<std::byte> data, QuicrAddress* out_from);
void poll();
};
}
+109
View File
@@ -0,0 +1,109 @@
#pragma once
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <string>
namespace tw::net::quicr {
/**
* Error categories surfaced by the QUICr stack.
*
* The socket-level values are taken straight from <cerrno> so a QuicrError can
* be built directly from `errno` after a failed UDP syscall (see from_errno).
* The protocol-level values use a negative range to stay clear of the errno
* space; they describe QUICr conditions that have no errno equivalent.
*/
enum class QuicrErrorType : int32_t {
// Protocol-level errors (no errno equivalent).
ConnectionClosed = -1,
Unknown = -2,
// Socket / errno-derived errors raised by UDP operations.
MessageTooLong = EMSGSIZE,
AddressFamilyNotSupported = EAFNOSUPPORT,
BadFileDescriptor = EBADF,
ConnectionReset = ECONNRESET,
WouldBlock = EWOULDBLOCK,
Interrupted = EINTR,
InvalidArgument = EINVAL,
NotConnected = ENOTCONN,
NotSocket = ENOTSOCK,
OperationNotSupported = EOPNOTSUPP,
TimedOut = ETIMEDOUT,
IoError = EIO,
NoBufferSpace = ENOBUFS,
NotEnoughMemory = ENOMEM,
DestinationAddressRequired = EDESTADDRREQ,
BrokenPipe = EPIPE,
};
struct QuicrError {
public:
QuicrError(QuicrErrorType type)
: type_(type), message_(map_quicr_error_type(type)) {}
QuicrError(QuicrErrorType type, std::string message)
: type_(type), message_(std::move(message)) {}
QuicrErrorType type() const { return type_; }
std::string message() const { return message_; }
/**
* Builds a QuicrError from a raw errno value (as returned by UDP socket
* syscalls). Unknown codes still carry the errno through and fall back to
* strerror() for their message.
*/
static QuicrError from_errno(int32_t err) {
return QuicrError(static_cast<QuicrErrorType>(err));
}
private:
static std::string map_quicr_error_type(QuicrErrorType type) {
switch (type) {
case QuicrErrorType::ConnectionClosed:
return "The QUICr connection has been closed.";
case QuicrErrorType::MessageTooLong:
return "The message is larger than the maximum supported datagram size.";
case QuicrErrorType::AddressFamilyNotSupported:
return "The address family is not supported.";
case QuicrErrorType::BadFileDescriptor:
return "The socket is not a valid file descriptor.";
case QuicrErrorType::ConnectionReset:
return "The connection was forcibly closed by the peer.";
case QuicrErrorType::WouldBlock:
return "The operation would block on a non-blocking socket.";
case QuicrErrorType::Interrupted:
return "The operation was interrupted by a signal before any data was transferred.";
case QuicrErrorType::InvalidArgument:
return "An invalid argument was supplied.";
case QuicrErrorType::NotConnected:
return "The socket is not connected.";
case QuicrErrorType::NotSocket:
return "The operation was attempted on a non-socket.";
case QuicrErrorType::OperationNotSupported:
return "The operation is not supported for this socket type or protocol.";
case QuicrErrorType::TimedOut:
return "The operation timed out.";
case QuicrErrorType::IoError:
return "An I/O error occurred.";
case QuicrErrorType::NoBufferSpace:
return "Insufficient buffer space was available to complete the operation.";
case QuicrErrorType::NotEnoughMemory:
return "Insufficient memory was available to complete the operation.";
case QuicrErrorType::DestinationAddressRequired:
return "A destination address is required for this operation.";
case QuicrErrorType::BrokenPipe:
return "The write end of the socket has been closed.";
case QuicrErrorType::Unknown:
return "Unknown QUICr error.";
default:
return std::string(std::strerror(static_cast<int>(type)));
}
}
QuicrErrorType type_;
std::string message_;
};
}
@@ -0,0 +1,61 @@
#pragma once
#include "quicr/QuicrFrameType.hpp"
#include <cstddef>
#include <cstdint>
#include <span>
#include <vector>
namespace tw::net::quicr {
struct QuicrFrame {
public:
uint64_t frame_number;
FrameType type;
bool is_reliable;
std::vector<std::byte> content;
static QuicrFrame make_hello() {
QuicrFrame frame;
frame.type = FrameType::Hello;
frame.is_reliable = true;
return frame;
}
static QuicrFrame make_hello_fin() {
QuicrFrame frame;
frame.type = FrameType::HelloFin;
frame.is_reliable = true;
return frame;
}
static QuicrFrame make_stream(std::vector<std::byte> content) {
QuicrFrame frame;
frame.type = FrameType::StreamBase;
frame.is_reliable = false;
frame.content = std::move(content);
return frame;
}
static QuicrFrame make_ack(std::vector<std::uint32_t> content) {
QuicrFrame frame;
frame.type = FrameType::Ack;
frame.is_reliable = true;
frame.content = std::move(std::vector<std::byte>(
std::as_bytes(std::span(content)).begin(),
std::as_bytes(std::span(content)).end())
);
return frame;
}
};
}
@@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
namespace tw::net::quicr {
enum FrameType : uint8_t {
Padding = 0x00,
KeepAlive = 0x01,
Ack = 0x02,
AckEcn = 0x03,
ResetStream = 0x04,
StopSending = 0x05,
Crypto = 0x06,
NewToken = 0x07,
// STREAM is 0x08..0x0f (low 3 bits are flags)
StreamBase = 0x08, // interpret specially
StreamUnreliable = 0x09,
Hello = 0x10,
HelloFin = 0x11,
HandshakeDone = 0x12
};
}
@@ -0,0 +1,29 @@
#pragma once
#include "QuicrFrame.hpp"
#include "quicr/QuicrPacketType.hpp"
#include <optional>
namespace tw::net::quicr {
class QuicrPacket {
public:
QuicrPacketType type;
uint64_t destination_id;
uint64_t local_id;
bool require_ack;
std::optional<uint32_t> packet_number;
uint32_t length;
std::vector<QuicrFrame> frames;
QuicrPacket()
: type(QuicrPacketType::Unknown), destination_id(0), local_id(0),
require_ack(false), packet_number({}), length(0), frames() {}
};
} // namespace tw::net::quicr
@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
namespace tw::net::quicr {
enum class QuicrPacketType : uint8_t {
Unknown,
Initial,
Handshake,
Established
};
}
@@ -0,0 +1,95 @@
#pragma once
#include "bytebuffer/ByteBuffer.hpp"
#include "quicr/QuicrFrame.hpp"
#include <cstddef>
#include <deque>
#include <map>
#include <set>
#include <vector>
namespace tw::net::quicr {
class QuicrConnection;
struct QuicrReliablePacket {
public:
uint32_t packet_number;
std::set<uint32_t> frame_numbers;
};
struct QuicrReliableFrame {
using Clock = std::chrono::steady_clock;
Clock::time_point deadline;
QuicrFrame frame;
};
/**
* Assembles next packet from frames.
*/
class QuicrReliabilityUnit {
using Clock = std::chrono::steady_clock;
const QuicrConnection* connection;
std::vector<uint32_t> m_acks_to_send;
std::map<uint32_t, QuicrReliableFrame*> awaiting_ack_frames;
std::map<uint32_t, QuicrReliablePacket> packets_in_flight;
uint32_t m_last_frame_number = 0;
uint32_t next_frame_number() {
return ++m_last_frame_number;
}
// uint64_t m_largest_received;
// uint64_t m_ack_bitfield;
// uint64_t m_frame_number;
// size_t encode_packet_header(RingByteBuffer& buffer, const QuicrConnection* connection);
// size_t encode_frame_header(RingByteBuffer& buffer, const QuicrFrame& frame);
// size_t encode_frame_body(RingByteBuffer& buffer, const QuicrFrame& frame);
// size_t encode_frame(RingByteBuffer& buffer, const QuicrFrame& frame);
public:
QuicrReliabilityUnit(const QuicrConnection* connection) :
connection{connection}
// m_largest_received{0},
// m_ack_bitfield{0},
// m_frame_number{0}
{ }
void on_ack_received(uint32_t frame_number);
/**
* Pushes packet to acknowledge
*/
void push_ack(uint32_t packet_number);
bool has_acks_to_send() {
return m_acks_to_send.size() > 0;
}
std::vector<uint32_t> pop_acks_to_send();
void push_reliable_frame(Clock::time_point deadline, QuicrFrame&& frame);
void push_reliable_frame(Clock::time_point deadline, QuicrFrame& frame);
bool has_reliable_frames_to_resend();
/**
* Pops all frames that should be re-send and marks them with new_packet_number.
*/
std::vector<QuicrFrame> pop_frames_to_resend(uint32_t new_packet_number);
};
}
@@ -0,0 +1,35 @@
#pragma once
#include "quicr/QuicrConnection.hpp"
#include "quicr/QuicrError.hpp"
#include "tl/expected.hpp"
namespace tw::net::quicr {
class QuicrStream {
QuicrConnection* m_connection;
bool m_is_reliable;
public:
QuicrStream(QuicrConnection* connection, bool is_reliable);
tl::expected<size_t, QuicrError> write(std::span<std::byte> data) {
auto send_r = m_connection->send_message(data, m_is_reliable);
if(!send_r) {
return tl::make_unexpected(send_r.error());
}
return data.size();
}
tl::expected<size_t, QuicrError> read_into(std::span<std::byte> target) {
auto read_r = m_connection->read_into(target);
if(!read_r) {
return tl::make_unexpected(read_r.error());
}
return *read_r;
}
};
}
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <optional>
#include <span>
#include <vector>
namespace tw::net::quicr {
struct VarInt {
uint64_t value = 0;
// byte length of the number in the stream. Use to adjust offset.
size_t bytes = 0;
VarInt(uint64_t value) {
if (value <= 63) {
bytes = 1;
} else if (value <= 16383) {
bytes = 2;
} else if (value <= 1073741823) {
bytes = 4;
}
bytes = 8;
this->value = value;
}
VarInt(uint64_t value, size_t bytes) {
this->value = value;
this->bytes = bytes;
}
static std::optional<VarInt> decode(std::span<const std::byte> in) {
if (in.empty()) return std::nullopt;
return VarInt(*(uint64_t*)in.data(), sizeof(uint64_t));
// uint8_t b0 = std::to_integer<uint8_t>(in[0]);
// uint8_t prefix = (b0 >> 6) & 0x03;
// size_t len = size_t(1) << prefix; // 1, 2, 4, 8
// if (in.size() < len) return std::nullopt;
// uint64_t v = (uint64_t)(b0 & 0x3f);
// for (size_t i = 1; i < len; ++i) {
// v = (v << 8) | std::to_integer<uint8_t>(in[i]);
// }
// return VarInt(v, len);
}
size_t encode(std::vector<std::byte>& out) {
// if (value <= 63) {
// out.push_back(std::byte(value));
// return 1;
// }
// if (value <= 16383) {
// out.push_back(std::byte(0x40 | ((value >> 8) & 0x3f)));
// out.push_back(std::byte(value & 0xff));
// return 2;
// }
// if (value <= 1073741823) {
// out.push_back(std::byte(0x80 | ((value >> 24) & 0x3f)));
// out.push_back(std::byte((value >> 16) & 0xff));
// out.push_back(std::byte((value >> 8) & 0xff));
// out.push_back(std::byte(value & 0xff));
// return 4;
// }
// 8-byte
// out.push_back(std::byte(0xc0 | ((value >> 56) & 0x3f)));
// out.push_back(std::byte((value >> 48) & 0xff));
// out.push_back(std::byte((value >> 40) & 0xff));
// out.push_back(std::byte((value >> 32) & 0xff));
// out.push_back(std::byte((value >> 24) & 0xff));
// out.push_back(std::byte((value >> 16) & 0xff));
// out.push_back(std::byte((value >> 8) & 0xff));
// out.push_back(std::byte(value & 0xff));
// insert value into span
for (int i = 0; i < 8; ++i) {
out.push_back(std::byte((value >> (i * 8)) & 0xFF));
}
return 8;
}
};
}
@@ -0,0 +1,25 @@
#pragma once
#include "quicr/QuicrEncoder.hpp"
namespace tw::net::quicr {
class QuicrAckFrame {
public:
QuicrAckFrame() = default;
};
template<>
class QuicrFrameCodec<QuicrAckFrame> {
public:
static size_t encode(ByteBufferWriter& writer, QuicrAckFrame& frame) {
}
static QuicrAckFrame decode(ByteBufferReader& reader) {
}
};
}
+537
View File
@@ -0,0 +1,537 @@
#include "quicr/QuicrConnection.hpp"
#include "bytebuffer/ByteBuffer.hpp"
#include "bytebuffer/ByteBufferReader.hpp"
#include "quicr/QuicrConnectionIdGenerator.hpp"
#include "quicr/QuicrEncoder.hpp"
#include "quicr/QuicrFrame.hpp"
#include "quicr/QuicrPacket.hpp"
#include "quicr/QuicrPacketType.hpp"
#include "quicr/VarInt.hpp"
#include "quicr/QuicrFrameType.hpp"
namespace tw::net::quicr {
tl::expected<size_t, QuicrError> QuicrConnection::write_datagram(std::span<std::byte> data) {
if(m_state == QuicrConnectionState::Closed) {
spdlog::warn("Attempted to write in Closed state");
return tl::make_unexpected(QuicrError::from_errno(ENOTCONN));
}
if(m_last_heartbeat_received < Clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2)) {
m_state = QuicrConnectionState::Closed;
return tl::make_unexpected(QuicrError::from_errno(ECONNRESET));
}
std::vector<std::byte> dgram;
dgram.insert(dgram.end(), data.begin(), data.end());
auto r = m_endpoint->send_to(dgram, m_peer_address);
if (!r) return tl::make_unexpected(r.error());
m_last_heartbeat_sent = Clock::now();
return data.size();
}
void QuicrConnection::send_initial_hello() {
m_reliability_unit->push_reliable_frame(Clock::now(), QuicrFrame::make_hello());
m_state = QuicrConnectionState::SentHello;
}
void QuicrConnection::send_hello() {
std::vector<std::byte> dgram;
if(m_state != QuicrConnectionState::Closed && m_state != QuicrConnectionState::SentHello) {
spdlog::warn("Attempted to send Hello in state {}, expected Closed", (int)m_state);
return;
}
VarInt(peer_id()).encode(dgram);
VarInt(self_id()).encode(dgram);
VarInt(FrameType::Hello).encode(dgram);
VarInt(0).encode(dgram);
VarInt(PROTOCOL_VERSION).encode(dgram);
VarInt(self_id()).encode(dgram);
m_state = QuicrConnectionState::SentHello;
auto r = write_datagram(dgram);
if(!r) {
spdlog::error("Failed to send HelloAck: {}", r.error().message());
}
}
bool QuicrConnection::process_hello(const QuicrPacket& packet, const QuicrFrame& frame) {
// auto frame_number_v = VarInt::decode(dgram.subspan(off));
// if (!frame_number_v) return false;
// uint64_t frame_number = frame_number_v->value;
// off += frame_number_v->bytes;
// auto versionV = VarInt::decode(dgram.subspan(off));
// if (!versionV) return false;
// uint64_t peer_version = versionV->value;
// off += versionV->bytes;
// auto peer_connection_id_v = VarInt::decode(dgram.subspan(off));
// if (!peer_connection_id_v) return false;
// uint64_t peer_connection_id = peer_connection_id_v->value;
// off += peer_connection_id_v->bytes;
// spdlog::info("Processing hello from: {}", peer_connection_id);
// if(m_state != QuicrConnectionState::Closed) {
// spdlog::warn("Received unexpected Hello in state {}, expected Closed", (int)m_state);
// return false;
// }
// if(peer_version != PROTOCOL_VERSION) {
// spdlog::warn("Unsupported protocol version: {}, expected {}", peer_version, PROTOCOL_VERSION);
// return false;
// }
if(m_state == QuicrConnectionState::Closed) {
m_peer_id = packet.local_id;
m_state = QuicrConnectionState::ReceivedHello;
m_reliability_unit->push_reliable_frame(Clock::now(), QuicrFrame::make_hello());
} else if(m_state == QuicrConnectionState::SentHello) {
m_peer_id = packet.local_id;
m_state = QuicrConnectionState::Established;
m_reliability_unit->push_reliable_frame(Clock::now(), QuicrFrame::make_hello_fin());
}
return true;
}
bool QuicrConnection::process_hello_fin(const QuicrPacket& packet, const QuicrFrame& frame) {
if(m_state == QuicrConnectionState::ReceivedHello) {
m_state = QuicrConnectionState::Established;
return true;
}
return false;
}
void QuicrConnection::send_hello_ack_frame() {
std::vector<std::byte> dgram;
VarInt(peer_id()).encode(dgram);
VarInt(self_id()).encode(dgram);
VarInt(FrameType::HelloFin).encode(dgram);
VarInt(PROTOCOL_VERSION).encode(dgram);
VarInt(self_id()).encode(dgram);
VarInt(peer_id()).encode(dgram); // acknoledge it's ID
auto r = write_datagram(dgram);
if(!r) {
spdlog::error("Failed to send HelloAck: {}", r.error().message());
}
}
bool QuicrConnection::process_hello_ack_frame(std::span<const std::byte> dgram, size_t& off) {
auto peer_version_v = VarInt::decode(dgram.subspan(off));
if (!peer_version_v) return false;
uint64_t peer_version = peer_version_v->value;
off += peer_version_v->bytes;
auto peer_connection_id_v = VarInt::decode(dgram.subspan(off));
if (!peer_connection_id_v) return false;
uint64_t peer_connection_id = peer_connection_id_v->value;
off += peer_connection_id_v->bytes;
auto echoed_connection_id_v = VarInt::decode(dgram.subspan(off));
if (!echoed_connection_id_v) return false;
uint64_t echoed_connection_id = echoed_connection_id_v->value;
off += echoed_connection_id_v->bytes;
if(m_state != QuicrConnectionState::SentHello) {
spdlog::warn("Received unexpected HelloAck in state {}, expected SentHello", (int)m_state);
return false;
}
if(peer_version != PROTOCOL_VERSION) {
spdlog::warn("Unsupported protocol version in HelloAck: {}, expected {}", peer_version, PROTOCOL_VERSION);
return false;
}
if(echoed_connection_id != self_id()) {
spdlog::warn("HelloAck echoed wrong connection ID: {}, expected {}", echoed_connection_id, self_id());
return false;
}
m_peer_id = peer_connection_id;
send_handshake_done();
m_state = QuicrConnectionState::Established;
return true;
}
void QuicrConnection::send_handshake_done() {
std::vector<std::byte> dgram;
VarInt(peer_id()).encode(dgram);
VarInt(self_id()).encode(dgram);
VarInt(FrameType::HandshakeDone).encode(dgram);
VarInt(PROTOCOL_VERSION).encode(dgram);
VarInt(self_id()).encode(dgram);
VarInt(peer_id()).encode(dgram);
auto r = write_datagram(dgram);
if(!r) {
spdlog::error("Failed to send Handshake Done: {}", r.error().message());
}
}
bool QuicrConnection::process_handshake_done(std::span<const std::byte> dgram, size_t& off) {
auto peer_version_v = VarInt::decode(dgram.subspan(off));
if (!peer_version_v) return false;
uint64_t peer_version = peer_version_v->value;
off += peer_version_v->bytes;
auto peer_connection_id_v = VarInt::decode(dgram.subspan(off));
if (!peer_connection_id_v) return false;
uint64_t peer_connection_id = peer_connection_id_v->value;
off += peer_connection_id_v->bytes;
auto echoed_connection_id_v = VarInt::decode(dgram.subspan(off));
if (!echoed_connection_id_v) return false;
uint64_t echoed_connection_id = echoed_connection_id_v->value;
off += echoed_connection_id_v->bytes;
if(peer_version != PROTOCOL_VERSION) {
spdlog::warn("Unsupported protocol version in HelloAck: {}, expected {}", peer_version, PROTOCOL_VERSION);
return false;
}
if(echoed_connection_id != self_id()) {
spdlog::warn("HelloAck echoed wrong connection ID: {}, expected {}", echoed_connection_id, self_id());
return false;
}
m_state = QuicrConnectionState::Established;
return true;
}
tl::expected<void, QuicrError>
QuicrConnection::send_message(std::span<std::byte> data, bool is_reliable) {
if(state() == QuicrConnectionState::Closed) {
return tl::make_unexpected(QuicrError(QuicrErrorType::ConnectionClosed));
}
m_outbound_messages.emplace_back(data.begin(), data.end());
return {};
}
bool QuicrConnection::process_stream_frame(uint64_t type, std::span<const std::byte> dgram, size_t& offset) {
uint32_t length = (uint32_t)dgram.size();
m_messages.push_back(std::vector<std::byte>(dgram.begin() + offset, dgram.begin() + offset + length));
offset += length;
return true;
// bool has_off = (type & STREAM_FLAG_OFF) != 0;
// bool has_len = (type & STREAM_FLAG_LEN) != 0;
// if (has_off) {
// auto off_val = VarInt::decode(dgram.subspan(offset));
// if (!off_val) return false;
// offset += off_val->bytes;
// }
// size_t payload_len;
// // if (has_len) {
// auto len_val = VarInt::decode(dgram.subspan(offset));
// if (!len_val) return false;
// offset += len_val->bytes;
// payload_len = len_val->value;
// if (offset + payload_len > dgram.size()) return false;
// // } else {
// // payload_len = dgram.size() - offset;
// // }
// auto payload = dgram.subspan(offset, payload_len);
// m_messages.push_back(std::vector<std::byte>(payload.begin(), payload.end()));
// offset += payload_len;
return true;
}
tl::expected<void, QuicrError> QuicrConnection::send_keep_alive() {
std::vector<std::byte> dgram;
VarInt(FrameType::KeepAlive).encode(dgram);
VarInt(m_self_id).encode(dgram);
auto r = m_endpoint->send_to(dgram, m_peer_address);
if (!r) return tl::make_unexpected(r.error());
m_last_heartbeat_sent = Clock::now();
return {};
}
// void QuicrConnection::update() {
// if(m_state == QuicrConnectionState::Established) {
// if(m_last_heartbeat_sent < std::chrono::steady_clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS)) {
// auto r = send_keep_alive();
// if(!r.has_value()) {
// spdlog::error("Failed to send heartbeat - closing connection: {}", r.error().message());
// m_state = QuicrConnectionState::Closed;
// }
// }
// if(m_last_heartbeat_received < std::chrono::steady_clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2)) {
// // Connection is considered lost if we haven't received a heartbeat for twice the interval
// spdlog::warn("Connection lost due to heartbeat timeout");
// m_state = QuicrConnectionState::Closed;
// }
// }
// }
//
bool QuicrConnection::process_ack_frame(const QuicrPacket& packet, const QuicrFrame& frame) {
ByteBufferReader reader(std::span(frame.content));
uint32_t num_acks = frame.content.size() / sizeof(uint32_t);
// reader.pop_bytes(&num_acks);
for(int i = 0; i < num_acks; i++) {
uint32_t acked_packet = 0;
reader.pop_bytes(&acked_packet);
m_reliability_unit->on_ack_received(acked_packet);
}
return true;
}
void QuicrConnection::process_datagram(std::span<std::byte> dgram) {
m_last_heartbeat_received = Clock::now();
QuicrPacket packet = QuicrDecoder::decode_packet(dgram);
if(packet.require_ack) {
m_reliability_unit->push_ack(packet.packet_number.value());
}
for(auto& frame : packet.frames) {
switch(frame.type) {
case FrameType::StreamBase:
case FrameType::StreamUnreliable: {
size_t offset = 0;
process_stream_frame(frame.type, frame.content, offset);
} break;
case FrameType::Hello:
process_hello(packet, frame);
break;
case FrameType::HelloFin:
process_hello_fin(packet, frame);
break;
case FrameType::Ack:
process_ack_frame(packet, frame);
break;
// case FrameType::HelloAck:
// process_hello_ack_frame(dgram.subspan(offset + packet.header_size), offset);
// break;
// case FrameType::HandshakeDone:
// process_handshake_done(dgram.subspan(offset + packet.header_size), offset);
// break;
default:
spdlog::warn("Unknown frame type: {}", static_cast<int>(frame.type));
break;
}
}
// auto destination_id_v = VarInt::decode(dgram.subspan(offset));
// if (!destination_id_v) return;
// uint64_t destination_id = destination_id_v->value;
// offset += destination_id_v->bytes;
// auto source_id_v = VarInt::decode(dgram.subspan(offset));
// if (!source_id_v) return;
// uint64_t source_id = source_id_v->value;
// offset += source_id_v->bytes;
// m_peer_id = source_id;
// while (offset < dgram.size()) {
// auto typeV = VarInt::decode(dgram.subspan(offset));
// if (!typeV) {
// spdlog::warn("Failed to decode frame type, dropping rest of datagram");
// return;
// }
// uint64_t t = typeV->value;
// offset += typeV->bytes;
// // bool is_reliable = *(bool*)(dgram.data() + offset);
// // uint64_t packet_number = 0;
// // if(is_reliable) {
// // packet_number = VarInt::decode(dgram.subspan(offset))->value;
// // offset += VarInt::decode(dgram.subspan(offset))->bytes;
// // }
// if (t == FrameType::Padding) {
// continue;
// }
// else if (t == FrameType::KeepAlive) {
// continue;
// }
// else if (t == FrameType::Hello) {
// spdlog::info("processing hello");
// process_hello(dgram, offset);
// continue;
// }
// else if (t == FrameType::HelloAck) {
// if (!process_hello_ack_frame(dgram, offset)) return;
// continue;
// }
// else if (t == FrameType::HandshakeDone) {
// if (!process_handshake_done(dgram, offset)) return;
// continue;
// }
// else if (t >= FrameType::StreamBase && t <= (FrameType::StreamBase | 0x07)) {
// if (!process_stream_frame(t, dgram, offset)) return;
// continue;
// }
// spdlog::warn("Unknown frame on {} 0x{:x}, dropping rest of datagram", self_id(), t);
// return;
// }
}
// void QuicrConnection::drain_socket() {
// while (true) {
// auto r = m_stream.read_into(m_recv_buffer);
// if (!r || *r == 0) {
// break;
// }
// m_last_heartbeat_received = Clock::now();
// auto dgram = std::span(m_recv_buffer.data(), *r);
// size_t offset = 0;
// auto peer_connection_id_v = VarInt::decode(dgram.subspan(offset));
// if (!peer_connection_id_v) {
// spdlog::warn("Failed to decode peer connection ID, dropping datagram");
// return;
// }
// uint64_t peer_connection_id = peer_connection_id_v->value;
// offset += peer_connection_id_v->bytes;
// if(peer_connection_id != peer_id()) {
// spdlog::warn("Received datagram with wrong peer connection ID: {}, expected {}, dropping datagram", peer_connection_id, peer_id());
// return;
// }
// process_datagram(dgram.subspan(offset));
// }
// }
tl::expected<size_t, QuicrError> QuicrConnection::read_into(std::span<std::byte> target) {
if(m_messages.empty()) {
return 0;
}
auto& msg = m_messages.front();
size_t msg_len = msg.size();
size_t to_copy = std::min(msg_len, target.size());
std::memcpy(target.data(), msg.data(), to_copy);
m_messages.pop_front();
if (to_copy < msg_len) {
spdlog::warn("Message truncated: {} bytes into {} byte buffer",
msg_len, target.size());
}
return msg_len; // return full message size so caller knows if truncated
}
void QuicrConnection::on_tick(std::chrono::steady_clock::time_point now) {
// if (now - m_last_heartbeat_sent > std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS)) {
// auto keep_alive_r = send_keep_alive();
// }
if(m_last_heartbeat_received < now - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2)) {
}
}
bool QuicrConnection::has_next_datagram() {
if(m_reliability_unit->has_reliable_frames_to_resend()) {
return true;
}
if(m_reliability_unit->has_acks_to_send()) {
return true;
}
if(!m_outbound_messages.empty()) {
return true;
}
return false;
}
std::vector<std::byte> QuicrConnection::pop_datagram() {
std::vector<std::byte> datagram(64*1024);
QuicrPacketType type = QuicrPacketType::Initial;
size_t offset = 0;
uint32_t packet_number = m_packet_number++;
QuicrPacketEncoder encoder(datagram, offset, type, packet_number, *this);
// encode ACK frame
{
auto acks = m_reliability_unit->pop_acks_to_send();
encoder.encode_ack_frame(acks);
}
// re-send frames
{
// pop already encoded frames
auto frames_to_resend = m_reliability_unit->pop_frames_to_resend(packet_number);
for(auto& frame : frames_to_resend) {
frame.frame_number = packet_number;
encoder.encode_frame(frame);
// auto deadline = Clock::now() + std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS);
// m_reliability_unit->push_reliable_frame_to_send(deadline, std::move(frame));
}
}
while(true) {
if(m_outbound_messages.empty()) {
break;
}
auto outbound = m_outbound_messages.front();
m_outbound_messages.pop_front();
encoder.encode_stream_frame(outbound, true);
}
m_last_heartbeat_sent = Clock::now();
return std::vector<std::byte>(datagram.begin(), datagram.begin() + encoder.size());
}
}
@@ -0,0 +1,29 @@
#include "quicr/QuicrConnectionListener.hpp"
#include "quicr/QuicrEndpoint.hpp"
#include <spdlog/spdlog.h>
namespace tw::net::quicr {
QuicrConnectionListener::QuicrConnectionListener(QuicrEndpoint* endpoint)
: m_listened_connections()
{
endpoint->assign_listener(this);
}
tl::expected<std::unique_ptr<QuicrConnectionListener>, QuicrError> QuicrConnectionListener::listen(QuicrEndpoint* endpoint) {
return std::unique_ptr<QuicrConnectionListener>(new QuicrConnectionListener(endpoint));
};
QuicrConnection* QuicrConnectionListener::listen() {
if(m_listened_connections.size() > 0) {
QuicrConnection* connection = m_listened_connections.front();
m_listened_connections.pop_front();
return connection;
}
return nullptr;
}
}
+185
View File
@@ -0,0 +1,185 @@
#include "quicr/QuicrEncoder.hpp"
#include "quicr/QuicrConnection.hpp"
#include "bytebuffer/ByteBufferReader.hpp"
#include "quicr/QuicrFrameType.hpp"
#include <spdlog/spdlog.h>
namespace tw::net::quicr {
QuicrPacket QuicrDecoder::decode_packet_header(std::span<std::byte> data, size_t &offset) {
ByteBufferReader reader(data);
QuicrPacket packet;
reader.pop_bytes(&packet.type);
reader.pop_bytes(&packet.destination_id);
reader.pop_bytes(&packet.local_id);
reader.pop_bytes(&packet.require_ack);
// if(packet.require_ack) {
uint32_t packet_number = 0;
reader.pop_bytes(&packet_number);
packet.packet_number = packet_number;
// }
// packet.require_ack = false;
uint32_t length = 0;
reader.pop_bytes(&length);
offset = data.size() - reader.remaining();
return packet;
}
QuicrPacket QuicrDecoder::decode_packet(std::span<std::byte> data) {
size_t offset = 0;
QuicrPacket packet = decode_packet_header(data, offset);
ByteBufferReader reader(data.subspan(offset));
while(reader.remaining()) {
FrameType frame_type;
reader.pop_bytes(&frame_type);
switch(frame_type) {
case FrameType::KeepAlive:
case FrameType::Padding: {
break;
}
case FrameType::Ack: {
uint8_t num_acks = 0;
reader.pop_bytes(&num_acks);
std::vector<uint32_t> acked_packets(num_acks);
reader.pop_bytes(acked_packets.data(), acked_packets.size() * sizeof(uint32_t));
packet.frames.push_back(QuicrFrame::make_ack(acked_packets));
break;
}
case FrameType::Hello: {
packet.require_ack = true;
packet.frames.push_back(QuicrFrame::make_hello());
break;
}
case FrameType::HelloFin: {
packet.require_ack = true;
packet.frames.push_back(QuicrFrame::make_hello_fin());
break;
}
case FrameType::StreamBase:
packet.require_ack = true;
case FrameType::StreamUnreliable: {
uint32_t size = 0;
reader.pop_bytes(&size);
std::vector<std::byte> content(size);
reader.pop_bytes(content.data(), size);
packet.frames.push_back(QuicrFrame::make_stream(content));
break;
}
default: {
throw std::runtime_error("Unrecognized frame type: " + std::to_string((uint8_t)frame_type));
spdlog::error("Unrecognized frame type: {}", (uint8_t)frame_type);
break;
}
}
}
return packet;
}
QuicrPacketEncoder::QuicrPacketEncoder(std::span<std::byte> target, size_t& offset,
QuicrPacketType type, std::optional<uint32_t> packet_number,
QuicrConnection& connection)
: m_target(target), m_offset(offset), m_type(type), m_connection(connection), m_writer(m_target),
size_val_offset(0), is_reliable_val_offset(0) {
m_writer.write_bytes((uint8_t*)&type);
m_writer.write_bytes(&m_connection.peer_id());
m_writer.write_bytes(&m_connection.self_id());
is_reliable_val_offset = m_writer.length();
bool require_ack = false; // TODO: When does it need the ACK?
m_writer.write_bytes(&require_ack);
//if(require_ack) {
uint32_t _packet_num = packet_number.value();
m_writer.write_bytes(&_packet_num);
//}
uint32_t length_offset = m_writer.remaining();
uint32_t length = 0;
m_writer.write_bytes(&length);
}
QuicrPacketEncoder& QuicrPacketEncoder::encode_stream_frame(std::span<std::byte> data, bool is_reliable) {
uint8_t frame_type = is_reliable ? FrameType::StreamBase : FrameType::StreamUnreliable;
if(is_reliable) {
set_as_reliable();
}
m_writer.write_bytes(&frame_type);
uint32_t length = data.size();
m_writer.write_bytes(&length);
m_writer.write_bytes(data);
return *this;
}
QuicrPacketEncoder& QuicrPacketEncoder::encode_ack_frame(std::vector<uint32_t>& acked_packets) {
if(!acked_packets.empty()) {
uint8_t ack_frame_type = FrameType::Ack;
uint8_t acks_count = acked_packets.size();
m_writer.write_bytes(&ack_frame_type);
m_writer.write_bytes(&acks_count);
for(auto& ack : acked_packets) {
m_writer.write_bytes(&ack);
}
}
return *this;
}
QuicrPacketEncoder& QuicrPacketEncoder::encode_frame(QuicrFrame& frame) {
m_offset += m_writer.write_bytes(&frame.type);
switch(frame.type) {
case FrameType::KeepAlive:
case FrameType::Padding:
case FrameType::Hello:
set_as_reliable();
break;
case FrameType::StreamBase:
set_as_reliable();
case FrameType::StreamUnreliable:
{
m_offset += m_writer.write_bytes(frame.content);
break;
}
case FrameType::Ack:
case FrameType::AckEcn:
case FrameType::ResetStream:
case FrameType::StopSending:
case FrameType::Crypto:
case FrameType::NewToken:
case FrameType::HandshakeDone:
set_as_reliable();
m_offset += m_writer.write_bytes(frame.content);
break;
default: {
break;
}
}
return *this;
}
}
+185
View File
@@ -0,0 +1,185 @@
#include "quicr/QuicrEndpoint.hpp"
#include "quicr/QuicrConnection.hpp"
#include "quicr/QuicrConnectionListener.hpp"
#include "quicr/QuicrEncoder.hpp"
#include "tl/expected.hpp"
#include <chrono>
#include <fcntl.h>
#include <memory>
#include <tracy/Tracy.hpp>
namespace tw::net::quicr {
QuicrEndpoint::QuicrEndpoint(int socket_fd)
: m_inbound_buffer(64 * 1024), m_socket_fd(socket_fd),
m_new_connection_handler(nullptr) {
}
tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> QuicrEndpoint::create_and_bind(int16_t port) {
auto endpoint = QuicrEndpoint::create();
if (!endpoint.has_value()) {
return tl::make_unexpected(endpoint.error());
}
auto bind_r = (*endpoint)->bind(port);
if(!bind_r) {
return tl::make_unexpected(bind_r.error());
}
return std::move(*endpoint);
}
tl::expected<std::unique_ptr<QuicrEndpoint>, QuicrError> QuicrEndpoint::create() {
const int domain = AF_INET;
int socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
if(socket_fd < 0) {
spdlog::error("Failed to create socket: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}
if(fcntl(socket_fd, F_SETFL, fcntl(socket_fd, F_GETFL, 0) | O_NONBLOCK, 1) == -1) {
spdlog::error("Failed to set non-blocking mode: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}
return std::unique_ptr<QuicrEndpoint>(new QuicrEndpoint(socket_fd));
}
tl::expected<void, QuicrError> QuicrEndpoint::bind(int port) {
const int domain = AF_INET;
struct sockaddr_in addr = {};
addr.sin_family = domain;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if(::bind(m_socket_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
spdlog::error("Failed to bind socket: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}
if(fcntl(m_socket_fd, F_SETFL, fcntl(m_socket_fd, F_GETFL, 0) | O_NONBLOCK, 1) == -1) {
spdlog::error("Failed to set non-blocking mode: {}", strerror(errno));
return tl::make_unexpected(QuicrError::from_errno(errno));
}
return {};
}
/**
* Creates new connection from current socket to the address.
*/
tl::expected<QuicrConnection*, QuicrError> QuicrEndpoint::connect(QuicrAddress address) {
auto connection = std::make_shared<QuicrConnection>(0, 0, address, this);
auto inserted_r = m_connections.emplace(connection->self_id(), connection);
if(!inserted_r.second) {
return nullptr;
}
inserted_r.first->second->send_initial_hello();
return inserted_r.first->second.get();
}
void QuicrEndpoint::process_datagram(std::span<std::byte> datagram, QuicrAddress from) {
ZoneScopedN("Process Datagram");
// parse first byte as packet type
if(datagram.size() < 1) {
return;
}
size_t off = 0;
QuicrPacket packet = QuicrDecoder::decode_packet_header(datagram, off);
auto connection = m_connections.find(packet.destination_id);
if(connection == m_connections.end()) {
spdlog::warn("New connection from: {}", from.to_string());
auto conn = std::make_shared<QuicrConnection>(0, packet.local_id, from, this);
auto emplaced = m_connections.emplace(conn->self_id(), conn);
emplaced.first->second->set_peer_id(packet.local_id);
emplaced.first->second->process_datagram(datagram);
m_connections.emplace(packet.destination_id, emplaced.first->second);
return;
}
auto prev_state = connection->second->state();
connection->second->process_datagram(datagram);
if(prev_state != QuicrConnectionState::Established && connection->second->state() == QuicrConnectionState::Established) {
if(m_new_connection_handler != nullptr) {
m_new_connection_handler->on_new_connection(connection->second.get());
}
}
}
tl::expected<size_t, QuicrError> QuicrEndpoint::send_to(std::span<std::byte> data, QuicrAddress to) {
size_t total = 0;
while(total < data.size_bytes()) {
ssize_t t = ::sendto(m_socket_fd, data.data() + total, data.size() - total, MSG_NOSIGNAL | MSG_DONTWAIT, to.sockaddr(), to.socklen());
if(t == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
return tl::make_unexpected(QuicrError::from_errno(errno));
}
total += t;
}
return total;
}
tl::expected<size_t, QuicrError> QuicrEndpoint::read_from_into(std::span<std::byte> data, QuicrAddress* out_from) {
struct sockaddr_storage sockaddr_from;
socklen_t from_length = sizeof( sockaddr_from );
int read_len = ::recvfrom(m_socket_fd, data.data(), data.size(), 0, (struct sockaddr*)&sockaddr_from, &from_length);
if(read_len == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
return tl::make_unexpected(QuicrError::from_errno(errno));
}
*out_from = std::move(QuicrAddress(sockaddr_from));
return read_len;
}
void QuicrEndpoint::poll() {
while(1) {
ZoneScopedN("Reading");
QuicrAddress address({}, 0);
auto r = read_from_into(std::span(m_inbound_buffer), &address);
if(!r || *r == 0) {
break;
}
process_datagram(std::span(m_inbound_buffer).subspan(0, *r), address);
}
auto now = std::chrono::steady_clock::now();
for(auto& connection : m_connections) {
ZoneScopedN("Per Connection");
while(connection.second->has_next_datagram()) {
auto datagram = connection.second->pop_datagram();
auto send_r = send_to(datagram, connection.second->address());
if(!send_r) {
spdlog::error("Failed to send to {} datagram: {}", connection.second->address().to_string(), send_r.error().message());
break;
}
}
}
}
}
+190
View File
@@ -0,0 +1,190 @@
#include "quicr/QuicrReliability.hpp"
#include "bytebuffer/ByteBuffer.hpp"
#include "quicr/QuicrConnection.hpp"
#include <chrono>
#include <immintrin.h>
namespace tw::net::quicr {
// size_t QuicrReliabilityUnit::encode_packet_header(RingByteBuffer& target, const QuicrConnection* connection) {
// size_t size = 0;
// target.write_bytes(&connection->peer_id());
// target.write_bytes(&connection->self_id());
// return size;
// }
// size_t QuicrReliabilityUnit::encode_frame_header(RingByteBuffer& buffer, const QuicrFrame& frame) {
// size_t size = 0;
// buffer.write_bytes(&frame.type);
// if(frame.is_reliable) {
// buffer.write_bytes(&frame.is_reliable);
// buffer.write_bytes(&frame.frame_number);
// }
// return size;
// }
// size_t QuicrReliabilityUnit::encode_frame_body(RingByteBuffer& buffer, const QuicrFrame& frame) {
// size_t size = 0;
// return size;
// }
// size_t QuicrReliabilityUnit::encode_frame(RingByteBuffer& buffer, const QuicrFrame& frame) {
// size_t size = 0;
// size += encode_frame_header(frame);
// size += encode_frame_body(frame);
// return size;
// }
// std::vector<std::byte> QuicrReliabilityUnit::pop_datagram() {
// size_t size = 0;
// std::vector<std::byte> datagram;
// RingByteBuffer byte_buf(datagram);
// // write header
// byte_buf.write_bytes(&connection->peer_id());
// byte_buf.write_bytes(&connection->self_id());
// size_t last_end = byte_buf.remaining_read();
// size += encode_packet_header();
// // resend frames
// for (const auto& [timestamp, frame] : awaiting_ack_frames) {
// if(timestamp < std::chrono::steady_clock::now() - std::chrono::seconds(1)) {
// size += encode_frame(byte_buf, frame);
// last_end = byte_buf.remaining_read();
// }
// }
// // write body
// while(size < 1100) {
// auto frame = frames.front();
// if(frame.is_reliable) {
// frame.frame_number = m_frame_number++;
// }
// size += encode_frame(byte_buf, frame);
// frames.pop_front();
// }
// return datagram;
// }
// void QuicrReliabilityUnit::process_frame(QuicrFrame frame) {
// if(frame.is_reliable) {
// if(m_largest_received == frame.frame_number) {
// return;
// }
// if(m_largest_received < frame.frame_number) {
// m_ack_bitfield <<= (frame.frame_number - m_largest_received);
// m_largest_received = frame.frame_number;
// } else if(m_largest_received > frame.frame_number) {
// m_ack_bitfield |= (1ULL << (m_largest_received - frame.frame_number));
// }
// }
// }
//
bool QuicrReliabilityUnit::has_reliable_frames_to_resend() {
return std::any_of(awaiting_ack_frames.begin(), awaiting_ack_frames.end(),
[](const auto& t) { return t.second->deadline < std::chrono::steady_clock::now(); });
}
void QuicrReliabilityUnit::push_ack(uint32_t packet_number) {
m_acks_to_send.push_back(packet_number);
}
void QuicrReliabilityUnit::on_ack_received(uint32_t packet_number) {
auto packet = packets_in_flight.find(packet_number);
if(packet != packets_in_flight.end()) {
for(auto frame : packet->second.frame_numbers) {
if(awaiting_ack_frames.erase(frame) == 0) {
spdlog::error("Failed to erase frame {} from awaiting_ack_frames", frame);
continue;
}
std::erase_if(packets_in_flight, [frame, packet_number](auto& packet) {
// skip current packet
if(packet.second.packet_number == packet_number) {
return false;
}
packet.second.frame_numbers.erase(frame);
return packet.second.frame_numbers.empty();
});
}
packets_in_flight.erase(packet);
}
// m_acks_to_send.push_back(frame_idx);
// std::erase_if(awaiting_ack_frames,
// [frame_idx](const auto& t) {
// return t.second.frame_number == frame_idx;
// });
}
std::vector<QuicrFrame> QuicrReliabilityUnit::pop_frames_to_resend(uint32_t new_packet_number) {
std::vector<QuicrFrame> resend_frames;
auto packet = packets_in_flight.try_emplace(new_packet_number, QuicrReliablePacket{new_packet_number, {}});
for(auto frame : awaiting_ack_frames) {
if(frame.second->deadline < Clock::now()) {
resend_frames.push_back(std::move(frame.second->frame));
packet.first->second.frame_numbers.insert(frame.first);
frame.second->deadline = Clock::now() + std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS);
}
}
// std::erase_if(awaiting_ack_frames, [&resend_frames](const auto& item) {
// if(item.first < std::chrono::steady_clock::now()) {
// resend_frames.push_back(item.second);
// return true;
// }
// return false;
// });
return resend_frames;
}
void QuicrReliabilityUnit::push_reliable_frame(Clock::time_point deadline, QuicrFrame&& frame) {
frame.is_reliable = true;
frame.frame_number = next_frame_number();
auto frame_number = frame.frame_number;
awaiting_ack_frames[frame_number] = new QuicrReliableFrame{deadline, std::move(frame)};
// awaiting_ack_frames.emplace_back(deadline, frame);
}
void QuicrReliabilityUnit::push_reliable_frame(Clock::time_point deadline, QuicrFrame& frame) {
frame.is_reliable = true;
frame.frame_number = next_frame_number();
auto frame_number = frame.frame_number;
awaiting_ack_frames[frame_number] = new QuicrReliableFrame{deadline, std::move(frame)};
// awaiting_ack_frames.emplace_back(deadline, frame);
}
std::vector<uint32_t> QuicrReliabilityUnit::pop_acks_to_send() {
auto acks = std::vector<uint32_t>(m_acks_to_send);
m_acks_to_send.clear();
return acks;
}
}
+45
View File
@@ -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/)
+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]") {
}
+346
View File
@@ -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());
}
+122
View File
@@ -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;
}