Files
Towards/modules/network/include/UdpStream.hpp
T

165 lines
4.9 KiB
C++
Raw Normal View History

2026-07-18 14:31:15 +02:00
#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;
}
};
}