#1 - quicr module
This commit is contained in:
@@ -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();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user