This commit is contained in:
Martin Slachta
2026-07-18 14:31:15 +02:00
commit a04f0dc262
3343 changed files with 1140208 additions and 0 deletions
@@ -0,0 +1,221 @@
#pragma once
#include "Address.hpp"
#include "NetworkError.hpp"
#include "bytebuffer/ByteBuffer.hpp"
#include "io/Read.hpp"
#include "protocol/quicr/QuicrConnectionIdGenerator.hpp"
#include "protocol/quicr/QuicrEndpoint.hpp"
#include "protocol/quicr/QuicrError.hpp"
#include "protocol/quicr/QuicrPacket.hpp"
#include "protocol/quicr/QuicrReliability.hpp"
#include <cstddef>
#include <chrono>
#include <deque>
#include <sys/socket.h>
#include <tl/expected.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 : Read<std::byte> {
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;
Address 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, NetworkError> write_datagram(std::span<std::byte> data);
public:
QuicrConnection(uint64_t self_id, uint64_t peer_id, Address 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 Address 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, NetworkError> 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);
// void update();
// void drain_socket();
tl::expected<size_t, NetworkError> read_into(std::span<std::byte> target) override;
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 "NetworkError.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>, NetworkError>
listen(QuicrEndpoint* endpoint);
QuicrConnection* listen();
void on_new_connection(QuicrConnection* connection) {
m_listened_connections.push_back(connection);
}
/**
* Receives single datagram.
*/
// tl::expected<size_t, NetworkError> 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 "protocol/quicr/QuicrFrame.hpp"
#include "protocol/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,70 @@
#pragma once
#include "Address.hpp"
#include "NetworkError.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
#include <tl/expected.hpp>
#include <memory>
#include <unordered_map>
#include <unistd.h>
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, Address 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>, NetworkError> create();
/**
* Creates the QUICr endpoint and binds it to a port.
*/
static tl::expected<std::unique_ptr<QuicrEndpoint>, NetworkError> create_and_bind(int16_t port);
void assign_listener(QuicrConnectionListener* listener) {
m_new_connection_handler = listener;
}
tl::expected<void, NetworkError> bind(int port);
tl::expected<QuicrConnection*, NetworkError> connect(Address address);
tl::expected<size_t, NetworkError> send_to(std::span<std::byte> data, Address to);
tl::expected<size_t, NetworkError> read_from_into(std::span<std::byte> data, Address* out_from);
void poll();
};
}
@@ -0,0 +1,34 @@
#pragma once
#include <algorithm>
#include <string>
namespace tw::net::quicr {
enum class QuicrErrorType {
ConnectionClosed
};
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_; }
private:
static std::string map_quicr_error_type(QuicrErrorType type) {
switch (type) {
case QuicrErrorType::ConnectionClosed:
return "ConnectionClosed";
default:
return "Unknown";
}
}
std::string message_;
QuicrErrorType type_;
};
}
@@ -0,0 +1,61 @@
#pragma once
#include "protocol/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 "protocol/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 "protocol/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,34 @@
#include "NetworkError.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
#include "tl/expected.hpp"
namespace tw::net::quicr {
class QuicrStream : Write<std::byte>, Read<std::byte> {
QuicrConnection* m_connection;
bool m_is_reliable;
public:
QuicrStream(QuicrConnection* connection, bool is_reliable);
tl::expected<size_t, NetworkError> write(std::span<std::byte> data) override {
auto send_r = m_connection->send_message(data, m_is_reliable);
if(!send_r) {
return tl::make_unexpected(NetworkError::from_errno(CONNECTION_RESET));
}
return *send_r;
}
tl::expected<size_t, NetworkError> read_into(std::span<std::byte> target) override {
auto read_r = m_connection->read_into(target);
if(!read_r) {
return tl::make_unexpected(NetworkError::from_errno(CONNECTION_RESET));
}
return *read_r;
}
};
}
@@ -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 "protocol/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) {
}
};
}