initial
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
project(tw_network)
|
||||
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
file(GLOB FILES
|
||||
src/*.cpp
|
||||
src/messenger/*.cpp
|
||||
src/protocol/quicr/*.cpp
|
||||
src/frames/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB HEADERS
|
||||
include/*.hpp
|
||||
include/exception/*.hpp
|
||||
include/io/*.hpp
|
||||
include/messenger/*.hpp
|
||||
include/packets/*.hpp
|
||||
include/metrics/*.hpp
|
||||
include/protocol/quicr/*.hpp
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME} OBJECT ${FILES})
|
||||
add_library(tw::network ALIAS tw_network)
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC FILE_SET HEADERS
|
||||
BASE_DIRS include
|
||||
FILES ${HEADERS})
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE 1)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
./include/
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
spdlog::spdlog
|
||||
tw::protocol
|
||||
tl::expected
|
||||
Tracy::TracyClient
|
||||
TracyClient
|
||||
EnTT::EnTT
|
||||
)
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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}};
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "metrics/NetworkStatsLogger.hpp"
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "frames/FrameCodec.hpp"
|
||||
|
||||
#include "bytebuffer/ByteBufferDecoder.hpp"
|
||||
|
||||
namespace tw::net::frame {
|
||||
|
||||
// static void encode(ByteBuffer& target, const Frame& frame) {
|
||||
// if(frame.frame_type() == quicr::FrameType::StreamBase) {
|
||||
// uint32_t frame_type = frame.frame_type();
|
||||
// target.write_bytes(&frame_type, sizeof(frame_type));
|
||||
|
||||
// uint32_t length = frame.buffer().size();
|
||||
// target.write_bytes(&length, sizeof(length));
|
||||
|
||||
// target.write_bytes((void*)frame.buffer().data(), frame.buffer().size());
|
||||
// return;
|
||||
// }
|
||||
|
||||
// spdlog::warn("Unknown frame type");
|
||||
// }
|
||||
|
||||
// static std::vector<Frame> decode(ByteBuffer& bytes) {
|
||||
// spdlog::info("Parsing frames");
|
||||
// std::vector<Frame> frames;
|
||||
|
||||
// // parse first byte as packet type
|
||||
// if(bytes.remaining_read() < 1) {
|
||||
// spdlog::warn("Nothing to read");
|
||||
// return {};
|
||||
// }
|
||||
|
||||
// size_t offset = 0;
|
||||
// size_t magic_search_length = 0;
|
||||
|
||||
// while (offset < bytes.remaining_read()) {
|
||||
// if(magic_search_length > 3) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// auto magic = ByteBufferDecoder<uint32_t>::decode(bytes);
|
||||
// if(magic != 0xDEADBEEF) {
|
||||
// spdlog::warn("DEADBEEF not found");
|
||||
// bytes.skip(1);
|
||||
// magic_search_length++;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// auto t = ByteBufferDecoder<uint32_t>::decode(bytes, 4);
|
||||
// if(!t) {
|
||||
// spdlog::warn("Failed to decode frame type bytes");
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if (*t == quicr::FrameType::Padding) {
|
||||
// spdlog::info("padding");
|
||||
// bytes.skip(2 * sizeof(uint32_t));
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (*t == quicr::FrameType::KeepAlive) {
|
||||
// spdlog::info("keep alive");
|
||||
// bytes.skip(2 * sizeof(uint32_t));
|
||||
// // TODO: dispatch to connection if you want per-conn ping
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// if (*t == quicr::FrameType::StreamBase) {
|
||||
// spdlog::info("Stream!");
|
||||
// auto length_r = ByteBufferDecoder<uint32_t>::decode(bytes, 8);
|
||||
// if(!length_r) {
|
||||
// spdlog::warn("Length could not be decoded");
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// std::vector<std::byte> payload(*length_r + 4 * sizeof(uint32_t));
|
||||
// auto popped_bytes = bytes.pop_bytes(payload); // skip length field
|
||||
// spdlog::info("Popped: {} ", popped_bytes);
|
||||
// if(popped_bytes == 0) {
|
||||
// spdlog::warn("Not enought bytes in the buffer");
|
||||
// break;
|
||||
// }
|
||||
|
||||
// std::vector<std::byte> payload_without_header(payload.begin() + 3 * sizeof(uint32_t), payload.end());
|
||||
|
||||
// frames.emplace_back(quicr::FrameType::StreamBase, payload_without_header);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// bytes.skip(2 * sizeof(uint32_t));
|
||||
// spdlog::warn("unknown frame {}", *t);
|
||||
|
||||
// // Unknown frame — can't skip safely
|
||||
// break;
|
||||
// }
|
||||
|
||||
// spdlog::info("Parsed {} frames", frames.size());
|
||||
|
||||
// return frames;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "messenger/Messenger.hpp"
|
||||
|
||||
namespace tw::net {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "messenger/MessengerDebugLog.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
MessengerDebugLog::MessengerDebugLog(const std::string& log_file_path) : m_log_file(log_file_path) {
|
||||
if (!m_log_file.is_open()) {
|
||||
throw std::runtime_error("Failed to open log file");
|
||||
}
|
||||
}
|
||||
|
||||
MessengerDebugLog::~MessengerDebugLog() {
|
||||
m_log_file.close();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include "metrics/HistoryBufferExporter.hpp"
|
||||
@@ -0,0 +1,556 @@
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
|
||||
#include "bytebuffer/ByteBuffer.hpp"
|
||||
#include "bytebuffer/ByteBufferReader.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionIdGenerator.hpp"
|
||||
#include "protocol/quicr/QuicrEncoder.hpp"
|
||||
#include "protocol/quicr/QuicrFrame.hpp"
|
||||
#include "protocol/quicr/QuicrPacket.hpp"
|
||||
#include "protocol/quicr/QuicrPacketType.hpp"
|
||||
#include "protocol/quicr/VarInt.hpp"
|
||||
#include "protocol/quicr/QuicrFrameType.hpp"
|
||||
#include <absl/strings/str_format.h>
|
||||
|
||||
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
tl::expected<size_t, NetworkError> QuicrConnection::write_datagram(std::span<std::byte> data) {
|
||||
if(m_state == QuicrConnectionState::Closed) {
|
||||
spdlog::warn("Attempted to write in Closed state");
|
||||
return tl::make_unexpected(NetworkError::from_errno(ENOTCONN));
|
||||
}
|
||||
|
||||
if(m_last_heartbeat_received < Clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2)) {
|
||||
m_state = QuicrConnectionState::Closed;
|
||||
return tl::make_unexpected(NetworkError::from_errno(CONNECTION_RESET));
|
||||
}
|
||||
|
||||
std::vector<std::byte> dgram;
|
||||
dgram.insert(dgram.end(), data.begin(), data.end());
|
||||
|
||||
auto r = m_endpoint->send_to(dgram, m_peer_address);
|
||||
if (!r) return tl::make_unexpected(r.error());
|
||||
|
||||
m_last_heartbeat_sent = Clock::now();
|
||||
return data.size();
|
||||
}
|
||||
|
||||
void QuicrConnection::send_initial_hello() {
|
||||
m_reliability_unit->push_reliable_frame(Clock::now(), QuicrFrame::make_hello());
|
||||
m_state = QuicrConnectionState::SentHello;
|
||||
}
|
||||
|
||||
|
||||
void QuicrConnection::send_hello() {
|
||||
std::vector<std::byte> dgram;
|
||||
|
||||
if(m_state != QuicrConnectionState::Closed && m_state != QuicrConnectionState::SentHello) {
|
||||
spdlog::warn("Attempted to send Hello in state {}, expected Closed", (int)m_state);
|
||||
return;
|
||||
}
|
||||
|
||||
VarInt(peer_id()).encode(dgram);
|
||||
VarInt(self_id()).encode(dgram);
|
||||
VarInt(FrameType::Hello).encode(dgram);
|
||||
VarInt(0).encode(dgram);
|
||||
VarInt(PROTOCOL_VERSION).encode(dgram);
|
||||
VarInt(self_id()).encode(dgram);
|
||||
|
||||
m_state = QuicrConnectionState::SentHello;
|
||||
auto r = write_datagram(dgram);
|
||||
if(!r) {
|
||||
spdlog::error("Failed to send HelloAck: {}", r.error().message());
|
||||
}
|
||||
}
|
||||
|
||||
bool QuicrConnection::process_hello(const QuicrPacket& packet, const QuicrFrame& frame) {
|
||||
// auto frame_number_v = VarInt::decode(dgram.subspan(off));
|
||||
// if (!frame_number_v) return false;
|
||||
// uint64_t frame_number = frame_number_v->value;
|
||||
// off += frame_number_v->bytes;
|
||||
|
||||
// auto versionV = VarInt::decode(dgram.subspan(off));
|
||||
// if (!versionV) return false;
|
||||
// uint64_t peer_version = versionV->value;
|
||||
// off += versionV->bytes;
|
||||
|
||||
// auto peer_connection_id_v = VarInt::decode(dgram.subspan(off));
|
||||
// if (!peer_connection_id_v) return false;
|
||||
// uint64_t peer_connection_id = peer_connection_id_v->value;
|
||||
// off += peer_connection_id_v->bytes;
|
||||
|
||||
// spdlog::info("Processing hello from: {}", peer_connection_id);
|
||||
|
||||
// if(m_state != QuicrConnectionState::Closed) {
|
||||
// spdlog::warn("Received unexpected Hello in state {}, expected Closed", (int)m_state);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if(peer_version != PROTOCOL_VERSION) {
|
||||
// spdlog::warn("Unsupported protocol version: {}, expected {}", peer_version, PROTOCOL_VERSION);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if(m_state == QuicrConnectionState::Closed) {
|
||||
m_peer_id = packet.local_id;
|
||||
m_state = QuicrConnectionState::ReceivedHello;
|
||||
m_reliability_unit->push_reliable_frame(Clock::now(), QuicrFrame::make_hello());
|
||||
} else if(m_state == QuicrConnectionState::SentHello) {
|
||||
m_peer_id = packet.local_id;
|
||||
m_state = QuicrConnectionState::Established;
|
||||
m_reliability_unit->push_reliable_frame(Clock::now(), QuicrFrame::make_hello_fin());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QuicrConnection::process_hello_fin(const QuicrPacket& packet, const QuicrFrame& frame) {
|
||||
if(m_state == QuicrConnectionState::ReceivedHello) {
|
||||
m_state = QuicrConnectionState::Established;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void QuicrConnection::send_hello_ack_frame() {
|
||||
std::vector<std::byte> dgram;
|
||||
|
||||
VarInt(peer_id()).encode(dgram);
|
||||
VarInt(self_id()).encode(dgram);
|
||||
VarInt(FrameType::HelloFin).encode(dgram);
|
||||
VarInt(PROTOCOL_VERSION).encode(dgram);
|
||||
VarInt(self_id()).encode(dgram);
|
||||
VarInt(peer_id()).encode(dgram); // acknoledge it's ID
|
||||
|
||||
auto r = write_datagram(dgram);
|
||||
if(!r) {
|
||||
spdlog::error("Failed to send HelloAck: {}", r.error().message());
|
||||
}
|
||||
}
|
||||
|
||||
bool QuicrConnection::process_hello_ack_frame(std::span<const std::byte> dgram, size_t& off) {
|
||||
auto peer_version_v = VarInt::decode(dgram.subspan(off));
|
||||
if (!peer_version_v) return false;
|
||||
uint64_t peer_version = peer_version_v->value;
|
||||
off += peer_version_v->bytes;
|
||||
|
||||
auto peer_connection_id_v = VarInt::decode(dgram.subspan(off));
|
||||
if (!peer_connection_id_v) return false;
|
||||
uint64_t peer_connection_id = peer_connection_id_v->value;
|
||||
off += peer_connection_id_v->bytes;
|
||||
|
||||
auto echoed_connection_id_v = VarInt::decode(dgram.subspan(off));
|
||||
if (!echoed_connection_id_v) return false;
|
||||
uint64_t echoed_connection_id = echoed_connection_id_v->value;
|
||||
off += echoed_connection_id_v->bytes;
|
||||
|
||||
if(m_state != QuicrConnectionState::SentHello) {
|
||||
spdlog::warn("Received unexpected HelloAck in state {}, expected SentHello", (int)m_state);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(peer_version != PROTOCOL_VERSION) {
|
||||
spdlog::warn("Unsupported protocol version in HelloAck: {}, expected {}", peer_version, PROTOCOL_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(echoed_connection_id != self_id()) {
|
||||
spdlog::warn("HelloAck echoed wrong connection ID: {}, expected {}", echoed_connection_id, self_id());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_peer_id = peer_connection_id;
|
||||
|
||||
send_handshake_done();
|
||||
|
||||
m_state = QuicrConnectionState::Established;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void QuicrConnection::send_handshake_done() {
|
||||
std::vector<std::byte> dgram;
|
||||
|
||||
VarInt(peer_id()).encode(dgram);
|
||||
VarInt(self_id()).encode(dgram);
|
||||
VarInt(FrameType::HandshakeDone).encode(dgram);
|
||||
VarInt(PROTOCOL_VERSION).encode(dgram);
|
||||
VarInt(self_id()).encode(dgram);
|
||||
VarInt(peer_id()).encode(dgram);
|
||||
|
||||
auto r = write_datagram(dgram);
|
||||
if(!r) {
|
||||
spdlog::error("Failed to send Handshake Done: {}", r.error().message());
|
||||
}
|
||||
}
|
||||
|
||||
bool QuicrConnection::process_handshake_done(std::span<const std::byte> dgram, size_t& off) {
|
||||
auto peer_version_v = VarInt::decode(dgram.subspan(off));
|
||||
if (!peer_version_v) return false;
|
||||
uint64_t peer_version = peer_version_v->value;
|
||||
off += peer_version_v->bytes;
|
||||
|
||||
auto peer_connection_id_v = VarInt::decode(dgram.subspan(off));
|
||||
if (!peer_connection_id_v) return false;
|
||||
uint64_t peer_connection_id = peer_connection_id_v->value;
|
||||
off += peer_connection_id_v->bytes;
|
||||
|
||||
auto echoed_connection_id_v = VarInt::decode(dgram.subspan(off));
|
||||
if (!echoed_connection_id_v) return false;
|
||||
uint64_t echoed_connection_id = echoed_connection_id_v->value;
|
||||
off += echoed_connection_id_v->bytes;
|
||||
|
||||
if(peer_version != PROTOCOL_VERSION) {
|
||||
spdlog::warn("Unsupported protocol version in HelloAck: {}, expected {}", peer_version, PROTOCOL_VERSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(echoed_connection_id != self_id()) {
|
||||
spdlog::warn("HelloAck echoed wrong connection ID: {}, expected {}", echoed_connection_id, self_id());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_state = QuicrConnectionState::Established;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
tl::expected<void, QuicrError>
|
||||
QuicrConnection::send_message(std::span<std::byte> data, bool is_reliable) {
|
||||
if(state() == QuicrConnectionState::Closed) {
|
||||
return tl::make_unexpected(QuicrError(QuicrErrorType::ConnectionClosed));
|
||||
}
|
||||
|
||||
m_outbound_messages.emplace_back(data.begin(), data.end());
|
||||
|
||||
return {};
|
||||
|
||||
// std::vector<std::byte> dgram;
|
||||
|
||||
// VarInt(peer_id()).encode(dgram);
|
||||
// VarInt(self_id()).encode(dgram);
|
||||
// VarInt(FrameType::StreamBase).encode(dgram);
|
||||
// VarInt(data.size()).encode(dgram);
|
||||
|
||||
// dgram.insert(dgram.end(), data.begin(), data.end());
|
||||
|
||||
// auto r = write_datagram(dgram);
|
||||
// if (!r) {
|
||||
// spdlog::error("Failed to send stream frame: {}", r.error().message());
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// return true;
|
||||
}
|
||||
|
||||
bool QuicrConnection::process_stream_frame(uint64_t type, std::span<const std::byte> dgram, size_t& offset) {
|
||||
uint32_t length = (uint32_t)dgram.size();
|
||||
|
||||
m_messages.push_back(std::vector<std::byte>(dgram.begin() + offset, dgram.begin() + offset + length));
|
||||
offset += length;
|
||||
|
||||
return true;
|
||||
|
||||
// bool has_off = (type & STREAM_FLAG_OFF) != 0;
|
||||
// bool has_len = (type & STREAM_FLAG_LEN) != 0;
|
||||
|
||||
// if (has_off) {
|
||||
// auto off_val = VarInt::decode(dgram.subspan(offset));
|
||||
// if (!off_val) return false;
|
||||
// offset += off_val->bytes;
|
||||
// }
|
||||
|
||||
// size_t payload_len;
|
||||
// // if (has_len) {
|
||||
// auto len_val = VarInt::decode(dgram.subspan(offset));
|
||||
// if (!len_val) return false;
|
||||
// offset += len_val->bytes;
|
||||
// payload_len = len_val->value;
|
||||
|
||||
// if (offset + payload_len > dgram.size()) return false;
|
||||
// // } else {
|
||||
// // payload_len = dgram.size() - offset;
|
||||
// // }
|
||||
|
||||
// auto payload = dgram.subspan(offset, payload_len);
|
||||
// m_messages.push_back(std::vector<std::byte>(payload.begin(), payload.end()));
|
||||
// offset += payload_len;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
tl::expected<void, NetworkError> QuicrConnection::send_keep_alive() {
|
||||
std::vector<std::byte> dgram;
|
||||
VarInt(FrameType::KeepAlive).encode(dgram);
|
||||
VarInt(m_self_id).encode(dgram);
|
||||
|
||||
auto r = m_endpoint->send_to(dgram, m_peer_address);
|
||||
if (!r) return tl::make_unexpected(r.error());
|
||||
|
||||
m_last_heartbeat_sent = Clock::now();
|
||||
return {};
|
||||
}
|
||||
|
||||
// void QuicrConnection::update() {
|
||||
// if(m_state == QuicrConnectionState::Established) {
|
||||
// if(m_last_heartbeat_sent < std::chrono::steady_clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS)) {
|
||||
// auto r = send_keep_alive();
|
||||
// if(!r.has_value()) {
|
||||
// spdlog::error("Failed to send heartbeat - closing connection: {}", r.error().message());
|
||||
// m_state = QuicrConnectionState::Closed;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if(m_last_heartbeat_received < std::chrono::steady_clock::now() - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2)) {
|
||||
// // Connection is considered lost if we haven't received a heartbeat for twice the interval
|
||||
// spdlog::warn("Connection lost due to heartbeat timeout");
|
||||
// m_state = QuicrConnectionState::Closed;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
bool QuicrConnection::process_ack_frame(const QuicrPacket& packet, const QuicrFrame& frame) {
|
||||
ByteBufferReader reader(std::span(frame.content));
|
||||
|
||||
uint32_t num_acks = frame.content.size() / sizeof(uint32_t);
|
||||
// reader.pop_bytes(&num_acks);
|
||||
|
||||
for(int i = 0; i < num_acks; i++) {
|
||||
uint32_t acked_packet = 0;
|
||||
reader.pop_bytes(&acked_packet);
|
||||
|
||||
m_reliability_unit->on_ack_received(acked_packet);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QuicrConnection::process_datagram(std::span<std::byte> dgram) {
|
||||
m_last_heartbeat_received = Clock::now();
|
||||
|
||||
QuicrPacket packet = QuicrDecoder::decode_packet(dgram);
|
||||
|
||||
if(packet.require_ack) {
|
||||
m_reliability_unit->push_ack(packet.packet_number.value());
|
||||
}
|
||||
|
||||
for(auto& frame : packet.frames) {
|
||||
switch(frame.type) {
|
||||
case FrameType::StreamBase:
|
||||
case FrameType::StreamUnreliable: {
|
||||
size_t offset = 0;
|
||||
process_stream_frame(frame.type, frame.content, offset);
|
||||
} break;
|
||||
case FrameType::Hello:
|
||||
process_hello(packet, frame);
|
||||
break;
|
||||
case FrameType::HelloFin:
|
||||
process_hello_fin(packet, frame);
|
||||
break;
|
||||
case FrameType::Ack:
|
||||
process_ack_frame(packet, frame);
|
||||
break;
|
||||
// case FrameType::HelloAck:
|
||||
// process_hello_ack_frame(dgram.subspan(offset + packet.header_size), offset);
|
||||
// break;
|
||||
// case FrameType::HandshakeDone:
|
||||
// process_handshake_done(dgram.subspan(offset + packet.header_size), offset);
|
||||
// break;
|
||||
default:
|
||||
spdlog::warn("Unknown frame type: {}", static_cast<int>(frame.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// auto destination_id_v = VarInt::decode(dgram.subspan(offset));
|
||||
// if (!destination_id_v) return;
|
||||
// uint64_t destination_id = destination_id_v->value;
|
||||
// offset += destination_id_v->bytes;
|
||||
|
||||
// auto source_id_v = VarInt::decode(dgram.subspan(offset));
|
||||
// if (!source_id_v) return;
|
||||
// uint64_t source_id = source_id_v->value;
|
||||
// offset += source_id_v->bytes;
|
||||
|
||||
// m_peer_id = source_id;
|
||||
|
||||
// while (offset < dgram.size()) {
|
||||
// auto typeV = VarInt::decode(dgram.subspan(offset));
|
||||
// if (!typeV) {
|
||||
// spdlog::warn("Failed to decode frame type, dropping rest of datagram");
|
||||
// return;
|
||||
// }
|
||||
// uint64_t t = typeV->value;
|
||||
// offset += typeV->bytes;
|
||||
|
||||
// // bool is_reliable = *(bool*)(dgram.data() + offset);
|
||||
|
||||
// // uint64_t packet_number = 0;
|
||||
// // if(is_reliable) {
|
||||
// // packet_number = VarInt::decode(dgram.subspan(offset))->value;
|
||||
// // offset += VarInt::decode(dgram.subspan(offset))->bytes;
|
||||
// // }
|
||||
|
||||
// if (t == FrameType::Padding) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// else if (t == FrameType::KeepAlive) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// else if (t == FrameType::Hello) {
|
||||
// spdlog::info("processing hello");
|
||||
// process_hello(dgram, offset);
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// else if (t == FrameType::HelloAck) {
|
||||
// if (!process_hello_ack_frame(dgram, offset)) return;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// else if (t == FrameType::HandshakeDone) {
|
||||
// if (!process_handshake_done(dgram, offset)) return;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// else if (t >= FrameType::StreamBase && t <= (FrameType::StreamBase | 0x07)) {
|
||||
// if (!process_stream_frame(t, dgram, offset)) return;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// spdlog::warn("Unknown frame on {} 0x{:x}, dropping rest of datagram", self_id(), t);
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
|
||||
// void QuicrConnection::drain_socket() {
|
||||
// while (true) {
|
||||
// auto r = m_stream.read_into(m_recv_buffer);
|
||||
// if (!r || *r == 0) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// m_last_heartbeat_received = Clock::now();
|
||||
|
||||
// auto dgram = std::span(m_recv_buffer.data(), *r);
|
||||
|
||||
// size_t offset = 0;
|
||||
// auto peer_connection_id_v = VarInt::decode(dgram.subspan(offset));
|
||||
// if (!peer_connection_id_v) {
|
||||
// spdlog::warn("Failed to decode peer connection ID, dropping datagram");
|
||||
// return;
|
||||
// }
|
||||
// uint64_t peer_connection_id = peer_connection_id_v->value;
|
||||
// offset += peer_connection_id_v->bytes;
|
||||
|
||||
// if(peer_connection_id != peer_id()) {
|
||||
// spdlog::warn("Received datagram with wrong peer connection ID: {}, expected {}, dropping datagram", peer_connection_id, peer_id());
|
||||
// return;
|
||||
// }
|
||||
|
||||
// process_datagram(dgram.subspan(offset));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
tl::expected<size_t, NetworkError> QuicrConnection::read_into(std::span<std::byte> target) {
|
||||
if(m_messages.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& msg = m_messages.front();
|
||||
size_t msg_len = msg.size();
|
||||
size_t to_copy = std::min(msg_len, target.size());
|
||||
|
||||
std::memcpy(target.data(), msg.data(), to_copy);
|
||||
m_messages.pop_front();
|
||||
|
||||
if (to_copy < msg_len) {
|
||||
spdlog::warn("Message truncated: {} bytes into {} byte buffer",
|
||||
msg_len, target.size());
|
||||
}
|
||||
|
||||
return msg_len; // return full message size so caller knows if truncated
|
||||
}
|
||||
|
||||
|
||||
void QuicrConnection::on_tick(std::chrono::steady_clock::time_point now) {
|
||||
// if (now - m_last_heartbeat_sent > std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS)) {
|
||||
// auto keep_alive_r = send_keep_alive();
|
||||
// }
|
||||
|
||||
if(m_last_heartbeat_received < now - std::chrono::milliseconds(TW_NET_HEARTBEAT_INTERVAL_IN_MILLIS * 2)) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool QuicrConnection::has_next_datagram() {
|
||||
if(m_reliability_unit->has_reliable_frames_to_resend()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(m_reliability_unit->has_acks_to_send()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!m_outbound_messages.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::byte> QuicrConnection::pop_datagram() {
|
||||
|
||||
std::vector<std::byte> datagram(64*1024);
|
||||
|
||||
QuicrPacketType type = QuicrPacketType::Initial;
|
||||
size_t offset = 0;
|
||||
uint32_t packet_number = m_packet_number++;
|
||||
QuicrPacketEncoder encoder(datagram, offset, type, packet_number, *this);
|
||||
|
||||
|
||||
// encode ACK frame
|
||||
{
|
||||
auto acks = m_reliability_unit->pop_acks_to_send();
|
||||
|
||||
encoder.encode_ack_frame(acks);
|
||||
}
|
||||
|
||||
// re-send frames
|
||||
{
|
||||
// pop already encoded frames
|
||||
auto frames_to_resend = m_reliability_unit->pop_frames_to_resend(packet_number);
|
||||
for(auto& frame : frames_to_resend) {
|
||||
frame.frame_number = packet_number;
|
||||
encoder.encode_frame(frame);
|
||||
|
||||
// auto deadline = Clock::now() + std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS);
|
||||
// m_reliability_unit->push_reliable_frame_to_send(deadline, std::move(frame));
|
||||
}
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(m_outbound_messages.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto outbound = m_outbound_messages.front();
|
||||
m_outbound_messages.pop_front();
|
||||
|
||||
encoder.encode_stream_frame(outbound, true);
|
||||
}
|
||||
|
||||
m_last_heartbeat_sent = Clock::now();
|
||||
|
||||
return std::vector<std::byte>(datagram.begin(), datagram.begin() + encoder.size());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
QuicrConnectionListener::QuicrConnectionListener(QuicrEndpoint* endpoint)
|
||||
: m_listened_connections()
|
||||
{
|
||||
endpoint->assign_listener(this);
|
||||
}
|
||||
|
||||
tl::expected<std::unique_ptr<QuicrConnectionListener>, NetworkError> QuicrConnectionListener::listen(QuicrEndpoint* endpoint) {
|
||||
return std::unique_ptr<QuicrConnectionListener>(new QuicrConnectionListener(endpoint));
|
||||
};
|
||||
|
||||
QuicrConnection* QuicrConnectionListener::listen() {
|
||||
if(m_listened_connections.size() > 0) {
|
||||
QuicrConnection* connection = m_listened_connections.front();
|
||||
m_listened_connections.pop_front();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
#include "protocol/quicr/QuicrEncoder.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include "bytebuffer/ByteBufferReader.hpp"
|
||||
#include "frames/Frame.hpp"
|
||||
#include "protocol/quicr/QuicrFrameType.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
QuicrPacket QuicrDecoder::decode_packet_header(std::span<std::byte> data, size_t &offset) {
|
||||
ByteBufferReader reader(data);
|
||||
QuicrPacket packet;
|
||||
|
||||
reader.pop_bytes(&packet.type);
|
||||
|
||||
reader.pop_bytes(&packet.destination_id);
|
||||
reader.pop_bytes(&packet.local_id);
|
||||
|
||||
reader.pop_bytes(&packet.require_ack);
|
||||
|
||||
// if(packet.require_ack) {
|
||||
uint32_t packet_number = 0;
|
||||
reader.pop_bytes(&packet_number);
|
||||
packet.packet_number = packet_number;
|
||||
// }
|
||||
|
||||
// packet.require_ack = false;
|
||||
|
||||
uint32_t length = 0;
|
||||
reader.pop_bytes(&length);
|
||||
|
||||
offset = data.size() - reader.remaining();
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
QuicrPacket QuicrDecoder::decode_packet(std::span<std::byte> data) {
|
||||
size_t offset = 0;
|
||||
QuicrPacket packet = decode_packet_header(data, offset);
|
||||
|
||||
ByteBufferReader reader(data.subspan(offset));
|
||||
|
||||
while(reader.remaining()) {
|
||||
FrameType frame_type;
|
||||
reader.pop_bytes(&frame_type);
|
||||
switch(frame_type) {
|
||||
case FrameType::KeepAlive:
|
||||
case FrameType::Padding: {
|
||||
break;
|
||||
}
|
||||
|
||||
case FrameType::Ack: {
|
||||
uint8_t num_acks = 0;
|
||||
reader.pop_bytes(&num_acks);
|
||||
|
||||
std::vector<uint32_t> acked_packets(num_acks);
|
||||
reader.pop_bytes(acked_packets.data(), acked_packets.size() * sizeof(uint32_t));
|
||||
|
||||
packet.frames.push_back(QuicrFrame::make_ack(acked_packets));
|
||||
break;
|
||||
}
|
||||
|
||||
case FrameType::Hello: {
|
||||
packet.require_ack = true;
|
||||
packet.frames.push_back(QuicrFrame::make_hello());
|
||||
break;
|
||||
}
|
||||
|
||||
case FrameType::HelloFin: {
|
||||
packet.require_ack = true;
|
||||
packet.frames.push_back(QuicrFrame::make_hello_fin());
|
||||
break;
|
||||
}
|
||||
|
||||
case FrameType::StreamBase:
|
||||
packet.require_ack = true;
|
||||
case FrameType::StreamUnreliable: {
|
||||
uint32_t size = 0;
|
||||
reader.pop_bytes(&size);
|
||||
|
||||
std::vector<std::byte> content(size);
|
||||
reader.pop_bytes(content.data(), size);
|
||||
packet.frames.push_back(QuicrFrame::make_stream(content));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
throw std::runtime_error("Unrecognized frame type: " + std::to_string((uint8_t)frame_type));
|
||||
spdlog::error("Unrecognized frame type: {}", (uint8_t)frame_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
QuicrPacketEncoder::QuicrPacketEncoder(std::span<std::byte> target, size_t& offset,
|
||||
QuicrPacketType type, std::optional<uint32_t> packet_number,
|
||||
QuicrConnection& connection)
|
||||
: m_target(target), m_offset(offset), m_type(type), m_connection(connection), m_writer(m_target),
|
||||
size_val_offset(0), is_reliable_val_offset(0) {
|
||||
|
||||
m_writer.write_bytes((uint8_t*)&type);
|
||||
|
||||
m_writer.write_bytes(&m_connection.peer_id());
|
||||
m_writer.write_bytes(&m_connection.self_id());
|
||||
|
||||
is_reliable_val_offset = m_writer.length();
|
||||
bool require_ack = false; // TODO: When does it need the ACK?
|
||||
m_writer.write_bytes(&require_ack);
|
||||
|
||||
//if(require_ack) {
|
||||
uint32_t _packet_num = packet_number.value();
|
||||
m_writer.write_bytes(&_packet_num);
|
||||
//}
|
||||
|
||||
uint32_t length_offset = m_writer.remaining();
|
||||
uint32_t length = 0;
|
||||
m_writer.write_bytes(&length);
|
||||
}
|
||||
|
||||
QuicrPacketEncoder& QuicrPacketEncoder::encode_stream_frame(std::span<std::byte> data, bool is_reliable) {
|
||||
uint8_t frame_type = is_reliable ? FrameType::StreamBase : FrameType::StreamUnreliable;
|
||||
|
||||
if(is_reliable) {
|
||||
set_as_reliable();
|
||||
}
|
||||
|
||||
m_writer.write_bytes(&frame_type);
|
||||
uint32_t length = data.size();
|
||||
m_writer.write_bytes(&length);
|
||||
m_writer.write_bytes(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
QuicrPacketEncoder& QuicrPacketEncoder::encode_ack_frame(std::vector<uint32_t>& acked_packets) {
|
||||
if(!acked_packets.empty()) {
|
||||
uint8_t ack_frame_type = FrameType::Ack;
|
||||
uint8_t acks_count = acked_packets.size();
|
||||
m_writer.write_bytes(&ack_frame_type);
|
||||
m_writer.write_bytes(&acks_count);
|
||||
|
||||
for(auto& ack : acked_packets) {
|
||||
m_writer.write_bytes(&ack);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
QuicrPacketEncoder& QuicrPacketEncoder::encode_frame(QuicrFrame& frame) {
|
||||
m_offset += m_writer.write_bytes(&frame.type);
|
||||
switch(frame.type) {
|
||||
case FrameType::KeepAlive:
|
||||
case FrameType::Padding:
|
||||
case FrameType::Hello:
|
||||
set_as_reliable();
|
||||
break;
|
||||
case FrameType::StreamBase:
|
||||
set_as_reliable();
|
||||
case FrameType::StreamUnreliable:
|
||||
{
|
||||
m_offset += m_writer.write_bytes(frame.content);
|
||||
break;
|
||||
}
|
||||
case FrameType::Ack:
|
||||
case FrameType::AckEcn:
|
||||
case FrameType::ResetStream:
|
||||
case FrameType::StopSending:
|
||||
case FrameType::Crypto:
|
||||
case FrameType::NewToken:
|
||||
case FrameType::HandshakeDone:
|
||||
set_as_reliable();
|
||||
m_offset += m_writer.write_bytes(frame.content);
|
||||
break;
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
#include "protocol/quicr/QuicrEncoder.hpp"
|
||||
#include "tl/expected.hpp"
|
||||
#include <chrono>
|
||||
#include <fcntl.h>
|
||||
#include <memory>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
QuicrEndpoint::QuicrEndpoint(int socket_fd)
|
||||
: m_inbound_buffer(64 * 1024), m_socket_fd(socket_fd),
|
||||
m_new_connection_handler(nullptr) {
|
||||
|
||||
}
|
||||
|
||||
tl::expected<std::unique_ptr<QuicrEndpoint>, NetworkError> QuicrEndpoint::create_and_bind(int16_t port) {
|
||||
auto endpoint = QuicrEndpoint::create();
|
||||
if (!endpoint.has_value()) {
|
||||
return tl::make_unexpected(endpoint.error());
|
||||
}
|
||||
|
||||
auto bind_r = (*endpoint)->bind(port);
|
||||
if(!bind_r) {
|
||||
return tl::make_unexpected(bind_r.error());
|
||||
}
|
||||
|
||||
return std::move(*endpoint);
|
||||
}
|
||||
|
||||
tl::expected<std::unique_ptr<QuicrEndpoint>, NetworkError> QuicrEndpoint::create() {
|
||||
const int domain = AF_INET;
|
||||
int socket_fd = socket(domain, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(socket_fd < 0) {
|
||||
spdlog::error("Failed to create socket: {}", strerror(errno));
|
||||
return tl::make_unexpected(NetworkError::from_errno(errno));
|
||||
}
|
||||
|
||||
if(fcntl(socket_fd, F_SETFL, fcntl(socket_fd, F_GETFL, 0) | O_NONBLOCK, 1) == -1) {
|
||||
spdlog::error("Failed to set non-blocking mode: {}", strerror(errno));
|
||||
return tl::make_unexpected(NetworkError::from_errno(errno));
|
||||
}
|
||||
|
||||
return std::unique_ptr<QuicrEndpoint>(new QuicrEndpoint(socket_fd));
|
||||
}
|
||||
|
||||
tl::expected<void, NetworkError> QuicrEndpoint::bind(int port) {
|
||||
const int domain = AF_INET;
|
||||
struct sockaddr_in addr = {};
|
||||
addr.sin_family = domain;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
if(::bind(m_socket_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
||||
spdlog::error("Failed to bind socket: {}", strerror(errno));
|
||||
return tl::make_unexpected(NetworkError::from_errno(errno));
|
||||
}
|
||||
|
||||
if(fcntl(m_socket_fd, F_SETFL, fcntl(m_socket_fd, F_GETFL, 0) | O_NONBLOCK, 1) == -1) {
|
||||
spdlog::error("Failed to set non-blocking mode: {}", strerror(errno));
|
||||
return tl::make_unexpected(NetworkError::from_errno(errno));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new connection from current socket to the address.
|
||||
*/
|
||||
tl::expected<QuicrConnection*, NetworkError> QuicrEndpoint::connect(Address address) {
|
||||
auto connection = std::make_shared<QuicrConnection>(0, 0, address, this);
|
||||
auto inserted_r = m_connections.emplace(connection->self_id(), connection);
|
||||
|
||||
if(!inserted_r.second) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inserted_r.first->second->send_initial_hello();
|
||||
|
||||
return inserted_r.first->second.get();
|
||||
}
|
||||
|
||||
void QuicrEndpoint::process_datagram(std::span<std::byte> datagram, Address from) {
|
||||
ZoneScopedN("Process Datagram");
|
||||
|
||||
// parse first byte as packet type
|
||||
if(datagram.size() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t off = 0;
|
||||
|
||||
QuicrPacket packet = QuicrDecoder::decode_packet_header(datagram, off);
|
||||
|
||||
auto connection = m_connections.find(packet.destination_id);
|
||||
|
||||
if(connection == m_connections.end()) {
|
||||
spdlog::warn("New connection from: {}", from.to_string());
|
||||
auto conn = std::make_shared<QuicrConnection>(0, packet.local_id, from, this);
|
||||
auto emplaced = m_connections.emplace(conn->self_id(), conn);
|
||||
emplaced.first->second->set_peer_id(packet.local_id);
|
||||
|
||||
emplaced.first->second->process_datagram(datagram);
|
||||
m_connections.emplace(packet.destination_id, emplaced.first->second);
|
||||
return;
|
||||
}
|
||||
|
||||
auto prev_state = connection->second->state();
|
||||
|
||||
connection->second->process_datagram(datagram);
|
||||
|
||||
if(prev_state != QuicrConnectionState::Established && connection->second->state() == QuicrConnectionState::Established) {
|
||||
if(m_new_connection_handler != nullptr) {
|
||||
m_new_connection_handler->on_new_connection(connection->second.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tl::expected<size_t, NetworkError> QuicrEndpoint::send_to(std::span<std::byte> data, Address to) {
|
||||
size_t total = 0;
|
||||
|
||||
while(total < data.size_bytes()) {
|
||||
ssize_t t = ::sendto(m_socket_fd, data.data() + total, data.size() - total, MSG_NOSIGNAL | MSG_DONTWAIT, to.sockaddr(), to.socklen());
|
||||
if(t == -1) {
|
||||
if(errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return tl::make_unexpected(NetworkError::from_errno(errno));
|
||||
}
|
||||
total += t;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
tl::expected<size_t, NetworkError> QuicrEndpoint::read_from_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;
|
||||
}
|
||||
|
||||
void QuicrEndpoint::poll() {
|
||||
while(1) {
|
||||
ZoneScopedN("Reading");
|
||||
Address address({}, 0);
|
||||
auto r = read_from_into(std::span(m_inbound_buffer), &address);
|
||||
if(!r || *r == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
process_datagram(std::span(m_inbound_buffer).subspan(0, *r), address);
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
for(auto& connection : m_connections) {
|
||||
ZoneScopedN("Per Connection");
|
||||
|
||||
while(connection.second->has_next_datagram()) {
|
||||
auto datagram = connection.second->pop_datagram();
|
||||
auto send_r = send_to(datagram, connection.second->address());
|
||||
if(!send_r) {
|
||||
spdlog::error("Failed to send to {} datagram: {}", connection.second->address().to_string(), send_r.error().message());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
#include "protocol/quicr/QuicrReliability.hpp"
|
||||
#include "bytebuffer/ByteBuffer.hpp"
|
||||
#include "metrics/NetworkStatsLogger.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include <chrono>
|
||||
#include <immintrin.h>
|
||||
|
||||
namespace tw::net::quicr {
|
||||
|
||||
// size_t QuicrReliabilityUnit::encode_packet_header(RingByteBuffer& target, const QuicrConnection* connection) {
|
||||
// size_t size = 0;
|
||||
|
||||
// target.write_bytes(&connection->peer_id());
|
||||
// target.write_bytes(&connection->self_id());
|
||||
|
||||
// return size;
|
||||
// }
|
||||
|
||||
// size_t QuicrReliabilityUnit::encode_frame_header(RingByteBuffer& buffer, const QuicrFrame& frame) {
|
||||
// size_t size = 0;
|
||||
|
||||
// buffer.write_bytes(&frame.type);
|
||||
|
||||
// if(frame.is_reliable) {
|
||||
// buffer.write_bytes(&frame.is_reliable);
|
||||
// buffer.write_bytes(&frame.frame_number);
|
||||
// }
|
||||
|
||||
// return size;
|
||||
// }
|
||||
|
||||
// size_t QuicrReliabilityUnit::encode_frame_body(RingByteBuffer& buffer, const QuicrFrame& frame) {
|
||||
// size_t size = 0;
|
||||
|
||||
|
||||
// return size;
|
||||
// }
|
||||
|
||||
// size_t QuicrReliabilityUnit::encode_frame(RingByteBuffer& buffer, const QuicrFrame& frame) {
|
||||
// size_t size = 0;
|
||||
|
||||
// size += encode_frame_header(frame);
|
||||
|
||||
// size += encode_frame_body(frame);
|
||||
|
||||
// return size;
|
||||
// }
|
||||
|
||||
// std::vector<std::byte> QuicrReliabilityUnit::pop_datagram() {
|
||||
// size_t size = 0;
|
||||
// std::vector<std::byte> datagram;
|
||||
// RingByteBuffer byte_buf(datagram);
|
||||
|
||||
// // write header
|
||||
// byte_buf.write_bytes(&connection->peer_id());
|
||||
// byte_buf.write_bytes(&connection->self_id());
|
||||
|
||||
// size_t last_end = byte_buf.remaining_read();
|
||||
|
||||
// size += encode_packet_header();
|
||||
|
||||
// // resend frames
|
||||
// for (const auto& [timestamp, frame] : awaiting_ack_frames) {
|
||||
// if(timestamp < std::chrono::steady_clock::now() - std::chrono::seconds(1)) {
|
||||
// size += encode_frame(byte_buf, frame);
|
||||
// last_end = byte_buf.remaining_read();
|
||||
// }
|
||||
// }
|
||||
|
||||
// // write body
|
||||
// while(size < 1100) {
|
||||
// auto frame = frames.front();
|
||||
|
||||
// if(frame.is_reliable) {
|
||||
// frame.frame_number = m_frame_number++;
|
||||
// }
|
||||
|
||||
// size += encode_frame(byte_buf, frame);
|
||||
// frames.pop_front();
|
||||
// }
|
||||
|
||||
// return datagram;
|
||||
// }
|
||||
|
||||
// void QuicrReliabilityUnit::process_frame(QuicrFrame frame) {
|
||||
// if(frame.is_reliable) {
|
||||
// if(m_largest_received == frame.frame_number) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if(m_largest_received < frame.frame_number) {
|
||||
// m_ack_bitfield <<= (frame.frame_number - m_largest_received);
|
||||
// m_largest_received = frame.frame_number;
|
||||
// } else if(m_largest_received > frame.frame_number) {
|
||||
// m_ack_bitfield |= (1ULL << (m_largest_received - frame.frame_number));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
bool QuicrReliabilityUnit::has_reliable_frames_to_resend() {
|
||||
return std::any_of(awaiting_ack_frames.begin(), awaiting_ack_frames.end(),
|
||||
[](const auto& t) { return t.second->deadline < std::chrono::steady_clock::now(); });
|
||||
}
|
||||
|
||||
void QuicrReliabilityUnit::push_ack(uint32_t packet_number) {
|
||||
m_acks_to_send.push_back(packet_number);
|
||||
}
|
||||
|
||||
void QuicrReliabilityUnit::on_ack_received(uint32_t packet_number) {
|
||||
auto packet = packets_in_flight.find(packet_number);
|
||||
if(packet != packets_in_flight.end()) {
|
||||
for(auto frame : packet->second.frame_numbers) {
|
||||
if(awaiting_ack_frames.erase(frame) == 0) {
|
||||
spdlog::error("Failed to erase frame {} from awaiting_ack_frames", frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
std::erase_if(packets_in_flight, [frame, packet_number](auto& packet) {
|
||||
// skip current packet
|
||||
if(packet.second.packet_number == packet_number) {
|
||||
return false;
|
||||
}
|
||||
|
||||
packet.second.frame_numbers.erase(frame);
|
||||
return packet.second.frame_numbers.empty();
|
||||
});
|
||||
}
|
||||
|
||||
packets_in_flight.erase(packet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// m_acks_to_send.push_back(frame_idx);
|
||||
// std::erase_if(awaiting_ack_frames,
|
||||
// [frame_idx](const auto& t) {
|
||||
// return t.second.frame_number == frame_idx;
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
std::vector<QuicrFrame> QuicrReliabilityUnit::pop_frames_to_resend(uint32_t new_packet_number) {
|
||||
std::vector<QuicrFrame> resend_frames;
|
||||
|
||||
auto packet = packets_in_flight.try_emplace(new_packet_number, QuicrReliablePacket{new_packet_number, {}});
|
||||
|
||||
for(auto frame : awaiting_ack_frames) {
|
||||
if(frame.second->deadline < Clock::now()) {
|
||||
resend_frames.push_back(std::move(frame.second->frame));
|
||||
packet.first->second.frame_numbers.insert(frame.first);
|
||||
frame.second->deadline = Clock::now() + std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
// std::erase_if(awaiting_ack_frames, [&resend_frames](const auto& item) {
|
||||
// if(item.first < std::chrono::steady_clock::now()) {
|
||||
// resend_frames.push_back(item.second);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// });
|
||||
return resend_frames;
|
||||
}
|
||||
|
||||
void QuicrReliabilityUnit::push_reliable_frame(Clock::time_point deadline, QuicrFrame&& frame) {
|
||||
frame.is_reliable = true;
|
||||
frame.frame_number = next_frame_number();
|
||||
auto frame_number = frame.frame_number;
|
||||
awaiting_ack_frames[frame_number] = new QuicrReliableFrame{deadline, std::move(frame)};
|
||||
|
||||
// awaiting_ack_frames.emplace_back(deadline, frame);
|
||||
}
|
||||
|
||||
void QuicrReliabilityUnit::push_reliable_frame(Clock::time_point deadline, QuicrFrame& frame) {
|
||||
frame.is_reliable = true;
|
||||
frame.frame_number = next_frame_number();
|
||||
auto frame_number = frame.frame_number;
|
||||
awaiting_ack_frames[frame_number] = new QuicrReliableFrame{deadline, std::move(frame)};
|
||||
// awaiting_ack_frames.emplace_back(deadline, frame);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> QuicrReliabilityUnit::pop_acks_to_send() {
|
||||
auto acks = std::vector<uint32_t>(m_acks_to_send);
|
||||
|
||||
m_acks_to_send.clear();
|
||||
|
||||
return acks;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "bytebuffer/ByteBuffer.hpp"
|
||||
#include "bytebuffer/ByteBufferDecoder.hpp"
|
||||
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
|
||||
struct DatagramHeader {
|
||||
uint32_t type;
|
||||
uint64_t secret_key;
|
||||
};
|
||||
|
||||
TEST_CASE("Byte buffer codec test", "[byte_buffer]") {
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "TestGenerators.hpp"
|
||||
#include "bytebuffer/ByteBuffer.hpp"
|
||||
#include "bytebuffer/ByteBufferDecoder.hpp"
|
||||
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
|
||||
TEST_CASE("Byte buffer insert", "[byte_buffer]") {
|
||||
std::vector<std::byte> bytes = generate_sequence(8);
|
||||
std::vector<std::byte> storage(12);
|
||||
|
||||
tw::net::RingByteBuffer byte_buffer(storage, false);
|
||||
REQUIRE(byte_buffer.remaining_write() == 12);
|
||||
REQUIRE(byte_buffer.remaining_read() == 0);
|
||||
|
||||
size_t written = byte_buffer.write_bytes(bytes);
|
||||
|
||||
REQUIRE(byte_buffer.remaining_write() == 4);
|
||||
REQUIRE(byte_buffer.remaining_read() == 8);
|
||||
REQUIRE(written == 8);
|
||||
}
|
||||
|
||||
TEST_CASE("Byte buffer pop", "[byte_buffer]") {
|
||||
std::vector<std::byte> bytes = generate_sequence(8);
|
||||
std::vector<std::byte> storage(12);
|
||||
|
||||
tw::net::RingByteBuffer byte_buffer(storage, false);
|
||||
size_t written = byte_buffer.write_bytes(bytes);
|
||||
|
||||
std::vector<std::byte> popped(6);
|
||||
size_t read = byte_buffer.pop_bytes(popped.data(), 6);
|
||||
|
||||
REQUIRE(read == 6);
|
||||
|
||||
REQUIRE(popped[0] == std::byte{1});
|
||||
REQUIRE(popped[1] == std::byte{2});
|
||||
REQUIRE(popped[2] == std::byte{3});
|
||||
REQUIRE(popped[3] == std::byte{4});
|
||||
REQUIRE(popped[4] == std::byte{5});
|
||||
REQUIRE(popped[5] == std::byte{6});
|
||||
|
||||
REQUIRE(byte_buffer.remaining_read() == 2);
|
||||
REQUIRE(byte_buffer.remaining_write() == 10);
|
||||
}
|
||||
|
||||
TEST_CASE("Byte buffer decoder", "[byte_buffer]") {
|
||||
std::vector<std::byte> bytes = generate_sequence(8);
|
||||
std::vector<std::byte> storage(12);
|
||||
|
||||
tw::net::RingByteBuffer byte_buffer(storage, false);
|
||||
size_t written = byte_buffer.write_bytes(bytes);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
project(tw_network_tests)
|
||||
|
||||
set(LIBS
|
||||
tw::network
|
||||
)
|
||||
|
||||
file(GLOB FILES
|
||||
./*.cpp
|
||||
./quicr/QuicrBasicTests.cpp
|
||||
./quicr/QuicrEndpointTests.cpp
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME}_sources OBJECT ${FILES})
|
||||
target_link_libraries(${PROJECT_NAME}_sources
|
||||
Catch2::Catch2WithMain
|
||||
${LIBS}
|
||||
tl::expected
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
add_executable(QuicrOverloadTest ./quicr/QuicrOverloadTests.cpp)
|
||||
add_executable(QuicrBenchmarks ./quicr/QuicrBenchmarks.cpp)
|
||||
|
||||
target_link_libraries(QuicrBenchmarks
|
||||
PRIVATE
|
||||
${LIBS}
|
||||
${PROJECT_NAME}_sources
|
||||
Tracy::TracyClient
|
||||
Catch2::Catch2WithMain
|
||||
tl::expected
|
||||
EnTT::EnTT
|
||||
)
|
||||
|
||||
target_link_libraries(QuicrOverloadTest
|
||||
PRIVATE
|
||||
${LIBS}
|
||||
${PROJECT_NAME}_sources
|
||||
Tracy::TracyClient
|
||||
TracyClient
|
||||
Catch2::Catch2WithMain
|
||||
tl::expected
|
||||
EnTT::EnTT
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${LIBS}
|
||||
${PROJECT_NAME}_sources
|
||||
Catch2::Catch2WithMain
|
||||
tl::expected
|
||||
EnTT::EnTT
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
|
||||
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
|
||||
catch_discover_tests(${PROJECT_NAME})
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "TestGenerators.hpp"
|
||||
#include "bytebuffer/ByteBufferDecoder.hpp"
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
#include "frames/FrameCodec.hpp"
|
||||
#include "protocol/quicr/QuicrFrameType.hpp"
|
||||
|
||||
using namespace tw::net;
|
||||
|
||||
TEST_CASE("Encode stream frame", "[frame_encoder]") {
|
||||
// std::vector<std::byte> bytes = generate_sequence(8);
|
||||
|
||||
// Frame frame(quicr::FrameType::StreamBase, bytes);
|
||||
|
||||
// ByteBuffer byte_buffer(16);
|
||||
|
||||
// frame::FrameCodec::encode(byte_buffer, frame);
|
||||
|
||||
// REQUIRE(byte_buffer.remaining_read() == 2 * sizeof(uint32_t) + 8);
|
||||
|
||||
// auto frame_type = ByteBufferDecoder<uint32_t>::decode(byte_buffer, 0);
|
||||
|
||||
// REQUIRE(frame_type);
|
||||
// REQUIRE(*frame_type == (uint32_t)quicr::FrameType::StreamBase);
|
||||
|
||||
// auto length = ByteBufferDecoder<uint32_t>::decode(byte_buffer, 4);
|
||||
|
||||
// REQUIRE(length);
|
||||
// REQUIRE(*length == 8);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
// #include "io/Read.hpp"
|
||||
// #include "messenger/Messenger.hpp"
|
||||
|
||||
// class MockReader : public Read<std::byte> {
|
||||
// size_t m_cursor;
|
||||
// std::string m_content;
|
||||
|
||||
// public:
|
||||
// MockReader(const std::string& content) :
|
||||
// m_cursor(0),
|
||||
// m_content(content) {
|
||||
|
||||
// }
|
||||
|
||||
// size_t read(std::span<std::byte> data) override {
|
||||
// size_t read_len = std::min(data.size(), m_content.size() - m_cursor);
|
||||
// if(read_len == 0) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// std::copy(m_content.begin() + m_cursor, m_content.begin() + m_cursor + read_len, data.begin());
|
||||
// m_cursor += read_len;
|
||||
// return read_len;
|
||||
// }
|
||||
// };
|
||||
|
||||
// class MockWriter : public Write<std::byte> {
|
||||
|
||||
// public:
|
||||
// MockWriter() {
|
||||
|
||||
// }
|
||||
|
||||
// size_t write(std::span<const std::byte> data) override {
|
||||
// }
|
||||
// };
|
||||
|
||||
// TEST_CASE("Test01", "[Messenger_Test]") {
|
||||
// MockReader reader("0Hello, World!");
|
||||
// MockWriter writer;
|
||||
// tw::net::Messenger messenger(&writer, &reader);
|
||||
|
||||
// REQUIRE(messenger.peek() == '0');
|
||||
// }
|
||||
@@ -0,0 +1,259 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <print>
|
||||
#include "Serializers.hpp"
|
||||
|
||||
struct TestMessage1 {
|
||||
uint32_t a, b, c, d;
|
||||
};
|
||||
|
||||
template<>
|
||||
class tw::net::Serializer<TestMessage1> {
|
||||
public:
|
||||
static bool serialize(Serialization& buffer, TestMessage1& mesg) {
|
||||
buffer.serialize(&mesg.a);
|
||||
buffer.serialize(&mesg.b);
|
||||
buffer.serialize(&mesg.c);
|
||||
buffer.serialize(&mesg.d);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// TEST_CASE("Test01", "[serializer]") {
|
||||
// TestMessage1 mesg = {
|
||||
// .a = 10,
|
||||
// .b = 100,
|
||||
// .c = 3,
|
||||
// .d = 0
|
||||
// };
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(1024);
|
||||
//
|
||||
// buffer.write(mesg);
|
||||
//
|
||||
// REQUIRE(mesg.a == 10);
|
||||
// REQUIRE(mesg.b == 100);
|
||||
// REQUIRE(mesg.c == 3);
|
||||
// REQUIRE(mesg.d == 0);
|
||||
//
|
||||
// TestMessage1 mesg2 = {};
|
||||
//
|
||||
// REQUIRE(mesg2.a == 0);
|
||||
// REQUIRE(mesg2.b == 0);
|
||||
// REQUIRE(mesg2.c == 0);
|
||||
// REQUIRE(mesg2.d == 0);
|
||||
//
|
||||
// buffer.read(mesg2);
|
||||
//
|
||||
// REQUIRE(mesg.a == mesg2.a);
|
||||
// REQUIRE(mesg.b == mesg2.b);
|
||||
// REQUIRE(mesg.c == mesg2.c);
|
||||
// REQUIRE(mesg.d == mesg2.d);
|
||||
// }
|
||||
//
|
||||
// TEST_CASE("ByteBufferRemainingTest", "[serializer]") {
|
||||
// TestMessage1 mesg = {
|
||||
// .a = 10,
|
||||
// .b = 100,
|
||||
// .c = 3,
|
||||
// .d = 0
|
||||
// };
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(4 * sizeof(uint32_t) + 10);
|
||||
// buffer.write(mesg);
|
||||
//
|
||||
// REQUIRE(buffer.remaining() == 10);
|
||||
// }
|
||||
//
|
||||
// TEST_CASE("ByteBufferOverflowsTest1", "[serializer]") {
|
||||
// TestMessage1 mesg = {
|
||||
// .a = 10,
|
||||
// .b = 100,
|
||||
// .c = 3,
|
||||
// .d = 0
|
||||
// };
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(2);
|
||||
// buffer.write(mesg);
|
||||
//
|
||||
// // nothing should be written
|
||||
// REQUIRE(buffer.remaining() == 2);
|
||||
// }
|
||||
//
|
||||
// TEST_CASE("TestMultipleMessagesOnePacket", "[serializer]") {
|
||||
// TestMessage1 mesg = {
|
||||
// .a = 10,
|
||||
// .b = 100,
|
||||
// .c = 3,
|
||||
// .d = 0
|
||||
// };
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(1024);
|
||||
//
|
||||
// buffer.write(mesg);
|
||||
// buffer.write(mesg);
|
||||
//
|
||||
// REQUIRE(mesg.a == 10);
|
||||
// REQUIRE(mesg.b == 100);
|
||||
// REQUIRE(mesg.c == 3);
|
||||
// REQUIRE(mesg.d == 0);
|
||||
//
|
||||
// TestMessage1 mesg2 = {};
|
||||
//
|
||||
// REQUIRE(mesg2.a == 0);
|
||||
// REQUIRE(mesg2.b == 0);
|
||||
// REQUIRE(mesg2.c == 0);
|
||||
// REQUIRE(mesg2.d == 0);
|
||||
//
|
||||
// buffer.read(mesg2);
|
||||
// REQUIRE(buffer.remaining() == sizeof(TestMessage1));
|
||||
// TestMessage1 mesg3 = {};
|
||||
// buffer.read(mesg3);
|
||||
//
|
||||
// REQUIRE(mesg.a == mesg2.a); REQUIRE(mesg.a == mesg3.a);
|
||||
// REQUIRE(mesg.b == mesg2.b); REQUIRE(mesg.b == mesg3.b);
|
||||
// REQUIRE(mesg.c == mesg2.c); REQUIRE(mesg.c == mesg3.c);
|
||||
// REQUIRE(mesg.d == mesg2.d); REQUIRE(mesg.d == mesg3.d);
|
||||
// }
|
||||
//
|
||||
// TEST_CASE("SerializationTest", "[serialization]") {
|
||||
// TestMessage1 mesg = {
|
||||
// .a = 10,
|
||||
// .b = 100,
|
||||
// .c = 3,
|
||||
// .d = 0
|
||||
// };
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(1024);
|
||||
// buffer.write(mesg);
|
||||
// char *ptr = (char*)&mesg.a;
|
||||
// }
|
||||
//
|
||||
// #define MAX_MESG_SIZE 256
|
||||
//
|
||||
// struct TestMessageString {
|
||||
// uint32_t mesg_size;
|
||||
// char mesg[MAX_MESG_SIZE];
|
||||
// };
|
||||
//
|
||||
// template<>
|
||||
// class tw::net::Serializer<TestMessageString> {
|
||||
// public:
|
||||
// static bool serialize(ByteBuffer& buffer, TestMessageString& mesg) {
|
||||
// buffer.serialize(&mesg.mesg_size);
|
||||
// buffer.bytes(mesg.mesg, mesg.mesg_size);
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// TEST_CASE("StringTest", "[serializer]") {
|
||||
// TestMessageString mesg = {
|
||||
// .mesg_size = sizeof("Hello World") - 1,
|
||||
// .mesg = "Hello World"
|
||||
// };
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(1024);
|
||||
//
|
||||
// buffer.write(mesg);
|
||||
//
|
||||
// TestMessageString mesg2 = {};
|
||||
//
|
||||
// buffer.read(mesg2);
|
||||
// mesg2.mesg[mesg2.mesg_size] = 0;
|
||||
//
|
||||
// REQUIRE(!strcmp(mesg2.mesg, "Hello World"));
|
||||
// }
|
||||
//
|
||||
//
|
||||
// #define MAX_ELEMENTS 10
|
||||
//
|
||||
// struct TestScalableMessageElement {
|
||||
// int32_t a, b, c, d;
|
||||
// };
|
||||
//
|
||||
//
|
||||
// template<>
|
||||
// class tw::net::Serializer<TestScalableMessageElement> {
|
||||
// public:
|
||||
// static bool serialize(ByteBuffer& buffer, TestScalableMessageElement& mesg) {
|
||||
// if(buffer.remaining() < 4 * sizeof(uint32_t)) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// buffer.serialize(&mesg.a);
|
||||
// buffer.serialize(&mesg.b);
|
||||
// buffer.serialize(&mesg.c);
|
||||
// buffer.serialize(&mesg.d);
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// struct TestScalableMessage {
|
||||
// uint32_t num_elements;
|
||||
// TestScalableMessageElement elements[MAX_ELEMENTS];
|
||||
// };
|
||||
//
|
||||
// template<>
|
||||
// class tw::net::Serializer<TestScalableMessage> {
|
||||
// public:
|
||||
// static bool serialize(ByteBuffer& buffer, TestScalableMessage& mesg) {
|
||||
// buffer.serialize(&mesg.num_elements);
|
||||
// int i = 0;
|
||||
// for(i = 0; i < mesg.num_elements; i++) {
|
||||
// bool is_there = true;
|
||||
// buffer.serialize(&is_there);
|
||||
//
|
||||
// if(!buffer.serialize(mesg.elements[i])) {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// TEST_CASE("ByteBufferOverflowsTest2", "[serializer]") {
|
||||
// TestScalableMessage mesg = { };
|
||||
//
|
||||
// for(int i = 0; i < 10; i++) {
|
||||
// mesg.elements[i] = {
|
||||
// .a = 1 * i,
|
||||
// .b = 2,
|
||||
// .c = 3 * i,
|
||||
// .d = 4
|
||||
// };
|
||||
// mesg.num_elements++;
|
||||
// }
|
||||
//
|
||||
// tw::net::ByteBuffer buffer(20);
|
||||
//
|
||||
// buffer.write(mesg);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// #include <glm/glm.hpp>
|
||||
//
|
||||
//
|
||||
// template<>
|
||||
// class tw::net::Serializer<glm::vec3> final {
|
||||
// public:
|
||||
// static bool serialize(ByteBuffer& buffer, glm::vec3& value) {
|
||||
// buffer.serialize(&value.x);
|
||||
// buffer.serialize(&value.y);
|
||||
// buffer.serialize(&value.z);
|
||||
// return true;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// TEST_CASE("Vec3Serialize", "[socket]") {
|
||||
// tw::net::ByteBuffer buffer(20);
|
||||
//
|
||||
// glm::vec3 vec = glm::vec3(10.0f, 5.0f, 3.14f);
|
||||
// buffer.write(vec);
|
||||
//
|
||||
// glm::vec3 vec2 = {};
|
||||
// buffer.read(vec2);
|
||||
//
|
||||
// REQUIRE(vec == vec2);
|
||||
// }
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "TestGenerators.hpp"
|
||||
|
||||
std::vector<std::byte> generate_sequence(const size_t size) {
|
||||
assert(size < UINT8_MAX);
|
||||
|
||||
std::vector<std::byte> result(size);
|
||||
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
result[i] = (std::byte)(i + 1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
std::vector<std::byte> generate_sequence(const size_t size);
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <barrier>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <span>
|
||||
#include <iostream>
|
||||
|
||||
#include "UdpStream.hpp"
|
||||
#include "Address.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
|
||||
TEST_CASE("Start two sockets and send message", "[udp]") {
|
||||
std::barrier create_sync_point(2);
|
||||
std::barrier send_sync_point(2);
|
||||
|
||||
const std::string message = "Hello, server!";
|
||||
|
||||
std::thread server_thread([&]() {
|
||||
auto r = tw::net::UdpStream::bind(tw::net::Address {"127.0.0.1", 6969});
|
||||
if(!r.has_value()) {
|
||||
std::cout << ("Failed to bind UDP stream: {}") << r.error().message() << std::endl;
|
||||
}
|
||||
|
||||
auto flag_result = r->set_non_blocking();
|
||||
|
||||
auto server = std::move(r.value());
|
||||
|
||||
create_sync_point.arrive_and_wait();
|
||||
send_sync_point.arrive_and_wait();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
std::vector<std::byte> buffer(1024);
|
||||
tw::net::Address from {{}, 0};
|
||||
auto read_result = server.read_into(std::span{buffer.data(), buffer.size()}, &from);
|
||||
|
||||
if(!read_result.has_value()) {
|
||||
spdlog::error("Failed to read from UDP stream: {}", read_result.error().message());
|
||||
}
|
||||
|
||||
// convert the buffer to a string
|
||||
std::string mesg(buffer.data(), buffer.data() + read_result.value());
|
||||
REQUIRE(mesg == message);
|
||||
});
|
||||
|
||||
std::thread client_thread([&]() {
|
||||
create_sync_point.arrive_and_wait();
|
||||
|
||||
auto connect_result = tw::net::UdpStream::to(tw::net::Address{"127.0.0.1", 6969});
|
||||
if(!connect_result.has_value()) {
|
||||
spdlog::error("Failed to bind UDP stream: {}", connect_result.error().mesg());
|
||||
}
|
||||
|
||||
auto client = std::move(connect_result.value());
|
||||
|
||||
std::string mesg = message;
|
||||
auto write_result = client.write(std::as_writable_bytes(std::span(mesg.begin(), mesg.end())));
|
||||
|
||||
if(!write_result.has_value() && write_result.value() == mesg.length()) {
|
||||
spdlog::error("Failed to write to UDP stream: {}", write_result.error().message());
|
||||
}
|
||||
|
||||
send_sync_point.arrive_and_wait();
|
||||
});
|
||||
|
||||
server_thread.join();
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
using namespace tw::net;
|
||||
using namespace tw::net::quicr;
|
||||
@@ -0,0 +1,452 @@
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
|
||||
#include "Address.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
#include "protocol/quicr/QuicrEncoder.hpp"
|
||||
#include "protocol/quicr/QuicrReliability.hpp"
|
||||
#include <barrier>
|
||||
#include <span>
|
||||
|
||||
using namespace tw::net::quicr;
|
||||
|
||||
TEST_CASE("Client begins with Hello datagram", "[quicr2]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
connection.send_initial_hello();
|
||||
|
||||
// should contain only the hello frame
|
||||
REQUIRE(connection.has_next_datagram() == true);
|
||||
auto buffer = connection.pop_datagram();
|
||||
|
||||
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
|
||||
|
||||
REQUIRE(header.type == QuicrPacketType::Initial);
|
||||
|
||||
REQUIRE(header.destination_id == connection.peer_id());
|
||||
REQUIRE(header.local_id == connection.self_id());
|
||||
|
||||
REQUIRE(header.frames.size() == 1);
|
||||
|
||||
REQUIRE(header.frames[0].type == FrameType::Hello);
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
}
|
||||
|
||||
TEST_CASE("Client wants to resend the Hello", "[quicr2]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
connection.send_initial_hello();
|
||||
|
||||
// should contain only the hello frame
|
||||
REQUIRE(connection.has_next_datagram() == true);
|
||||
|
||||
auto buffer = connection.pop_datagram();
|
||||
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == true);
|
||||
|
||||
buffer = connection.pop_datagram();
|
||||
header = QuicrDecoder::decode_packet(buffer);
|
||||
|
||||
REQUIRE(header.type == QuicrPacketType::Initial);
|
||||
|
||||
REQUIRE(header.destination_id == connection.peer_id());
|
||||
REQUIRE(header.local_id == connection.self_id());
|
||||
|
||||
REQUIRE(header.frames.size() == 1);
|
||||
|
||||
REQUIRE(header.frames[0].type == FrameType::Hello);
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
}
|
||||
|
||||
TEST_CASE("Closed connection will setup connection IDs after Hello", "[quicr2]") {
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Connection reacts to Hello with ACK & Hello", "[quicr2]") {
|
||||
QuicrConnection client(0, 0, tw::net::Address({}), nullptr);
|
||||
client.send_initial_hello();
|
||||
|
||||
auto hello = client.pop_datagram();
|
||||
|
||||
QuicrConnection server(0, 0, tw::net::Address({}), nullptr);
|
||||
|
||||
server.process_datagram(hello);
|
||||
|
||||
REQUIRE(server.has_next_datagram() == true);
|
||||
|
||||
auto dgram = server.pop_datagram();
|
||||
|
||||
QuicrPacket packet = QuicrDecoder::decode_packet(dgram);
|
||||
|
||||
REQUIRE(packet.type == QuicrPacketType::Initial);
|
||||
|
||||
REQUIRE(packet.destination_id == server.peer_id());
|
||||
REQUIRE(packet.local_id == server.self_id());
|
||||
|
||||
REQUIRE(packet.frames.size() == 2);
|
||||
|
||||
REQUIRE(std::any_of(packet.frames.begin(), packet.frames.end(), [](const QuicrFrame& f) { return f.type == FrameType::Ack; }));
|
||||
REQUIRE(std::any_of(packet.frames.begin(), packet.frames.end(), [](const QuicrFrame& f) { return f.type == FrameType::Hello; }));
|
||||
}
|
||||
|
||||
TEST_CASE("Both connections have correct IDs after Initial exchange", "[quicr2]") {
|
||||
QuicrConnection client(0, 0, tw::net::Address({}), nullptr);
|
||||
client.send_initial_hello();
|
||||
|
||||
auto client_hello = client.pop_datagram();
|
||||
|
||||
QuicrConnection server(0, 0, tw::net::Address({}), nullptr);
|
||||
|
||||
server.process_datagram(client_hello);
|
||||
|
||||
auto server_hello = server.pop_datagram();
|
||||
|
||||
client.process_datagram(server_hello);
|
||||
|
||||
REQUIRE(client.self_id() == server.peer_id());
|
||||
REQUIRE(client.peer_id() == server.self_id());
|
||||
}
|
||||
|
||||
TEST_CASE("When client receives ACK, it won't send the packet again", "[quicr2]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
connection.send_initial_hello();
|
||||
|
||||
// should contain only the hello frame
|
||||
REQUIRE(connection.has_next_datagram() == true);
|
||||
|
||||
auto buffer = connection.pop_datagram();
|
||||
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
|
||||
std::vector<std::byte> target(1200);
|
||||
size_t offset = 0;
|
||||
|
||||
std::vector<uint32_t> acks = { header.packet_number.value() };
|
||||
QuicrFrame hello_frame = QuicrFrame::make_hello();
|
||||
|
||||
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection);
|
||||
encoder
|
||||
.encode_ack_frame(acks);
|
||||
|
||||
|
||||
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
}
|
||||
|
||||
TEST_CASE("Connection don't send ACK when packet has no reliable frames", "[quicr3]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
|
||||
auto buffer = connection.pop_datagram();
|
||||
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
|
||||
|
||||
std::vector<std::byte> target(1200);
|
||||
size_t offset = 0;
|
||||
|
||||
std::string message = "Hello world";
|
||||
|
||||
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection);
|
||||
encoder
|
||||
.encode_stream_frame(std::as_writable_bytes(std::span(message)), false);
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
|
||||
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
}
|
||||
|
||||
TEST_CASE("Connection sends ACK when the packet has reliable frames", "[quicr3]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
|
||||
auto buffer = connection.pop_datagram();
|
||||
QuicrPacket header = QuicrDecoder::decode_packet(buffer);
|
||||
|
||||
std::vector<std::byte> target(1200);
|
||||
size_t offset = 0;
|
||||
|
||||
std::string message = "Hello world";
|
||||
|
||||
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection);
|
||||
encoder
|
||||
.encode_stream_frame(std::as_writable_bytes(std::span(message)), true);
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == false);
|
||||
|
||||
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
|
||||
|
||||
REQUIRE(connection.has_next_datagram() == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Connection applies to ACK to all packets that sent the frame", "[quicr2]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
connection.send_initial_hello();
|
||||
|
||||
auto dgram1 = connection.pop_datagram();
|
||||
auto packet1 = QuicrDecoder::decode_packet(dgram1);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
|
||||
|
||||
auto dgram2 = connection.pop_datagram();
|
||||
auto packet2 = QuicrDecoder::decode_packet(dgram2);
|
||||
|
||||
REQUIRE(packet1.packet_number.value() != packet2.packet_number.value());
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(TW_NET_HELLO_RETRY_INTERVAL_IN_MILLIS));
|
||||
|
||||
std::vector<std::byte> target(1200);
|
||||
size_t offset = 0;
|
||||
QuicrConnection connection2(0, 0, tw::net::Address({}), nullptr);
|
||||
|
||||
std::vector<uint32_t> acks = { packet1.packet_number.value() };
|
||||
QuicrPacketEncoder encoder(target, offset, QuicrPacketType::Initial, 0, connection2);
|
||||
encoder
|
||||
.encode_ack_frame(acks);
|
||||
|
||||
connection.process_datagram(std::span(target).subspan(0, encoder.size()));
|
||||
|
||||
REQUIRE(!connection.has_next_datagram());
|
||||
}
|
||||
|
||||
TEST_CASE("Connection can be established", "[quicr2]") {
|
||||
std::barrier create_sync_point(2);
|
||||
std::barrier send_sync_point(2);
|
||||
std::barrier client_send_sync_point(2);
|
||||
std::string mesg = "Hello world";
|
||||
std::string client_msg = "Client hello";
|
||||
|
||||
std::thread server_thread([&]() {
|
||||
auto endpoint_r = QuicrEndpoint::create();
|
||||
|
||||
REQUIRE(endpoint_r);
|
||||
|
||||
auto endpoint = std::move(endpoint_r.value());
|
||||
|
||||
REQUIRE(endpoint->bind(6971));
|
||||
|
||||
auto listener_r = QuicrConnectionListener::listen(endpoint.get());
|
||||
REQUIRE(listener_r);
|
||||
auto listener = std::move(listener_r.value());
|
||||
|
||||
create_sync_point.arrive_and_wait();
|
||||
|
||||
QuicrConnection* connection = nullptr;
|
||||
|
||||
// wait for connection
|
||||
while(connection == nullptr) {
|
||||
endpoint->poll();
|
||||
connection = listener->listen();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
endpoint->poll();
|
||||
|
||||
spdlog::info("Connection established with peer id: 0x{:x}", connection->peer_id());
|
||||
|
||||
// write whole message
|
||||
auto bytes = std::as_writable_bytes(std::span(mesg.begin(), mesg.end()));
|
||||
auto r = connection->send_message(bytes, true);
|
||||
if(!r) {
|
||||
spdlog::error("Failed to write to connection");
|
||||
}
|
||||
|
||||
REQUIRE(r);
|
||||
|
||||
endpoint->poll();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_sync_point.arrive_and_wait();
|
||||
client_send_sync_point.arrive_and_wait();
|
||||
|
||||
endpoint->poll();
|
||||
});
|
||||
|
||||
std::thread client_thread([&]() {
|
||||
create_sync_point.arrive_and_wait();
|
||||
|
||||
auto endpoint_r = QuicrEndpoint::create();
|
||||
REQUIRE(endpoint_r);
|
||||
auto endpoint = std::move(*endpoint_r);
|
||||
|
||||
auto connection_result = endpoint->connect(tw::net::Address {"127.0.0.1", 6971}); // QuicrConnection::connect(Address{"127.0.0.1", 6970});
|
||||
REQUIRE(connection_result);
|
||||
|
||||
auto conn = std::move(*connection_result);
|
||||
|
||||
spdlog::info("Client ID: {}", conn->self_id());
|
||||
|
||||
while(conn->state() != QuicrConnectionState::Established) {
|
||||
endpoint->poll();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
endpoint->poll();
|
||||
|
||||
spdlog::info("Connection established");
|
||||
|
||||
send_sync_point.arrive_and_wait();
|
||||
|
||||
endpoint->poll();
|
||||
|
||||
std::string buffer(1024, '\0');
|
||||
spdlog::info("Waiting to receive message from server...");
|
||||
auto r = conn->read_into(std::as_writable_bytes(std::span(buffer.data(), buffer.size())));
|
||||
if(!r) {
|
||||
spdlog::error("Failed to read from connection: {}", r.error().message());
|
||||
}
|
||||
|
||||
spdlog::info("Received: [{}], {}", r.value(), buffer.substr(0, r.value()));
|
||||
|
||||
REQUIRE(buffer.substr(0, r.value()) == mesg);
|
||||
|
||||
auto bytes = std::as_writable_bytes(std::span(client_msg.begin(), client_msg.end()));
|
||||
conn->send_message(bytes, true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
client_send_sync_point.arrive_and_wait();
|
||||
});
|
||||
|
||||
client_thread.join();
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
TEST_CASE("Send large datagram", "[quicr2]") {
|
||||
std::barrier create_sync_point(2);
|
||||
std::barrier send_sync_point(2);
|
||||
std::barrier client_send_sync_point(2);
|
||||
std::string mesg = std::string(2000, 'a');
|
||||
|
||||
std::string client_msg = "Client hello";
|
||||
|
||||
std::thread server_thread([&]() {
|
||||
auto endpoint_r = QuicrEndpoint::create();
|
||||
|
||||
REQUIRE(endpoint_r);
|
||||
|
||||
auto endpoint = std::move(endpoint_r.value());
|
||||
|
||||
REQUIRE(endpoint->bind(6970));
|
||||
|
||||
auto listener_r = QuicrConnectionListener::listen(endpoint.get());
|
||||
REQUIRE(listener_r);
|
||||
auto listener = std::move(listener_r.value());
|
||||
|
||||
create_sync_point.arrive_and_wait();
|
||||
|
||||
QuicrConnection* connection = nullptr;
|
||||
|
||||
// wait for connection
|
||||
while(connection == nullptr) {
|
||||
endpoint->poll();
|
||||
connection = listener->listen();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
endpoint->poll();
|
||||
|
||||
spdlog::info("Connection established with peer id: 0x{:x}", connection->peer_id());
|
||||
|
||||
// write whole message
|
||||
auto bytes = std::as_writable_bytes(std::span(mesg.begin(), mesg.end()));
|
||||
auto r = connection->send_message(bytes, true);
|
||||
if(!r) {
|
||||
spdlog::error("Failed to write to connection");
|
||||
}
|
||||
|
||||
REQUIRE(r);
|
||||
|
||||
endpoint->poll();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_sync_point.arrive_and_wait();
|
||||
client_send_sync_point.arrive_and_wait();
|
||||
|
||||
endpoint->poll();
|
||||
});
|
||||
|
||||
std::thread client_thread([&]() {
|
||||
create_sync_point.arrive_and_wait();
|
||||
|
||||
auto endpoint_r = QuicrEndpoint::create();
|
||||
REQUIRE(endpoint_r);
|
||||
auto endpoint = std::move(*endpoint_r);
|
||||
|
||||
spdlog::info("Connecting");
|
||||
auto connection_result = endpoint->connect(tw::net::Address {"127.0.0.1", 6970}); // QuicrConnection::connect(Address{"127.0.0.1", 6970});
|
||||
if(!connection_result) {
|
||||
spdlog::error("Failed to connect to server: {}", connection_result.error().message());
|
||||
}
|
||||
|
||||
auto conn = std::move(*connection_result);
|
||||
|
||||
spdlog::info("Client ID: {}", conn->self_id());
|
||||
|
||||
while(conn->state() != QuicrConnectionState::Established) {
|
||||
endpoint->poll();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
}
|
||||
endpoint->poll();
|
||||
|
||||
spdlog::info("Connection established");
|
||||
|
||||
send_sync_point.arrive_and_wait();
|
||||
|
||||
endpoint->poll();
|
||||
|
||||
std::string buffer(64 * 1024, '\0');
|
||||
spdlog::info("Waiting to receive message from server...");
|
||||
auto r = conn->read_into(std::as_writable_bytes(std::span(buffer.data(), buffer.size())));
|
||||
if(!r) {
|
||||
spdlog::error("Failed to read from connection: {}", r.error().message());
|
||||
}
|
||||
|
||||
spdlog::info("Received: [{}], {}", r.value(), buffer.substr(0, r.value()));
|
||||
|
||||
REQUIRE(buffer.substr(0, r.value()) == mesg);
|
||||
|
||||
auto bytes = std::as_writable_bytes(std::span(client_msg.begin(), client_msg.end()));
|
||||
conn->send_message(bytes, true);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
client_send_sync_point.arrive_and_wait();
|
||||
});
|
||||
|
||||
client_thread.join();
|
||||
server_thread.join();
|
||||
}
|
||||
|
||||
TEST_CASE("Sending message through closed connection returns error", "[quicr2]") {
|
||||
QuicrConnection connection(0, 0, tw::net::Address({}), nullptr);
|
||||
|
||||
REQUIRE(connection.state() == QuicrConnectionState::Closed);
|
||||
|
||||
std::string mesg = "Hello world";
|
||||
auto bytes = std::as_writable_bytes(std::span(mesg.begin(), mesg.end()));
|
||||
auto send_r = connection.send_message(bytes, true);
|
||||
REQUIRE(!send_r);
|
||||
|
||||
REQUIRE(send_r.error().type() == QuicrErrorType::ConnectionClosed);
|
||||
}
|
||||
|
||||
TEST_CASE("Frame can close the connection", "[quicr3]") {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
#include "TcpListener.hpp"
|
||||
#include "bytebuffer/ByteBufferReader.hpp"
|
||||
#include "bytebuffer/ByteBufferWriter.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
#include "io/Read.hpp"
|
||||
#include "io/Write.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#define PORT 6970
|
||||
|
||||
#define FRAMES_PER_SECOND 60
|
||||
|
||||
#define SECONDS_OF_TESTING 10
|
||||
|
||||
void server_func(std::atomic<bool>& is_done, tw::net::Write<std::byte>* writer, tw::net::Read<std::byte>* reader) {
|
||||
double value = 0.0f;
|
||||
|
||||
std::vector<std::byte> inbound_buffer(1200);
|
||||
size_t inbound_length = 0;
|
||||
|
||||
std::vector<std::byte> outbound_buffer(1200);
|
||||
|
||||
while(!is_done) {
|
||||
auto read_r = reader->read_into(std::span(inbound_buffer).subspan(inbound_length));
|
||||
inbound_length += *read_r;
|
||||
|
||||
uint32_t frame_number = 0;
|
||||
|
||||
auto decoder = tw::net::ByteBufferReader(std::span(inbound_buffer).subspan(0, inbound_length));
|
||||
while(decoder.remaining()) {
|
||||
auto read_r = decoder.pop_bytes(&frame_number);
|
||||
if(!read_r) {
|
||||
break;
|
||||
}
|
||||
|
||||
double velocity = 0.0f;
|
||||
read_r = decoder.pop_bytes(&velocity);
|
||||
if(!read_r) {
|
||||
break;
|
||||
}
|
||||
|
||||
value += velocity;
|
||||
|
||||
// encode response
|
||||
tw::net::ByteBufferWriter encoder((std::span<std::byte>(outbound_buffer)));
|
||||
encoder.write_bytes(&frame_number);
|
||||
|
||||
encoder.write_bytes(&value);
|
||||
|
||||
auto write_r = writer->write(std::span(outbound_buffer).subspan(0, encoder.length()));
|
||||
if(!write_r) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// move bytes back
|
||||
memcpy(inbound_buffer.data(), inbound_buffer.data() + decoder.position(), decoder.remaining());
|
||||
}
|
||||
}
|
||||
|
||||
void client_func(std::atomic<bool>& is_done, tw::net::Write<std::byte>* writer, tw::net::Read<std::byte>* reader) {
|
||||
std::vector<std::byte> outbound_buffer(1200);
|
||||
std::vector<std::byte> inbound_buffer(1200);
|
||||
|
||||
uint32_t frame_number = 0;
|
||||
|
||||
while(!is_done) {
|
||||
tw::net::ByteBufferWriter writer(outbound_buffer);
|
||||
|
||||
writer.write_bytes(&frame_number);
|
||||
double random = std::sin(frame_number);
|
||||
writer.write_bytes(&random);
|
||||
|
||||
// writer.write_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
double derivation_func(uint32_t frame_number) {
|
||||
return std::sin((double)frame_number / 25.0f);
|
||||
}
|
||||
|
||||
void test_quic() {
|
||||
std::atomic<bool> client_is_done = false;
|
||||
|
||||
std::thread server_thread([&]() {
|
||||
auto server_endpoint = tw::net::quicr::QuicrEndpoint::create().value();
|
||||
assert(server_endpoint->bind(PORT));
|
||||
|
||||
auto listener_r = tw::net::quicr::QuicrConnectionListener::listen(server_endpoint.get());
|
||||
auto listener = std::move(listener_r.value());
|
||||
|
||||
tw::net::quicr::QuicrConnection* connection = nullptr;
|
||||
while(connection == nullptr) {
|
||||
server_endpoint->poll();
|
||||
connection = listener->listen();
|
||||
}
|
||||
|
||||
uint32_t frame_number = 0;
|
||||
|
||||
std::vector<std::byte> buffer(1200);
|
||||
std::vector<std::byte> outbound_buffer(1200);
|
||||
|
||||
double value = 0.0f;
|
||||
|
||||
while(true) {
|
||||
if(client_is_done) {
|
||||
break;
|
||||
}
|
||||
|
||||
server_endpoint->poll();
|
||||
|
||||
auto read_r = connection->read_into(buffer);
|
||||
|
||||
if(read_r.has_value() && *read_r > 0) {
|
||||
ZoneScopedN("Server read");
|
||||
tw::net::ByteBufferReader reader((std::span<std::byte>(buffer).subspan(0, read_r.value())));
|
||||
|
||||
uint32_t frame_number = 0;
|
||||
reader.pop_bytes(&frame_number);
|
||||
|
||||
double velocity = 0;
|
||||
reader.pop_bytes(&velocity);
|
||||
|
||||
value += velocity;
|
||||
}
|
||||
|
||||
tw::net::ByteBufferWriter writer(outbound_buffer);
|
||||
writer.write_bytes(&frame_number);
|
||||
writer.write_bytes(&value);
|
||||
|
||||
auto send_r = connection->send_message(std::span(outbound_buffer).subspan(0, writer.length()), false);
|
||||
assert(send_r.has_value());
|
||||
|
||||
server_endpoint->poll();
|
||||
frame_number++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
});
|
||||
|
||||
std::thread client_thread([&client_is_done]() {
|
||||
auto client_endpoint = tw::net::quicr::QuicrEndpoint::create().value();
|
||||
auto connection = client_endpoint->connect({"127.0.0.1", PORT}).value();
|
||||
|
||||
while(connection->state() != tw::net::quicr::Established) {
|
||||
client_endpoint->poll();
|
||||
}
|
||||
|
||||
std::vector<std::byte> outbound_buffer(1200);
|
||||
std::vector<std::byte> inbound_buffer(1200);
|
||||
|
||||
std::map<uint32_t, std::chrono::steady_clock::time_point> sent_at;
|
||||
|
||||
int32_t countdown = FRAMES_PER_SECOND * SECONDS_OF_TESTING;
|
||||
|
||||
std::ofstream quicr_csv("quicr.csv");
|
||||
std::ofstream quicr_integration_csv("quicr_integration.csv");
|
||||
uint32_t frame_number = 0;
|
||||
double position = 0;
|
||||
|
||||
while(true) {
|
||||
if(countdown <= 0) {
|
||||
client_is_done.store(true);
|
||||
break;
|
||||
}
|
||||
|
||||
client_endpoint->poll();
|
||||
tw::net::ByteBufferWriter writer(outbound_buffer);
|
||||
|
||||
writer.write_bytes(&frame_number);
|
||||
double random = derivation_func(frame_number);
|
||||
writer.write_bytes(&random);
|
||||
|
||||
auto send_r = connection->send_message(std::span(outbound_buffer).subspan(0, writer.length()), false);
|
||||
assert(send_r.has_value());
|
||||
|
||||
sent_at.emplace(frame_number, std::chrono::steady_clock::now());
|
||||
|
||||
auto read_r = connection->read_into(std::span<std::byte>(inbound_buffer));
|
||||
if(read_r.has_value() && *read_r > 0) {
|
||||
tw::net::ByteBufferReader reader(std::span<std::byte>(inbound_buffer).subspan(0, read_r.value()));
|
||||
|
||||
uint32_t _frame_number = 0;
|
||||
reader.pop_bytes(&_frame_number);
|
||||
|
||||
if(!sent_at.contains(_frame_number)) {
|
||||
spdlog::warn("Frame {} not sent", _frame_number);
|
||||
continue;
|
||||
}
|
||||
|
||||
reader.pop_bytes(&position);
|
||||
|
||||
auto rtt = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - sent_at[_frame_number]).count();
|
||||
|
||||
spdlog::info("Frame {} received after {}ms", _frame_number, rtt);
|
||||
sent_at.erase(_frame_number);
|
||||
quicr_csv << _frame_number << "," << rtt << "," << position << std::endl;
|
||||
countdown--;
|
||||
}
|
||||
|
||||
quicr_integration_csv << frame_number << "," << position << std::endl;
|
||||
|
||||
client_endpoint->poll();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
frame_number++;
|
||||
}
|
||||
});
|
||||
|
||||
server_thread.join();
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
void test_tcp() {
|
||||
std::atomic<bool> client_is_done(false);
|
||||
|
||||
std::thread server_thread([&]() {
|
||||
tw::net::Address address {"127.0.0.1", PORT};
|
||||
auto server_listener = tw::net::TcpListener::listen(address, PORT).value();
|
||||
|
||||
std::optional<tw::net::TcpStream> stream;
|
||||
while(true) {
|
||||
auto stream_r = server_listener.listen();
|
||||
if(stream_r) {
|
||||
stream = std::move(*stream_r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto non_blocking_r = stream->set_non_blocking();
|
||||
|
||||
std::vector<std::byte> buffer(1200);
|
||||
std::vector<std::byte> outbound_buffer(1200);
|
||||
uint32_t frame_number = 0;
|
||||
int32_t countdown = FRAMES_PER_SECOND * SECONDS_OF_TESTING;
|
||||
|
||||
double value = 0.0f;
|
||||
|
||||
while(!client_is_done) {
|
||||
auto read_r = stream->read_into(buffer);
|
||||
|
||||
if(read_r.has_value() && *read_r > 0) {
|
||||
tw::net::ByteBufferReader reader((std::span<std::byte>(buffer).subspan(0, read_r.value())));
|
||||
|
||||
while(reader.remaining() > 0) {
|
||||
uint32_t _frame_number = 0;
|
||||
reader.pop_bytes(&_frame_number);
|
||||
|
||||
double velocity = 0;
|
||||
reader.pop_bytes(&velocity);
|
||||
value += velocity;
|
||||
|
||||
countdown--;
|
||||
}
|
||||
}
|
||||
|
||||
tw::net::ByteBufferWriter writer(outbound_buffer);
|
||||
writer.write_bytes(&frame_number);
|
||||
writer.write_bytes(&value);
|
||||
|
||||
auto send_r = stream->write(std::span(outbound_buffer).subspan(0, writer.length()));
|
||||
assert(send_r.has_value());
|
||||
|
||||
frame_number++;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
});
|
||||
|
||||
std::thread client_thread([&client_is_done]() {
|
||||
auto client_stream = tw::net::TcpStream::connect({"127.0.0.1", PORT}).value();
|
||||
auto non_blocking_r = client_stream.set_non_blocking();
|
||||
|
||||
std::vector<std::byte> outbound_buffer(1200);
|
||||
std::vector<std::byte> inbound_buffer(1200);
|
||||
|
||||
std::map<uint32_t, std::chrono::steady_clock::time_point> sent_at;
|
||||
int32_t countdown = FRAMES_PER_SECOND * SECONDS_OF_TESTING;
|
||||
uint32_t frame_number = 0;
|
||||
|
||||
// open file tcp.csv
|
||||
std::ofstream tcp_csv("tcp.csv");
|
||||
std::ofstream tcp_integration_csv("tcp_integration.csv");
|
||||
|
||||
double position = 0.0f;
|
||||
|
||||
while(true) {
|
||||
if(countdown <= 0) {
|
||||
client_is_done.store(true);
|
||||
break;
|
||||
}
|
||||
|
||||
tw::net::ByteBufferWriter writer(outbound_buffer);
|
||||
|
||||
writer.write_bytes(&frame_number);
|
||||
double random = derivation_func(frame_number);
|
||||
writer.write_bytes(&random);
|
||||
|
||||
auto send_r = client_stream.write(std::span(outbound_buffer).subspan(0, writer.length()));
|
||||
assert(send_r.has_value());
|
||||
|
||||
sent_at.emplace(frame_number, std::chrono::steady_clock::now());
|
||||
frame_number++;
|
||||
|
||||
auto read_r = client_stream.read_into(std::span<std::byte>(inbound_buffer));
|
||||
if(read_r.has_value() && *read_r > 0) {
|
||||
tw::net::ByteBufferReader reader(std::span<std::byte>(inbound_buffer).subspan(0, read_r.value()));
|
||||
|
||||
while(reader.remaining() > 0) {
|
||||
uint32_t _frame_number = 0;
|
||||
reader.pop_bytes(&_frame_number);
|
||||
|
||||
reader.pop_bytes(&position);
|
||||
|
||||
auto rtt = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - sent_at[_frame_number]).count();
|
||||
|
||||
spdlog::info("Frame {} received after {}ms", _frame_number, rtt);
|
||||
tcp_csv << _frame_number << "," << rtt << "," << position << std::endl;
|
||||
countdown--;
|
||||
}
|
||||
}
|
||||
|
||||
tcp_integration_csv << frame_number << "," << position << std::endl;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
|
||||
tcp_csv.close();
|
||||
});
|
||||
|
||||
server_thread.join();
|
||||
client_thread.join();
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_quic();
|
||||
|
||||
test_tcp();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
|
||||
using namespace tw::net;
|
||||
using namespace tw::net::quicr;
|
||||
|
||||
TEST_CASE("Endpoint registers new connection with correct ID", "[quicr2]") {
|
||||
auto endpoint_r = QuicrEndpoint::create();
|
||||
REQUIRE(endpoint_r);
|
||||
auto& server_endpoint = *endpoint_r.value();
|
||||
REQUIRE(server_endpoint.bind(6972));
|
||||
|
||||
auto client_endpoint_r = QuicrEndpoint::create();
|
||||
REQUIRE(client_endpoint_r);
|
||||
auto& client_endpoint = *client_endpoint_r.value();
|
||||
|
||||
auto connect_r = client_endpoint.connect(Address{"127.0.0.1", 6972});
|
||||
REQUIRE(connect_r);
|
||||
QuicrConnection& connection = *connect_r.value();
|
||||
REQUIRE(connection.self_id() != 0);
|
||||
REQUIRE(connection.peer_id() != 0);
|
||||
|
||||
connection.send_initial_hello();
|
||||
|
||||
client_endpoint.poll();
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
server_endpoint.poll();
|
||||
|
||||
auto clients = server_endpoint.clients();
|
||||
REQUIRE(clients.size() == 2);
|
||||
|
||||
REQUIRE(((clients[0].first == connection.peer_id()) || (clients[1].first == connection.peer_id())));
|
||||
REQUIRE(clients[0].second->peer_id() == connection.self_id());
|
||||
REQUIRE(clients[1].second->peer_id() == connection.self_id());
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Testing overloading the listener and how much can it handle.
|
||||
*/
|
||||
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include "Address.hpp"
|
||||
#include "protocol/quicr/QuicrConnection.hpp"
|
||||
#include "protocol/quicr/QuicrEndpoint.hpp"
|
||||
#include "protocol/quicr/QuicrConnectionListener.hpp"
|
||||
|
||||
using namespace tw::net;
|
||||
using namespace tw::net::quicr;
|
||||
|
||||
std::atomic<bool> is_stopped(false);
|
||||
|
||||
void got_signal(int) {
|
||||
is_stopped.store(true);
|
||||
}
|
||||
|
||||
void register_signal_handler() {
|
||||
struct sigaction sa;
|
||||
memset( &sa, 0, sizeof(sa) );
|
||||
sa.sa_handler = got_signal;
|
||||
sigfillset(&sa.sa_mask);
|
||||
sigaction(SIGINT,&sa,NULL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
register_signal_handler();
|
||||
// spdlog::set_pattern("[%H:%M:%S] [thread %t] %v");
|
||||
const int NUM_CONNECTIONS = 500;
|
||||
std::thread server_thread([&]() {
|
||||
auto server_endpoint_r = QuicrEndpoint::create();
|
||||
assert(server_endpoint_r);
|
||||
|
||||
auto server_endpoint = std::move(*server_endpoint_r);
|
||||
assert(server_endpoint->bind(8100));
|
||||
|
||||
auto listen_r = QuicrConnectionListener::listen(server_endpoint.get());
|
||||
assert(listen_r);
|
||||
auto listen = std::move(listen_r.value());
|
||||
|
||||
|
||||
struct ConnectionTestSession {
|
||||
QuicrConnection *connection;
|
||||
bool is_answered;
|
||||
|
||||
std::vector<std::byte> buffer;
|
||||
|
||||
ConnectionTestSession(QuicrConnection *connection)
|
||||
: connection(connection), is_answered(false),
|
||||
buffer(1024 * 16) {}
|
||||
};
|
||||
|
||||
std::unordered_map<Address, ConnectionTestSession*> connections;
|
||||
uint32_t answered_count = 0;
|
||||
uint32_t num_connections = 0;
|
||||
|
||||
while(answered_count < NUM_CONNECTIONS) {
|
||||
if(is_stopped) break;
|
||||
server_endpoint->poll();
|
||||
|
||||
auto new_connection = listen->listen();
|
||||
if(new_connection) {
|
||||
connections[new_connection->address()] = new ConnectionTestSession(new_connection);
|
||||
num_connections++;
|
||||
spdlog::warn("Num connections: {}", num_connections);
|
||||
}
|
||||
|
||||
for(auto& connection : connections) {
|
||||
// assert(!connection.second->is_answered);
|
||||
auto read_r = connection.second->connection->read_into(connection.second->buffer);
|
||||
assert(read_r);
|
||||
if(connection.second->is_answered) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string mesg(connection.second->buffer.begin(), connection.second->buffer.begin() + *read_r);
|
||||
std::transform(mesg.begin(), mesg.end(), mesg.begin(), ::toupper);
|
||||
|
||||
connection.second->connection->send_message(std::as_writable_bytes(std::span(mesg)), true);
|
||||
|
||||
connection.second->is_answered = true;
|
||||
answered_count++;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
spdlog::warn("DONE: got all answers");
|
||||
});
|
||||
|
||||
std::vector<std::unique_ptr<QuicrEndpoint>> endpoints(NUM_CONNECTIONS);
|
||||
std::vector<QuicrConnection*> connections(NUM_CONNECTIONS);
|
||||
std::vector<bool> established_counts(NUM_CONNECTIONS, false);
|
||||
|
||||
for(int i = 0; i < NUM_CONNECTIONS; i++) {
|
||||
endpoints[i] = QuicrEndpoint::create().value();
|
||||
|
||||
connections[i] = endpoints[i]->connect(Address{"127.0.0.1", 8100}).value();
|
||||
}
|
||||
|
||||
std::atomic<uint32_t> established_count(0);
|
||||
spdlog::info("Starting overload test with {} connections", NUM_CONNECTIONS);
|
||||
|
||||
while(!is_stopped && established_count.load() < NUM_CONNECTIONS) {
|
||||
for(int i = 0; i < NUM_CONNECTIONS; i++) {
|
||||
{
|
||||
endpoints[i]->poll();
|
||||
}
|
||||
if(!established_counts[i] && connections[i]->state() == QuicrConnectionState::Established) {
|
||||
established_counts[i] = true;
|
||||
established_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_thread.join();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user