#1 - quicr module

This commit is contained in:
Martin Slachta
2026-07-22 17:34:44 +02:00
parent a04f0dc262
commit f4174eb0c7
177 changed files with 5309 additions and 2265 deletions
@@ -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) {
}
};
}