relsend.c

/** @file relsend.c
 *  @data May 3, 2009
 */

#ifndef CONFIG_RELSEND
#error This is used for relsend only.
#endif /* CONFIG_RELSEND */

#include "common.h"
#include "config.h"
#include "crc.h"
#include "protocol.h"
#include "udp.h"

/* ------------------------------------------------------------------------ */
/** A general purpose buffer for sending and receiving. */
static msg_buffer_t g_buffer;

/** The file pointer. */
static FILE * g_file;

/** The INIT message. */
static msg_init_t g_init;

/** The resolved host information. */
static peer_t g_peer;

/** The number of RETRY messages sent. */
static size_t g_retries;

/** The START message. */
static msg_start_t g_start;

/* ------------------------------------------------------------------------ */
static int parse_args(int argc, const char * argv[]);
static int poll_abort_retry(void);
static int poll_buffer(size_t ms, int * timeout_flag);
static int process_abort_retry(int * processed_flag);
static int process_file(void);
static int process_socket(void);
static int receive_buffer(size_t ms, const char * msg_type);
static int send_buffer(void);
static int transmit_data(u_long start, u_long end);

/* ------------------------------------------------------------------------ */
/** The main entry point of the program.
 *
 *  @param argc The number of arguments.
 *  @param argv The arguments.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
int main(int argc, const char * argv[])
{
    /* Parse the arguments; obtain the peer and file name. */
    RETURN_IF_FAILURE(parse_args(argc, argv));

    /* Open the file. */
    g_file = fopen(g_init.name, "rb");
    if (NULL == g_file) {
        fprintf(stderr, "Cannot read file '%s'.\n", g_init.name);
        RETURN_FAILURE();
    }

    /* Transfer the file now that the file is open. */
    if (EXIT_SUCCESS != process_file()) {
        fclose(g_file);
        RETURN_FAILURE();
    }

    /* Close the file and return successfully. */
    RETURN_IF_FALSE(0 == fclose(g_file));
    return EXIT_SUCCESS;
}


/* ------------------------------------------------------------------------ */
/** Parses the command line arguments.
 *
 *  @param argc The number of arguments.
 *  @param argv The arguments.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int parse_args(int argc, const char * argv[])
{
    assert(NULL != argv);

    /* Check the number of arguments. */
    if (argc != 4) {
        fprintf(stderr, "usage: relsend file host port\n");
        RETURN_FAILURE();
    }

    /* Check the file name is valid. */
    if (EXIT_SUCCESS != protocol_name_verify(argv[1])) {
        fprintf(stderr, "The name '%s' is invalid.\n", argv[1]);
        RETURN_FAILURE();
    }

    /* The file name is the first argument. */
    strcpy(g_init.name, argv[1]);

    /* Parse the port number. */
    if (1 != sscanf(argv[3], "%hu", &g_peer.port)) {
        fprintf(stderr, "Invalid port number '%s'.\n", argv[3]);
        RETURN_FAILURE();
    }

    /* Resolve the host. */
    if (EXIT_SUCCESS != udp_resolve(argv[2], argv[3], &g_peer)) {
        fprintf(stderr, "Cannot resolve host name '%s'.\n", argv[2]);
        RETURN_FAILURE();
    }

    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Processes ABORT and RETRY messages if they have already arrived.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int poll_abort_retry(void)
{
    int timeout_flag;
    int processed_flag;

    /* Check if a message has already arrived from the peer. */
    RETURN_IF_FAILURE(poll_buffer(0, &timeout_flag));

    /* Return successfully if no message has arrived. */
    if (timeout_flag == 1)
        return EXIT_SUCCESS;

    /* Return successfully if the message is an ABORT or RETRY message. */
    RETURN_IF_FAILURE(process_abort_retry(&processed_flag));
    if (processed_flag == 1)
        return EXIT_SUCCESS;

    /* TODO Allow and ignore valid messages with invalid id values. */

    /* Only ABORT and RETRY messages are allowed. */
    fprintf(stderr, "Received unexpected message.\n");
    RETURN_FAILURE();
}

/* ------------------------------------------------------------------------ */
/** Waits for a message from the host or a timeout.
 *
 *  @param ms The millisecond timeout.
 *  @param timeout_flag A flag set to one if there is a timeout.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int poll_buffer(size_t ms, int * timeout_flag)
{
    double timeout;

    assert(NULL != timeout_flag);

    /* Calculate the absolute time for the timeout in units of clock_t. */
    timeout = (double)clock() + ((CLOCKS_PER_SEC * (double)ms) / 1000.0);

    /* Loop until failure, timeout, or received buffer. */
    for (;;) {
        peer_t peer;

        /* Calculate the remaining time in milliseconds. */
        ms = (size_t)((timeout - (double)clock()) * 1000.0 / CLOCKS_PER_SEC);
        if (EXIT_SUCCESS != udp_wait(ms, timeout_flag)) {
            fprintf(stderr, "Socket failure.\n");
            RETURN_FAILURE();
        }

        /* Return successfully if there is a timeout. */
        if (*timeout_flag == 1)
            break;

        /* Put the received data into g_buffer. */
        if (EXIT_SUCCESS != udp_recv(
            g_buffer.data,
            sizeof(g_buffer.data),
            &g_buffer.size,
            &peer)) {
            fprintf(stderr, "Socket failure.\n");
            RETURN_FAILURE();
        }

        /* Return successfully only if it's the right peer. */
        if (peer.address == g_peer.address && peer.port == g_peer.port)
            break;

        fprintf(stderr, "Warning: Ignoring packet from unknown peer.\n");
    }

    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Processes ABORT and RETRY messages waiting in g_buffer.  This function
 *  recursively calls transmit_data when it processes RETRY messages.
 *
 *  @param processed_flag Set to one if g_buffer contains ABORT or RETRY.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int process_abort_retry(int * processed_flag)
{
    msg_abort_t abort;
    msg_retry_t retry;

    assert(NULL != processed_flag);

    *processed_flag = 1;

    /* Check for an ABORT message. */
    if (MSG_TYPE_ABORT == g_buffer.data[0] &&
        EXIT_SUCCESS == msg_abort_read(&abort, &g_buffer)) {
        /* Check for the valid id. */
        if (abort.id != g_start.id) {
            fprintf(stderr, "Warning: Ignoring packet with bad id.\n");
            return EXIT_SUCCESS;
        }

        fprintf(stderr, "Received ABORT message: %s\n", abort.reason);
        RETURN_FAILURE();
    }

    /* Check for a RETRY message. */
    if (MSG_TYPE_RETRY == g_buffer.data[0] &&
        EXIT_SUCCESS == msg_retry_read(&retry, &g_buffer)) {
        /* Check for the valid id. */
        if (retry.id != g_start.id) {
            fprintf(stderr, "Warning: Ignoring packet with bad id.\n");
            return EXIT_SUCCESS;
        }

        /* Check for a valid packet range. */
        if (retry.last >= protocol_packet_count(&g_init)) {
            fprintf(stderr, "Received invalid RETRY message.\n");
            RETURN_FAILURE();
        }

        /* TODO Do not allow "stuck" retries. */

        g_retries += (retry.last - retry.first) + 1;
        fprintf(
            stderr,
            "Receiving RETRY message for packets %lu to %lu.\n",
            retry.first,
            retry.last);


        /* Recursively transmit the requested range of packets. */
        RETURN_IF_FAILURE(transmit_data(retry.first, retry.last + 1));
        return EXIT_SUCCESS;
    }

    /* Arriving here indicates the message is not ABORT or RETRY. */
    *processed_flag = 0;
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Performs all functionality while the file is open.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int process_file(void)
{
    /* Create the UDP socket. */
    if (EXIT_SUCCESS != udp_start_client()) {
        fprintf(stderr, "Cannot create socket.\n");
        RETURN_FAILURE();
    }

    /* Transfer the file now that the socket is open. */
    if (EXIT_SUCCESS != process_socket()) {
        udp_stop();
        RETURN_FAILURE();
    }

    /* Destroy UDP socket. */
    if (EXIT_SUCCESS != udp_stop()) {
        fprintf(stderr, "Error closing socket.\n");
        RETURN_FAILURE();
    }

    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Performs all functionality while the socket is open.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int process_socket(void)
{
    msg_abort_t abort;

    assert(NULL != g_file);

    /* Determine the checksum and length. */
    /* TODO Display meaningful error messages. */
    RETURN_IF_FAILURE(crc_file(g_file, &g_init.checksum));
    RETURN_IF_FALSE(0 == fseek(g_file, 0, SEEK_END));
    RETURN_IF_FALSE((u_long)-1 != (g_init.filesize = (u_long)ftell(g_file)));
    if (g_init.filesize == 0) {
        fprintf(stderr, "Cannot send zero length file.\n");
        RETURN_FAILURE();
    }

    /* Computes the time to transfer the file. */
    stopwatch_start();

    /* Build and send the INIT message. */
    g_init.type = MSG_TYPE_INIT;
    g_init.datasize = RELSEND_DATASIZE;
    msg_init_write(&g_buffer, &g_init);
    RETURN_IF_FAILURE(send_buffer());

    /* Receive a message. */
    RETURN_IF_FAILURE(receive_buffer(RELSEND_TIMEOUT, "START"));

    /* Check for an ABORT message. */
    if (g_buffer.data[0] == MSG_TYPE_ABORT
        && EXIT_SUCCESS == msg_abort_read(&abort, &g_buffer)) {
        fprintf(stderr, "Received ABORT message: %s\n", abort.reason);
        RETURN_FAILURE();
    }

    /* Receive the START message. */
    if (EXIT_SUCCESS != msg_start_read(&g_start, &g_buffer)) {
        fprintf(stderr, "Received invalid START message.\n");
        RETURN_FAILURE();
    }

    /* Transmit all the DATA messages. */
    RETURN_IF_FAILURE(transmit_data(0, protocol_packet_count(&g_init)));

    /* Wait for the DONE message. */
    printf("All packets transmitted; waiting for DONE message.\n");
    /* TODO Do not allow "stuck" RETRY messages. */
    for (;;) {
        msg_done_t done;
        int        processed_flag;

        /* Wait for any kind of message, checking for timeouts. */
        RETURN_IF_FAILURE(receive_buffer(RELSEND_TIMEOUT, "DONE"));

        /* Start over if this is an ABORT or RETRY. */
        RETURN_IF_FAILURE(process_abort_retry(&processed_flag));
        if (processed_flag == 1)
            continue;

        /* Allow only DONE messages. */
        if (EXIT_SUCCESS != msg_done_read(&done, &g_buffer)) {
            /* TODO Allow and ignore valid messages with invalid id values. */
            fprintf(stderr, "Received invalid DONE message.\n");
            RETURN_FAILURE();
        }

        /* Ignore invalid ids. */
        if (done.id != g_start.id) {
            fprintf(stderr, "Warning: Ignoring packet with bad id.\n");
            continue;
        }

        /* Otherwise transmission completed successfully. */
        printf("Transfer complete: '%s'.\n\n", g_init.name);

        /* Display statistics. */
        stopwatch_stop(
            g_init.filesize,
            g_retries,
            protocol_packet_count(&g_init));
        break;
    }

    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Receives a buffer from the peer, not allowing timeouts.
 *
 *  @param ms The millisecond timeout.
 *  @param msg The type of message that is expected.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int receive_buffer(size_t ms, const char * msg_type)
{
    int timeout_flag;

    assert(NULL != msg_type);

    /* Check if a message arrived within the timeout. */
    RETURN_IF_FAILURE(poll_buffer(ms, &timeout_flag));

    /* Return unsuccessfully if there is a timeout. */
    if (timeout_flag == 1) {
        fprintf(stderr, "Timeout waiting for %s message.\n", msg_type);
        RETURN_FAILURE();
    }

    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Sends the global buffer to the global peer.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int send_buffer(void)
{
    /* Send the global buffer to the global peer. */
    if (EXIT_SUCCESS != udp_send(&g_peer, g_buffer.data, g_buffer.size)) {
        fprintf(stderr, "Failed to send UDP datagram.\n");
        RETURN_FAILURE();
    }

    /* Reset the global buffer for clarity in the debugger. */
    memset(&g_buffer, 0, sizeof(g_buffer));
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------ */
/** Transmits a range of DATA packets.
 *
 *  @param start The first packet (inclusive).
 *  @param end The last packet (exclusive).
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int transmit_data(u_long start, u_long end)
{
    msg_data_t data;

    assert(start < end);
    assert(end <= protocol_packet_count(&g_init));

    /* These parts of the DATA message don't change. */
    data.type = MSG_TYPE_DATA;
    data.id = g_start.id;

    /* Loop over all packets in the range. */
    for (data.packet = start; data.packet < end; data.packet++) {
        long    offset;
        u_short size;

        /* Recursively poll and transmit RETRY messages. */
        RETURN_IF_FAILURE(poll_abort_retry());

        /* Determine what to read and send from the file. */
        offset = protocol_packet_offset(&g_init, data.packet);
        size = protocol_data_size(&g_init, data.packet);

        /* Read the data from the file. */
        /* TODO Display meaningful error messages. */
        RETURN_IF_FALSE(0 == fseek(g_file, offset, SEEK_SET));
        RETURN_IF_FALSE(size == (u_short)fread(
            data.data,
            1,
            (size_t)size,
            g_file));

        /* Build and send the DATA message. */
        msg_data_write(&g_buffer, &data, size);
        RETURN_IF_FAILURE(send_buffer());
    }

    return EXIT_SUCCESS;
}
Valid HTML 4.01 Valid CSS