udp.c

/** @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;
}
Valid HTML 4.01 Valid CSS