/*
Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
Queen in Right of Canada (Communications Research Center Canada)
Copyright (C) 2024
Matthias P. Braendli, matthias.braendli@mpb.li
http://www.opendigitalradio.org
*/
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#pragma once
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "ThreadsafeQueue.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
namespace Socket {
struct InetAddress {
struct sockaddr_storage addr = {};
struct sockaddr *as_sockaddr() { return reinterpret_cast(&addr); };
void resolveUdpDestination(const std::string& destination, int port);
std::string to_string() const;
};
/** This class represents a UDP packet.
*
* A UDP packet contains a payload (sequence of bytes) and an address. For
* outgoing packets, the address is the destination address. For incoming
* packets, the address tells the user from what source the packet arrived from.
*/
class UDPPacket
{
public:
UDPPacket();
UDPPacket(size_t initSize);
std::vector buffer;
InetAddress address;
};
/**
* This class represents a socket for sending and receiving UDP packets.
*
* A UDP socket is the sending or receiving point for a packet delivery service.
* Each packet sent or received on a datagram socket is individually
* addressed and routed. Multiple packets sent from one machine to another may
* be routed differently, and may arrive in any order.
*/
class UDPSocket
{
public:
/** Create a new socket that will not be bound to any port. To be used
* for data output.
*/
UDPSocket();
/** Create a new socket.
* @param port The port number on which the socket will be bound
*/
UDPSocket(int port);
/** Create a new socket.
* @param port The port number on which the socket will be bound
* @param name The IP address on which the socket will be bound.
* It is used to bind the socket on a specific interface if
* the computer have many NICs.
*/
UDPSocket(int port, const std::string& name);
~UDPSocket();
UDPSocket(const UDPSocket& other) = delete;
const UDPSocket& operator=(const UDPSocket& other) = delete;
UDPSocket(UDPSocket&& other);
const UDPSocket& operator=(UDPSocket&& other);
/** Close the already open socket, and create a new one. Throws a runtime_error on error. */
void reinit(int port);
void reinit(int port, const std::string& name);
void init_receive_multicast(int port, const std::string& local_if_addr, const std::string& mcastaddr);
void close(void);
void send(UDPPacket& packet);
void send(const std::vector& data, InetAddress destination);
void send(const std::string& data, InetAddress destination);
UDPPacket receive(size_t max_size);
void setMulticastSource(const char* source_addr);
void setMulticastTTL(int ttl);
/** Set blocking mode. By default, the socket is blocking.
* throws a runtime_error on error.
*/
void setBlocking(bool block);
SOCKET getNativeSocket() const;
int getPort() const;
private:
void join_group(const char* groupname, const char* if_addr = nullptr);
void post_init();
protected:
SOCKET m_sock = INVALID_SOCKET;
int m_port = 0;
std::string m_multicast_source = "";
};
/* UDP packet receiver supporting receiving from several ports at once */
class UDPReceiver {
public:
void add_receive_port(int port, const std::string& bindto, const std::string& mcastaddr);
struct ReceivedPacket {
std::vector packetdata;
InetAddress received_from;
int port_received_on;
};
class Interrupted {};
class Timeout {};
/* Returns one or several packets,
* throws a Timeout on timeout, Interrupted on EINTR, a runtime_error
* on error. */
std::vector receive(int timeout_ms);
private:
void m_run(void);
std::vector m_sockets;
};
class TCPSocket {
public:
TCPSocket();
~TCPSocket();
TCPSocket(const TCPSocket& other) = delete;
TCPSocket& operator=(const TCPSocket& other) = delete;
TCPSocket(TCPSocket&& other);
TCPSocket& operator=(TCPSocket&& other);
bool valid(void) const;
void connect(const std::string& hostname, int port, bool nonblock = false);
void connect(const std::string& hostname, int port, int timeout_ms);
void listen(int port, const std::string& name);
void close(void);
/* Enable TCP keepalive. See
* https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html
*/
void enable_keepalive(int time, int intvl, int probes);
/* throws a runtime_error on failure, an invalid socket on timeout */
TCPSocket accept(int timeout_ms);
/* returns -1 on error, doesn't work on nonblocking sockets */
ssize_t sendall(const void *buffer, size_t buflen);
/** Send data over the TCP connection.
* @param data The buffer that will be sent.
* @param size Number of bytes to send.
* @param timeout_ms number of milliseconds before timeout, or 0 for infinite timeout
* return number of bytes sent, 0 on timeout, or throws runtime_error.
*/
ssize_t send(const void* data, size_t size, int timeout_ms=0);
class Interrupted {};
/* Returns number of bytes read, 0 on disconnect.
* Throws Interrupted on EINTR, runtime_error on error */
ssize_t recv(void *buffer, size_t length, int flags);
class Timeout {};
/* Returns number of bytes read, 0 on disconnect or refused connection.
* Throws a Timeout on timeout, Interrupted on EINTR, a runtime_error
* on error
*/
ssize_t recv(void *buffer, size_t length, int flags, int timeout_ms);
SOCKET get_sockfd() const { return m_sock; }
private:
explicit TCPSocket(int sockfd);
explicit TCPSocket(int sockfd, InetAddress remote_address);
SOCKET m_sock = -1;
InetAddress m_remote_address;
friend class TCPClient;
};
/* Implements a TCP receiver that auto-reconnects on errors */
class TCPClient {
public:
void connect(const std::string& hostname, int port);
/* Returns numer of bytes read, 0 on auto-reconnect, -1
* on interruption.
* Throws a runtime_error on error */
ssize_t recv(void *buffer, size_t length, int flags, int timeout_ms);
private:
void reconnect(void);
TCPSocket m_sock;
std::string m_hostname;
int m_port;
};
/* Helper class for TCPDataDispatcher, contains a queue of pending data and
* a sender thread. */
class TCPConnection
{
public:
TCPConnection(TCPSocket&& sock);
TCPConnection(const TCPConnection&) = delete;
TCPConnection& operator=(const TCPConnection&) = delete;
~TCPConnection();
ThreadsafeQueue > queue;
private:
std::atomic m_running;
std::thread m_sender_thread;
TCPSocket m_sock;
void process(void);
};
/* Send a TCP stream to several destinations, and automatically disconnect destinations
* whose buffer overflows.
*/
class TCPDataDispatcher
{
public:
TCPDataDispatcher(size_t max_queue_size, size_t buffers_to_preroll);
~TCPDataDispatcher();
TCPDataDispatcher(const TCPDataDispatcher&) = delete;
TCPDataDispatcher& operator=(const TCPDataDispatcher&) = delete;
void start(int port, const std::string& address);
void write(const std::vector& data);
private:
void process();
size_t m_max_queue_size;
size_t m_buffers_to_preroll;
std::atomic m_running = ATOMIC_VAR_INIT(false);
std::string m_exception_data;
std::thread m_listener_thread;
TCPSocket m_listener_socket;
std::mutex m_mutex;
std::deque > m_preroll_queue;
std::list m_connections;
};
struct TCPReceiveMessage { virtual ~TCPReceiveMessage() {}; };
struct TCPReceiveMessageDisconnected : public TCPReceiveMessage { };
struct TCPReceiveMessageEmpty : public TCPReceiveMessage { };
struct TCPReceiveMessageData : public TCPReceiveMessage {
TCPReceiveMessageData(std::vector d) : data(d) {};
std::vector data;
};
/* A TCP Server to receive data, which abstracts the handling of connects and disconnects.
*/
class TCPReceiveServer {
public:
TCPReceiveServer(size_t blocksize);
~TCPReceiveServer();
TCPReceiveServer(const TCPReceiveServer&) = delete;
TCPReceiveServer& operator=(const TCPReceiveServer&) = delete;
void start(int listen_port, const std::string& address);
// Return an instance of a subclass of TCPReceiveMessage that contains up to blocksize
// bytes of data, or TCPReceiveMessageEmpty if no data is available.
std::shared_ptr receive();
private:
void process();
size_t m_blocksize = 0;
ThreadsafeQueue > m_queue;
std::atomic m_running = ATOMIC_VAR_INIT(false);
std::string m_exception_data;
std::thread m_listener_thread;
TCPSocket m_listener_socket;
};
/* A TCP client that abstracts the handling of connects and disconnects.
*/
class TCPSendClient {
public:
TCPSendClient(const std::string& hostname, int port);
~TCPSendClient();
TCPSendClient(const TCPSendClient&) = delete;
TCPSendClient& operator=(const TCPSendClient&) = delete;
struct ErrorStats {
std::string last_error = "";
size_t num_reconnects = 0;
bool has_seen_new_errors = false;
};
/* Throws a runtime_error when the process thread isn't running */
ErrorStats sendall(const std::vector& buffer);
private:
void process();
std::string m_hostname;
int m_port;
bool m_is_connected = false;
TCPSocket m_sock;
static constexpr size_t MAX_QUEUE_SIZE = 512;
ThreadsafeQueue > m_queue;
std::atomic m_running;
std::string m_exception_data;
std::thread m_sender_thread;
TCPSocket m_listener_socket;
std::atomic m_num_reconnects = ATOMIC_VAR_INIT(0);
size_t m_num_reconnects_prev = 0;
std::mutex m_error_mutex;
std::string m_last_error = "";
};
}