fprime/Drv/SocketIpDriver/SocketHelper.cpp
2021-09-16 15:35:13 -07:00

267 lines
8.9 KiB
C++

/*
* SocketHelper.cpp
*
* Created on: May 28, 2020
* Author: tcanham
*/
#include <Fw/Types/BasicTypes.hpp>
#include <Fw/Types/Assert.hpp>
#include <Drv/SocketIpDriver/SocketHelper.hpp>
#include <Fw/Logger/Logger.hpp>
// This implementation has primarily implemented to isolate
// the socket interface from the F' Fw::Buffer class.
// There is a macro in VxWorks (m_data) that collides with
// the m_data member in Fw::Buffer.
#ifdef TGT_OS_TYPE_VXWORKS
#include <socket.h>
#include <inetLib.h>
#include <fioLib.h>
#include <hostLib.h>
#include <ioLib.h>
#include <vxWorks.h>
#include <sockLib.h>
#include <fioLib.h>
#include <taskLib.h>
#include <sysLib.h>
#include <errnoLib.h>
#include <string.h>
#elif defined TGT_OS_TYPE_LINUX || TGT_OS_TYPE_DARWIN
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <cerrno>
#include <arpa/inet.h>
#else
#error OS not supported for IP Socket Communications
#endif
#include <cstdio>
#include <cstring>
namespace Drv {
// put network specific state we need to isolate here
struct SocketState {
struct sockaddr_in m_udpAddr; //!< UDP server address, maybe unused
};
SocketHelper::SocketHelper() : m_state(new SocketState)
,m_socketInFd(-1)
,m_socketOutFd(-1)
,m_sendUdp(false)
,m_timeoutSeconds(0)
,m_timeoutMicroseconds(0)
,m_port(0)
{
}
SocketHelper::~SocketHelper() {
delete this->m_state;
}
SocketIpStatus SocketHelper::configure(
const char* hostname,
const U16 port,
const bool send_udp,
const U32 timeout_seconds,
const U32 timeout_microseconds
) {
this->m_sendUdp = send_udp;
this->m_timeoutSeconds = timeout_seconds;
this->m_timeoutSeconds = timeout_seconds;
this->m_port = port;
// copy hostname to local copy
(void)strncpy(this->m_hostname,hostname,MAX_HOSTNAME_SIZE);
// null terminate
this->m_hostname[MAX_HOSTNAME_SIZE-1] = 0;
return SOCK_SUCCESS;
}
bool SocketHelper::isOpened() {
return (-1 != this->m_socketInFd);
}
void SocketHelper::close() {
if (this->m_socketInFd != -1) {
(void) ::close(this->m_socketInFd);
}
this->m_socketInFd = -1;
this->m_socketOutFd = -1;
}
SocketIpStatus SocketHelper::open() {
SocketIpStatus status = SOCK_SUCCESS;
// Only the input (TCP) socket needs closing
if (this->m_socketInFd != -1) {
(void) ::close(this->m_socketInFd); // Close open sockets, to force a re-open
}
this->m_socketInFd = -1;
// Open a TCP socket for incoming commands, and outgoing data if not using UDP
status = this->openProtocol(SOCK_STREAM);
if (status != SOCK_SUCCESS) {
return status;
}
// If we need UDP sending, attempt to open UDP
if (this->m_sendUdp && this->m_socketOutFd == -1 && (status = this->openProtocol(SOCK_DGRAM, false)) != SOCK_SUCCESS) {
(void) ::close(this->m_socketInFd);
return status;
}
// Not sending UDP, reuse our input TCP socket
else if (!this->m_sendUdp) {
this->m_socketOutFd = this->m_socketInFd;
}
// Coding error, if not successful here
FW_ASSERT(status == SOCK_SUCCESS);
return status;
}
SocketIpStatus SocketHelper::openProtocol(NATIVE_INT_TYPE protocol, bool isInput) {
NATIVE_INT_TYPE socketFd = -1;
struct sockaddr_in address;
// Clear existing file descriptors
if (isInput and this->m_socketInFd != -1) {
(void) ::close(this->m_socketInFd);
this->m_socketInFd = -1;
} else if (not isInput and this->m_socketOutFd != -1) {
(void) ::close(this->m_socketOutFd);
this->m_socketOutFd = -1;
}
// Acquire a socket, or return error
if ((socketFd = ::socket(AF_INET, protocol, 0)) == -1) {
return SOCK_FAILED_TO_GET_SOCKET;
}
// Set up the address port and name
address.sin_family = AF_INET;
address.sin_port = htons(this->m_port);
#if defined TGT_OS_TYPE_VXWORKS || TGT_OS_TYPE_DARWIN
address.sin_len = static_cast<U8>(sizeof(struct sockaddr_in));
#endif
// Get the IP address from host
#ifdef TGT_OS_TYPE_VXWORKS
address.sin_addr.s_addr = inet_addr(this->m_hostname);
#else
// Set timeout socket option
struct timeval timeout;
timeout.tv_sec = this->m_timeoutSeconds;
timeout.tv_usec = this->m_timeoutMicroseconds;
// set socket write to timeout after 1 sec
if (setsockopt(socketFd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<char *>(&timeout), sizeof(timeout)) < 0) {
(void) ::close(socketFd);
return SOCK_FAILED_TO_SET_SOCKET_OPTIONS;
}
// Get possible IP addresses
struct hostent *host_entry;
if ((host_entry = gethostbyname(this->m_hostname)) == NULL || host_entry->h_addr_list[0] == NULL) {
::close(socketFd);
return SOCK_FAILED_TO_GET_HOST_IP;
}
// First IP address to socket sin_addr
if (inet_pton(address.sin_family, m_hostname, &(address.sin_addr)) < 0) {
::close(socketFd);
return SOCK_INVALID_IP_ADDRESS;
};
#endif
// If TCP, connect to the socket to allow for communication
if (protocol != SOCK_DGRAM && connect(socketFd, reinterpret_cast<struct sockaddr *>(&address),
sizeof(address)) < 0) {
::close(socketFd);
return SOCK_FAILED_TO_CONNECT;
}
// Store the UDP address for later
else if (protocol == SOCK_DGRAM) {
FW_ASSERT(sizeof(this->m_state->m_udpAddr) == sizeof(address), sizeof(this->m_state->m_udpAddr), sizeof(address));
memcpy(&this->m_state->m_udpAddr, &address, sizeof(this->m_state->m_udpAddr));
}
// Set the member socket variable
if (isInput) {
this->m_socketInFd = socketFd;
} else {
this->m_socketOutFd = socketFd;
}
Fw::Logger::logMsg("Connected to %s:%hu for %s using %s\n", reinterpret_cast<POINTER_CAST>(m_hostname), m_port,
reinterpret_cast<POINTER_CAST>(isInput ? "uplink" : "downlink"),
reinterpret_cast<POINTER_CAST>((protocol == SOCK_DGRAM) ? "udp" : "tcp"));
return SOCK_SUCCESS;
}
void SocketHelper::send(U8* data, const U32 size) {
U32 total = 0;
// Prevent transmission before connection, or after a disconnect
if (this->m_socketOutFd == -1) {
return;
}
// Attempt to send out data
for (U32 i = 0; i < MAX_SEND_ITERATIONS && total < size; i++) {
I32 sent = 0;
// Output to send UDP
if (this->m_sendUdp) {
sent = ::sendto(this->m_socketOutFd, data + total, size - total, SOCKET_SEND_FLAGS,
reinterpret_cast<struct sockaddr *>(&this->m_state->m_udpAddr), sizeof(this->m_state->m_udpAddr));
}
// Output to send TCP
else {
sent = ::send(this->m_socketOutFd, data + total, size - total, SOCKET_SEND_FLAGS);
}
// Error is EINTR, just try again
if (sent == -1 && errno == EINTR) {
continue;
}
// Error bad file descriptor is a close
else if (sent == -1 && errno == EBADF) {
Fw::Logger::logMsg("[ERROR] Server disconnected\n");
this->close();
this->m_socketOutFd = -1;
break;
}
// Error returned, and it wasn't an interrupt
else if (sent == -1 && errno != EINTR) {
Fw::Logger::logMsg("[ERROR] IP send failed ERRNO: %d UDP: %d\n", errno, m_sendUdp);
break;
}
// Successful write
else {
total += sent;
}
}
}
SocketIpStatus SocketHelper::recv(U8* data, I32 &size) {
SocketIpStatus status = SOCK_SUCCESS;
// Attempt to recv out data
size = ::recv(m_socketInFd, data, MAX_RECV_BUFFER_SIZE, SOCKET_RECV_FLAGS);
// Error returned, and it wasn't an interrupt, nor a reset
if (size == -1 && errno != EINTR && errno != ECONNRESET) {
Fw::Logger::logMsg("[ERROR] IP recv failed ERRNO: %d\n", errno);
status = SOCK_READ_ERROR; // Stop recv task on error
}
// Error is EINTR, just try again
else if (size == -1 && errno == EINTR) {
status = SOCK_INTERRUPTED_TRY_AGAIN;
}
// Zero bytes read means a closed socket
else if (size == 0 || errno == ECONNRESET) {
status = SOCK_READ_DISCONNECTED;
}
return status;
}
}