/** @file udp.c
* @date April 24, 2009
*/
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <unistd.h>
#include "common.h"
#include "config.h"
#include "udp.h"
#ifdef DEBUG
/** A macro to call udp_dump only in the DEBUG configuration. */
#define UDP_DUMP(_TEXT, _PEER, _BUFFER, _SIZE) \
udp_dump(_TEXT, _PEER, _BUFFER, _SIZE)
static void udp_dump(
const char * text,
const peer_t * peer,
const void * buffer,
size_t size);
#else /* DEBUG */
/** This macro does nothing in the RELEASE configuration. */
#define UDP_DUMP(_TEXT, _PEER, _BUFFER, _SIZE)
#endif /* DEBUG */
/** The definition for a socket. */
typedef int SOCKET;
/** The time the last packet was sent. */
static struct timeval g_sendtime;
/** The socket managed by the module. */
static SOCKET g_socket;
static int udp_resolve_helper(
const struct addrinfo * info);
static void udp_pace(size_t bytes);
/* ------------------------------------------------------------------------- */
int udp_recv(
void * buffer,
size_t size,
size_t * actual,
peer_t * peer)
{
int result;
socklen_t sizeof_sin;
struct sockaddr_in sin;
assert(NULL != buffer);
assert(NULL != actual);
assert(NULL != peer);
assert(size > 0);
assert(g_socket != 0);
/* Receive the datagram. */
sizeof_sin = sizeof(sin);
result = recvfrom(
g_socket,
(char *)buffer,
(int)size,
0,
(struct sockaddr *)&sin,
&sizeof_sin);
/* Check for errors. */
RETURN_IF_FALSE(result != -1);
RETURN_IF_FALSE(result != 0);
RETURN_IF_FALSE(result > 0);
RETURN_IF_FALSE((unsigned int)sizeof_sin == sizeof(sin));
/* Store the result. */
*actual = (size_t)result;
peer->port = ntohs(sin.sin_port);
peer->address = sin.sin_addr.s_addr;
/* Display the buffer in the DEBUG build. */
UDP_DUMP("From", peer, buffer, *actual);
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
int udp_resolve(const char * name, const char * port, peer_t * peer)
{
struct addrinfo hints;
struct addrinfo * info;
const struct sockaddr_in * sin;
assert(NULL != name);
assert(NULL != port);
assert(NULL != peer);
/* Set hints for getaddrinfo. */
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = (int)IPPROTO_UDP;
/* Get the host information. */
RETURN_IF_FALSE(0 == getaddrinfo(name, port, &hints, &info));
RETURN_IF_FALSE(NULL != info);
/* Check each field using the helper function. */
if (EXIT_SUCCESS != udp_resolve_helper(info))
{
freeaddrinfo(info);
RETURN_FAILURE();
}
/* Store the result into the peer structure. */
sin = (struct sockaddr_in *)info->ai_addr;
peer->address = sin->sin_addr.s_addr;
peer->port = ntohs(sin->sin_port);
/* Free the block allocated by getaddrinfo, and return successfully. */
freeaddrinfo(info);
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
int udp_send(const peer_t * peer, const void * buffer, size_t size)
{
struct sockaddr_in sin;
const char * buf;
int len;
assert(NULL != peer);
assert(NULL != buffer);
assert(size > 0);
assert(g_socket != 0);
/* Display information about the buffer. */
UDP_DUMP("To", peer, buffer, size);
/* Do not exceed MAX_KBPS. */
udp_pace(size);
/* Prepare the structure for the remote address. */
memset(&sin, 0, sizeof(sin));
sin.sin_addr.s_addr = peer->address;
sin.sin_family = AF_INET;
sin.sin_port = htons(peer->port);
/* Prepare some pointers and values for the loop. */
buf = (const char *)buffer;
len = (int)size;
/* Loop until all the data is sent. */
while (len > 0)
{
int result;
/* Send the buffer. */
result = sendto(
g_socket,
buf,
len,
0,
(const struct sockaddr *)&sin,
sizeof(sin));
/* Check for errors or incomplete sends. */
RETURN_IF_FALSE(result > 0);
assert(result <= len);
len -= result;
buf += result;
}
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
int udp_start_client(void)
{
assert(g_socket == 0);
/* Create the socket and check for errors. */
g_socket = socket(AF_INET, SOCK_DGRAM, (int)IPPROTO_UDP);
RETURN_IF_FALSE(g_socket != 0);
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
int udp_start_server(u_short port)
{
struct sockaddr_in sin;
assert(g_socket == 0);
/* Create the unbound socket. */
RETURN_IF_FAILURE(udp_start_client());
/* Bind the socket to the port. */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (0 != bind(g_socket, (struct sockaddr *)&sin, sizeof(sin)))
{
/* Close the socket if there was a problem. */
(void)close(g_socket);
g_socket = 0;
RETURN_FAILURE();
}
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
int udp_stop(void)
{
assert(g_socket != 0);
/* Close the socket. */
RETURN_IF_FALSE(0 == close(g_socket));
g_socket = 0;
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
int udp_wait(size_t ms, int * timeout)
{
int result;
fd_set fds;
struct timeval tv;
assert(g_socket != 0);
*timeout = 0;
/* Prepare a file descriptor set for the select function. */
FD_ZERO(&fds);
FD_SET(g_socket, &fds);
/* Prepare the timeout. */
memset(&tv, 0, sizeof(tv));
tv.tv_usec = (long)ms * 1000L;
tv.tv_sec = tv.tv_usec / 1000000L;
tv.tv_usec %= 1000000L;
/* Wait for a datagram or a timeout. */
result = select(1 + g_socket, &fds, NULL, NULL, &tv);
if (0 == result)
{
*timeout = 1;
return EXIT_SUCCESS;
}
/* Check for an error. */
RETURN_IF_FALSE(result > 0);
/* Return successfully. */
return EXIT_SUCCESS;
}
#ifdef DEBUG
/* ------------------------------------------------------------------------- */
/** Displays information about a buffer received or sent.
*
* @param text The text to display To or From.
* @param peer The peer address.
* @param buffer The buffer of data.
* @param size The number of bytes in the buffer.
*/
static void udp_dump(
const char * text,
const peer_t * peer,
const void * buffer,
size_t size)
{
struct in_addr addr;
size_t count;
const u_char * ptr;
assert(NULL != buffer);
/* Display the From or To address. */
ptr = (const u_char *)buffer;
addr.s_addr = peer->address;
printf("%s: %s:%hu\n{", text, inet_ntoa(addr), peer->port);
/* Loop over the bytes of the buffer. */
count = 0;
while (size-- > 0)
{
/* Print the byte in hex. */
printf(" %02x", (unsigned int)*ptr);
ptr++;
/* Don't bother to print long messages fully. */
if (++count == 24)
{
printf(" ...");
break;
}
}
/* Clean up. */
printf(" }\n\n");
}
#endif /* DEBUG */
/* ------------------------------------------------------------------------- */
/** A helper function for udp_resolve. This function simply verifies
* the address info returned from getaddrinfo is valid.
*
* @param info The address information.
*
* @return EXIT_SUCCESS or EXIT_FAILURE.
*/
static int udp_resolve_helper(const struct addrinfo * info)
{
assert(NULL != info);
/* Verify each field of the structure. */
RETURN_IF_FALSE(info->ai_family == PF_INET);
RETURN_IF_FALSE(info->ai_socktype == SOCK_DGRAM);
RETURN_IF_FALSE(info->ai_addrlen == sizeof(struct sockaddr_in));
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
/** A function that delays so the transfer rate never exceeds MAX_KBPS.
*
* @param bytes The number of bytes about to be sent.
*/
static void udp_pace(size_t bytes)
{
struct timeval now;
struct timeval ready;
struct timeval timeout;
/* Get the current time. */
gettimeofday(&now, NULL);
/* Compute the time that is okay to send. */
ready = g_sendtime;
ready.tv_usec += (((suseconds_t)bytes * 8) * 1000) / UDP_MAX_KBPS;
/* Compute the time to wait before sending. */
timeout.tv_sec = ready.tv_sec - now.tv_sec;
timeout.tv_usec = ready.tv_usec - now.tv_usec;
while (timeout.tv_usec < 0) {
timeout.tv_sec--;
timeout.tv_usec += 1000000;
}
/* Sleep if necessary. */
if (timeout.tv_sec >= 0)
select(0, NULL, NULL, NULL, &timeout);
/* Record the current time for the next pace calculation. */
g_sendtime = now;
}