#pragma once #include #include #include #include #include #include #include #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 { 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 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 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 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 to(int32_t socket_fd, const Address& address) { return UdpStream(socket_fd, address); } tl::expected write(std::span 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 read_into(std::span 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 read_into(std::span 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; } }; }