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