/** @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));
}