UDPFileTransferClient-Server / rcopy.c
rcopy.c
Raw
// rcopy - client side			    
// By Connor McKee

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include "gethostbyname.h"
#include "networks.h"
#include "safeUtil.h"
#include "cpe464.h"
#include "pdu.h"
#include "pollLib.h"
#include "window.h"

#define MAXBUF 80
#define ACK_EOF 1
#define NO_ACK_EOF 0
#define GOT_EOF 1
#define NO_EOF 0


// rcopy states
typedef enum STATE {
	INIT, DONE, FILENAME, FILE_OK, RECV_DATA, EOF_ACK
} STATE;

// Information grabbed from CL arguments for use throughout state machine
typedef struct {
	char *fromFilename;
	char *toFilename;
	FILE *toFile;
	uint32_t windowSize;
	uint16_t bufferSize;
	double errRate;
} rcopyInfo;

void talkToServer(int socketNum, struct sockaddr_in6 * server);
STATE establishConnection (ConnectionData *server, rcopyInfo metadata, uint32_t *clientSeqNum);
void processCopy(int socket, rcopyInfo metadata, struct sockaddr_in6 server);
void sendFilenameRequest(int socket, struct sockaddr_in6 *server, uint32_t sequenceNumber, uint8_t *fileNamePayload, uint8_t fnameLen);
STATE processFilenameResponse(ConnectionData *server, rcopyInfo metadata);
STATE processFilenameRetry(ConnectionData *server, int *retries);
STATE processRecvData(ConnectionData *server, rcopyInfo *metadata, uint32_t *clientSeqNum, uint32_t *expectedSeqNum, uint32_t *eofSeqNum, Window *window);
STATE processOpenFile (ConnectionData *server, rcopyInfo *metadata);
STATE processEOFACK(ConnectionData *server, rcopyInfo *metadata, uint32_t *clientSeqNum, uint32_t *expectedSeqNum, uint32_t *eofSeqNum, Window *window);
void writeDataToFile(ConnectionData *server, rcopyInfo *metadata, uint8_t *PDU, int recvLen);
int flushBuffer(ConnectionData *server, rcopyInfo *metadata, Window *window, uint32_t *expectedSeqNum);
int processSREJ(ConnectionData *server, rcopyInfo *metadata, Window *window, int acknowledgeEOF, int gotEOF, uint32_t *clientSeqNum, uint32_t *expectedSeqNum, uint32_t *eofSeqNum);
void sendDataRR(ConnectionData *server, Window *window, uint32_t *clientSeqNum, uint32_t seqNum);
void sendSREJ(ConnectionData *server, uint32_t *clientSeqNum, uint32_t expectedSeqNum);
void sendEOFACK(ConnectionData *server, uint32_t *clientSeqNum);
void cleanExit(Window *window, rcopyInfo *metadata);
int readFromStdin(char * buffer);
int checkArgs(int argc, char * argv[], rcopyInfo *metadata);
double getRate(char * argv[]);


int main (int argc, char *argv[])
 {
	int socketNum = 0;				
	struct sockaddr_in6 server;		// Supports 4 and 6 but requires IPv6 struct
	int portNumber = 0;
	rcopyInfo metadata;
	
	setupPollSet();

	portNumber = checkArgs(argc, argv, &metadata);
	
	sendErr_init(metadata.errRate, DROP_ON, FLIP_ON, DEBUG_ON, RSEED_ON);
	
	socketNum = setupUdpClientToServer(&server, argv[6], portNumber);
	
	addToPollSet(socketNum);

	processCopy(socketNum, metadata, server);
	
	close(socketNum);

	return 0;
}

void processCopy(int socket, rcopyInfo metadata, struct sockaddr_in6 serverAddr) {
	ConnectionData server;
	server.address = serverAddr;
	server.socket = socket;
	uint32_t clientSeqNum = 0;
	uint32_t expectedSeqNum = 0;
	uint32_t eofSeqNum = 0;
	//Window window;
	Window window = createWindow(metadata.windowSize);
	STATE state = INIT;

	while (state != DONE) {
		switch (state) {
			case INIT:
				state = establishConnection(&server, metadata, &clientSeqNum);
				break;

			case FILENAME:
				state = processFilenameResponse(&server, metadata);
				break;

			case FILE_OK:
				state = processOpenFile(&server, &metadata);
				break;

			case RECV_DATA:
				state = processRecvData(&server, &metadata, &clientSeqNum, &expectedSeqNum, &eofSeqNum, &window);
				break;

			case EOF_ACK:
				state = processEOFACK(&server, &metadata, &clientSeqNum, &expectedSeqNum, &eofSeqNum, &window);
				break;

			case DONE:
				// need to cleanly exit (free the table)
				printf("Finished the file transfer. Exiting.\n");
				cleanExit(&window, &metadata);
				exit(0);
				break;

			default:
				printf("in default!! Shouldn't be here\n");
				break;

		}
	}
}


STATE processEOFACK(ConnectionData *server, rcopyInfo *metadata, uint32_t *clientSeqNum, uint32_t *expectedSeqNum, uint32_t *eofSeqNum, Window *window) {
	STATE nextState = DONE;

	// if the expected seq num is the same as the eof seq num, done! send the EOF ACK, otherwise, there's some missing data
	if (*expectedSeqNum == *eofSeqNum) {
		// send the EOF ACK 
		sendEOFACK(server, clientSeqNum);
		return DONE;
	}

	// since there's missing data, start processing the SREJs needed to correct the buffer. break when you find a seqnum == eofseqnum
	// Since the EOF was already seen, we don't care about getting a repeat EOF. Do not acknowledge subsequent EOFs sent
	processSREJ(server, metadata, window, NO_ACK_EOF, GOT_EOF, clientSeqNum, expectedSeqNum, eofSeqNum);

	// Done processing and fixing data, MAY NEED TO FLUSH THE BUFFER ONCE MORE IN CASE OF GOOD DATA ABOVE THE FINAL HOLE

	// Really done now, send the EOF ACK and cleanly exit
	sendEOFACK(server, clientSeqNum);

	return nextState;
}



STATE processRecvData(ConnectionData *server, rcopyInfo *metadata, uint32_t *clientSeqNum, uint32_t *expectedSeqNum, uint32_t *eofSeqNum, Window *window) {
	int recvLen = 0;
	uint32_t currentSeqNum = 0;
	int holeLoc = 0;
	uint8_t packet[MAX_BUFFER] = {0};
	STATE nextState = DONE;
	// loop to process data packets
	while (1) {
		if (pollCall(POLL_WAIT_10_SEC) != -1) {
			// there's some data to be received
			memset(packet, 0, MAX_BUFFER);
			recvLen = safeRecvfrom(server->socket, packet, MAX_BUFFER, 0, (struct sockaddr *) (&(server->address)), &(server->len));
			
			// first, ensure CRC validity. if not valid, discard and wait for more data
			if (!validChecksum(packet, recvLen)) {
				continue;
			}

			// Get the current packet's seqNum
			currentSeqNum = getSeqNumber(packet);

			// verify correct flag type
			// If EOF received, go to EOF ack state to cleanup writing data to file
			if(packet[FLAG_LOC] == EOF_FLAG) {
				// Set the EOF sequence number
				(*eofSeqNum) = currentSeqNum;
				nextState = EOF_ACK;
				break;
			} else if(packet[FLAG_LOC] != DATA_FLAG) {
				// discard and try receive more data
				continue;
			}

			// MAIN DATA PROCESSING
			if (currentSeqNum == *expectedSeqNum) {
				// Data is as it should be! 
				writeDataToFile(server, metadata, packet, recvLen);

				// before RR'ing, check to see if anything higher is in buffer. Write anything valid to file and update expected
				// loop to check holes, (any more valid packets above)
				(*expectedSeqNum)++;
				if ((holeLoc = flushBuffer(server, metadata, window, expectedSeqNum)) < 0) {
					// No holes found, expected sequence number was updated to reflect where we now are
				} else {
					// Hole found. This is the expected sequence number
					(*expectedSeqNum) = holeLoc;
				}

				sendDataRR(server, window, clientSeqNum, *expectedSeqNum);
				
			} else if(currentSeqNum < *expectedSeqNum) {
				// Got some old data that is already written to disk. RR the expectedSeqNum
				sendDataRR(server, window, clientSeqNum, *expectedSeqNum);
				
			} else {
				// currentSeq is bigger, we are missing some data! Need to send out SREJs for all missing data
				// Buffer this data, go to processing SREJS and flushing the buffer
				addPDUToWindow(window, packet, recvLen);

				// process SREJs, acknowledge the EOF if found and break to go into EOF ack state
				if (processSREJ(server, metadata, window, ACK_EOF, NO_EOF, clientSeqNum, expectedSeqNum, eofSeqNum)) {
					// Found an EOF when trying to process SREJs. eofSeqNum was set
					nextState = EOF_ACK;
					break;
				}
			}
		
		} else {
			// No data has been sent by the server for ten seconds. exit.
			printf("Data poll timed out. Server terminated\n");
			nextState = DONE;
			break;
		}
	}

	return nextState;
}


// returns an indication if EOF was found in the middle of trying to process SREJs
int processSREJ(ConnectionData *server, rcopyInfo *metadata, Window *window, int acknowledgeEOF, int gotEOF, uint32_t *clientSeqNum, uint32_t *expectedSeqNum, uint32_t *eofSeqNum) {
	int recvLen = 0;
	int holeLoc = 0;
	uint32_t currentSeqNum = 0;
	uint8_t packet[MAX_BUFFER] = {0};

	// Only send SREJ once
	sendSREJ(server, clientSeqNum, *expectedSeqNum);

	while(1) {

		// Process recv
		if (pollCall(POLL_WAIT_10_SEC) != -1) {
			memset(packet, 0, MAX_BUFFER);
			recvLen = safeRecvfrom(server->socket, packet, MAX_BUFFER, 0, (struct sockaddr *) (&(server->address)), &(server->len));

			// Check for CRC validity
			if (!validChecksum(packet, recvLen)) {
				continue;
			}

			currentSeqNum = getSeqNumber(packet);

			// Need to check flags
			if(packet[FLAG_LOC] == EOF_FLAG) {
				// if user set to acknowledge, set and return now. Else, keep processing SREJs
				if (acknowledgeEOF) {
					(*eofSeqNum) = currentSeqNum;
					return 1;
				} else {
					// if we got the expected eof then we're done!
					if (*expectedSeqNum == *eofSeqNum) {
						return 1;
					} else {
						sendSREJ(server, clientSeqNum, *expectedSeqNum);
					}

					continue;
				}

			} else if(packet[FLAG_LOC] != DATA_FLAG) {
				// discard and try receive more data
				continue;
			
			} 

			if (currentSeqNum > *expectedSeqNum) {
				// buffer data 
				addPDUToWindow(window, packet, recvLen);

			} else if (currentSeqNum < *expectedSeqNum) {
				// Send an RR for expected
				sendDataRR(server, window, clientSeqNum, *expectedSeqNum);
			
			} else {
				writeDataToFile(server, metadata, packet, recvLen);
				// got expected, write and flush data above now. Check for holes. no holes -> send RR for new expected and exit. update expected and loop else
				(*expectedSeqNum)++;
				if ((holeLoc = flushBuffer(server, metadata, window, expectedSeqNum)) < 0) {
					// No holes found, expected sequence number was updated to reflect where we now are
					sendDataRR(server, window, clientSeqNum, *expectedSeqNum);
					break;
				} else {
					// Hole found. This is the expected sequence number
					*expectedSeqNum = holeLoc;
					// send SREJ
					sendSREJ(server, clientSeqNum, *expectedSeqNum);
				}
			}

		} else {
			printf("Server is unresponsive. Terminating.\n");
			cleanExit(window, metadata);
			exit(1);
		}
	}

	// EOF not found while processing
	return 0;
}


void sendEOFACK(ConnectionData *server, uint32_t *clientSeqNum) {
	uint8_t payload = 0;

	uint8_t *PDU = createPDU(*clientSeqNum, EOF_ACK_FLAG, &payload, 0);

	safeSendto(server->socket, PDU, HEADER_LEN, 0, (struct sockaddr *) (&(server->address)), server->len);

	(*clientSeqNum)++;
}

void sendSREJ(ConnectionData *server, uint32_t *clientSeqNum, uint32_t expectedSeqNum) {
	uint8_t payload[MAX_BUFFER] = {0};
	expectedSeqNum = htonl(expectedSeqNum);
	memcpy(payload, &expectedSeqNum, sizeof(uint32_t));

	uint8_t *PDU = createPDU(*clientSeqNum, SREJ_FLAG, payload, sizeof(uint32_t));

	safeSendto(server->socket, PDU, sizeof(uint32_t)+HEADER_LEN, 0, (struct sockaddr *) (&(server->address)), server->len);

	(*clientSeqNum)++;
}

// will return the location of a hole in the buffer
int flushBuffer(ConnectionData *server, rcopyInfo *metadata, Window *window, uint32_t *expectedSeqNum) {
	int i = *expectedSeqNum;
	int end = (*expectedSeqNum) + metadata->windowSize + 1;
	uint8_t localPacket[MAX_BUFFER];
	int localSize = 0;
	int index = 0;
	int hole = -1;

	// loop through window buffer to find good data. Go from expected around the circle back to starting pos.
	// if an entry is invalid, break, this is the new expected. If entry is valid, write it to disk and continue through buffer.
	for(; i < end; i++) {
		index = i%(metadata->windowSize);

		// Found an invalid entry in table (hole)
		if (!(window->table)[i%(metadata->windowSize)].isValid) {
			hole = i;
			break;
		}

		// copy the window entry pdu data to a local variable before writing (MEMORY BUG AVOIDANCE)
		memset(localPacket, 0, MAX_BUFFER);
		copyPDULocal(window, localPacket, &localSize, index);

		writeDataToFile(server, metadata, localPacket, localSize);

		markEntryInvalid(window, index);

		(*expectedSeqNum)++;
	}

	return hole;
}


// Sends an RR packet for the specified seqNum
void sendDataRR(ConnectionData *server, Window *window, uint32_t *clientSeqNum, uint32_t seqNum) {
	uint8_t payload[MAX_BUFFER] = {0};
	seqNum = htonl(seqNum);
	memcpy(payload, &seqNum, sizeof(uint32_t));

	uint8_t *PDU = createPDU(*clientSeqNum, RR_FLAG, payload, sizeof(uint32_t));

	safeSendto(server->socket, PDU, sizeof(uint32_t)+HEADER_LEN, 0, (struct sockaddr *) (&(server->address)), server->len);

	(*clientSeqNum)++;
}

// Write's a data packet payload to disk
void writeDataToFile(ConnectionData *server, rcopyInfo *metadata, uint8_t *PDU, int recvLen) {
	uint8_t data[MAX_BUFFER] = {0};
	// get the data to write to file
	memcpy(data, &PDU[HEADER_LEN], recvLen-HEADER_LEN);

	// Write to file
	if (fwrite(data, sizeof(uint8_t), recvLen-HEADER_LEN, metadata->toFile) == 0) {
		//printf("An error occurred while writing to the local file. Exiting\n");
		perror("Error occurred while trying to write to the local file: ");
		exit(1);
	}
}

// Attempts to open the local file specified for writing
STATE processOpenFile (ConnectionData *server, rcopyInfo *metadata) {
	FILE *toFile = NULL;
	STATE nextState = DONE;

	if((toFile = fopen(metadata->toFilename, "w")) == NULL) {
		// Error opening/creating file for writing
		printf("Error on open for output of file: <%s> -> ", metadata->toFilename);
		fflush(stdout);
		perror("");
		nextState = DONE;

	} else {
		// File has been opened for writing data to
		metadata->toFile = toFile;
		nextState = RECV_DATA;
	}

	return nextState;
}

// Processes the server's response to the filename exchange packet
STATE processFilenameResponse(ConnectionData *server, rcopyInfo metadata) {
	// process the reponse from the server. 
	// return to init if server does not reply, done if bad fname, file ok other
	static int retries = 0;
	uint8_t packet[MAX_BUFFER] = {0};
	int recvLen = 0;
	STATE nextState = INIT;

	if ((nextState = processFilenameRetry(server, &retries)) == FILE_OK) {
		// receive the filename response, server is trying to send a packet
		recvLen = safeRecvfrom(server->socket, packet, MAX_BUFFER, 0, (struct sockaddr *) (&(server->address)), &(server->len));

		// Check for CRC error
		if (in_cksum((unsigned short *) packet, recvLen) != 0) {
			nextState = INIT;

		// filename response lost, got a data packet
		} else if (packet[FLAG_LOC] == DATA_FLAG) {
			nextState = FILE_OK;

		// Check for bad filename response
		} else if (packet[HEADER_LEN] == FNAME_BAD) {
			printf("Error: file <%s> not found on server.\n", metadata.fromFilename);
			nextState = DONE;
		} 

	}

	return nextState;
}

// Tries to process the initial filename exchange packet up to ten times
STATE processFilenameRetry(ConnectionData *server, int *retries) {
	// calls poll with one second timeout (10 times) waiting for the file ok response from server
	// if timeout, we're headed back to init to try and resend the filename packet
	STATE nextState = INIT;
	int currentSocket = 0;
	
	// Check somewhere if the retry counter has exceeded the 10 tries? maybe check this in processfilenamereponse

	if ((currentSocket = pollCall(POLL_WAIT_1_SEC)) != -1) {
		// receiving a response, check that the server validated the requested filename
		nextState = FILE_OK;
	} else {
		// poll timed out after 1 second, increment the counter and return to INIT
		(*retries)++;
		nextState = INIT;
	}

	// if retries > 9, nextstate = done, server terminated or not valid
	if ((*retries) > 9) {
		nextState = DONE;
	}

	return nextState;
}


STATE establishConnection (ConnectionData *server, rcopyInfo metadata, uint32_t *clientSeqNum) {
	// attempts to establish connection to a server. Successfully will return the FILENAME next state
	server -> len = sizeof(struct sockaddr_in6);
	uint8_t *PDU;
	int payloadLen = 0;
	uint8_t filenamePayload[MAX_BUFFER];
	uint32_t windowSize = htonl(metadata.windowSize);
	uint16_t bufferSize = htons(metadata.bufferSize);
	uint8_t filenameLen = strlen(metadata.fromFilename) + 1;

	// form the payload before attaching the Header
	memcpy(filenamePayload, &windowSize, sizeof(uint32_t));
	memcpy(&filenamePayload[4], &bufferSize, sizeof(uint16_t));
	memcpy(&filenamePayload[6], &filenameLen, sizeof(uint8_t));
	memcpy(&filenamePayload[7], metadata.fromFilename, filenameLen);

	payloadLen = sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint8_t) + filenameLen;

	PDU = createPDU(*clientSeqNum, FNAME_REQUEST_FLAG, filenamePayload, payloadLen);

	safeSendto(server->socket, PDU, payloadLen+HEADER_LEN, 0, (struct sockaddr *) (&(server->address)), server->len);

	return FILENAME;
}

// Sends the initial filename request packet to the server
void sendFilenameRequest(int socket, struct sockaddr_in6 *server, uint32_t sequenceNumber, uint8_t *fileNamePayload, uint8_t fnameLen) {
	uint8_t *PDU;
	int serverAddrLen = sizeof(struct sockaddr_in6);
	int dataLen = fnameLen + 1;

	PDU = createPDU(sequenceNumber, FNAME_REQUEST_FLAG, fileNamePayload, dataLen);
	
	safeSendto(socket, PDU, dataLen + HEADER_LEN, 0, (struct sockaddr *) server, serverAddrLen);
}

// Error checks and converts the rate passed through CL
double getRate(char * argv[]) {
	double rate = 0.0;
	rate = atof(argv[5]);

	// rate == 0 || ----- error rate can be 0
	if (rate > 1.0 || rate < 0) {
		printf("error-rate must be between 0-1\n");
		exit(1);
	}

	return rate;
}

// Checks the CL Args
int checkArgs(int argc, char * argv[], rcopyInfo *metadata)
{
	/* check command line arguments  */
	if (argc != 8)
	{
		printf("usage: %s from-filename to-filename window-size buffer-size error-rate remote-machine remote-port\n", argv[0]);
		exit(1);
	}
	
	// Do argument error checking here
	// Checks args, fills in metadata, and returns port number
	if (strlen(argv[1]) > (MAX_FNAME_LEN-1)) {
		printf("filename %s is too long. Exiting\n", argv[1]);
		exit(1);
	}

	if (strlen(argv[2]) > (MAX_FNAME_LEN-1)) {
		printf("filename %s is too long. Exiting\n", argv[2]);
		exit(1);
	}

	if (atoi(argv[4]) > 1400 || atoi(argv[4]) == 0) {
		printf("Buffer size invalid. Buffer size must be a number 1400 or less.\n");
		exit(1);
	}

	// Error check and get the error rate
	metadata -> errRate = getRate(argv);

	// Add the rest of the data to metadata
	metadata -> fromFilename = argv[1];
	metadata -> toFilename = argv[2];
	metadata -> windowSize = (uint32_t) atoi(argv[3]);
	metadata -> bufferSize = (uint16_t) atoi(argv[4]);
	
	int portNumber = 0;
	
	portNumber = atoi(argv[7]);
		
	return portNumber;
}

// Closes the file stream and frees memory allocated for the buffer
void cleanExit(Window *window, rcopyInfo *metadata) {
	// close the file stream
	fclose(metadata->toFile);
	free(window->table);
}