fprime/Drv/Ip/IpSocket.hpp
M Starch 9ed68fdd06
Correcting shutdown/close usage; refactoring TcpServer (#2950)
* Correcting shutdown/close usage; refactoring TcpServer

* Fixing Tcp

* Review and CI fixes

* ...and the other half of the fixes
2024-10-16 09:36:34 -07:00

216 lines
10 KiB
C++

// ======================================================================
// \title IpSocket.hpp
// \author mstarch
// \brief hpp file for IpSocket core implementation classes
//
// \copyright
// Copyright 2009-2020, by the California Institute of Technology.
// ALL RIGHTS RESERVED. United States Government Sponsorship
// acknowledged.
//
// ======================================================================
#ifndef DRV_IP_IPHELPER_HPP_
#define DRV_IP_IPHELPER_HPP_
#include <FpConfig.hpp>
#include <IpCfg.hpp>
#include <Os/Mutex.hpp>
namespace Drv {
struct SocketDescriptor final {
PlatformIntType fd = -1; //!< Used for all sockets to track the communication file descriptor
PlatformIntType serverFd = -1; //!< Used for server sockets to track the listening file descriptor
};
/**
* \brief Status enumeration for socket return values
*/
enum SocketIpStatus {
SOCK_SUCCESS = 0, //!< Socket operation successful
SOCK_FAILED_TO_GET_SOCKET = -1, //!< Socket open failed
SOCK_FAILED_TO_GET_HOST_IP = -2, //!< Host IP lookup failed
SOCK_INVALID_IP_ADDRESS = -3, //!< Bad IP address supplied
SOCK_FAILED_TO_CONNECT = -4, //!< Failed to connect socket
SOCK_FAILED_TO_SET_SOCKET_OPTIONS = -5, //!< Failed to configure socket
SOCK_INTERRUPTED_TRY_AGAIN = -6, //!< Interrupted status for retries
SOCK_READ_ERROR = -7, //!< Failed to read socket
SOCK_DISCONNECTED = -8, //!< Failed to read socket with disconnect
SOCK_FAILED_TO_BIND = -9, //!< Failed to bind to socket
SOCK_FAILED_TO_LISTEN = -10, //!< Failed to listen on socket
SOCK_FAILED_TO_ACCEPT = -11, //!< Failed to accept connection
SOCK_SEND_ERROR = -13, //!< Failed to send after configured retries
SOCK_NOT_STARTED = -14, //!< Socket has not been started
SOCK_FAILED_TO_READ_BACK_PORT = -15, //!< Failed to read back port from connection
SOCK_NO_DATA_AVAILABLE = -16, //!< No data available or read operation would block
SOCK_ANOTHER_THREAD_OPENING = -17 //!< Another thread is opening
};
/**
* \brief Helper base-class for setting up Berkeley sockets
*
* Certain IP headers have conflicting definitions with the m_data member of various types in fprime. TcpHelper
* separates the ip setup from the incoming Fw::Buffer in the primary component class preventing this collision.
*/
class IpSocket {
public:
IpSocket();
virtual ~IpSocket(){};
/**
* \brief configure the ip socket with host and transmission timeouts
*
* Configures the IP handler (Tcp, Tcp server, and Udp) to use the given hostname and port. When multiple ports are
* used for send/receive these settings affect the send direction (as is the case for udp). Hostname DNS translation
* is left up to the caller and thus hostname must be an IP address in dot-notation of the form "x.x.x.x". Port
* cannot be set to 0 as dynamic port assignment is not supported.
*
* Note: for UDP sockets this is equivalent to `configureSend` and only sets up the transmission direction of the
* socket. A separate call to `configureRecv` is required to receive on the socket and should be made before the
* `open` call has been made.
*
* \param hostname: socket uses for outgoing transmissions (and incoming when tcp). Must be of form x.x.x.x
* \param port: port socket uses for outgoing transmissions (and incoming when tcp). Must NOT be 0.
* \param send_timeout_seconds: send timeout seconds portion
* \param send_timeout_microseconds: send timeout microseconds portion. Must be less than 1000000
* \return status of configure
*/
SocketIpStatus configure(const char* hostname, const U16 port, const U32 send_timeout_seconds,
const U32 send_timeout_microseconds);
/**
* \brief open the IP socket for communications
*
* This will open the IP socket for communication. This method error checks and validates properties set using the
* `configure` method. Tcp sockets will open bidirectional communication assuming the `configure` function was
* previously called. Udp sockets allow `configureRecv` and `configure`/`configureSend` calls to configure for
* each direction separately and may be operated in a single-direction or bidirectional mode. This call returns a
* status of SOCK_SEND means the port is ready for transmissions and any other status should be treated as an error
* with the socket not capable of sending nor receiving. This method will properly close resources on any
* unsuccessful status.
*
* In the case of server components (TcpServer) this function will block until a client has connected.
*
* Note: delegates to openProtocol for protocol specific implementation
*
* \param socketDescriptor: socket descriptor to update with opened port
* \return status of open
*/
SocketIpStatus open(SocketDescriptor& socketDescriptor);
/**
* \brief send data out the IP socket from the given buffer
*
* Sends data out of the IpSocket. This outgoing transmission will be retried several times if the transmission
* fails to send all the data. Retries are globally configured in the `IpCfg.hpp` header. Should the
* socket be unavailable, SOCK_DISCONNECTED is returned and the socket should be reopened using the `open` call.
* This can happen even when the socket has already been opened should a transmission error/closure be detected.
* Unless an error is received, all data will have been transmitted.
*
* Note: delegates to `sendProtocol` to send the data
*
* \param fd: file descriptor to send to
* \param data: pointer to data to send
* \param size: size of data to send
* \return status of the send, SOCK_DISCONNECTED to reopen, SOCK_SUCCESS on success, something else on error
*/
SocketIpStatus send(const SocketDescriptor& socketDescriptor, const U8* const data, const U32 size);
/**
* \brief receive data from the IP socket from the given buffer
*
* Receives data from the IpSocket. Should the socket be unavailable, SOCK_DISCONNECTED will be returned and
* the socket should be reopened using the `open` call. This can happen even when the socket has already been opened
* should a transmission error/closure be detected. Since this blocks until data is available, it will retry as long
* as EINTR is set and less than a max number of iterations has passed. This function will block to receive data and
* will retry (up to a configured set of retries) as long as EINTR is returned.
*
* Note: delegates to `recvProtocol` to send the data
*
* \param socketDescriptor: socket descriptor to recv from
* \param data: pointer to data to fill with received data
* \param size: maximum size of data buffer to fill
* \return status of the send, SOCK_DISCONNECTED to reopen, SOCK_SUCCESS on success, something else on error
*/
SocketIpStatus recv(const SocketDescriptor& fd, U8* const data, U32& size);
/**
* \brief closes the socket
*
* Closes the socket opened by the open call. In this case of the TcpServer, this does NOT close server's listening
* port but will close the active client connection.
*
* \param socketDescriptor: socket descriptor to close
*/
void close(const SocketDescriptor& socketDescriptor);
/**
* \brief shutdown the socket
*
* Shuts down the socket opened by the open call. In this case of the TcpServer, this does shut down server's
* listening port, but rather shuts down the active client.
*
* A shut down begins the termination of communication. The underlying socket will coordinate a clean shutdown, and
* it is safe to close the socket once a recv with 0 size has returned or an appropriate timeout has been reached.
*
* \param socketDescriptor: socket descriptor to shutdown
*/
void shutdown(const SocketDescriptor& socketDescriptor);
PROTECTED:
/**
* \brief Check if the given port is valid for the socket
*
* Some ports should be allowed for sockets and disabled on others (e.g. port 0 is a valid tcp server port but not a
* client. This will check the port and return "true" if the port is valid, or "false" otherwise. In the default
* implementation, all ports are considered valid.
*
* \param port: port to check
* \return true if valid, false otherwise
*/
virtual bool isValidPort(U16 port);
/**
* \brief setup the socket timeout properties of the opened outgoing socket
* \param socketDescriptor: socket descriptor to setup
* \return status of timeout setup
*/
SocketIpStatus setupTimeouts(PlatformIntType socketFd);
/**
* \brief converts a given address in dot form x.x.x.x to an ip address. ONLY works for IPv4.
* \param address: address to convert
* \param ip4: IPv4 representation structure to fill
* \return: status of conversion
*/
static SocketIpStatus addressToIp4(const char* address, void* ip4);
/**
* \brief Protocol specific open implementation, called from open.
* \param socketDescriptor: (output) socket descriptor opened. Only valid on SOCK_SUCCESS. Otherwise will be invalid
* \return status of open
*/
virtual SocketIpStatus openProtocol(SocketDescriptor& fd) = 0;
/**
* \brief Protocol specific implementation of send. Called directly with retry from send.
* \param socketDescriptor: socket descriptor to send to
* \param data: data to send
* \param size: size of data to send
* \return: size of data sent, or -1 on error.
*/
virtual I32 sendProtocol(const SocketDescriptor& socketDescriptor, const U8* const data, const U32 size) = 0;
/**
* \brief Protocol specific implementation of recv. Called directly with error handling from recv.
* \param socket: socket descriptor to recv from
* \param data: data pointer to fill
* \param size: size of data buffer
* \return: size of data received, or -1 on error.
*/
virtual I32 recvProtocol(const SocketDescriptor& socketDescriptor, U8* const data, const U32 size) = 0;
U32 m_timeoutSeconds;
U32 m_timeoutMicroseconds;
U16 m_port; //!< IP address port used
char m_hostname[SOCKET_MAX_HOSTNAME_SIZE]; //!< Hostname to supply
};
} // namespace Drv
#endif /* DRV_SOCKETIPDRIVER_SOCKETHELPER_HPP_ */