protocol.c

/** @file protocol.c
 *  @date April 24, 2009
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include "common.h"
#include "protocol.h"

/** A structure that helps read from a buffer. */
typedef struct reader_t {

    /** A pointer to the buffer. */
    const msg_buffer_t * buffer;

    /** The index of the next byte to read. */
    size_t index;

} reader_t;

/** A structure that helps write to a buffer. */
typedef struct writer_t {

    /** A pointer to the buffer. */
    msg_buffer_t * buffer;

} writer_t;

static void init_reader(reader_t * reader, const msg_buffer_t * buffer);
static void init_writer(writer_t * writer, msg_buffer_t * buffer);
static int read_uchar(reader_t * reader, u_char * value);
static int read_ushort(reader_t * reader, u_short * value);
static int read_ulong(reader_t * reader, u_long * value);
static int read_string(reader_t * reader, char * value, size_t size);
static void write_uchar(const writer_t * writer, u_char value);
static void write_ushort(const writer_t * writer, u_short value);
static void write_ulong(const writer_t * writer, u_long value);
static void write_string(const writer_t * writer, const char * value);

/* ------------------------------------------------------------------------- */
int msg_abort_read(msg_abort_t * msg, const msg_buffer_t * buffer)
{
    reader_t reader;

    assert(NULL != msg);
    assert(NULL != buffer);

    /* Initialize the reader. */
    init_reader(&reader, buffer);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(&reader, &msg->type));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->id));
    RETURN_IF_FAILURE(read_string(&reader, msg->reason, REASON_SIZE));

    /* Verify the values. */
    RETURN_IF_FALSE(msg->type == MSG_TYPE_ABORT);

    /* TODO Verify no exta bytes were sent for this message and all others. */
    /* RETURN_IF_FALSE(reader->index == msg->size); */

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
void msg_abort_write(msg_buffer_t * buffer, const msg_abort_t * msg)
{
    writer_t writer;

    assert(NULL != buffer);
    assert(NULL != msg);

    assert(msg->type == MSG_TYPE_ABORT);

    /* Initialize the writer and write the values. */
    init_writer(&writer, buffer);
    write_uchar(&writer, msg->type);
    write_ulong(&writer, msg->id);
    write_string(&writer, msg->reason);
}

/* ------------------------------------------------------------------------- */
int msg_data_read(msg_data_t * msg, const msg_buffer_t * buffer)
{
    size_t   index;
    reader_t reader;

    assert(NULL != msg);
    assert(NULL != buffer);

    /* Initialize the reader. */
    init_reader(&reader, buffer);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(&reader, &msg->type));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->id));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->packet));

    /* Read the array. */
    for (index = 0; reader.index < buffer->size; index++) {
        u_char ch;

        RETURN_IF_FAILURE(read_uchar(&reader, &ch));
        msg->data[index] = ch;
    }

    /* Verify the values. */
    RETURN_IF_FALSE(msg->type == MSG_TYPE_DATA);

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
void msg_data_write(
    msg_buffer_t     * buffer,
    const msg_data_t * msg,
    u_short            datasize)
{
    writer_t writer;
    u_short  index;

    assert(NULL != buffer);
    assert(NULL != msg);
    assert(datasize >= DATA_MIN_SIZE);
    assert(datasize <= DATA_MAX_SIZE);

    assert(msg->type == MSG_TYPE_DATA);

    /* Initialize the writer and write the values. */
    init_writer(&writer, buffer);
    write_uchar(&writer, msg->type);
    write_ulong(&writer, msg->id);
    write_ulong(&writer, msg->packet);

    /* Write the array. */
    for (index = 0; index < datasize; index++)
        write_uchar(&writer, msg->data[index]);
}

/* ------------------------------------------------------------------------- */
int msg_done_read(msg_done_t * msg, const msg_buffer_t * buffer)
{
    reader_t reader;

    assert(NULL != msg);
    assert(NULL != buffer);

    /* Initialize the reader. */
    init_reader(&reader, buffer);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(&reader, &msg->type));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->id));

    /* Verify the values. */
    RETURN_IF_FALSE(msg->type == MSG_TYPE_DONE);

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
void msg_done_write(msg_buffer_t * buffer, const msg_done_t * msg)
{
    writer_t writer;

    assert(NULL != buffer);
    assert(NULL != msg);

    assert(msg->type == MSG_TYPE_DONE);

    /* Initialize the writer and write the values. */
    init_writer(&writer, buffer);
    write_uchar(&writer, msg->type);
    write_ulong(&writer, msg->id);
}

/* ------------------------------------------------------------------------- */
int msg_init_read(msg_init_t * msg, const msg_buffer_t * buffer)
{
    reader_t reader;

    assert(NULL != msg);
    assert(NULL != buffer);

    /* Initialize the reader. */
    init_reader(&reader, buffer);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(&reader, &msg->type));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->checksum));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->filesize));
    RETURN_IF_FAILURE(read_ushort(&reader, &msg->datasize));
    RETURN_IF_FAILURE(read_string(&reader, msg->name, NAME_SIZE));

    /* Verify the values. */
    RETURN_IF_FALSE(msg->type == MSG_TYPE_INIT);
    RETURN_IF_FALSE(msg->filesize > 0);
    RETURN_IF_FALSE(msg->datasize >= DATA_MIN_SIZE);
    RETURN_IF_FALSE(msg->datasize <= DATA_MAX_SIZE);
    RETURN_IF_FAILURE(protocol_name_verify(msg->name));

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
void msg_init_write(msg_buffer_t * buffer, const msg_init_t * msg)
{
    writer_t writer;

    assert(NULL != buffer);
    assert(NULL != msg);

    assert(msg->type == MSG_TYPE_INIT);
    assert(msg->filesize > 0);
    assert(msg->datasize >= DATA_MIN_SIZE);
    assert(msg->datasize <= DATA_MAX_SIZE);
    assert(EXIT_SUCCESS == protocol_name_verify(msg->name));

    /* Initialize the writer and write the values. */
    init_writer(&writer, buffer);
    write_uchar(&writer, msg->type);
    write_ulong(&writer, msg->checksum);
    write_ulong(&writer, msg->filesize);
    write_ushort(&writer, msg->datasize);
    write_string(&writer, msg->name);
}

/* ------------------------------------------------------------------------- */
int msg_retry_read(msg_retry_t * msg, const msg_buffer_t * buffer)
{
    reader_t reader;

    assert(NULL != msg);
    assert(NULL != buffer);

    /* Initialize the reader. */
    init_reader(&reader, buffer);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(&reader, &msg->type));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->id));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->first));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->last));

    /* Verify the values. */
    RETURN_IF_FALSE(msg->type == MSG_TYPE_RETRY);
    RETURN_IF_FALSE(msg->first <= msg->last);

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
void msg_retry_write(msg_buffer_t * buffer, const msg_retry_t * msg)
{
    writer_t writer;

    assert(NULL != buffer);
    assert(NULL != msg);

    assert(msg->type == MSG_TYPE_RETRY);
    assert(msg->first <= msg->last);

    /* Initialize the writer and write the values. */
    init_writer(&writer, buffer);
    write_uchar(&writer, msg->type);
    write_ulong(&writer, msg->id);
    write_ulong(&writer, msg->first);
    write_ulong(&writer, msg->last);
}

/* ------------------------------------------------------------------------- */
int msg_start_read(msg_start_t * msg, const msg_buffer_t * buffer)
{
    reader_t reader;

    assert(NULL != msg);
    assert(NULL != buffer);

    /* Initialize the reader. */
    init_reader(&reader, buffer);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(&reader, &msg->type));
    RETURN_IF_FAILURE(read_ulong(&reader, &msg->id));

    /* Verify the values. */
    RETURN_IF_FALSE(msg->type == MSG_TYPE_START);

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
void msg_start_write(msg_buffer_t * buffer, const msg_start_t * msg)
{
    writer_t writer;

    assert(NULL != buffer);
    assert(NULL != msg);

    assert(msg->type == MSG_TYPE_START);

    /* Initialize the writer and write the values. */
    init_writer(&writer, buffer);
    write_uchar(&writer, msg->type);
    write_ulong(&writer, msg->id);
}

/* ------------------------------------------------------------------------- */
u_short protocol_data_size(const msg_init_t * msg, u_long packet)
{
    u_long packets;

    assert(NULL != msg);

    /* Determine the total number of packets. */
    packets = protocol_packet_count(msg);
    assert(packet < packets);

    /* Return the remainder for the last packet. */
    if (packet == packets - 1)
        return msg->filesize % msg->datasize;

    /* Otherwise, return the full data size. */
    return msg->datasize;
}

/* ------------------------------------------------------------------------- */
int protocol_name_verify(const char * name)
{
    size_t len;
    size_t dots;

    assert(NULL != name);

    /* Check the length. */
    len = strlen(name);
    RETURN_IF_FALSE(len > 0);
    RETURN_IF_FALSE(len <= NAME_MAX_LENGTH);

    /* Check each character. */
    for (dots = 0; *name != '\0'; name++) {

        if (isalpha((int)*name))
            continue;

        /* Check for too many dots. */
        RETURN_IF_FALSE(*name == '.');
        RETURN_IF_FALSE(++dots == 1);
    }

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
u_long protocol_packet_count(const msg_init_t * msg)
{
    u_long packets;

    assert(NULL != msg);

    /* First, divide the file size by the datasize. */
    packets = msg->filesize / msg->datasize;

    /* Add an additional packet if there is a remainder. */
    if (msg->filesize % msg->datasize != 0)
        packets++;

    /* Return the total number of packets. */
    return packets;
}

/* ------------------------------------------------------------------------- */
long protocol_packet_offset(const msg_init_t * msg, u_long packet)
{
    assert(NULL != msg);
    assert(packet < protocol_packet_count(msg));

    return (long)(packet * msg->datasize);
}

/* ------------------------------------------------------------------------- */
/** Initializes the reader based on the buffer.
 *
 *  @param reader The writer.
 *  @param buffer The buffer.
 */
static void init_reader(reader_t * reader, const msg_buffer_t * buffer)
{
    assert(NULL != reader);
    assert(NULL != buffer);

    reader->buffer = buffer;
    reader->index  = 0;
}

/* ------------------------------------------------------------------------- */
/** Initializes the writer based on the buffer.
 *
 *  @param writer The writer.
 *  @param buffer The buffer.
 */
static void init_writer(writer_t * writer, msg_buffer_t * buffer)
{
    assert(NULL != writer);
    assert(NULL != buffer);

    writer->buffer       = buffer;
    writer->buffer->size = 0;
}

/* ------------------------------------------------------------------------- */
/** Reads a string from the buffer.
 *
 *  @param reader The destination.
 *  @param value The value.
 *  @param size The maximum size of the string.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int read_string(reader_t * reader, char * value, size_t size)
{
    size_t count;

    assert(NULL != reader);
    assert(NULL != value);

    /* Loop over each character. */
    count = 0;
    for (;;) {
        u_char ch;

        /* Read and store the next character. */
        RETURN_IF_FAILURE(read_uchar(reader, &ch));
        value[count++] = (char)ch;

        /* Check for the NULL terminator. */
        if (ch == 0)
            break;

        /* Ensure the string length is valid. */
        RETURN_IF_FALSE(count < size);
    }

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
/** Reads a u_char from the buffer.
 *
 *  @param reader The destination.
 *  @param value The value.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int read_uchar(reader_t * reader, u_char * value)
{
    assert(NULL != reader);
    assert(NULL != reader->buffer);
    assert(NULL != value);

    /* Verify this is a valid character. */
    RETURN_IF_FALSE(reader->index < reader->buffer->size);

    /* Read the value. */
    *value = reader->buffer->data[reader->index++];

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
/** Reads a u_long from the buffer.
 *
 *  @param reader The destination.
 *  @param value The value.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int read_ulong(reader_t * reader, u_long * value)
{
    u_char ch[4];

    assert(NULL != reader);
    assert(NULL != value);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(reader, &ch[0]));
    RETURN_IF_FAILURE(read_uchar(reader, &ch[1]));
    RETURN_IF_FAILURE(read_uchar(reader, &ch[2]));
    RETURN_IF_FAILURE(read_uchar(reader, &ch[3]));

    /* Combine the values. */
    *value = (u_long)(
        ((u_long)ch[0] << 24) |
        ((u_long)ch[1] << 16) |
        ((u_long)ch[2] <<  8) |
        ((u_long)ch[3] <<  0));

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
/** Reads a u_short from the buffer.
 *
 *  @param reader The destination.
 *  @param value The value.
 *
 *  @return EXIT_SUCCESS or EXIT_FAILURE.
 */
static int read_ushort(reader_t * reader, u_short * value)
{
    u_char ch[2];

    assert(NULL != reader);
    assert(NULL != value);

    /* Read the values. */
    RETURN_IF_FAILURE(read_uchar(reader, &ch[0]));
    RETURN_IF_FAILURE(read_uchar(reader, &ch[1]));

    /* Combine the values. */
    *value = (u_short)((ch[0] << 8) | ch[1]);

    /* Return successfully. */
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------------------- */
/** Writes a string to a buffer.
 *
 *  @param writer The writer.
 *  @param value The value.
 */
static void write_string(const writer_t * writer, const char * value)
{
    assert(NULL != value);

    /* Loop over each character. */
    for (;;) {
        /* Write the character. */
        write_uchar(writer, (u_char)(*value));

        /* Check for the NULL terminator. */
        if (*value == '\0')
            break;

        /* Advance to the next character in the string. */
        value++;
    }
}

/* ------------------------------------------------------------------------- */
/** Writes a u_char to a buffer.
 *
 *  @param writer The writer.
 *  @param value The value.
 */
static void write_uchar(const writer_t * writer, u_char value)
{
    assert(NULL != writer);
    assert(NULL != writer->buffer);
    assert(writer->buffer->size < PACKET_MAX_SIZE);

    /* Write the value. */
    writer->buffer->data[writer->buffer->size++] = value;
}

/* ------------------------------------------------------------------------- */
/** Writes a u_long to a buffer.
 *
 *  @param writer The writer.
 *  @param value The value.
 */
static void write_ulong(const writer_t * writer, u_long value)
{
    /* Write the values. */
    write_uchar(writer, (u_char)((value >> 24) & 0xff));
    write_uchar(writer, (u_char)((value >> 16) & 0xff));
    write_uchar(writer, (u_char)((value >>  8) & 0xff));
    write_uchar(writer, (u_char)((value >>  0) & 0xff));
}

/* ------------------------------------------------------------------------- */
/** Writes a u_short to a buffer.
 *
 *  @param writer The writer.
 *  @param value The value.
 */
static void write_ushort(const writer_t * writer, u_short value)
{
    /* Write the values. */
    write_uchar(writer, (u_char)((value >> 8) & 0xff));
    write_uchar(writer, (u_char)((value >> 0) & 0xff));
}
Valid HTML 4.01 Valid CSS