165 lines
4.9 KiB
C++
165 lines
4.9 KiB
C++
|
|
#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;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|