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
+182
View File
@@ -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 {
/**
* IP Address
*/
struct Address {
private:
sockaddr_storage m_storage {};
public:
Address(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;
}
Address(sockaddr_storage& storage)
: m_storage(storage)
{ }
Address(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 Address& 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 Address& other) const { return !(*this == other); }
/**
* Retained for source compatibility. Prefer operator==.
*/
bool equals(const Address& other) const { return *this == other; }
};
}
template<>
struct std::hash<tw::net::Address> {
std::size_t operator()(const tw::net::Address& 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;
}
};
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <cerrno>
#include <cstring>
#include <string>
namespace tw::net {
enum NetworkErrorType {
MESSAGE_TOO_LONG = 90,
ADDRESS_FAMILY_NOT_SUPPORTED = 97,
BAD_FILE_DESCRIPTOR = 9,
CONNECTION_RESET = 104,
WOULD_BLOCK = 11,
INTERRUPED = 4,
INVALID_ARGUMENT = 22,
NOT_CONNECTED = 107,
NOT_SOCKET = 88,
OPERATION_NOT_SUPPORTED = 95,
TIMED_OUT = 110,
IO_ERROR = 5,
NO_BUFFER_SPACE = 105,
NOT_ENOUGH_MEMORY = 12,
DESTINATION_ADDRESS_REQUIRED = 89,
BROKEN_PIPE = 32
};
struct NetworkError {
NetworkErrorType m_type;
public:
static NetworkError from_errno(int err) {
return { static_cast<NetworkErrorType>(err) };
}
std::string message() const {
switch (m_type) {
case BAD_FILE_DESCRIPTOR:
return "The socket is not a valid file descriptor";
case CONNECTION_RESET:
return "A connection was forcibly closed by a peer.";
case INTERRUPED:
return "The function was interrupted by a signal that was caught, before any data was available.";
case INVALID_ARGUMENT:
return "The MSG_OOB flag is set and no out-of-band data is available.";
case NOT_CONNECTED:
return "A function is attempted on connection-mode socket that is not connected.";
case NOT_SOCKET:
return "Socket operation on non-socket.";
case OPERATION_NOT_SUPPORTED:
return "The specified flags are not supported for this socket type or protocol.";
case TIMED_OUT:
return "The connection timed out during connection establishment, or due to a transmission timeout on active connection.";
case IO_ERROR:
return "An I/O error occurred while reading from or writing to the file system.";
case NO_BUFFER_SPACE:
return "Insufficient resources were available in the system to perform the operation.";
case NOT_ENOUGH_MEMORY:
return "Insufficient memory was available to complete the operation.";
case DESTINATION_ADDRESS_REQUIRED:
return "The destination address is required for this operation.";
case BROKEN_PIPE:
return "The write end of a pipe or socket has been closed.";
default:
return std::string(strerror(static_cast<int>(m_type)));
}
}
};
}
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <cstring>
#include <functional>
#include <fmt/format.h>
namespace tw::net {
class NetworkResult {
private:
int m_errno;
NetworkResult(int e) :
m_errno(e)
{
}
public:
bool is(int err) {
return m_errno == err;
}
inline bool is_ok() {
return m_errno == 0;
}
static NetworkResult ok() {
return {0};
}
static NetworkResult from_errno(int e) {
return NetworkResult(e);
}
NetworkResult& and_then(std::function<void(NetworkResult)> handler) {
if(!is_ok()) {
handler(*this);
}
return *this;
}
const char* mesg() const {
return strerror(m_errno);
}
};
}
template<>
struct fmt::formatter<tw::net::NetworkResult> : fmt::formatter<std::string>
{
auto format(tw::net::NetworkResult my, format_context &ctx) const -> decltype(ctx.out())
{
return formatter<string_view>::format(my.mesg(), ctx);
}
};
+125
View File
@@ -0,0 +1,125 @@
#pragma once
#include "io/Write.hpp"
#include "io/Read.hpp"
#include <limits>
#include <span>
#include <spdlog/spdlog.h>
namespace tw::net {
class Serialization;
template<typename T>
class Serializer final {
public:
static_assert(sizeof(T), "Invalid type for serialization");
static bool serialize(tw::net::Serialization& buffer, T& value);
};
template<typename T>
concept Serializable = requires(tw::net::Serialization& b, T& t){
Serializer<T>::serialize(b, t);
};
/**
* Handles serialization of data
*/
class Serialization {
union {
Write<std::byte>* m_writeable;
Read<std::byte>* m_readable;
} m_io;
bool m_is_reading;
Serialization(Write<std::byte>* writeable) :
m_io({
.m_writeable = writeable
}),
m_is_reading(false)
{ }
Serialization(Read<std::byte>* writeable) :
m_io({
.m_readable = writeable
}),
m_is_reading(true)
{ }
public:
static Serialization reading(Read<std::byte>* read) {
return Serialization(read);
}
static Serialization writing(Write<std::byte>* writeable) {
return Serialization(writeable);
}
const bool is_reading() const {
return m_is_reading;
}
template<Serializable T>
bool serialize(T& value) {
return Serializer<T>::serialize(*this, value);
}
/**
* Special implementation that supports pointers. Useful for numeric types.
*/
template<typename T>
bool
serialize(T* value, std::enable_if<std::numeric_limits<T>::is_integer, T>::type* = 0) {
// TODO: enable only if trivially copyable
// if constexpr (std::is_trivially_copyable_v<T>) {
// if(m_is_reading) {
// const std::string packet_name = typeid(T).name();
// return m_io.m_readable->read(std::as_writable_bytes(std::span{value, 1}));
// } else {
// auto bytes = std::as_writable_bytes(std::span{value, 1});
// size_t num_writen = m_io.m_writeable->write(bytes);
// spdlog::info("Pico");
// return true;
// }
// } else {
// throw std::runtime_error("Cannot serialize non-trivially copyable type");
// }
return true;
}
bool serialize(float* target) {
union {
float floating;
unsigned int integer;
} float_val;
float_val.floating = *target;
bool result = serialize(&float_val.integer);
*target = float_val.floating;
return result;
}
bool serialize(char* buffer, size_t length) {
// if(m_is_reading) {
// return m_io.m_readable->read(std::as_writable_bytes(std::span{buffer, length}));
// } else {
// return m_io.m_writeable->write(std::as_writable_bytes(std::span{buffer, length}));
// }
return true;
}
void flush() {
if(!m_is_reading) {
m_io.m_writeable->flush();
}
}
};
}
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#include <string>
#include "Serialization.hpp"
namespace tw::net {
#define SERIALIZER(class_name, buffer, message) \
template<> bool tw::net::Serializer<class_name>::serialize(Serialization& buffer, class_name& message)
template<typename T>
class VectorSerializer final {
public:
static bool serialize(tw::net::Serialization& buffer, std::vector<T>& values) {
uint32_t size = values.size();
buffer.serialize(&size);
if(buffer.is_reading()) {
values = std::vector<T>(size);
}
for(int i = 0; i < values.size(); i++) {
buffer.serialize(values[i]);
}
return true;
}
};
template<>
class Serializer<std::string> final {
public:
static bool serialize(tw::net::Serialization& buffer, std::string& value) {
uint32_t length = value.length();
buffer.serialize(&length);
if(buffer.is_reading()) {
value = std::string();
value.resize(length);
}
buffer.serialize(value.data(), length);
return true;
}
};
}
+84
View File
@@ -0,0 +1,84 @@
#pragma once
#include <utility>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <tl/expected.hpp>
#include "Address.hpp"
#include "NetworkResult.hpp"
#include "TcpStream.hpp"
namespace tw::net {
class TcpListener {
int m_socket_fd;
Address m_address;
int m_port;
TcpListener(int socket_fd, Address address, int port) :
m_socket_fd(socket_fd),
m_address(address),
m_port(port)
{ }
public:
tl::expected<void, NetworkResult> set_non_blocking() const {
if(fcntl(m_socket_fd, F_SETFL, O_NONBLOCK, 1) == -1) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
return {};
}
TcpListener& operator=(TcpListener&& other) = delete;
TcpListener& operator=(const TcpListener& other) = delete;
TcpListener(const TcpListener& other) = delete;
TcpListener(TcpListener&& other) :
m_socket_fd(std::exchange(other.m_socket_fd, -1)),
m_address(other.m_address)
{ }
constexpr const Address& address() const { return m_address; }
static tl::expected<TcpListener, NetworkResult> listen(const Address &address, int port) {
const int domain = AF_INET;
int socket_fd = socket(domain, SOCK_STREAM, 0);
if(socket_fd < 0) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
TcpListener listener(socket_fd, address, port);
if(::bind(listener.m_socket_fd, address.sockaddr(), address.socklen())) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
if(::listen(listener.m_socket_fd, 10) < 0) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
return std::move(listener);
}
tl::expected<TcpStream, NetworkResult> listen() {
sockaddr_storage their_addr;
socklen_t addr_size = sizeof(their_addr);
int their_socket_fd = ::accept(m_socket_fd, (sockaddr*)&their_addr, &addr_size);
if(their_socket_fd < 0) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
spdlog::debug("Accepted connection");
return TcpStream{their_socket_fd, Address{their_addr}};
}
};
}
+116
View File
@@ -0,0 +1,116 @@
#pragma once
#include <cstddef>
#include <spdlog/spdlog.h>
#include <utility>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <fcntl.h>
#include "Address.hpp"
#include "NetworkError.hpp"
#include "NetworkResult.hpp"
#include "io/Read.hpp"
#include "tl/expected.hpp"
#include "io/Write.hpp"
namespace tw::net {
class TcpStream : public Write<std::byte>, public Read<std::byte> {
int m_socket_fd;
Address m_address;
public:
constexpr int socket_fd() const { return m_socket_fd; }
constexpr tl::expected<void, NetworkResult> set_non_blocking() const {
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(NetworkResult::from_errno(errno));
}
int flag = 1;
setsockopt(m_socket_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
return {};
}
TcpStream(int socket_fd, Address address) :
m_socket_fd(socket_fd),
m_address(address)
{ }
void move_from(TcpStream&& other) {
m_socket_fd = std::exchange(other.m_socket_fd, -1);
m_address = other.m_address;
}
TcpStream& operator=(TcpStream&& other) {
move_from(std::move(other));
return *this;
}
TcpStream& operator=(const TcpStream& other) = delete;
TcpStream(TcpStream&& other) :
m_socket_fd(std::exchange(other.m_socket_fd, -1)),
m_address(other.m_address)
{ }
static tl::expected<TcpStream, NetworkResult> connect(const Address &address) {
const int domain = AF_INET;
int socket_fd = socket(domain, SOCK_STREAM, 0);
if(socket_fd < 0) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
TcpStream stream(socket_fd, address);
if(::connect(stream.m_socket_fd, address.sockaddr(), address.socklen())) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
return stream;
}
tl::expected<size_t, NetworkError> write(std::span<std::byte> data) override {
uint32_t num_attempts = 0;
size_t total = 0;
while(total < data.size_bytes()) {
ssize_t t = ::send(m_socket_fd, data.data() + total, data.size() - total, MSG_NOSIGNAL | MSG_DONTWAIT);
if(t == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
num_attempts++;
continue;
}
return tl::make_unexpected(NetworkError::from_errno(errno));
}
total += t;
}
return total;
}
tl::expected<size_t, NetworkError> read_into(std::span<std::byte> data) override {
int read_len = ::recv(m_socket_fd, data.data(), data.size(), 0);
if(read_len == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
return tl::make_unexpected(NetworkError::from_errno(errno));
}
return read_len;
}
size_t flush() override {
return 0;
}
};
}
+164
View File
@@ -0,0 +1,164 @@
#pragma once
#include <cstddef>
#include <spdlog/spdlog.h>
#include <utility>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include "Address.hpp"
#include "NetworkError.hpp"
#include "NetworkResult.hpp"
#include "io/Read.hpp"
#include "tl/expected.hpp"
#include "io/Write.hpp"
namespace tw::net {
class UdpStream : public Write<std::byte> {
int m_socket_fd;
Address m_address;
public:
UdpStream(int socket_fd, Address address) :
m_socket_fd(socket_fd),
m_address(address)
{ }
public:
constexpr int socket_fd() const { return m_socket_fd; }
constexpr const Address& peer_address() const { return m_address; }
constexpr tl::expected<void, NetworkError> set_non_blocking() const {
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(NetworkError::from_errno(errno));
}
return {};
}
UdpStream() : m_address({}) { }
UdpStream& operator=(UdpStream&& other) {
this->m_socket_fd = std::exchange(other.m_socket_fd, -1);
this->m_address = other.m_address;
return *this;
}
UdpStream& operator=(const UdpStream& other) = delete;
UdpStream(const UdpStream& other) :
m_socket_fd(other.m_socket_fd),
m_address(other.m_address) {
}
UdpStream(UdpStream&& other) :
m_socket_fd(std::exchange(other.m_socket_fd, -1)),
m_address(other.m_address)
{ }
static tl::expected<UdpStream, NetworkError> bind(const Address& address) {
const int domain = AF_INET;
int socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
if(socket_fd < 0) {
return tl::make_unexpected(NetworkError::from_errno(errno));
}
if(::bind(socket_fd, address.sockaddr(), address.socklen()) < 0) {
return tl::make_unexpected(NetworkError::from_errno(errno));
}
return UdpStream(socket_fd, Address(address));
}
static tl::expected<UdpStream, NetworkResult> to(const Address &address) {
const int domain = AF_INET;
int socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
if(socket_fd < 0) {
return tl::make_unexpected(NetworkResult::from_errno(errno));
}
UdpStream stream(socket_fd, address);
// if(::connect(stream.m_socket_fd, (sockaddr*)&address.address, sizeof(address.address))) {
// return tl::make_unexpected(NetworkResult::from_errno(errno));
// }
return stream;
}
static tl::expected<UdpStream, NetworkError> to(int32_t socket_fd, const Address& address) {
return UdpStream(socket_fd, address);
}
tl::expected<size_t, NetworkError> write(std::span<std::byte> data) override {
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, m_address.sockaddr(), m_address.socklen());
if(t == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
return tl::make_unexpected(NetworkError::from_errno(errno));
}
total += t;
}
return total;
}
tl::expected<size_t, NetworkError> read_into(std::span<std::byte> data) {
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(NetworkError::from_errno(errno));
}
if(read_len > 0) {
if(!Address(sockaddr_from).equals(m_address)) {
spdlog::warn("Received datagram from unexpected address {}, expected {}", Address(sockaddr_from).to_string(), m_address.to_string());
return 0;
}
} else {
spdlog::warn("Empty datagram from {}", m_address.to_string());
}
return read_len;
}
tl::expected<size_t, NetworkError> read_into(std::span<std::byte> data, Address* 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(NetworkError::from_errno(errno));
}
*out_from = std::move(Address(sockaddr_from));
return read_len;
}
size_t flush() override {
return 0;
}
};
}
@@ -0,0 +1,126 @@
#pragma once
#include <cstring>
#include <optional>
#include <span>
#include <spdlog/spdlog.h>
#include <vector>
namespace tw::net {
/**
* Circular byte buffer.
*/
class RingByteBuffer {
public:
RingByteBuffer(std::span<std::byte> target, bool is_for_reading) : buffer(target), writeOffset(is_for_reading ? target.size() : 0) {}
size_t peek_bytes(void* dst, size_t size, size_t offset = 0) {
if(remaining_read() - offset < size) {
return 0;
}
size_t cursor = (readOffset + offset) % buffer.size();
if(cursor + size <= buffer.size()) {
std::memcpy(dst, buffer.data() + cursor, size);
} else {
size_t firstPart = buffer.size() - cursor;
std::memcpy(dst, buffer.data() + cursor, firstPart);
std::memcpy((std::byte*)dst + firstPart, buffer.data(), size - firstPart);
}
return size;
}
template<typename T>
size_t pop_bytes(T* dst) {
return pop_bytes(dst, sizeof(T));
}
size_t pop_bytes(void* dst, size_t size) {
size_t peeked = peek_bytes(dst, size);
if(peeked < size) {
return 0;
}
skip(size);
return size;
}
size_t pop_bytes(std::span<std::byte> dst) {
return pop_bytes(dst.data(), dst.size());
}
template<typename T>
size_t write_bytes(const T *data) {
return write_bytes((void*)data, sizeof(T));
}
size_t write_bytes(void* data, size_t size) {
return write_bytes(std::span<const std::byte>{(std::byte*)data, (std::byte*)data + size});
}
size_t write_bytes(std::span<const std::byte> data) {
if(remaining_write() < data.size()) {
return 0;
}
if(writeOffset + data.size() <= buffer.size()) {
std::memcpy(buffer.data() + writeOffset, data.data(), data.size());
writeOffset += data.size();
} else {
size_t firstPart = buffer.size() - writeOffset;
std::memcpy(buffer.data() + writeOffset, data.data(), firstPart);
std::memcpy(buffer.data(), data.data() + firstPart, data.size() - firstPart);
writeOffset = (writeOffset + data.size()) % buffer.size();
}
return data.size();
}
size_t remaining_write() const {
if(writeOffset >= readOffset) {
return buffer.size() - writeOffset + readOffset;
} else {
return readOffset - writeOffset;
}
}
size_t remaining_read() const {
if(writeOffset >= readOffset) {
return writeOffset - readOffset;
}
return buffer.size() - readOffset + writeOffset;
}
void reset() {
readOffset = 0;
writeOffset = 0;
}
void skip(size_t bytes) {
readOffset = (readOffset + std::min(bytes, remaining_read())) % buffer.size();
spdlog::info("New read offset: {}", readOffset);
}
void skip_write(size_t bytes) {
writeOffset = (writeOffset + std::min(bytes, remaining_write())) % buffer.size();
}
std::span<std::byte> get_next_available_block() {
if (writeOffset >= readOffset) {
return std::span(buffer.data() + writeOffset, buffer.size() - writeOffset);
} else {
return std::span(buffer.data() + writeOffset, readOffset - writeOffset);
}
}
private:
std::span<std::byte> buffer;
size_t readOffset = 0;
size_t writeOffset = 0;
};
} // namespace tw::net
@@ -0,0 +1,27 @@
#pragma once
#include "bytebuffer/ByteBuffer.hpp"
#include <type_traits>
namespace tw::net {
template<typename T, typename Enable = void>
struct ByteBufferCodec
{
static size_t encoding(RingByteBuffer&, T*, size_t offset)
{
static_assert(sizeof(T) == 0, "No decoder for this type");
}
};
/**
* Default implementation for trivially copyable types
*/
template<typename T>
struct ByteBufferCodec<T, std::enable_if_t<std::is_trivially_copyable_v<T>>>
{
static size_t encoding(RingByteBuffer& buf, T* target, size_t offset)
{
return buf.peek_bytes(target, sizeof(T), offset);
}
};
}
@@ -0,0 +1,55 @@
#pragma once
#include "ByteBuffer.hpp"
namespace tw::net {
template<typename T, typename Enable = void>
struct ByteBufferCodec
{
static T bytes(RingByteBuffer&, size_t)
{
static_assert(sizeof(T) == 0, "No decoder for this type");
}
};
/**
* Default implementation for trivially copyable types
*/
template<typename T>
struct ByteBufferCodec<T, std::enable_if_t<std::is_trivially_copyable_v<T>>>
{
static std::optional<T> encoding(RingByteBuffer& buf, size_t offset = 0)
{
T value;
size_t r = buf.peek_bytes(&value, sizeof(T), offset);
if(r < sizeof(T)) {
return {};
}
return value;
}
};
struct ByteBufferDecoder {
ByteBufferDecoder(RingByteBuffer& buf) : m_buf(buf) {}
template<typename T>
std::optional<T> pop(size_t offset = 0)
{
std::optional<T> s = ByteBufferCodec<T>::bytes(m_buf, offset);
m_buf.skip(sizeof(T));
return s;
}
template<typename T>
std::optional<T> peek(size_t offset = 0) {
return ByteBufferCodec<T>::bytes(m_buf, offset);
}
private:
RingByteBuffer& m_buf;
};
}
@@ -0,0 +1,25 @@
#pragma once
#include "ByteBuffer.hpp"
#include "bytebuffer/ByteBufferCodec.hpp"
namespace tw::net {
struct ByteBufferDecoder {
ByteBufferDecoder(RingByteBuffer& buf) : m_buf(buf) {}
template<typename T>
std::optional<T> push(size_t offset = 0)
{
std::optional<T> s = ByteBufferCodec<T>::bytes(m_buf, offset);
m_buf.skip(sizeof(T));
return s;
}
private:
RingByteBuffer& m_buf;
};
}
@@ -0,0 +1,78 @@
#pragma once
#include <cstring>
#include <span>
#include <spdlog/spdlog.h>
namespace tw::net {
/**
* Circular byte buffer.
*/
class ByteBufferReader {
public:
ByteBufferReader(std::span<std::byte> target) : buffer(target), readOffset(0) {}
ByteBufferReader(std::span<const std::byte> target) : buffer(target), readOffset(0) {}
size_t peek_bytes(void* dst, size_t size, size_t offset = 0) {
if(remaining() - offset < size) {
spdlog::warn("Could not peek entire frame, remaining: {}/{}", remaining() - offset, size);
return 0;
}
size_t cursor = readOffset + offset;
// if(cursor + size <= buffer.size()) {
std::memcpy(dst, buffer.data() + cursor, size);
// } else {
// size_t firstPart = buffer.size() - cursor;
// std::memcpy(dst, buffer.data() + cursor, firstPart);
// std::memcpy((std::byte*)dst + firstPart, buffer.data(), size - firstPart);
// }
return size;
}
template<typename T>
size_t pop_bytes(T* dst) {
return pop_bytes(dst, sizeof(T));
}
size_t pop_bytes(void* dst, size_t size) {
size_t peeked = peek_bytes(dst, size);
if(peeked < size) {
spdlog::warn("Could not read entire frame, peeked only: {}/{}", peeked, size);
return 0;
}
skip(size);
return size;
}
size_t pop_bytes(std::span<std::byte> dst) {
return pop_bytes(dst.data(), dst.size());
}
size_t position() const {
return readOffset;
}
size_t remaining() const {
return buffer.size() - readOffset;
}
void reset() {
readOffset = 0;
}
void skip(size_t bytes) {
readOffset = (readOffset + std::min(bytes, remaining()));
}
private:
std::span<const std::byte> buffer;
size_t readOffset = 0;
};
} // namespace tw::net
@@ -0,0 +1,38 @@
#pragma once
#include "io/Read.hpp"
#include "ByteBuffer.hpp"
namespace tw::net {
class ByteBufferStreamReader {
public:
static size_t read(Read<std::byte>* from, RingByteBuffer* to) {
auto block = to->get_next_available_block();
auto r = from->read_into(block);
if(!r || *r == 0) {
return 0;
}
to->skip_write(*r);
if(*r == block.size()) {
auto next_block = to->get_next_available_block();
if(next_block.size() == 0) {
return *r;
}
auto r2 = from->read_into(next_block);
if(!r2 || *r2 == 0) {
return *r;
}
to->skip_write(*r2);
return *r + *r2;
}
return *r;
}
};
}
@@ -0,0 +1,64 @@
#pragma once
#include <cstring>
#include <span>
#include <spdlog/spdlog.h>
namespace tw::net {
/**
* Circular byte buffer.
*/
class ByteBufferWriter {
public:
ByteBufferWriter(std::span<std::byte> target) : buffer(target) {}
template<typename T>
size_t write_bytes(const T *data) {
return write_bytes((void*)data, sizeof(T));
}
size_t write_bytes(void* data, size_t size) {
return write_bytes(std::span<const std::byte>{(std::byte*)data, (std::byte*)data + size});
}
size_t write_bytes(std::span<const std::byte> data) {
if(remaining() < data.size()) {
return 0;
}
if(writeOffset + data.size() <= buffer.size()) {
std::memcpy(buffer.data() + writeOffset, data.data(), data.size());
writeOffset += data.size();
} else {
size_t firstPart = buffer.size() - writeOffset;
std::memcpy(buffer.data() + writeOffset, data.data(), firstPart);
std::memcpy(buffer.data(), data.data() + firstPart, data.size() - firstPart);
writeOffset = (writeOffset + data.size()) % buffer.size();
}
return data.size();
}
constexpr size_t length() const {
return writeOffset;
}
size_t remaining() const {
return buffer.size() - writeOffset;
}
void reset() {
writeOffset = 0;
}
void skip_write(size_t bytes) {
writeOffset = (writeOffset + std::min(bytes, remaining()));
}
private:
std::span<std::byte> buffer;
size_t writeOffset = 0;
};
} // namespace tw::net
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#define GET_REF(attribute, name) \
inline const typeof(attribute)& name() const { return attribute; }
#define GET_MUT_REF(attribute, name) \
GET_REF(attribute, name) \
inline typeof(attribute)& name() { return attribute; }
#define GET(attribute, name) \
inline typeof(attribute) name() const { return attribute; }
@@ -0,0 +1,17 @@
#pragma once
#include "Address.hpp"
#include "NetworkError.hpp"
namespace tw::net {
class SocketError {
Address m_address;
NetworkError m_error;
public:
SocketError(Address address, NetworkError network_error);
};
}
@@ -0,0 +1,13 @@
#pragma once
#include <exception>
namespace tw::net {
class ByteBufferOverflowException : public std::exception {
const char* what() const noexcept override {
return "Byte buffer overflow";
}
};
}
@@ -0,0 +1,13 @@
#pragma once
#include <exception>
namespace tw::net {
class SocketClosedException : public std::exception {
public:
const char* what() const noexcept override {
return "Socket closed";
}
};
}
@@ -0,0 +1,29 @@
#pragma once
#include <cstring>
#include <exception>
namespace tw::net {
class SocketException : public std::exception {
int m_errno;
public:
int errno() {
return m_errno;
}
SocketException() {
}
SocketException(int errno) :
m_errno(errno)
{ }
const char* what() const noexcept override {
return std::strerror(m_errno);
}
};
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "common.hpp"
#include "protocol/quicr/QuicrFrameType.hpp"
#include <cstddef>
#include <vector>
namespace tw::net {
class Frame {
quicr::FrameType m_frame_type;
std::vector<std::byte> m_buffer;
public:
GET(m_frame_type, frame_type);
GET_REF(m_buffer, buffer);
Frame(quicr::FrameType type, std::vector<std::byte> buffer) :
m_frame_type(type),
m_buffer(buffer) {};
};
}
@@ -0,0 +1,15 @@
#pragma once
#include "bytebuffer/ByteBuffer.hpp"
#include "frames/Frame.hpp"
namespace tw::net::frame {
class FrameCodec {
public:
static void encode(RingByteBuffer& target, const Frame& frame);
static std::vector<Frame> decode(RingByteBuffer& target);
};
}
@@ -0,0 +1,65 @@
#pragma once
#include "Read.hpp"
#include <spdlog/spdlog.h>
#include <vector>
namespace tw::net {
template<typename T>
class BufferReader : public Read<T> {
Read<T>* m_readable;
std::vector<T> m_buffer;
size_t m_head;
size_t m_tail;
size_t remaining_size() {
return m_head - m_tail;
}
public:
BufferReader(Read<T>* readable, size_t buffer_size) :
m_readable(readable),
m_buffer(buffer_size),
m_head(0),
m_tail(0) {
}
size_t read(std::span<T> target) override {
size_t read_size = std::min(remaining_size(), target.size());
std::copy(m_buffer.begin() + m_tail,
m_buffer.begin() + m_tail + read_size,
target.begin());
spdlog::info("Read {} bytes", read_size);
m_tail += read_size;
// read next chunk
if(m_tail == m_head && read_size < target.size()) {
spdlog::info("Reading next chunk");
m_head = m_readable->read(std::span<T>(m_buffer.begin(), m_buffer.end()));
m_tail = 0;
}
if(target.size() > read_size && m_head > 0) {
read_size += read(std::span<T>(target.begin() + read_size, target.end()));
}
return read_size;
}
std::optional<T> peek() {
if(remaining_size() > 0) {
return m_buffer[m_tail];
}
return std::nullopt;
}
};
}
@@ -0,0 +1,55 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <vector>
#include "Write.hpp"
namespace tw::net {
template<typename T>
class BufferWriter : public Write<T> {
private:
Write<T>* m_writeable;
std::vector<T> m_buffer;
uint32_t m_head;
public:
size_t remaining_size() {
return m_buffer.size() - m_head;
}
BufferWriter(Write<T>* writeable, size_t buffer_size) :
m_writeable(writeable),
m_buffer(buffer_size),
m_head(0)
{ }
virtual size_t write(std::span<T> data) override {
if(remaining_size() < data.size()) {
size_t write_size = flush();
write_size += m_writeable->write_into(data);
m_head = 0;
return write_size;
}
std::copy(data.begin(), data.end(), m_buffer.begin() + m_head);
size_t write_size = data.size();
m_head += data.size();
return write_size;
}
virtual size_t flush() override {
m_writeable->write_into(std::span<T>(m_buffer.begin(), m_buffer.begin() + m_head));
size_t write_size = m_head;
m_head = 0;
return write_size;
}
};
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "NetworkError.hpp"
#include "tl/expected.hpp"
#include <span>
namespace tw::net {
template<typename T>
class Read {
public:
virtual ~Read() = default;
virtual tl::expected<size_t, NetworkError> read_into(std::span<T> target) = 0;
tl::expected<size_t, NetworkError> read_exact_into(std::span<std::byte> data) {
size_t total_read = 0;
while (total_read < data.size()) {
auto read = this->read_into(data.subspan(total_read));
if(!read.has_value()) {
if(read.error().m_type == NetworkErrorType::WOULD_BLOCK) {
continue;
} else {
return read;
}
}
total_read += read.value();
}
return total_read;
}
tl::expected<std::vector<std::byte>, NetworkError> read_exact(size_t size) {
std::vector<std::byte> buffer(size);
auto result = this->read_exact_into(std::span{buffer});
if(result.has_value()) {
return buffer;
}
return tl::make_unexpected(result.error());
}
};
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "NetworkError.hpp"
#include <tl/expected.hpp>
#include <limits>
#include <span>
#include <string>
#include <type_traits>
namespace tw::net {
template<typename T>
class Write {
public:
virtual ~Write() = default;
virtual tl::expected<size_t, NetworkError> write(std::span<T> data) = 0;
tl::expected<size_t, NetworkError> write(const std::string& data) {
return write(std::span<std::byte>((std::byte*)(data.c_str()), data.size()));
}
template<typename TNum,
typename std::enable_if_t<std::is_integral<TNum>::value || std::is_enum<TNum>::value, bool> = true>
tl::expected<size_t, NetworkError> write(TNum data) {
return write(std::as_writable_bytes(std::span{&data, 1}));
}
virtual size_t flush() = 0;
};
}
@@ -0,0 +1,168 @@
#pragma once
#include <concepts>
#include <functional>
#include <google/protobuf/message.h>
#include <span>
#include "Address.hpp"
#include "Messenger.hpp"
#include "NetworkError.hpp"
#include "TcpStream.hpp"
#include "packets/Packet.hpp"
#include "packets/LoginPacket.hpp"
#include "protocol/quicr/QuicrConnection.hpp"
namespace tw::net {
/**
* Contains handlers for each message type. Calls this handler when message comes in.
*/
class MessageHandler {
private:
// std::optional<Messenger<std::byte, quicr::QuicrConnection>> m_quicr_messenger;
// Messenger<std::byte, TcpStream> m_server_messenger;
std::unique_ptr<quicr::QuicrEndpoint> m_quicr_endpoint;
quicr::QuicrConnection* m_quicr_connection;
std::vector<std::function<tl::expected<void, NetworkError>(std::span<std::byte>)>> m_handlers;
std::unique_ptr<quicr::QuicrEndpoint> create_endpoint() {
auto endpoint_r = quicr::QuicrEndpoint::create();
if(!endpoint_r) {
spdlog::error("Failed to create QuicrEndpoint: {}", endpoint_r.error().message());
throw std::runtime_error("Failed to create QuicrEndpoint");
}
return std::make_unique<quicr::QuicrEndpoint>(std::move(endpoint_r.value()));
}
public:
const bool is_connected() const {
return m_quicr_connection->state() == quicr::QuicrConnectionState::Established;
}
MessageHandler(MessageHandler&& m)
// : m_server_messenger{std::move(m.m_server_messenger)},
:
m_handlers(std::move(m.m_handlers)),
m_quicr_endpoint(std::move(m.m_quicr_endpoint)),
m_quicr_connection(m.m_quicr_connection) {
}
MessageHandler(Address address) :
m_quicr_endpoint(create_endpoint()),
m_quicr_connection(m_quicr_endpoint->connect(address).value()),
m_handlers(100) {
spdlog::info("Connected to server at {}", address.to_string());
}
// MessageHandler(Messenger<std::byte, TcpStream>&& server_messenger) :
// // m_server_messenger{std::move(server_messenger)},
// m_quicr_connection(std::move(server_messenger.connection())),
// m_handlers(100) {
// }
template<typename T>
constexpr void set_handler(const std::function<void(T*)> handler) {
PacketType type = Message<T>::value;
m_handlers[type] = [handler, this](std::span<std::byte> data) -> tl::expected<void, NetworkError> {
T result = {};
result.ParseFromArray(data.data(), data.size());
// spdlog::info("Deserialized message [{}]: {}", (int32_t)Message<T>::value, result.DebugString());
handler(&result);
// if(m_server_messenger.peek().has_value() && m_server_messenger.peek().value() == Message<T>::value) {
// tl::expected<T, NetworkError> mesg = m_server_messenger.pop<T>(nullptr);
// if(!mesg.has_value()) {
// return tl::make_unexpected(mesg.error());
// }
// handler(&mesg.value());
// }
return {};
};
}
constexpr void set_raw_handler(uint32_t type, const std::function<tl::expected<void, NetworkError>(std::span<std::byte>)> handler) {
m_handlers[type] = handler;
}
void update() {
m_quicr_endpoint->poll();
while(true) {
std::vector<std::byte> buffer(64 * 1024);
auto read_r = m_quicr_connection->read_into(buffer);
if(!read_r) {
spdlog::error("Failed to read from QUICr stream: {}", read_r.error().message());
break;
}
if(*read_r == 0) {
break;
}
uint32_t type = reinterpret_cast<uint32_t*>(buffer.data())[0];
if(m_handlers[type] == nullptr) {
spdlog::warn("Unknown message type: {}", type);
throw std::runtime_error("Unknown message type: {}");
break;
}
auto handler_r = m_handlers[type](std::span<std::byte>(buffer.data(), *read_r).subspan(sizeof(uint32_t)));
if(!handler_r) {
spdlog::error("Handler error");
break;
}
}
// while(m_server_messenger.peek().has_value() && m_server_messenger.peek().value().has_value()) {
// std::optional<PacketType> type = m_server_messenger.peek().value();
// if(type >= m_handlers.size() || m_handlers[type.value()] == nullptr) {
// spdlog::warn("Unknown message type: {}", (int)type.value());
// break;
// }
// auto r = m_handlers[type.value()]();
// if(!r) {
// spdlog::error("Failed to handle message: {}", r.error().message());
// }
// }
}
template<std::derived_from<google::protobuf::Message> T>
tl::expected<size_t, NetworkError> send(T& mesg) {
std::string payload;
if(!mesg.SerializeToString(&payload)) {
spdlog::error("Failed to serialize message");
return 0;
}
int32_t length = payload.length();
if(length == 0) {
return 0;
}
std::vector<std::byte> bytes(length + sizeof(uint32_t));
uint32_t type = Message<T>::value;
auto payload_bytes = std::as_writable_bytes(std::span(payload));
memcpy(bytes.data(), &type, sizeof(type));
memcpy(bytes.data() + sizeof(uint32_t), payload_bytes.data(), payload_bytes.size());
auto send_r = m_quicr_connection->send_message(bytes, false);
if(!send_r) {
spdlog::error("Failed to send message: {}", send_r.error().message());
return 0;
}
return payload_bytes.size();
}
};
}
@@ -0,0 +1,209 @@
#pragma once
#include <immintrin.h>
#include <optional>
#include <span>
#include <google/protobuf/message.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <tracy/Tracy.hpp>
#include "NetworkError.hpp"
#include "packets/Packet.hpp"
#include "MessageRegistry.hpp"
#include "protocol/quicr/QuicrFrameType.hpp"
#include "tl/expected.hpp"
namespace tw::net {
template<typename TData, std::derived_from<Write<TData>> TOutput>
class Messenger {
private:
const uint32_t MAX_MESG_BODY_SIZE = 65536;
const uint32_t MESG_MAGIC = 0x1DEADBEE;
TOutput m_stream;
std::optional<PacketType> m_next_packet_type;
bool m_is_skipping;
uint32_t m_buffered_size;
size_t m_mesg_size;
size_t m_read_head;
std::vector<std::byte> m_input_buffer;
public:
Messenger(Messenger && m) :
m_stream(std::move(m.m_stream)),
m_next_packet_type(m.m_next_packet_type),
m_input_buffer(std::move(m.m_input_buffer)),
m_buffered_size(m.m_buffered_size),
m_is_skipping(m.m_is_skipping),
m_mesg_size(m.m_mesg_size),
m_read_head(m.m_read_head)
{
// m_stream.set_non_blocking();
}
Messenger(TOutput&& stream) :
m_stream(std::move(stream)),
m_input_buffer(MAX_MESG_BODY_SIZE),
m_buffered_size(0),
m_is_skipping(false),
m_mesg_size(0),
m_read_head(0)
{
// m_stream.set_non_blocking();
}
Messenger<TData, TOutput> operator=(const Messenger<TData, TOutput>&) = delete;
Messenger<TData, TOutput> operator=(Messenger<TData, TOutput>&& m) {
m_stream = std::move(m.m_stream);
m_next_packet_type = m.m_next_packet_type;
m_input_buffer = std::move(m.m_input_buffer);
m_buffered_size = m.m_buffered_size;
m_is_skipping = m.m_is_skipping;
m_mesg_size = m.m_mesg_size;
m_read_head = m.m_read_head;
}
template <std::derived_from<google::protobuf::Message> T>
tl::expected<size_t, NetworkError> send(T &content) {
ZoneScopedN("Messenger::send");
auto id = (int32_t)Message<T>::value;
std::string payload;
if(!content.SerializeToString(&payload)) {
spdlog::error("Failed to serialize message");
throw std::runtime_error("Serialization failed");
}
// spdlog::info("Sending {}: {}", (int)Message<T>::value, content.DebugString());
int32_t length = payload.length();
if(length == 0) {
return 0;
}
// append encoded id & length before payload and write it to the stream
//
const uint32_t HEADER_SIZE = 4 + 4 + 4 + 4;
std::string message;
message.resize(HEADER_SIZE + payload.length());
const uint32_t magic = 0xDEADBEEF;
const uint32_t frame_type = quicr::FrameType::StreamBase;
std::memcpy(message.data(), &magic, sizeof(magic));
std::memcpy(message.data() + sizeof(magic), &frame_type, sizeof(frame_type));
std::memcpy(message.data() + sizeof(frame_type) + sizeof(magic), &length, sizeof(length));
std::memcpy(message.data() + sizeof(frame_type) + sizeof(magic) + sizeof(length), &id, sizeof(id));
// std::memcpy(message.data() + sizeof(id) + sizeof(length), &MESG_MAGIC, sizeof(MESG_MAGIC));
std::memcpy(message.data() + HEADER_SIZE, payload.data(), payload.length());
auto write_result = m_stream.write(std::as_writable_bytes(std::span(message)));
if(!write_result.has_value()) {
return tl::make_unexpected(write_result.error());
}
return write_result.value();
}
int32_t m_packet_peek_size = 0;
tl::expected<std::optional<PacketType>, NetworkError> peek() {
ZoneScopedN("Messenger::peek");
if(m_next_packet_type.has_value()) {
return m_next_packet_type;
}
if(m_read_head < 4) {
auto result = m_stream.read_into(std::as_writable_bytes(std::span{(char*)m_input_buffer.data(), sizeof(PacketType) - m_read_head}));
if(!result.has_value()) {
return tl::make_unexpected(result.error());
}
m_read_head += result.value();
if(m_read_head < 4) {
return {};
}
}
if(m_read_head < 8) {
auto result = m_stream.read_into(std::as_writable_bytes(std::span{(char*)m_input_buffer.data() + m_read_head, 8 - m_read_head}));
if(!result.has_value()) {
return tl::make_unexpected(result.error());
}
m_read_head += result.value();
if(m_read_head < 8) {
return {};
}
m_mesg_size = *reinterpret_cast<uint32_t*>(m_input_buffer.data() + 4);
}
if(m_read_head < m_mesg_size + 8) {
if(m_mesg_size + 8 > m_input_buffer.size()) {
return tl::make_unexpected(NetworkError(NetworkErrorType::NOT_ENOUGH_MEMORY));
}
auto result = m_stream.read_into(std::as_writable_bytes(std::span{(char*)m_input_buffer.data() + m_read_head, m_mesg_size + 8 - m_read_head}));
if(!result.has_value()) {
return tl::make_unexpected(result.error());
}
m_read_head += result.value();
if(m_read_head < m_mesg_size + 8) {
return {};
}
}
m_next_packet_type = (PacketType)(*reinterpret_cast<int32_t*>(m_input_buffer.data()));
return m_next_packet_type;
}
template<typename T>
tl::expected<T, NetworkError> pop(size_t* out_size) {
ZoneScopedN("Messenger::pop");
T result = {};
result.ParseFromArray(m_input_buffer.data() + 8, m_mesg_size);
// spdlog::info("Received {}: {}", (int)m_next_packet_type.value(), result.DebugString());
m_read_head = 0;
m_mesg_size = 0;
m_next_packet_type = {};
return result;
}
void skip() {
}
void clear() {
m_next_packet_type = std::nullopt;
int message_length = 0;
int size = sizeof(message_length);
// m_stream.read_exact(std::as_writable_bytes(std::span{&message_length, 1}));
std::vector<char> data(message_length);
// m_stream.read_exact(std::as_writable_bytes(std::span{data.data(), (size_t)message_length}));
// m_input_buffer.reset();
}
};
}
@@ -0,0 +1,29 @@
#pragma once
#include "MessageRegistry.hpp"
#include <spdlog/spdlog.h>
#include <string>
#include <fstream>
class MessengerDebugLog {
public:
MessengerDebugLog(MessengerDebugLog&& m) :
m_log_file(std::move(m.m_log_file))
{ }
MessengerDebugLog(const std::string& log_file_path);
~MessengerDebugLog();
template<typename T>
void log_send(const T& message) {
spdlog::info("Sending [{}]: {}", (int)tw::Message<T>::value, message.DebugString());
}
template<typename T>
void log_recv(const T& message) {
spdlog::info("Received [{}]: {}", (int)tw::Message<T>::value, message.DebugString());
}
private:
std::ofstream m_log_file;
};
@@ -0,0 +1,157 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <stdexcept>
#include <vector>
#include <print>
namespace tw::net {
template<typename T>
struct AverageOp {
uint32_t count;
T sum;
AverageOp() :
count(0),
sum{} {
}
void add(const T value) {
sum += value;
count++;
}
T result() const {
return count == 0 ? 0 : sum / count;
}
};
template<typename T>
struct SumOp {
T sum;
void add(const T value) {
sum += value;
}
T result() const {
return sum;
}
};
template<typename T, typename Interval,
typename Operation = SumOp<T>,
typename Clock = std::chrono::steady_clock>
class BucketMetric {
T m_min, m_max;
std::vector<T> m_metric;
std::vector<uint32_t> m_bucket_idx;
Operation m_op;
uint32_t m_offset;
uint32_t m_right, m_left;
std::string m_format;
const uint32_t get_bucket(Clock::time_point time_point) const {
return std::chrono::floor<Interval>(time_point).time_since_epoch().count() - m_offset;
}
public:
BucketMetric(std::string format, uint32_t size) :
m_metric(size),
m_bucket_idx(size),
m_right(0), m_left(0),
m_offset(0),
m_format(format)
{
m_offset = get_bucket(Clock::now());
}
const T max() const {
return m_max;
}
const T min() const {
return m_min;
}
const std::string& format() const {
return m_format;
}
size_t max_size() const {
return m_metric.size();
}
void push(T value) {
auto time = Clock::now();
size_t bucket = get_bucket(time) % m_metric.size();
size_t idx = get_bucket(time);
// set result to correct bucket
if(m_right != bucket) {
m_metric[m_right] = m_op.result();
m_min = std::min(m_min, m_op.result());
m_max = std::max(m_max, m_op.result());
m_bucket_idx[m_right] = idx++;
m_right++;
m_op = {};
}
// reset all buckets until the required one
for(; m_right != bucket; m_right = (m_right + 1) % m_metric.size()) {
m_metric[m_right] = {};
m_bucket_idx[m_right] = idx++;
if(m_right == m_left) {
m_left = (m_left + 1) % m_metric.size();
}
}
m_op.add(value);
}
const size_t get_size() const {
return m_right - m_left + (m_left > m_right ? m_metric.size() : 0);
}
const T get(uint32_t idx) const {
if(idx > get_size()) {
throw std::invalid_argument("`idx` cannot be higher than buffer size");
}
return m_metric[m_left + idx].result();
}
std::span<T> get_head() {
return std::span(m_metric).subspan(m_left, (m_right > m_left ? m_right : m_metric.size()));
}
std::span<uint32_t> get_head_timeline() {
return std::span(m_bucket_idx).subspan(m_left, (m_right > m_left ? m_right : m_bucket_idx.size()));
}
std::span<T> get_tail() {
if(m_right > m_left) {
return std::span<T>();
}
return std::span(m_metric).subspan(0, m_right);
}
std::span<uint32_t> get_tail_timeline() {
if(m_right > m_left) {
return std::span<T>();
}
return std::span(m_bucket_idx).subspan(0, m_right);
}
};
}
@@ -0,0 +1,80 @@
#pragma once
#include "common.hpp"
#include <optional>
#include <string>
#include <vector>
/**
* Stores history of a property for data analysis
*/
template<typename TKey, typename TValue>
class HistoryBuffer {
private:
size_t m_head, m_tail;
std::vector<std::pair<TKey, TValue>> m_buffer;
public:
size_t size() const {
if(m_tail > m_head) {
return max_size() - m_tail + m_head;
}
return (m_head - m_tail) % max_size();
}
size_t max_size() const { return m_buffer.size(); }
bool is_full(float percentage) {
return size() / (float)max_size() > percentage;
}
void clear() {
m_tail = m_head = 0;
}
GET_REF(m_buffer, buffer);
HistoryBuffer(TKey default_key, TValue&& default_value, size_t size) :
m_head(0), m_tail(0), m_buffer(size) {
}
std::optional<const TValue*> get(TKey key) const {
for(size_t i = m_tail; i != m_head; (i++) % max_size()) {
if(m_buffer[i].first > key) {
return {};
}
if(m_buffer[i].first == key) {
return &m_buffer[i].second;
}
}
return {};
}
bool set(TKey key, const TValue& value) {
if(key < m_buffer.at(m_tail).first) {
return false;
}
int i = m_tail + 1;
if(m_tail != m_head) {
for(i = m_tail + 1; i != m_head; i++) {
if(m_buffer.at(i).first > key) {
m_buffer[(i - 1) % max_size()] = std::make_pair(key, value);
m_head++;
return true;
} else {
m_buffer[(i - 1) % max_size()] = m_buffer[i];
}
}
}
m_buffer[(i - 1) % max_size()] = std::make_pair(key, value);
m_head = (m_head + 1) % max_size();
return true;
}
};
@@ -0,0 +1,33 @@
#pragma once
#include "metrics/HistoryBuffer.hpp"
#include <filesystem>
#include <fstream>
/**
* Exports HistoryBuffer into a CSV.
*/
template<typename TKey, typename TValue>
class HistoryBufferExporter {
std::fstream m_output_file;
public:
HistoryBufferExporter(std::filesystem::path output_path) {
m_output_file = std::fstream(output_path, std::ios::out);
if(!m_output_file.is_open()) {
throw std::runtime_error("Failed to open output file");
}
}
/**
* Writes the history buffer if full and clears it.
* @return bool: true if buffer was full and exported. False otherwise.
*/
bool write(const uint32_t entity, TKey key, TValue value) {
m_output_file << entity << "," << key << "," << value.x << "," << value.y << "," << value.z << "\n";
return true;
}
};
@@ -0,0 +1,134 @@
#pragma once
#include <cstdint>
#include <chrono>
#include <filesystem>
#include <cassert>
#include "Address.hpp"
#include "BucketMetric.hpp"
#include "packets/Packet.hpp"
#include "MessageRegistry.hpp"
namespace tw::net {
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
struct NetworkSendInfo {
PacketType message_type;
bool is_sent_by_us;
Address target;
TimePoint timepoint;
std::span<uint8_t> buffer;
NetworkSendInfo(
PacketType message_type,
bool is_sent_by_us,
const Address& target,
const std::span<uint8_t> buffer
) :
message_type(message_type),
is_sent_by_us(is_sent_by_us),
target(target),
timepoint(std::chrono::steady_clock::now()),
buffer(buffer)
{
}
};
class NetworkStatsLogger {
private:
std::vector<NetworkSendInfo> m_backlog;
std::vector<uint8_t> m_buffer;
size_t m_left, m_right;
std::optional<std::ostream> m_output;
using Interval = std::chrono::seconds;
BucketMetric<uint32_t, Interval, AverageOp<uint32_t>> m_ping_metric;
BucketMetric<uint32_t, Interval, SumOp<uint32_t>> m_outgoing;
BucketMetric<uint32_t, Interval, SumOp<uint32_t>> m_incoming;
public:
NetworkStatsLogger() :
m_backlog(10000, {MESSAGE_PACKET, false, Address({}, 0), {}}),
m_buffer(1000000),
m_left(0), m_right(0),
m_ping_metric("ms", 1000),
m_outgoing("b/s", 1000),
m_incoming("b/s", 1000)
{ }
void set_file_output(std::filesystem::path path);
size_t get_size() {
return m_right - m_left + (m_right < m_left ? m_backlog.size() : 0);
}
NetworkSendInfo& get_item(uint32_t idx) {
return m_backlog[(m_left + idx) % m_backlog.size()];
}
std::span<uint8_t> allocate_memory_for_buffer(size_t size) {
uint32_t start = m_right;
if(m_buffer.size() - m_right < size) {
// throw away packets from the start to make space
for(; m_backlog[m_left].buffer.data() < m_buffer.data() + start + size &&
m_left != m_right; m_left = (m_left + 1) % m_buffer.size()) { }
start = 0;
}
uint32_t end = start + size;
return std::span<uint8_t>(m_buffer.begin() + start, m_buffer.begin() + end);
}
// constexpr void log(PacketType message_type, bool is_sent, const Address& target, const ByteBuffer& content) {
// std::span<uint8_t> span = allocate_memory_for_buffer(content.size());
// memcpy(span.data(), content.data().data(), content.size());
// m_right = (m_right + 1) % m_backlog.size();
// m_backlog[m_right] = NetworkSendInfo(message_type, is_sent, target, span);
// }
// constexpr void log_receive(
// PacketType message_type,
// const Address& from
// ) {
// log(message_type, false, from, content);
// m_incoming.push(content.size());
// }
// constexpr void log_send(
// PacketType message_type,
// const Address& to
// ) {
// log(message_type, true, to, content);
// m_outgoing.push(content.size());
// }
void log_ping(uint32_t ping) {
m_ping_metric.push(ping);
}
BucketMetric<uint32_t, Interval, AverageOp<uint32_t>>& ping(){
return m_ping_metric;
}
BucketMetric<uint32_t, Interval>& outgoing(){
return m_outgoing;
}
BucketMetric<uint32_t, Interval>& incoming(){
return m_incoming;
}
};
}
@@ -0,0 +1,67 @@
#pragma once
#include "Entity.pb.h"
#include "PlayerMove.pb.h"
#include "WorldState.pb.h"
#include "Login.pb.h"
#include "Packet.hpp"
#include "Serialization.hpp"
const int MAX_USERNAME_LENGTH = 128;
struct LoginPacket {
uint32_t username_length;
char username[MAX_USERNAME_LENGTH];
};
// template<>
// class Message<LoginPacket> {
// public:
// static constexpr PacketType value = LOGIN_REQUEST_MSG;
// };
template<>
class tw::net::Serializer<LoginPacket> final {
public:
static bool serialize(Serialization& buffer, LoginPacket& value) {
buffer.serialize(&value.username_length);
buffer.serialize(value.username, value.username_length);
return true;
}
};
// inline void to_json(json& j, const LoginPacket& value) {
// j = json{
// {"username_length", value.username_length},
// {"username", std::string(value.username, value.username_length)}
// };
// }
// inline void from_json(const json& j, LoginPacket& value) {
// j.at("username_length").get_to(value.username_length);
// j.at("username").get_to(value.username);
// }
struct LoginStatusPacket {
bool is_okay;
LoginStatusPacket() {
}
LoginStatusPacket(bool is_okay) :
is_okay(is_okay)
{
}
};
template<>
class tw::net::Serializer<LoginStatusPacket> final {
public:
static bool serialize(Serialization& buffer, LoginStatusPacket& value) {
return buffer.serialize(&value.is_okay);
}
};
@@ -0,0 +1,26 @@
#pragma once
#include "Serializers.hpp"
// #define PACKET(name) struct #name {
// template<>
// class tw::net::Serializer<const PacketType> final {
// public:
// static bool serialize(tw::net::Serialization& buffer, const PacketType& value) {
// uint32_t v = value;
// return buffer.serialize((uint32_t*)&v);
// }
// };
// template<>
// class tw::net::Serializer<PacketType> final {
// public:
// static bool serialize(tw::net::Serialization& buffer, PacketType& value) {
// return buffer.serialize((uint32_t*)&value);
// }
// };
@@ -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) {
}
};
}