// mysocket.cpp
//

#include <stdio.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

//---------------------------------------------------------------------------
//
// BuildSockAddr
//
// Description:
//
//    Local utility function for converting an IP address in string format
//    and a port number to a struct sockaddr representation.
//
// Parameters:
//
//    SockAddr - Pointer to struct sockaddr struct to be filled in
//    IPAddress - NULL terminated IP address string (e.g. "10.0.0.1")
//    Port - Address port number
//
// Return Value:
//
// Remarks:
//
//    This function assumes that SockAddr is a valid pointer.
//
//    If IPAddress == NULL, then INADDR_ANY is used.
//
//---------------------------------------------------------------------------
static void BuildSockAddr(struct sockaddr * const SockAddr, char const * const IPAddress, unsigned short const Port)
{
	struct sockaddr_in * const SIn = (struct sockaddr_in *)SockAddr;

	assert(SIn != NULL);

	SIn->sin_family = AF_INET;
	SIn->sin_addr.s_addr = (IPAddress != NULL) ? inet_addr(IPAddress) : htonl(INADDR_ANY);
	SIn->sin_port = htons(Port);
}

//---------------------------------------------------------------------------
//
// MySocketOpen
//
// Description:
//
//    Creates a new socket and binds to a local name.
//
// Parameters:
//
//    Type - Must be either SOCK_STREAM or SOCK_DGRAM
//    Port - Local port number to bind to
//
// Return Value:
//
//    If no error occurs, a new socket descriptor.  Otherwise, -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketOpen(int const Type, unsigned short const Port)
{
	assert((Type == SOCK_STREAM) || (Type == SOCK_DGRAM));

	int Protocol = (Type == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP;

	int Socket = socket(AF_INET, Type, Protocol);

	if (Socket != -1)
	{
		struct sockaddr SockAddr = {0};

		BuildSockAddr(&SockAddr, NULL, Port);

		if (bind(Socket, &SockAddr, sizeof(SockAddr)) != 0)
		{
			close(Socket);
			Socket = -1;
		}
	}

	return Socket;
}

//---------------------------------------------------------------------------
//
// MySocketConnect
//
// Description:
//
//    Establishes a new connection to a peer.
//
// Parameters:
//
//    Socket - Valid socket descriptor returned from MySocketOpen
//    ToIPAddress - IP address of peer to connect to
//    ToPort - Port number of peer to connect to
//
// Return Value:
//
//    Zero if no error occurs, otherwise -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketConnect(int const Socket, char const * const ToIPAddress, unsigned short const ToPort)
{
	struct sockaddr SockAddr = {0};

	BuildSockAddr(&SockAddr, ToIPAddress, ToPort);

	return connect(Socket, &SockAddr, sizeof(SockAddr));
}

//---------------------------------------------------------------------------
//
// MySocketSend
//
// Description:
//
//    Transmits data to a peer on a connection oriented socket.
//
// Parameters:
//
//    Socket - Valid socket descriptor returned from MySocketOpen
//    Data - Pointer to buffer containing data to send
//    Length - Number of bytes Data points to
//
// Return Value:
//
//    Number of bytes actually sent.  Otherwise, -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketSend(int const Socket, unsigned char const * const Data, size_t const Length)
{
	return send(Socket, Data, Length, 0);
}

//---------------------------------------------------------------------------
//
// MySocketSendTo
//
// Description:
//
//    Transmits a datagram to a peer using a connectionless socket.
//
// Parameters:
//
//    Socket - Valid socket descriptor returned from MySocketOpen
//    Data - Pointer to buffer containing data to send
//    Length - Number of bytes Data points to
//    ToIPAddress - IP address of the peer that receives the datagram
//    ToPort - Port number of the peer that receives the datagram
//
// Return Value:
//
//    Number of bytes actually sent.  Otherwise, -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketSendTo(int const Socket, unsigned char const * const Data, size_t const Length, char const * const ToIPAddress, unsigned short const ToPort)
{
	struct sockaddr SockAddr = {0};

	BuildSockAddr(&SockAddr, ToIPAddress, ToPort);

	return sendto(Socket, Data, Length, 0, &SockAddr, sizeof(SockAddr));
}

//---------------------------------------------------------------------------
//
// MySocketRecv
//
// Description:
//
//    Retrieves data buffered for the connected socket
//
// Parameters:
//
//    Socket - Valid socket descriptor returned from MySocketOpen
//    Data - Pointer to buffer that should receive the data
//    Length - Number of bytes Data points to
//
// Return Value:
//
//    Number of bytes received.  Otherwise, -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketRecv(int const Socket, unsigned char * const Data, size_t const Length)
{
	return recv(Socket, Data, Length, 0);
}

//---------------------------------------------------------------------------
//
// MySocketRecvFrom
//
// Description:
//
//    Retrieves a datagram buffered for the connectionless socket
//
// Parameters:
//
//    Socket - Valid socket descriptor returned from MySocketOpen
//    Data - Pointer to buffer that should receive the data
//    Length - Number of bytes Data points to
//    FromIPAddress - Buffer to recieve a human readable string
//                    representation of the IP address of the peer that
//                    sent the data
//    FromIPAddressLength - When called this is the length of FromIPAddress
//                          Upon return it is the length of the string copied
//                          into FromIPAddress
//
// Return Value:
//
//    Number of bytes received.  Otherwise, -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketRecvFrom(int const Socket, unsigned char * const Data, size_t const Length, char * const FromIPAddress, size_t * const FromIPAddressLength)
{
	int ReturnValue = 0;

	struct sockaddr_in SockAddr = {0};
	size_t SockAddrLength = sizeof(SockAddr);

	ReturnValue = recvfrom(Socket, Data, Length, 0, (struct sockaddr *)&SockAddr, &SockAddrLength);

	if (ReturnValue > 0)
	{
		*FromIPAddressLength = snprintf(FromIPAddress, *FromIPAddressLength, "%s:%u", inet_ntoa(SockAddr.sin_addr), SockAddr.sin_port);
	}

	return ReturnValue;
}

//---------------------------------------------------------------------------
//
// MySocketClose
//
// Description:
//
//    Closes the socket descriptor
//
// Parameters:
//
//    Socket - Valid socket descriptor returned from MySocketOpen
//
// Return Value:
//
//    Zero if no error occurs, otherwise -1.
//
// Remarks:
//
//---------------------------------------------------------------------------
int MySocketClose(int const Socket)
{
	shutdown(Socket, SHUT_RDWR);
	return close(Socket);
}
