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