diff --git a/.gitignore b/.gitignore index 63fd01b7e0ff4cff41d4cdab8ae1f8270f45d408..6f3007984042ed242514f672889e8f278a7576c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.o .vscode/ -test_logs/ +tests_logs/* log_files/ *.zip linksimulator/ diff --git a/Makefile b/Makefile index e96ac90eeb313fc61506c9a147802d8d74f65992..2e5aabb1e5fe2ce0a3820b4ca918ac4bcc3c05ea 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ HEADERS_DIR = -Iheaders LDFLAGS = -lz # Adapt these as you want to fit with your project -SENDER_SOURCES = $(wildcard src/sender.c src/log.c src/our_utils.c src/packet_interface.c src/sender_utils.c) -RECEIVER_SOURCES = $(wildcard src/receiver.c src/log.c src/our_utils.c src/packet_interface.c src/receiver_utils.c) +SENDER_SOURCES = $(wildcard src/sender.c src/log.c src/utils.c src/packet_interface.c src/sender_utils.c) +RECEIVER_SOURCES = $(wildcard src/receiver.c src/log.c src/utils.c src/packet_interface.c src/receiver_utils.c) SENDER_OBJECTS = $(SENDER_SOURCES:.c=.o) RECEIVER_OBJECTS = $(RECEIVER_SOURCES:.c=.o) diff --git a/src/log.c b/src/log.c index 25874a85e483f67b32d15a94547d30ba7ee6684a..e73784e105bf809c2fad0489348988d3e51dd2cd 100644 --- a/src/log.c +++ b/src/log.c @@ -4,6 +4,7 @@ #include "log.h" + /* Prints `len` bytes starting from `bytes` to stderr */ void dump(const uint8_t *bytes, size_t len) { for (size_t i = 0; i < len;) { diff --git a/src/our_utils.h b/src/our_utils.h deleted file mode 100644 index 421c0dae01cc65dc592bf4cadfe457a498f5519a..0000000000000000000000000000000000000000 --- a/src/our_utils.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file our_utils.h - * @brief This header contains utils that were coded for the packet sending on inginious - * @date 2022-03-17 - */ -#ifndef __OUR_UTILS_H_ -#define __OUR_UTILS_H_ - -#include <arpa/inet.h> -#include <errno.h> -#include <netdb.h> -#include <netinet/in.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <unistd.h> - - -/* Resolve the resource name to an usable IPv6 address - * @address: The name to resolve - * @rval: Where the resulting IPv6 address descriptor should be stored - * @return: NULL if it succeeded, or a pointer towards - * a string describing the error if any. - * (const char* means the caller cannot modify or free the return value, - * so do not use malloc!) - */ -const char * real_address(const char *address, struct sockaddr_in6 *rval); - - -/* Creates a socket and initialize it - * @source_addr: if !NULL, the source address that should be bound to this socket - * @src_port: if >0, the port on which the socket is listening - * @dest_addr: if !NULL, the destination address to which the socket should send data - * @dst_port: if >0, the destination port to which the socket should be connected - * @return: a file descriptor number representing the socket, - * or -1 in case of error (explanation will be printed on stderr) - */ -int create_socket(struct sockaddr_in6 *source_addr, int src_port, struct sockaddr_in6 *dest_addr, int dst_port); - - -/* Block the caller until a message is received on sfd, - * and connect the socket to the source addresse of the received message - * @sfd: a file descriptor to a bound socket but not yet connected - * @return: 0 in case of success, -1 otherwise - * @POST: This call is idempotent, it does not 'consume' the data of the message, - * and could be repeated several times blocking only at the first call. - */ -int wait_for_client(int sfd); - - -#endif \ No newline at end of file diff --git a/src/packet_interface.c b/src/packet_interface.c index 7193b58b4576d683aeaca5b315a60f789ebad22c..a255a01a67b5e0921fc35806f8cfb9950da3e79a 100644 --- a/src/packet_interface.c +++ b/src/packet_interface.c @@ -55,27 +55,33 @@ pkt_status_code pkt_decode_ack_nack(const char *data, const size_t len, pkt_t *p pkt_status_code pkt_decode_data_fec(const char *data, const size_t len, pkt_t *pkt) { - uint16_t length; - memcpy((void *) &length, (void *) &data[1], 2); - length = ntohs(length); + uint16_t payload_length, actual_indicated_length; + ptypes_t type = (data[0] & TYPE_MASK) >> TYPE_SHIFT; + + memcpy((void *) &actual_indicated_length, (void *) &data[1], 2); + actual_indicated_length = ntohs(actual_indicated_length); + payload_length = (type == PTYPE_DATA) ? actual_indicated_length : MAX_PAYLOAD_SIZE; // Fec are always of length 512 if ( len < PKT_MIN_HEADERLEN ) return E_NOHEADER; - if ( len > PKT_MAX_LEN || length > MAX_PAYLOAD_SIZE ) + if ( len > PKT_MAX_LEN || payload_length > MAX_PAYLOAD_SIZE ) return E_LENGTH; - ptypes_t type = (data[0] & TYPE_MASK) >> TYPE_SHIFT; + if ( type == PTYPE_FEC && (data[0] & TR_MASK ) != 0 ) return E_TR; - size_t expected_len = sizeof(header_t) + TIMESTAMP_SIZE + CRC_SIZE + length; + size_t expected_len = sizeof(header_t) + TIMESTAMP_SIZE + CRC_SIZE + payload_length; - if ( length > 0 ) + if ( payload_length > 0 ) expected_len += CRC_SIZE; - if ( len != expected_len ) + if ( len != expected_len && (data[0] & TR_MASK) == 0) + { return E_UNCONSISTENT; + } + // We set the TR to 0 in order to calculcate the CRC on the header char modified_header[8]; @@ -94,11 +100,11 @@ pkt_status_code pkt_decode_data_fec(const char *data, const size_t len, pkt_t *p @brief : We don't check the checksum and the window here **/ uint32_t crc2; - if ( (type == PTYPE_DATA && (data[0] & TR_MASK ) == 0 && length > 0) || type == PTYPE_FEC ) + if ((data[0] & TR_MASK ) == 0 && payload_length > 0 ) { - memcpy((void *) &crc2, &data[12+length], 4); + memcpy((void *) &crc2, &data[12+payload_length], 4); crc2 = ntohl((uint32_t) crc2); - if (calculate_crc(&data[12], length) != crc2) + if (calculate_crc(&data[12], payload_length) != crc2) return E_CRC; pkt_set_crc2(pkt, crc2); } @@ -107,18 +113,18 @@ pkt_status_code pkt_decode_data_fec(const char *data, const size_t len, pkt_t *p memcpy((void *) ×tamp, &data[4], 4); memcpy(&(pkt->header.front), data, 1); - pkt_set_length(pkt, length); + pkt_set_length(pkt, actual_indicated_length); pkt_set_seqnum(pkt, seqnum); pkt_set_timestamp(pkt, timestamp); pkt_set_crc1(pkt, crc1); - pkt_set_payload(pkt, &data[12], length); + pkt_set_payload(pkt, &data[12], payload_length); return PKT_OK; } pkt_status_code pkt_decode(const char *data, const size_t len, pkt_t *pkt) { ptypes_t type = ((data[0]) & TYPE_MASK) >> TYPE_SHIFT; - if ( type == PTYPE_ACK || type == PTYPE_NACK) + if (type == PTYPE_ACK || type == PTYPE_NACK) { return pkt_decode_ack_nack(data, len, pkt); } else @@ -145,7 +151,7 @@ pkt_status_code pkt_encode_ACK_NACK(const pkt_t *pkt, char *buf, size_t *len) pkt_status_code pkt_encode_DATA_FEC(const pkt_t *pkt, char *buf, size_t *len) { // Let's first copy the header - if ( *len < 12 ) return E_NOMEM; + if ( *len < PKT_MIN_HEADERLEN ) return E_NOMEM; memcpy((void *) &buf[0], (void *) &(pkt->header.front), 1); uint16_t n_length = htons(pkt_get_length(pkt)); memcpy((void *) &buf[1], (void *) &n_length, 2); @@ -157,20 +163,20 @@ pkt_status_code pkt_encode_DATA_FEC(const pkt_t *pkt, char *buf, size_t *len) size_t required_size; ptypes_t type = pkt_get_type(pkt); - uint16_t length = (type == PTYPE_DATA && pkt_get_tr(pkt) == 0) ? pkt_get_length(pkt) : MAX_PAYLOAD_SIZE; - required_size = sizeof(header_t) + TIMESTAMP_SIZE + CRC_SIZE + length; + uint16_t payload_length = (type == PTYPE_DATA && pkt_get_tr(pkt) == 0) ? pkt_get_length(pkt) : MAX_PAYLOAD_SIZE; // Cause FEC payload is always max size + required_size = sizeof(header_t) + TIMESTAMP_SIZE + CRC_SIZE + payload_length; if ( pkt_get_length(pkt) > 0 ) required_size += CRC_SIZE; if ( *len < required_size ) return E_NOMEM; - *len = sizeof(header_t) + TIMESTAMP_SIZE + CRC_SIZE + length; - if ( pkt_get_length(pkt) > 0 ) + *len = sizeof(header_t) + TIMESTAMP_SIZE + CRC_SIZE + payload_length; + if ( payload_length > 0 ) { - memcpy((void *) &buf[12], (void *) pkt->payload, length); - uint32_t crc2 = htonl(calculate_crc(&buf[12], length)); - memcpy((void *) &buf[12 + length], (void *) &crc2, CRC_SIZE); + memcpy((void *) &buf[12], (void *) pkt->payload, payload_length); + uint32_t crc2 = htonl(calculate_crc(&buf[12], payload_length)); + memcpy((void *) &buf[12 + payload_length], (void *) &crc2, CRC_SIZE); *len = *len + CRC_SIZE; } return PKT_OK; diff --git a/src/packet_interface.h b/src/packet_interface.h index 181dc94a21d823b623621c5a7de5645da6d627d9..930f455041fe902838849fdeab72af186c6faca1 100644 --- a/src/packet_interface.h +++ b/src/packet_interface.h @@ -10,6 +10,7 @@ #include <stdio.h> /* ssize_t */ #include <zlib.h> +#include "log.h" /* Taille maximale permise pour le payload */ #define MAX_PAYLOAD_SIZE 512 @@ -68,7 +69,7 @@ typedef enum { #define TR_MASK 0x20 #define WINDOW_MASK 0x1F #define TR_SETTER_TO_ZERO 0xDF -#define SECOND_TO_LAST_PKT 0xFFFFFFFF // Use to specify it was the second to last pkt sent by the sender +#define SECOND_TO_LAST_PKT 0x3AB33922 // Use to specify it was the second to last pkt sent by the sender -> Need to be random ? #define TYPE_SHIFT 6 #define TR_SHIFT 5 diff --git a/src/receiver.c b/src/receiver.c index b4b3b0f53cc59e30e192b8b87c58e51552fba512..899f07301471cdf7219921c7c8952bb3e5d579c5 100644 --- a/src/receiver.c +++ b/src/receiver.c @@ -60,9 +60,8 @@ int main(int argc, char **argv) { return (EXIT_FAILURE); } - ASSERT(stats_filename == NULL); + receiver_read_write_loop(socket, (const char *) stats_filename); - receiver_read_write_loop(socket); close(socket); free(addr); return EXIT_SUCCESS; diff --git a/src/receiver_utils.c b/src/receiver_utils.c index eead2ab97dfad023412beaa5d31143df41de6f83..7a0f85d848b418bf60dc47bdfa7584afbf4fa591 100644 --- a/src/receiver_utils.c +++ b/src/receiver_utils.c @@ -1,5 +1,17 @@ #include "receiver_utils.h" + +/** + * @brief The function is going to check if we need to send an ACK or NACK on the @pfd, + * and will send at most one packet [either an ACK or NACK or nothing]. + * + * @param pfd: The pollfd struct that we used to get the file descriptor to send the messsage on. + * @param state: The current stae of the receiver. + * + * @return 0 upon success, else -1 with a message printed on the stderr. + * + * @modifies: state +*/ int send_if_inneed(struct pollfd * pfd, receiver_state_t * state) { // Priority to NACKs @@ -23,8 +35,8 @@ int send_if_inneed(struct pollfd * pfd, receiver_state_t * state) return -1; } DEBUG("Sent NACK that [%d] has been truncated", pkt_get_seqnum(state->nack_to_send[nack_idx])); - DEBUG_DUMP(buffer, len); pkt_del(state->nack_to_send[nack_idx]); + state->stats->nack_sent++; state->nack_to_send[nack_idx] = NULL; } else if (state->ack_to_send != NULL) /* There's an ack to send */ { @@ -42,66 +54,144 @@ int send_if_inneed(struct pollfd * pfd, receiver_state_t * state) return -1; } DEBUG("Sent ACK saying we are waiting for %d, timestamp %d", pkt_get_seqnum(state->ack_to_send), pkt_get_timestamp(state->ack_to_send)); - DEBUG_DUMP(buffer, len); pkt_del(state->ack_to_send); + state->stats->ack_sent++; state->ack_to_send = NULL; } return 0; } -int consume_data(receiver_state_t * state, uint8_t seqnum_to_consume) +/** + * @brief Write the payload of the packet in the state->recvd_data_buf[seqnum_to_consume] to the stdout. + * + * @param state: The current stae of the receiver. + * @param seqnum_to_consume: The seqnum of the packet to consume. + * + * @return 0 upon success, else -1 with a message printed on the stderr. + * + * @modifies: state. + */ +int consume_data_pkt(receiver_state_t * state, uint8_t seqnum_to_consume) { - size_t written = fwrite((void *) pkt_get_payload(state->recvd_buf[seqnum_to_consume]), sizeof(char), (size_t) pkt_get_length(state->recvd_buf[seqnum_to_consume]), stdout); - if (written != pkt_get_length(state->recvd_buf[seqnum_to_consume])) + ASSERT(state->recvd_data_buf[seqnum_to_consume] != NULL); + size_t written = fwrite((void *) pkt_get_payload(state->recvd_data_buf[seqnum_to_consume]), sizeof(char), (size_t) pkt_get_length(state->recvd_data_buf[seqnum_to_consume]), stdout); + if (written != pkt_get_length(state->recvd_data_buf[seqnum_to_consume])) { ERROR("Couldn't write the full packet content"); return -1; } fflush(stdout); - state->latest_timestamp = pkt_get_timestamp(state->recvd_buf[seqnum_to_consume]); - pkt_del(state->recvd_buf[seqnum_to_consume]); - state->recvd_buf[seqnum_to_consume] = NULL; + state->latest_timestamp = pkt_get_timestamp(state->recvd_data_buf[seqnum_to_consume]); + pkt_del(state->recvd_data_buf[seqnum_to_consume]); + state->recvd_data_buf[seqnum_to_consume] = NULL; return 0; +} + +/** + * @brief This function checks if 4 consecutive packets, starting from the positions_to_start, have been received. + * + * @param state: The current stae of the receiver. + * @param position_to_start: The position from which to start the inspection. + * + * @returns 1 Upon true else 0. + */ +uint16_t next_four_packets_received(receiver_state_t * state, uint16_t position_to_start) +{ + /* + 256 is dividable by 4 if the the sender respects the constraint of sending a FEC each 4 packets then + we just have to check increasingly only. Hence why the loop consition checks if the next 4 packets are received + and doesn't check the circular condition. + */ + ASSERT(position_to_start < TWO_EXP_EIGHT); + if ( position_to_start > TWO_EXP_EIGHT - FEC_CALCULATED_ON) + return 0; + + for (uint16_t i = position_to_start; i < position_to_start + FEC_CALCULATED_ON; i++) + { + if (state->recvd_data_buf[i] == NULL) + return 0; + } + return 1; +} + +/** + * @brief Checks if we can consume data from the buffer. + * + * @param state: The current receiver state. + * @param latest_packet_received: The latest valid packet we received + * + * @return The number of packets to consume. + * @modifies: state. + */ +uint16_t can_consume(receiver_state_t * state, const pkt_t * latest_packet_received){ + // We have received the last 4 paquets harmless or we just received the last packet + int to_consume = 0; + ASSERT(state->next_to_consume >= 0 && state->next_to_consume < TWO_EXP_EIGHT); + if (pkt_get_length(latest_packet_received) == 0) + { // Last packet we read everything from the buffer + for (uint16_t i = state->next_to_consume; state->recvd_data_buf[i] != NULL; i = (i + 1) % TWO_EXP_EIGHT ) + to_consume++; + } else + { // We only read blocks of 4 packets. + uint16_t start_to_consume = state->next_to_consume; + for (; next_four_packets_received(state, start_to_consume % TWO_EXP_EIGHT); start_to_consume+=4) + to_consume+=4; + } + return to_consume; } +/** + * @brief Updates the state, upon new data entrance. This function is the core function that handles the writing of the + * date to the stdout. Updates the receiver window, last_received_in_order; etc. + * + * @param state: The current receiver state. + * @param pkt: The data packet that just been received. + * + * @returns: 0 upon success else -1 with an error message on the stderr. + * + * @modifies: state + */ int update_buffer_upon_new_data(receiver_state_t * state, const pkt_t * pkt) { // Find free space - state->curr_recv_window = (state->curr_recv_window > 0) ? state->curr_recv_window-1 : 0; - uint8_t seqnum = pkt_get_seqnum(pkt); + // New packet - if (state->recvd_buf[seqnum] == NULL) + if (state->recvd_data_buf[seqnum] == NULL) { pkt_t * pkt_to_store = pkt_new(); if (pkt_to_store == NULL) return -1; memcpy((void *) pkt_to_store, (void *) pkt, sizeof(pkt_t)); - state->recvd_buf[seqnum] = pkt_to_store; + state->recvd_data_buf[seqnum] = pkt_to_store; + state->curr_recv_window = (state->curr_recv_window > 0) ? state->curr_recv_window-1 : 0; + } else { + state->stats->packet_duplicated++; } + // Update last received in order if (seqnum == (state->last_received_in_order + 1) % TWO_EXP_EIGHT) { state->last_received_in_order = seqnum; - } else - { - DEBUG("Out of sequence"); - return 0; + uint16_t idx = seqnum; + for (;state->recvd_data_buf[idx] != NULL; idx = (idx + 1) % TWO_EXP_EIGHT) + state->last_received_in_order = idx; } + uint16_t to_consume = can_consume(state, pkt); + if (to_consume == 0) + return 0; - // We only reach here when we received packets in sequence. - - uint16_t next_wait = state->last_received_in_order; - while (state->recvd_buf[next_wait] != NULL) + DEBUG("Going to consume the next %d packets.", to_consume); + while (to_consume > 0) { DEBUG("Consuming packet : %d | curr_recv_window = %d, recv_window_start = %d", - next_wait, state->curr_recv_window, state->recv_window_start); + state->next_to_consume, state->curr_recv_window, state->recv_window_start); - if (consume_data(state, next_wait) != 0) return -1; - state->last_received_in_order = next_wait; - state->curr_recv_window = (state->curr_recv_window < RECV_MAX_SLCTV_RPT_WDW) ? state->curr_recv_window+1 : RECV_MAX_SLCTV_RPT_WDW; + if (consume_data_pkt(state, state->next_to_consume) != 0) return -1; + state->curr_recv_window = (state->curr_recv_window < RECV_MAX_SLCTV_RPT_WDW) ? state->curr_recv_window + 1 : RECV_MAX_SLCTV_RPT_WDW; state->recv_window_start = (state->recv_window_start + 1) % TWO_EXP_EIGHT; - next_wait = (next_wait + 1) % TWO_EXP_EIGHT; + state->next_to_consume = (state->next_to_consume + 1) % TWO_EXP_EIGHT; + to_consume--; } return 0; } @@ -111,7 +201,8 @@ int update_buffer_upon_new_data(receiver_state_t * state, const pkt_t * pkt) * there will be an ACK to send. * * @param state: The connection state structure. - * @param pkt: The packet for which we should prepare ack. Can be nullable in that case we just send the ack latest received + * @param pkt: The packet for which we should prepare ack. Can be nullable in that case we just send the ack latest received. + * * @returns 0 upon success else -1 * * @modifies: @state. @@ -152,16 +243,21 @@ int prepare_ack_to_send(receiver_state_t * state) } /** - * @brief This function handles PTYPE_DATA arriving packets amd updates the state. + * @brief This function handles PTYPE_DATA arriving packets and updates the state. + * + * @param state: The receiver state. + * @param pkt: The DATA packet. * - * @param state: The receiver state - * @param pkt: The DATA packet * @returns 0 upon success else -1. * * @modifies: state. */ int handle_data_pkt(receiver_state_t * state, const pkt_t * pkt) { + state->stats->data_received++; + DEBUG("Received data packet seqnum %d with timestamp %d | current_window_size : %d, current_window_start : %d", + pkt_get_seqnum(pkt), pkt_get_timestamp(pkt), state->curr_recv_window, state->recv_window_start); + if (update_buffer_upon_new_data(state, pkt) != 0) return -1; if (prepare_ack_to_send(state) != 0) return -1; @@ -182,6 +278,145 @@ int handle_data_pkt(receiver_state_t * state, const pkt_t * pkt) state->last_data_packet = pkt_get_seqnum(pkt); return 0; +} + +/** + * @brief: This function uses the fec packet to recover the missing packet with missing_seqnum as seqnum. + * + * @param state: The receiver state. + * @param pkt: The FEC packet we just received. + * @param missing_seqnum: The packet we are going to recover from the fec packet. + * + * @returns 0 upon success else -1 with an error message on stderr. + * + * @modifies: state. + */ +int use_fec(receiver_state_t * state, const pkt_t * fec, uint16_t missing_seqnum) +{ + pkt_t * new_packet = pkt_new(); + if ( new_packet == NULL ) + { + ERROR("An error occured when initiating a new packet in order to use FEC"); + return -1; + } + uint16_t fec_seqnum = pkt_get_seqnum(fec); + pkt_set_type(new_packet, PTYPE_DATA); + pkt_set_seqnum(new_packet, missing_seqnum); + pkt_set_length(new_packet, pkt_get_length(fec)); + for ( uint16_t idx = fec_seqnum; idx < fec_seqnum + FEC_CALCULATED_ON; idx++ ) + { + if ( state->recvd_data_buf[idx] != NULL) + pkt_set_length(new_packet, pkt_get_length(new_packet) ^ pkt_get_length(state->recvd_data_buf[idx])); + } + uint16_t payload_length = pkt_get_length(new_packet); + uint8_t new_payload[payload_length]; + uint8_t * fec_payload = (uint8_t *) pkt_get_payload(fec); + uint8_t * other_payloads[FEC_CALCULATED_ON-1]; + + for ( uint16_t idx = fec_seqnum, idx_other_payloads = 0; idx < fec_seqnum + FEC_CALCULATED_ON; idx++ ) + { + if ( state->recvd_data_buf[idx] != NULL) + { + other_payloads[idx_other_payloads] = (uint8_t *) pkt_get_payload(state->recvd_data_buf[idx]); + idx_other_payloads++; + } + } + + for ( uint16_t i = 0; i < payload_length; i++ ) + { + new_payload[i] = fec_payload[i]; + for ( uint16_t idx_other_payloads = 0; idx_other_payloads < FEC_CALCULATED_ON-1; idx_other_payloads++ ) + new_payload[i] = new_payload[i] ^ other_payloads[idx_other_payloads][i]; + } + pkt_set_payload(new_packet, (char *) new_payload, payload_length); + + DEBUG("Used FEC packet [%d] to recover data packet with seqnum [%d] | The new packet payload length is %d", + fec_seqnum, missing_seqnum, payload_length); + + // Add packet to received data buffer and treat the packet + // We pass the packet to the rest as if it had just came from the network + if (handle_data_pkt(state, new_packet) != 0) return -1; + + pkt_del(new_packet); + state->stats->packet_recovered++; + return 0; +} + +/** + * @brief: This function checks whether fec with seqnum as fec_seqnum, can be used to recover + * a missing packet. + * + * @param state: The receiver state. + * @param fec_seqnum: The seqnum of the fec packet that we just received. + * @param missing_seqnum: The pointer to the variable to which put the missind_seqnum if the the fec can be used. + * + * @returns: 1 upon True else 0. if True missing_seqnum set to the packet that can be recoverd by the fec. + */ +int can_fec_be_used(receiver_state_t * state, uint16_t fec_seqnum, uint16_t * missing_seqnum) +{ + uint16_t idx = fec_seqnum; + uint16_t in_range_missing = 0; + uint16_t in_range_available = 0; + for (; idx < fec_seqnum + FEC_CALCULATED_ON; idx++) + { + if (state->recvd_data_buf[idx] != NULL) + { + in_range_available++; + } else + { + in_range_missing++; + *missing_seqnum = idx; + } + } + return (in_range_available == FEC_CALCULATED_ON-1 && in_range_missing == 1); +} + +/** + * @brief: This function checks the potential usage of the fec, if the fec can be used then it uses the fec else it doesn't. + * + * @param state: The receiver state. + * @param pkt: The fec packet. + * + * @returns: 0 upon success else -1 + */ +int potential_usage_of_fec(receiver_state_t * state, const pkt_t * fec) +{ + uint16_t fec_seqnum = pkt_get_seqnum(fec); + uint16_t missing_data_seqnum = 0; + if ( can_fec_be_used(state, fec_seqnum, &missing_data_seqnum) ) + { + if ( use_fec(state, fec, missing_data_seqnum) != 0 ) return -1; + } else + { + DEBUG("Received FEC with seqnum [%d] but wasn't used", fec_seqnum); + } + return 0; +} + +/** + * @brief This function handles PTYPE_FEC arriving packets and updates the state. + * + * @param state: The receiver state. + * @param pkt: The DATA packet. + * @returns 0 upon success else -1. + * + * @requires: The packet is in the current receiving window + * + * @modifies: state. + */ +int handle_fec_pkt(receiver_state_t * state, const pkt_t * pkt) +{ + state->stats->fec_received++; + ASSERT(state != NULL && pkt != NULL); + uint8_t seqnum = pkt_get_seqnum(pkt); + if (seqnum + 3 < state->last_received_in_order ) + { + DEBUG("Received FEC with seqnum [%d] but wasn't used since last received in order : %d", + pkt_get_seqnum(pkt), state->last_received_in_order); + state->stats->packet_ignored++; + return 0; + } + return potential_usage_of_fec(state, pkt); } /** @@ -200,10 +435,14 @@ int handle_data_pkt(receiver_state_t * state, const pkt_t * pkt) */ int handle_truncated_pkt(receiver_state_t * state, const pkt_t * pkt) { + state->stats->data_truncated_received++; + DEBUG("Received a truncated packet with seqnum %d", pkt_get_seqnum(pkt)); + if (pkt_get_type(pkt) != PTYPE_DATA) return 0; + int free_idx = 0; for (; free_idx < RECV_MAX_SLCTV_RPT_WDW && state->nack_to_send[free_idx] != NULL; free_idx++); - if ( free_idx == 0 ) return 0; + if ( free_idx == 32 ) return 0; pkt_t * pkt_to_send = pkt_new(); if (pkt_to_send == NULL) return -1; @@ -223,7 +462,7 @@ int handle_truncated_pkt(receiver_state_t * state, const pkt_t * pkt) * and modifies @state. * * @requires: - * - @pkt was validly decoded by pkt_decoded(). + * - @pkt was validly decoded by pkt_decoded().l * * @param pkt: The packet received. * @param state: The current connection state. @@ -233,9 +472,6 @@ int handle_truncated_pkt(receiver_state_t * state, const pkt_t * pkt) */ int handle_valid_pkt(receiver_state_t * state, const pkt_t * pkt) { - // Feck - Not handled for now - if (pkt_get_type(pkt) != PTYPE_DATA) return 0; - // Is it in the receive_window ? uint8_t seqnum = pkt_get_seqnum(pkt); int in_window = (seqnum >= state->recv_window_start); @@ -247,16 +483,22 @@ int handle_valid_pkt(receiver_state_t * state, const pkt_t * pkt) if (!in_window) { - DEBUG("Received packet [%d] Out of window with %d | receive window start at : %d (included) till %d (excluded)", - seqnum, pkt_get_timestamp(pkt), state->recv_window_start, seqnum < (state->recv_window_start + RECV_MAX_SLCTV_RPT_WDW) % TWO_EXP_EIGHT); + DEBUG("Received packet [%d] Out of window with timestamp %d | receive window start at : %d (included) till %d (excluded)", + seqnum, pkt_get_timestamp(pkt), state->recv_window_start, (state->recv_window_start + RECV_MAX_SLCTV_RPT_WDW) % TWO_EXP_EIGHT); return prepare_ack_to_send(state); } uint8_t tr = pkt_get_tr(pkt); - if (tr == 1) /* If truncated */ + if (tr == 1) + { /* If truncated */ return handle_truncated_pkt(state, pkt); - else /* If not truncated */ + } else if (pkt_get_type(pkt) == PTYPE_DATA) + { /* Type DATA */ return handle_data_pkt(state, pkt); + } else if (pkt_get_type(pkt) == PTYPE_FEC) + { /* Type FEC */ + return handle_fec_pkt(state, pkt); + } return 0; } @@ -266,7 +508,7 @@ int handle_valid_pkt(receiver_state_t * state, const pkt_t * pkt) * * @warning Modifiers state * - * @param pfd: The struct pollfd sur lequel cherche le fd et lire. + * @param pfd: The struct pollfd sur lequel cherche le fd et lire. * @returns 0 upon success and -1 in cas of failure. */ int handle_incoming(struct pollfd * pfd, receiver_state_t * state) @@ -285,16 +527,14 @@ int handle_incoming(struct pollfd * pfd, receiver_state_t * state) pkt_status_code pkt_status = pkt_decode(buffer, read_bytes, pkt); if (pkt_status == 0) { - DEBUG("Received packet seqnum %d with timestamp %d | current_window_size : %d, current_window_start : %d", - pkt_get_seqnum(pkt), pkt_get_timestamp(pkt), state->curr_recv_window, state->recv_window_start); - if (handle_valid_pkt(state, (const pkt_t *) pkt) != 0) return -1; - } else + } else { - /* If the packet has been damaged we can discuss if it's better to send a ACK - or not but for now we just ignore it */ - DEBUG("Received a damaged packet with %d status.", pkt_status); - return prepare_ack_to_send(state); + DEBUG("Received a damaged packet with %d status. and seqnum as %d", pkt_status, pkt_get_seqnum(pkt)); + // See if there's a FEC that can be used and update buffer + pkt_del(pkt); + state->stats->packet_ignored++; + return 0; } pkt_del(pkt); return 0; @@ -315,7 +555,7 @@ void reception_loop(struct pollfd * pfd, receiver_state_t * state) { int ready = poll(pfd, 1, -1); - if (ready == -1 || pfd->revents & POLLERR) /* In case of error on socket */ + if (ready == -1 || pfd->revents & POLLERR) /* In case of error on socket */ { DEBUG("Error on socket"); return; @@ -325,7 +565,7 @@ void reception_loop(struct pollfd * pfd, receiver_state_t * state) { if (handle_incoming(pfd, state) != 0) return; - if (state->last_data_packet < TWO_EXP_EIGHT) /* This means we received last data packet transfer */ + if (state->last_data_packet < TWO_EXP_EIGHT) /* This means we received last data packet transfer */ gettimeofday(&last_packet_received, NULL); } else if (pfd->revents & POLLOUT) @@ -334,7 +574,7 @@ void reception_loop(struct pollfd * pfd, receiver_state_t * state) return; } - if (state->last_data_packet < TWO_EXP_EIGHT) + if (state->last_data_packet < TWO_EXP_EIGHT) /* This is a signal we use between us in order to mark the last datapacket containing data */ { gettimeofday(¤t_time, NULL); if (current_time.tv_sec - last_packet_received.tv_sec > RECEIVER_INACTIVE_TIMEOUT) @@ -353,45 +593,51 @@ void reception_loop(struct pollfd * pfd, receiver_state_t * state) */ receiver_state_t * state_new() { - receiver_state_t * toReturn = (receiver_state_t *) calloc(1, sizeof(receiver_state_t)); - if (toReturn == NULL) return NULL; + receiver_state_t * to_return = (receiver_state_t *) calloc(1, sizeof(receiver_state_t)); + if (to_return == NULL) return NULL; + + to_return->stats = calloc(sizeof(transfer_stats_t), 1); + if (to_return->stats == NULL) + { + free(to_return); + return NULL; + } for (size_t i = 0; i < TWO_EXP_EIGHT; i++) - toReturn->recvd_buf[i] = NULL; + to_return->recvd_data_buf[i] = NULL; for (size_t i = 0; i < RECV_MAX_SLCTV_RPT_WDW; i++) - toReturn->nack_to_send[i] = NULL; + to_return->nack_to_send[i] = NULL; - toReturn->curr_recv_window = RECV_MAX_SLCTV_RPT_WDW; - toReturn->recv_window_start = 0; - toReturn->last_received_in_order = -1; // Sign indicating that it hasn't received something - toReturn->ack_to_send = NULL; // Sign that there's no ack to send - toReturn->transfer_done = 0; - toReturn->last_packet = 256; // No validseqnum will reach this value, max value 255 - toReturn->last_data_packet = 256; - return toReturn; + to_return->curr_recv_window = RECV_MAX_SLCTV_RPT_WDW; + to_return->recv_window_start = 0; + to_return->last_received_in_order = -1; // Sign indicating that it hasn't received something + to_return->ack_to_send = NULL; // Sign that there's no ack to send + to_return->transfer_done = 0; + to_return->next_to_consume = 0; + to_return->last_packet = 256; // No valid seqnum will reach this value, max value 255 + to_return->last_data_packet = 256; + return to_return; } /** * @brief This function frees the memory allocated by the state_new() function. - * @state: The structure to free. + * @param state: The structure to free. + * */ void state_del(receiver_state_t * state) { ASSERT(state != NULL); for (int i = 0; i < 31; i++) - pkt_del(state->recvd_buf[i]); + pkt_del(state->recvd_data_buf[i]); free(state->ack_to_send); + free(state->stats); free(state); + } -/** - * @brief This main loop for the receiver. - * @param sfd: A valid socket. - * @returns As soon as an error occurs or, a the total transfer came through. - */ -void receiver_read_write_loop(int sfd) +void receiver_read_write_loop(int sfd, const char * pathname) { receiver_state_t * state = state_new(); struct pollfd * pfd = (struct pollfd *) calloc(1, sizeof(struct pollfd)); @@ -405,7 +651,9 @@ void receiver_read_write_loop(int sfd) pfd->events = POLLIN | POLLOUT; if (wait_for_client(sfd) == 0) reception_loop(pfd, state); - DEBUG("Done with done status equal to %d", state->transfer_done); + DEBUG("Done the transfer with done status being %s", (state->transfer_done) ? "true" : "false"); + + write_stats_to_file(pathname, state->stats, RECEIVER); state_del(state); free(pfd); } \ No newline at end of file diff --git a/src/receiver_utils.h b/src/receiver_utils.h index ba9152f0607f94594082a58005d923c597d8ae73..1f77b62006483afcfa2f59ea326724f3ae2a1a43 100644 --- a/src/receiver_utils.h +++ b/src/receiver_utils.h @@ -21,7 +21,7 @@ #include <unistd.h> #include "log.h" -#include "our_utils.h" +#include "utils.h" #include "packet_interface.h" @@ -31,6 +31,7 @@ #define TIMER_MULTIPLIER_BY_1 1 #define TIMER_MULTIPLIER_BY_2 2 #define RECEIVER_INACTIVE_TIMEOUT 20 +#define FEC_CALCULATED_ON 4 /* Represent the receiver's connection state */ @@ -40,21 +41,25 @@ typedef struct __attribute__((__packed__)) uint8_t recv_window_start; uint8_t last_received_in_order; uint32_t latest_timestamp; - pkt_t * recvd_buf[TWO_EXP_EIGHT]; + pkt_t * recvd_data_buf[TWO_EXP_EIGHT]; pkt_t * ack_to_send; pkt_t * nack_to_send[RECV_MAX_SLCTV_RPT_WDW]; uint8_t transfer_done; uint16_t last_packet; uint16_t last_data_packet; + uint16_t next_to_consume; + transfer_stats_t * stats; } receiver_state_t; /** - * Loop reading on socket and printing to the stdout - * @sfd : The socket file descriptor. It is both bound and connected. - * @return: as soon as the whole transfer is done. + * @brief Loop reading on socket and printing to the stdout + * @param sfd : The socket file descriptor. It is both bound and connected. + * @param pathname: Pathname to where write the stats. + * + * @returns As soon as an error occurs or, the total transfer came through. */ -void receiver_read_write_loop(int sfd); +void receiver_read_write_loop(int sfd, const char * pathname); /** * Creates and initialize a receiver_state_t structure @@ -66,7 +71,7 @@ receiver_state_t * state_new(); /** * Deletes the receiver_state_t structure pointed by the * state argument. - * @state: the state to delete. + * @param state: the state to delete. * @return: / */ void state_del(receiver_state_t * state); diff --git a/src/sender.c b/src/sender.c index 9fe8f3692788c8612d4c4080ad87053d185b2c4e..4a809651f2bb5476072fccc9bb1ac4f35899f1b9 100644 --- a/src/sender.c +++ b/src/sender.c @@ -46,17 +46,10 @@ int main(int argc, char **argv) { return print_usage(argv[0]); } - // DEBUG_DUMP("Some bytes", 11); // You can use it with any pointer type - ASSERT(fec_enabled == false); - ASSERT(stats_filename == NULL); DEBUG("Sender has following arguments: \n\t\tfilename is %s,\n\t\tstats_filename is %s,\n\t\tfec_enabled is %d,\n\t\treceiver_ip is %s,\n\t\treceiver_port is %u", filename, stats_filename, fec_enabled, receiver_ip, receiver_port); - // Now let's code! - // Alright :-) - - // *** Step 1: Create a socket AND connect *** struct sockaddr_in6 receiver_addr; if (real_address(receiver_ip, &receiver_addr) != NULL) @@ -65,7 +58,7 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - // create_socket also create a connection using connection() !! + // create_socket() also creates a connection using connect() int socket_fd = create_socket(NULL, 0, &receiver_addr, receiver_port); if (socket_fd == -1) { @@ -106,7 +99,7 @@ int main(int argc, char **argv) { pfd[0].fd = socket_fd; pfd[0].events = POLLIN | POLLOUT; - sender_state_t *state = state_new(); + sender_state_t *state = state_new(fec_enabled); if (state == NULL) { free(pfd); @@ -118,7 +111,7 @@ int main(int argc, char **argv) { struct timeval closing_pkt_sent_time; struct timeval curr_time; - while (state->last_pkt_sent != CLOSING_PKT || state->s_window_size != MAX_WINDOW_SIZE) + while ((state->last_pkt_sent != CLOSING_PKT) || (state->s_window_size != MAX_WINDOW_SIZE)) { // Blocking system call int rvalue = poll(pfd, 1, -1); // -1 means that there are no setted time out @@ -134,15 +127,15 @@ int main(int argc, char **argv) { // Setting a timer only when waiting for the very last ACK gettimeofday(&curr_time, NULL); - if (state->last_pkt_sent == CLOSING_PKT && ((curr_time.tv_sec - closing_pkt_sent_time.tv_sec) > SENDER_INACTIVE_TIMEOUT)) + if (state->last_pkt_sent == CLOSING_PKT && ((time_milliseconds(&curr_time) - time_milliseconds(&closing_pkt_sent_time)) > SENDER_INACTIVE_TIMEOUT)) { - DEBUG("The sender hasn't received any news from the receiver for too long so it TIMEOUT"); + DEBUG("The sender hasn't received any news from the receiver for too long so it TIMEOUT."); break; } - if (pfd->revents & POLLIN) + if ((pfd->revents & POLLIN) && (pfd->revents & POLLOUT)) { - DEBUG("The sender is reading from the socket"); + DEBUG("The sender is reading from the socket."); rvalue = handle_returning_ack_nack(state, socket_fd); if (rvalue == -1) { @@ -156,22 +149,31 @@ int main(int argc, char **argv) { } else if (can_send(state) && (pfd->revents & POLLOUT)) { - DEBUG("The sender will send a pkt on the socket, the current sender window size is: %d", state->s_window_size); + DEBUG("The sender will send a pkt on the socket, the current sender window size is: %d | receiver window size: %d", + state->s_window_size, state->r_window_size); rvalue = read_and_send(state, sending_fd, socket_fd); if (rvalue == -1) { - free(pfd); - state_del(state); - close(socket_fd); - close(sending_fd); - ERROR("read_and_send function failed"); - return EXIT_FAILURE; + if (state->fec_enabled && state->last_pkt_sent == CLOSING_PKT) + { + DEBUG("The very last PTYPE_FEC could not be send because the receiver probably disconnected which is not a problem !"); + break; + } + else + { + free(pfd); + state_del(state); + close(socket_fd); + close(sending_fd); + ERROR("read_and_send function failed"); + return EXIT_FAILURE; + } } - // Let's start the timer for the last pkt sent + // Let's start the timer after the closing pkt (PTYPE_DATA with length = 0) if (state->last_pkt_sent == CLOSING_PKT) { gettimeofday(&closing_pkt_sent_time, NULL); - DEBUG("A timer of -> %ds <- has started after sending the closing pkt for the first time !", SENDER_INACTIVE_TIMEOUT); + DEBUG("A timer of -> %dms <- has started after sending the last PTYPE_DATA pkt !", SENDER_INACTIVE_TIMEOUT); } } else if (pfd->revents & POLLOUT) @@ -179,11 +181,11 @@ int main(int argc, char **argv) { rvalue = checking_timer(state, socket_fd); if (rvalue == -1) { - // If an error occured when trying to send back the CLOSING_PKT, + // If an error occured when trying to send back the CLOSING_PKT (so when the last FEC has been sent), // we guess that the receiver has simply disconnected and the ACK of the CLOSING_PKT was lost. if (state->last_pkt_sent == CLOSING_PKT) { - DEBUG("The sender can't send anything to the receiver anymore (which has probably disconnected) so it'll disconnect !"); + DEBUG("The sender can't send anything to the receiver anymore (which has probably disconnected) so the sender will also disconnect !"); break; } else @@ -198,7 +200,13 @@ int main(int argc, char **argv) { } } } - DEBUG("Sender disconnected"); + DEBUG("Sender disconnected"); + + if (stats_filename != NULL) + { + write_stats_to_file(stats_filename, state->stats, SENDER); + } + free(pfd); state_del(state); close(sending_fd); diff --git a/src/sender_utils.c b/src/sender_utils.c index b70b96b5c9c20d74874c69f39e99978bc04338e0..18acddf0131e7c1b910a14fc15eec27d8ebdb249 100644 --- a/src/sender_utils.c +++ b/src/sender_utils.c @@ -1,7 +1,7 @@ #include "sender_utils.h" -sender_state_t *state_new() +sender_state_t *state_new(bool fec_enabled) { sender_state_t *state = (sender_state_t *) calloc(1, sizeof(sender_state_t)); if (state == NULL) @@ -21,13 +21,20 @@ sender_state_t *state_new() { state->map_seqnum_to_buffer_place[i] = OUT_OFF_WINDOW; } - state->last_pkt_sent = RANDOM_DATA_PKT; // default + state->last_pkt_sent = RANDOM_PKT; // default + state->fec_enabled = fec_enabled; + if (fec_enabled) + { + state->FEC = pkt_new(); + state->FEC_nbr = 0; + } + state->stats = calloc(sizeof(transfer_stats_t), 1); return state; } void state_del(sender_state_t *state) { - // To be clean, we free the pkt that might not have been freed (in case of an error or timeout) + // To be sure, we free the pkt that might not have been freed (in case of an error or timeout) for (uint8_t i = 0; i < WINDOW_SIZE; i++) { if (state->buffer[i] != NULL) @@ -35,12 +42,17 @@ void state_del(sender_state_t *state) pkt_del(state->buffer[i]); } } + if (state->fec_enabled) + { + pkt_del(state->FEC); + } + free(state->stats); free(state); } bool can_send(sender_state_t *state) { - if (state->last_pkt_sent == RANDOM_DATA_PKT) + if (state->last_pkt_sent == RANDOM_PKT) { return (state->r_window_size > 0) && (state->s_window_size > 0); } @@ -50,6 +62,12 @@ bool can_send(sender_state_t *state) // it was the end of the file (so that I can set a timer for timeout) return state->s_window_size == MAX_WINDOW_SIZE; } + // Case: we're in FEC mode, the closing pkt has been sent but we will still send a last FEC if possible + else if ((state->fec_enabled) && (state->last_pkt_sent == CLOSING_PKT) && (state->FEC_nbr > 0)) + { + return true; + } + // Case last FEC has been sended else { return false; @@ -80,7 +98,8 @@ int send_pkt(sender_state_t *state, pkt_t *pkt, uint8_t position, int socket_fd) state->buffer[position] = pkt; struct timeval time; gettimeofday(&time, NULL); - state->timers[position] = time.tv_sec; + state->timers[position] = time; + state->stats->data_sent++; return 0; } @@ -94,6 +113,7 @@ int handle_returning_ack_nack(sender_state_t *state, int socket_fd) pkt_status_code pkt_status = pkt_decode(buffer, read_bytes, pkt); if (pkt_status != PKT_OK) { + state->stats->packet_ignored++; pkt_del(pkt); DEBUG("Decode function on a received pkt failed with status: %d so we discard the pkt", pkt_status); return 0; @@ -103,23 +123,35 @@ int handle_returning_ack_nack(sender_state_t *state, int socket_fd) uint8_t r_window = pkt_get_window(pkt); pkt_del(pkt); + if (pkt_type != PTYPE_ACK && pkt_type != PTYPE_NACK) + { + state->stats->packet_ignored++; + DEBUG("The sender has received a pkt with the type PTYPE_ACK or PTYPE_NACK so it discard it !"); + return 0; + } + // Handling NACK: if (pkt_type == PTYPE_NACK) { - if (state->map_seqnum_to_buffer_place[seqnum] == OUT_OFF_WINDOW) + state->stats->nack_received++; + uint8_t place = state->map_seqnum_to_buffer_place[seqnum]; + if (place == OUT_OFF_WINDOW) { DEBUG("The NACK with the seqnum: %d is out of the sended window so it has been discarded", seqnum); } else { - DEBUG("The NACK with the seqnum: %d has been received", seqnum); - state->r_window_size = r_window; - if (send_pkt(state, pkt, seqnum, socket_fd) == -1) return -1; + state->stats->packet_retransmitted++; + DEBUG("The NACK with the seqnum: %d has been received, so the pkt will be sent back", seqnum); + state->r_window_size = r_window - 1; // -1 Because the receiver doesn't count yet the sended pkt + pkt_t *n_pkt = state->buffer[place]; + if (send_pkt(state, n_pkt, place, socket_fd) == -1) return -1; } } // Handling ACK: else { + state->stats->ack_received++; DEBUG("The ACK with the seqnum: %d has been received", seqnum); uint8_t seqnum_nack = seqnum; uint8_t seqnum_ack = seqnum == 0 ? MAX_SEQNUM_SIZE : seqnum - 1; @@ -127,7 +159,7 @@ int handle_returning_ack_nack(sender_state_t *state, int socket_fd) uint8_t place_last_ack = state->map_seqnum_to_buffer_place[seqnum_ack]; uint8_t place_last_nack = state->map_seqnum_to_buffer_place[seqnum_nack]; - // Nothing need to be acked + // Nothing need to be acknowledged if (place_last_ack == OUT_OFF_WINDOW) { // Checking if it's in my window: @@ -138,23 +170,37 @@ int handle_returning_ack_nack(sender_state_t *state, int socket_fd) } else { - DEBUG("The receiver is asking AGAIN, the seqnum: %d", seqnum_nack); - state->r_window_size = r_window; + state->stats->packet_retransmitted++; + DEBUG("The receiver is asking AGAIN the seqnum: %d so the sender sends it back", seqnum_nack); + state->r_window_size = r_window - 1; // -1 Because the receiver doesn't count yet the sended pkt pkt_t *n_pkt = state->buffer[place_last_nack]; if (send_pkt(state, n_pkt, place_last_nack, socket_fd) == -1) return -1; return 0; } } - // Some pkt need to be acked + // Some pkt need to be acknowledged else { state->r_window_size = r_window; uint8_t upper_bound = (place_last_ack + 1) % WINDOW_SIZE; // The oldest seqnum sended - DEBUG("The sender is cumulatively acknowledging [%d : %d[ (place in the buffer)", state->tail, upper_bound); + DEBUG("The sender is cumulatively acknowledging [%d : %d[ (place in the buffer) | [%d, %d[ (seqnum)", + state->tail, upper_bound, pkt_get_seqnum(state->buffer[state->tail]), seqnum_nack); // A do while is necessary here in case we want to make a full revolution (ack from 1 to 1 not included for example) do { + struct timeval time; + gettimeofday(&time, NULL); + unsigned long long int delta_time = time_milliseconds(&time) - time_milliseconds(&state->timers_first_send[state->tail]); + if (state->stats->min_rtt == 0 || state->stats->min_rtt > delta_time) + { + state->stats->min_rtt = delta_time; + } + if (state->stats->max_rtt < delta_time) + { + state->stats->max_rtt = delta_time; + } + pkt_t *n_pkt = state->buffer[state->tail]; seqnum = pkt_get_seqnum(n_pkt); state->map_seqnum_to_buffer_place[seqnum] = OUT_OFF_WINDOW; @@ -169,6 +215,7 @@ int handle_returning_ack_nack(sender_state_t *state, int socket_fd) // Send back the asked ACK if there is one to send back if (place_last_nack != OUT_OFF_WINDOW) { + state->stats->packet_retransmitted++; pkt_t *n_pkt = state->buffer[place_last_nack]; if (send_pkt(state, n_pkt, place_last_nack, socket_fd) == -1) return -1; } @@ -184,10 +231,10 @@ int checking_timer(sender_state_t *state, int socket_fd) if (state->buffer[state->tail] != NULL) { gettimeofday(&time, NULL); - time_t curr_time = time.tv_sec; // When the timer is over, we send the packet back - if ((curr_time - state->timers[state->tail]) >= TIMER_LIMIT) + if ((time_milliseconds(&time) - time_milliseconds(&state->timers[state->tail])) >= TIMER_LIMIT) { + state->stats->packet_retransmitted++; pkt_t *pkt = state->buffer[state->tail]; DEBUG("The pkt with seqnum: %d has timeout", pkt_get_seqnum(pkt)); if (send_pkt(state, pkt, state->tail, socket_fd) == -1) return -1; @@ -196,69 +243,140 @@ int checking_timer(sender_state_t *state, int socket_fd) return 0; } -int read_and_send(sender_state_t *state, int sending_fd, int socket_fd) +bool is_it_EOF(int sending_fd) { - state->s_window_size--; - state->r_window_size--; - // Checking if we're at the end of the file off_t curr_position = lseek(sending_fd, 0, SEEK_CUR); off_t end_position = lseek(sending_fd, 0, SEEK_END); // put the reader cursor back lseek(sending_fd, curr_position, SEEK_SET); + return (bool) (curr_position == end_position); +} - pkt_t *pkt = pkt_new(); - ssize_t nbr_byte_read; +void construct_FEC(sender_state_t *state, pkt_t *pkt) +{ + if (state->FEC_nbr == 0) + { + pkt_set_seqnum(state->FEC, pkt_get_seqnum(pkt)); + } + + uint16_t length = pkt_get_length(state->FEC) ^ pkt_get_length(pkt); + pkt_set_length(state->FEC, length); + + uint8_t payload[MAX_PAYLOAD_SIZE]; + uint8_t *p1 = (uint8_t *) pkt_get_payload(state->FEC); + uint8_t *p2 = (uint8_t *) pkt_get_payload(pkt); + for (int i = 0; i < MAX_PAYLOAD_SIZE; i++) + { + payload[i] = p1[i] ^ p2[i]; + } + pkt_set_payload(state->FEC, (const char *) payload, MAX_PAYLOAD_SIZE); + + state->FEC_nbr++; +} - // The file has already been read but we need to send an empty DATA - if (curr_position == end_position) +int send_FEC(sender_state_t *state, int socket_fd) +{ + if (state->last_pkt_sent == CLOSING_PKT) { - nbr_byte_read = 0; - state->last_pkt_sent = CLOSING_PKT; - DEBUG("The CLOSING pkt has been sent !"); + DEBUG("Sending LAST FEC pkt with seqnum: %d", pkt_get_seqnum(state->FEC)); } else { - nbr_byte_read = read(sending_fd, pkt->payload, MAX_PAYLOAD_SIZE); + DEBUG("Sending FEC pkt with seqnum: %d", pkt_get_seqnum(state->FEC)); + } + + char packet_to_be_sent[PKT_MAX_LEN]; + size_t len = PKT_MAX_LEN; + pkt_status_code pkt_status = pkt_encode(state->FEC, packet_to_be_sent, &len); + if (pkt_status != PKT_OK) + { + ERROR("pkt_encode failed with status: %d", pkt_status); + return -1; } + + ssize_t sent = send(socket_fd, packet_to_be_sent, len, 0); + if (sent == -1) + { + ERROR("The sending (using the function send from <sys/socket.h>) of the pkt failed"); + return -1; + } + memset(state->FEC, 0, sizeof(pkt_t)); + state->FEC_nbr = 0; + state->stats->fec_sent++; + return 0; +} - pkt_set_type(pkt, PTYPE_DATA); - pkt_set_tr(pkt, 0); - pkt_set_window(pkt, state->s_window_size); - pkt_set_length(pkt, nbr_byte_read); - pkt_set_seqnum(pkt, state->next_seqnum); - // Sending a specific timestamp to let the receiver knows, it is the second to last pkt - curr_position = lseek(sending_fd, 0, SEEK_CUR); - if (state->last_pkt_sent == RANDOM_DATA_PKT && curr_position == end_position) +int read_and_send(sender_state_t *state, int sending_fd, int socket_fd) +{ + // Checking whether I need to send a PTYPE_FEC or PTYPE_DATA + if ((state->fec_enabled) && ((state->FEC_nbr == 4) || ((state->last_pkt_sent == CLOSING_PKT) && (state->FEC_nbr > 0)))) { - pkt_set_timestamp(pkt, SECOND_TO_LAST_PKT); - state->last_pkt_sent = LAST_DATA_PKT; - DEBUG("The LAST DATATYPE is being sent !"); + return send_FEC(state, socket_fd); } else { - pkt_set_timestamp(pkt, 0); - } - // put the reader cursor back - lseek(sending_fd, curr_position, SEEK_SET); - - // We set the TR to 0 in order to calculcate the CRC on the header - char modified_header[8]; - memcpy((void *) &modified_header, (void *) &pkt->header, 8); - modified_header[0] = modified_header[0] & TR_SETTER_TO_ZERO; - uint32_t crc1 = htonl(calculate_crc(modified_header, 8)); - pkt_set_crc1(pkt, crc1); + state->s_window_size--; + state->r_window_size--; + + pkt_t *pkt = pkt_new(); + ssize_t nbr_byte_read; + bool is_EOF = is_it_EOF(sending_fd); - uint32_t crc2 = htonl(calculate_crc((char *) pkt->payload, (uint32_t) pkt_get_length(pkt))); - pkt_set_crc2(pkt, crc2); + // The file has already been read but we need to send an empty DATA pkt with length = 0 + if (is_EOF) + { + nbr_byte_read = 0; + state->last_pkt_sent = CLOSING_PKT; + DEBUG("The CLOSING pkt is being sent !"); + } + else + { + nbr_byte_read = read(sending_fd, pkt->payload, MAX_PAYLOAD_SIZE); + } + + pkt_set_type(pkt, PTYPE_DATA); + pkt_set_tr(pkt, 0); + pkt_set_window(pkt, state->s_window_size); + pkt_set_length(pkt, nbr_byte_read); + pkt_set_seqnum(pkt, state->next_seqnum); + // Sending a specific timestamp to let the receiver know, it is the second to last pkt + is_EOF = is_it_EOF(sending_fd); + if (state->last_pkt_sent == RANDOM_PKT && is_EOF) + { + pkt_set_timestamp(pkt, SECOND_TO_LAST_PKT); + state->last_pkt_sent = LAST_DATA_PKT; + DEBUG("The LAST PTYPE_DATA is being sent !"); + } + else + { + pkt_set_timestamp(pkt, 0); + } + + // We set the TR to 0 in order to calculcate the CRC on the header + char modified_header[8]; + memcpy((void *) &modified_header, (void *) &pkt->header, 8); + modified_header[0] = modified_header[0] & TR_SETTER_TO_ZERO; + uint32_t crc1 = htonl(calculate_crc(modified_header, 8)); + pkt_set_crc1(pkt, crc1); - if (send_pkt(state, pkt, state->head, socket_fd) == -1) return -1; + uint32_t crc2 = htonl(calculate_crc((char *) pkt->payload, (uint32_t) pkt_get_length(pkt))); + pkt_set_crc2(pkt, crc2); - state->map_seqnum_to_buffer_place[pkt_get_seqnum(pkt)] = state->head; - state->head = (state->head + 1) % WINDOW_SIZE; + if (send_pkt(state, pkt, state->head, socket_fd) == -1) return -1; + if (state->fec_enabled) + { + construct_FEC(state, pkt); + } + struct timeval timer; + gettimeofday(&timer, NULL); + state->timers_first_send[state->head] = timer; - // Careful we need to convert to uint16_t to avoid overflow - state->next_seqnum = (uint8_t) (((uint16_t) state->next_seqnum) + 1) % SEQNUM_RANGE; + state->map_seqnum_to_buffer_place[pkt_get_seqnum(pkt)] = state->head; + state->head = (state->head + 1) % WINDOW_SIZE; - return 0; + // Careful we need to convert to uint16_t to avoid overflow + state->next_seqnum = (uint8_t) (((uint16_t) state->next_seqnum) + 1) % SEQNUM_RANGE; + return 0; + } } \ No newline at end of file diff --git a/src/sender_utils.h b/src/sender_utils.h index c6eb106e9197e1f4d5ed78279f9436a67fce29da..4013d8b54294ce0d4898ee2eb5763cec5445c765 100644 --- a/src/sender_utils.h +++ b/src/sender_utils.h @@ -13,20 +13,23 @@ #include <unistd.h> #include "log.h" -#include "our_utils.h" +#include "utils.h" #include "packet_interface.h" #define SEQNUM_RANGE 256 -#define TIMER_LIMIT 2 // It's in seconds, in this network, the latency to send is = [0, 2s] -#define SENDER_INACTIVE_TIMEOUT 30 // Only waits 30sec for the ACK of the CLOSING_PKT (the very last sended pkt) +#define TIMER_LIMIT 2000 // It's in milli seconds, in this network, the latency to send is = [0, 2000ms] +#define SENDER_INACTIVE_TIMEOUT 30000 // Only waits 30sec (it's in milliseconds) for the ACK of the CLOSING_PKT (the very last sended pkt) #define WINDOW_SIZE 31 #define OUT_OFF_WINDOW 255 -// It is used to identify what kind of pkt was sent lately -#define RANDOM_DATA_PKT 0 -#define LAST_DATA_PKT 1 -#define CLOSING_PKT 2 +// It is used to identify what kind of PTYPE_DATA pkt was sent lately +// /!\ It doesn't trace the sended FEC ! +typedef enum { + RANDOM_PKT = 0, + LAST_DATA_PKT = 1, + CLOSING_PKT = 2 +} last_sended_pkt_t; /** @@ -35,22 +38,28 @@ typedef struct state { uint8_t r_window_size; // receiver buffer space uint8_t s_window_size; // sender (our) buffer space - time_t timers[WINDOW_SIZE]; // Time in seconds (corresponds to the timers of the sended pkt) - pkt_t *buffer[WINDOW_SIZE]; // When the buffer fields are not used, they MUST be se to NULL + struct timeval timers[WINDOW_SIZE]; // timeval struct corresponding to the last time the pkt was sended + struct timeval timers_first_send[WINDOW_SIZE]; // timeval struct corresponding to the first time the pkt was sended (only used for the stats) + pkt_t *buffer[WINDOW_SIZE]; // When the buffer fields are not used, they MUST be se to NULL uint8_t head; // place last element insert +1 in the buffer (free place) | Head and tail are used to know uint8_t tail; // place oldest element insert in the buffer | the start and end of the sender window (of the sended pkt) uint8_t next_seqnum; uint8_t map_seqnum_to_buffer_place[SEQNUM_RANGE]; // Default value is: OUT_OFF_WINDOW - uint8_t last_pkt_sent; // Can either be: RANDOM_DATA_PKT, LAST_DATA_PKT or CLOSING_PKT + last_sended_pkt_t last_pkt_sent; // Can either be: RANDOM_PKT, LAST_DATA_PKT or CLOSING_PKT + bool fec_enabled; + pkt_t *FEC; // The pkt FEC in construction + uint8_t FEC_nbr; // The number of PTYPE_DATA stacked on FEC + transfer_stats_t *stats; } sender_state_t; /** - * @brief Creation of the UNIQUE structure representing the sender state. + * @brief Creation of the UNIQUE structure representing the sender state. * - * @return sender_state_t* : The structure reprensting the state of the sender. + * @param fec_enabled : bool: variable telling whether the FEC are enabled or not + * @return sender_state_t* : The structure representing the state of the sender. */ -sender_state_t *state_new(); +sender_state_t *state_new(bool fec_enabled); /** * @brief Deletion if the structure representing the sender state. @@ -72,7 +81,7 @@ void state_del(sender_state_t *state); bool can_send(sender_state_t *state); /** - * @brief Cautious this function is used for sending and sending back pkt ! + * @brief Cautious this function is used for sending and sending back PTYPE_DATA pkt ! * * @param state : The variable representing the sender (state). * @param pkt : The pkt to be sent. @@ -102,8 +111,34 @@ int handle_returning_ack_nack(sender_state_t *state, int socket_fd); int checking_timer(sender_state_t *state, int socket_fd); /** - * @brief When this function is called, the sender MUST be allowed to send a data pkt. - * It sends the next pkt and update the variable 'last_pkt_sent' of state. + * @brief Determines wheter the read file offset is at the end of the file + * + * @param sending_fd : The file descriptor of the file you want to know if you're at the end. + * @return true : The offset of sending_fd is at the end of the file + * @return false : The offset of sending_file is not at the end of the file + */ +bool is_it_EOF(int sending_fd); + +/** + * @brief It performs the XOR operation between state->FEC and pkt and stores the result in state->FEC + * + * @param state : The variable representing the sender (state). + * @param pkt + */ +void construct_FEC(sender_state_t *state, pkt_t *pkt); + +/** + * @brief It sends the FEC pkt contains in state->FEC. + * + * @param state : The variable representing the sender (state). + * @param socket_fd : The socket on which pkt are sent. + * @return int : 0 if no error, -1 otherwise + */ +int send_FEC(sender_state_t *state, int socket_fd); + +/** + * @brief When this function is called, the sender MUST be allowed to send a pkt. + * It sends the next pkt and update (if necessary) the variable 'last_pkt_sent' of state. * * @param state : The variable representing the sender (state). * @param sending_fd : The file descriptor of the file to be send diff --git a/src/test.c b/src/test.c deleted file mode 100644 index 7ba56388dc88091c8738bc7fd085b6de874f57fc..0000000000000000000000000000000000000000 --- a/src/test.c +++ /dev/null @@ -1,19 +0,0 @@ -#include <arpa/inet.h> -#include <zlib.h> -#include <stdio.h> -#include <unistd.h> -#include <string.h> -#include <stdlib.h> - -#include "packet_interface.h" - - -int main(int argc, char const *argv[]) -{ - if (argc < 2) - return 1; - pkt_t * pkt = pkt_new(); - int err = pkt_decode(argv[1], 10, pkt); - printf("Returned %d\n", err); - return 0; -} diff --git a/src/our_utils.c b/src/utils.c similarity index 57% rename from src/our_utils.c rename to src/utils.c index 6793cb48f51901ab1eb31bc9126cb5b3a3ac3356..435fe402c2030ea21878c5dabceba6823af36347 100644 --- a/src/our_utils.c +++ b/src/utils.c @@ -1,19 +1,5 @@ -#include <arpa/inet.h> -#include <errno.h> -#include <fcntl.h> -#include <netdb.h> -#include <netinet/in.h> -#include <poll.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <unistd.h> - -#include "log.h" -#include "our_utils.h" +#include "utils.h" + const char * real_address(const char *address, struct sockaddr_in6 *rval) { @@ -107,4 +93,77 @@ int wait_for_client(int sfd) ipv6_to_str_unexpanded(text, &(cast->sin6_addr)); DEBUG("Successfully connected to IPv6 addresss: %s, port : %d", text, ntohs(cast->sin6_port)); return 0; +} + +unsigned long long int time_milliseconds(struct timeval *time) +{ + return ((unsigned long long int) time->tv_sec * 1000) + ((unsigned long long int) time->tv_usec / 1000); +} + +void write_stats_to_file(const char * pathname, transfer_stats_t * stats_file, agents_t caller) +{ + if (pathname == NULL || stats_file == NULL) + return; + + int fd = open(pathname, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU); + if (fd == -1) + { + // We can use strerror cause here the error is known since we are using standard library open + DEBUG("%s", strerror(errno)); + return; + } + int ret = 0; + char buffer[100]; + ret = sprintf((char *) &buffer, "data_sent:%llu\n", stats_file->data_sent); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "data_received:%llu\n", stats_file->data_received); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "data_truncated_received:%llu\n", stats_file->data_truncated_received); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "fec_sent:%llu\n", stats_file->fec_sent); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "fec_received:%llu\n", stats_file->fec_received); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "ack_sent:%llu\n", stats_file->ack_sent); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "ack_received:%llu\n", stats_file->ack_received); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "nack_received:%llu\n", stats_file->nack_received); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "packet_ignored:%llu\n", stats_file->packet_ignored); + ret = write(fd, buffer, strlen(buffer)); + + if (caller == RECEIVER) + { + ret = sprintf((char *) &buffer, "packet_duplicated:%llu\n", stats_file->packet_duplicated); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "packet_recovered:%llu\n", stats_file->packet_recovered); + ret = write(fd, buffer, strlen(buffer)); + } + + if (caller == SENDER) + { + ret = sprintf((char *) &buffer, "min_rtt:%llu\n", stats_file->min_rtt); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "max_rtt:%llu\n", stats_file->max_rtt); + ret = write(fd, buffer, strlen(buffer)); + + ret = sprintf((char *) &buffer, "packet_retransmitted:%llu\n", stats_file->packet_retransmitted); + ret = write(fd, buffer, strlen(buffer)); + } + + close(fd); + ret--; // dump + DEBUG("Wrote the transfer statistics to %s.", pathname); + return; } \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000000000000000000000000000000000000..4d07cc142cc7f67fb7b42b10931d56dbdcccf150 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,103 @@ +#ifndef __UTILS_H_ +#define __UTILS_H_ + +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <netinet/in.h> +#include <poll.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "log.h" + + +typedef struct __attribute__((__packed__)) +{ + unsigned long long int data_sent; + unsigned long long int data_received; + unsigned long long int data_truncated_received; + unsigned long long int fec_sent; + unsigned long long int fec_received; + unsigned long long int ack_sent; + unsigned long long int ack_received; + unsigned long long int nack_sent; + unsigned long long int nack_received; + unsigned long long int packet_ignored; + unsigned long long int packet_duplicated; + unsigned long long int packet_recovered; + unsigned long long int min_rtt; + unsigned long long int max_rtt; + unsigned long long int packet_retransmitted; +} transfer_stats_t; + +/* The type of agents */ +typedef enum{ + SENDER, + RECEIVER, +} agents_t; + + +/** + * @brief Resolve the resource name to an usable IPv6 address + * @param address: The name to resolve + * @param rval: Where the resulting IPv6 address descriptor should be stored + * @param return: NULL if it succeeded, or a pointer towards + * a string describing the error if any. + * (const char* means the caller cannot modify or free the return value, + * so do not use malloc!) + */ +const char * real_address(const char *address, struct sockaddr_in6 *rval); + + +/** + * @brief: Creates a socket and initialize it + * @param source_addr: if !NULL, the source address that should be bound to this socket. + * @param src_port: if >0, the port on which the socket is listening. + * @param dest_addr: if !NULL, the destination address to which the socket should send data. + * @param dst_port: if >0, the destination port to which the socket should be connected. + * + * @return: a file descriptor number representing the socket + * or -1 in case of error (explanation will be printed on stderr) + */ +int create_socket(struct sockaddr_in6 *source_addr, int src_port, struct sockaddr_in6 *dest_addr, int dst_port); + + +/** + * @brief Block the caller until a message is received on sfd, + * and connect the socket to the source addresse of the received message + * @param sfd: a file descriptor to a bound socket but not yet connected + * @param return: 0 in case of success, -1 otherwise + * + * @warning: This call is idempotent, it does not 'consume' the data of the message, + * and could be repeated several times blocking only at the first call. + */ +int wait_for_client(int sfd); + +/** + * @brief Return the in milliseconds + * + * @param time : Structure representing the in seconds an mico seconds + * @return long long int : return the time in milliseconds + */ +unsigned long long int time_milliseconds(struct timeval *time); + +/** + * @brief Writes the content of the stats_file in the pathname file. + * + * @param pathname: The pathname to the stats file. + * @param stats_file: Pointer to the stats file output. + * @param caller: To know the caller of the function. + * + * + * @returns In case of an error it is printed on the stderr. + */ +void write_stats_to_file(const char * pathname, transfer_stats_t * stats_file, agents_t caller); + +#endif \ No newline at end of file diff --git a/tests/advanced_test.sh b/tests/advanced_test.sh index 297f4975a7d70e7169204bf2d55e9e46bdbfa445..92dfd19a8e06cd45dd4d8ac729a3633747ac09fd 100755 --- a/tests/advanced_test.sh +++ b/tests/advanced_test.sh @@ -1,84 +1,134 @@ #!/bin/bash +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + if [ -z "$1" ]; then echo "Not given the file to send" exit 1 fi +# If the directory does not exist, we create it +if [ ! -d "tests_logs/advanced_tests/" ]; then + mkdir 'tests_logs/advanced_tests/' 2>/dev/null +fi + FILENAME=$1 BASENAME=$(basename $FILENAME) BSN_PRE="${BASENAME%.*}" BSN_EXT="${BASENAME##*.}" -TEST_OUTPUT_FILES="unwanted_logs/advanced_tests/${BSN_PRE}" -GREEN='\033[0;32m' -NC='\033[0m' -# If the directory does not exist, we create it -if [ ! -d "unwanted_logs/advanced_tests/" ]; then - mkdir 'unwanted_logs/advanced_tests/' 2>/dev/null -fi -mkdir "${TEST_OUTPUT_FILES}/" 2>/dev/null +TEST_OUTPUT_FILES="tests_logs/advanced_tests/${BSN_PRE}" +mkdir "${TEST_OUTPUT_FILES}/" 2>/dev/null -touch "${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_received_file.${BSN_EXT}" \ - "${TEST_OUTPUT_FILES}/adv_valgrind_${BSN_PRE}_receiver.log" \ - "${TEST_OUTPUT_FILES}/adv_valgrind_${BSN_PRE}_sender.log" +MODES=( + 'with_FEC' + 'without_FEC' +) +ERROR_RATE=20 +CUT_RATE=30 +DELAY=0 +JITTER=0 +LOSS_RATE=25 -# The next 2 lines come from: https://unix.stackexchange.com/questions/55913/whats-the-easiest-way-to-find-an-unused-local-port -# We use this to be sure we're using unused port -port1=$(comm -23 <(seq 65000 65200 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) -port2=$(comm -23 <(seq 65000 65200 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) +echo -e "The linksimulator paramateres are: + \t\t-ERROR RATE: ${ERROR_RATE}% \t-DELAY: ${DELAY}ms + \t\t-CUT RATE: ${CUT_RATE}% \t-JITTER: ${JITTER}ms + \t\t-LOSS RATE: ${LOSS_RATE}%\n" -# We first launch the link simulator -./linksimulator/link_sim -p $port2 -P $port1 -l 20 -d 300 -e 8 -c 12 -R \ - &>${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_link.log & link_pid=$! +for MODE in "${MODES[@]}"; do + + mkdir "${TEST_OUTPUT_FILES}/${MODE}/" 2>/dev/null + DIR="${TEST_OUTPUT_FILES}/${MODE}" + + touch "${DIR}/adv_${BSN_PRE}_received_file.${BSN_EXT}" \ + "${DIR}/adv_valgrind_${BSN_PRE}_receiver.log" \ + "${DIR}/adv_valgrind_${BSN_PRE}_sender.log" \ + "${DIR}/${BSN_PRE}_receiver_stats.csv" \ + "${DIR}/${BSN_PRE}_sender_stats.csv" -# We launch the receiver and capture its output -valgrind --leak-check=full --log-file=${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_valgrind_receiver.log \ - ./receiver ::1 $port1 1> ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_received_file.${BSN_EXT} \ - 2> ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_receiver.log & receiver_pid=$! -cleanup() -{ - kill -9 $receiver_pid - kill -9 $link_pid - exit 0 -} -trap cleanup SIGINT # Kill the background procces in case of ^-C - -# We start the transfer -if ! valgrind --leak-check=full --log-file=${TEST_OUTPUT_FILES}/adv_valgrind_${BSN_PRE}_receiver.log \ - ./sender -f ${FILENAME} ::1 $port2 2> ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_sender.log ; then - echo "Crash du sender!" - cat ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_sender.log - err=1 # We record the error -fi -sleep 5 # We wait 5s for the receiver to finish up + # The next 2 lines come from: https://unix.stackexchange.com/questions/55913/whats-the-easiest-way-to-find-an-unused-local-port + # We use this to be sure we're using unused port + port1=$(comm -23 <(seq 65000 65200 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) + port2=$(comm -23 <(seq 65000 65200 | sort) <(ss -Htan | awk '{print $4}' | cut -d':' -f2 | sort -u) | shuf | head -n 1) -if kill -0 $receiver_pid &> /dev/null ; then - echo "The receiver didn't stop at the end of the transfer!" - kill -9 $receiver_pid - err=1 -else # We check the return value of the receiver - if ! wait $receiver_pid ; then - echo "Crash of the receiver!" - cat ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_receiver.log + + ##### Launching the link simulator ##### + ./linksimulator/link_sim -p $port2 -P $port1 -l $LOSS_RATE -d $DELAY -e $ERROR_RATE -c $CUT_RATE -j $JITTER \ + &>${DIR}/adv_${BSN_PRE}_link.log & link_pid=$! + + ##### Launching the receiver and capturinig its output ##### + valgrind --leak-check=full --log-file=${DIR}/adv_valgrind_${BSN_PRE}_receiver.log \ + ./receiver ::1 $port1 -s ${DIR}/${BSN_PRE}_receiver_stats.csv 1> ${DIR}/adv_${BSN_PRE}_received_file.${BSN_EXT} \ + 2> ${DIR}/adv_${BSN_PRE}_receiver.log & receiver_pid=$! + + cleanup() + { + kill -9 $receiver_pid + kill -9 $link_pid + exit 0 + } + trap cleanup SIGINT # Kill the background procces in case of ^-C + + # Checking the mode (with out without FEC) + if [ $MODE = "with_FEC" ]; then + # We start the transfer + if ! valgrind --leak-check=full --log-file=${DIR}/adv_valgrind_${BSN_PRE}_sender.log \ + ./sender -f ${FILENAME} ::1 $port2 -c -s ${DIR}/${BSN_PRE}_sender_stats.csv 2> ${DIR}/adv_${BSN_PRE}_sender.log ; then + echo "The sender crashed!" + cat ${DIR}/adv_${BSN_PRE}_sender.log + err=1 # We record the error + fi + else + # We start the transfer + if ! valgrind --leak-check=full --log-file=${DIR}/adv_valgrind_${BSN_PRE}_sender.log \ + ./sender -f ${FILENAME} ::1 $port2 -s ${DIR}/${BSN_PRE}_sender_stats.csv 2> ${DIR}/adv_${BSN_PRE}_sender.log ; then + echo "The sender crashed!" + cat ${DIR}/adv_${BSN_PRE}_sender.log + err=1 # We record the error + fi + fi + + sleep 5 # We wait 5s for the receiver to finish up + + if kill -0 $receiver_pid &> /dev/null ; then + echo "The receiver didn't stop at the end of the transfer!" + kill -9 $receiver_pid err=1 + else # We check the return value of the receiver + if ! wait $receiver_pid ; then + echo "Crash of the receiver!" + cat ${DIR}/adv_${BSN_PRE}_receiver.log + err=1 + fi fi -fi -# Stop the link simulator -kill -9 $link_pid -wait $link_pid 2>/dev/null + # Stop the link simulator + kill -9 $link_pid + wait $link_pid 2>/dev/null -# We verify that the transfer ran through properly -if [[ "$(md5sum ${FILENAME} | awk '{print $1}')" != "$(md5sum ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_received_file.${BSN_EXT} | awk '{print $1}')" ]]; then - echo "The transfer corrupted the file!" - echo "Binary difference between the 2 files: (expected vs actual)" - diff -C 9 <(od -Ax -t x1z ${FILENAME}) <(od -Ax -t x1z ${TEST_OUTPUT_FILES}/adv_${BSN_PRE}_received_file.${BSN_EXT}) - exit 1 -else - echo -e "${GREEN}The transfer has succeeded!${NC}" - exit ${err:-0} # In case of error, we return the error code -fi \ No newline at end of file + # We verify that the transfer ran through properly + if [[ "$(md5sum ${FILENAME} | awk '{print $1}')" != "$(md5sum ${DIR}/adv_${BSN_PRE}_received_file.${BSN_EXT} | awk '{print $1}')" ]]; then + echo "The transfer corrupted the file!" + echo "Binary difference between the 2 files: (expected vs actual)" + diff -C 9 <(od -Ax -t x1z ${FILENAME}) <(od -Ax -t x1z ${DIR}/adv_${BSN_PRE}_received_file.${BSN_EXT}) + if [ $MODE = "with_FEC" ]; then + echo -e "${RED}The transfer (with FEC) has failed!${NC}" + else + echo -e "${RED}The transfer (without FEC) has failed!${NC}" + fi + exit 1 + else + if [ $MODE = "with_FEC" ]; then + echo -e "${GREEN}The transfer (with FEC) has succeeded!${NC}" + else + echo -e "${GREEN}The transfer (without FEC) has succeeded!${NC}" + fi + fi +done + +exit ${err:-0} # In case of error, we return the error code \ No newline at end of file diff --git a/tests/performances_tests.py b/tests/performances_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..92b09e9b858ca3e17992a17a2f7399d105196e57 --- /dev/null +++ b/tests/performances_tests.py @@ -0,0 +1,23 @@ + +""" +Return a dictionary of the different values found in the receiver stats (.csv) file +""" +def read_receiver_stats(filename): + dictionary = {} + with open(filename, 'r') as f: + for line in f.readlines(): + key, value = line.split(":") + dictionary[key] = int(value) + return dictionary + +""" +Return a dictionary of the different values found in the sender stats (.csv) file +""" +def read_sender_stats(filename): + dictionary = {} + with open(filename, 'r') as f: + for line in f.readlines(): + key, value = line.split(":") + dictionary[key] = int(value) + return dictionary + diff --git a/tests/performances_tests.sh b/tests/performances_tests.sh new file mode 100644 index 0000000000000000000000000000000000000000..e13371856e44ad86e2b3e9486af8dadbd01f875d --- /dev/null +++ b/tests/performances_tests.sh @@ -0,0 +1,16 @@ +#!/bin/bash + + +# File use for the test of performance +file_to_transfer='tests_files/smile.png' + +# Chosen parameters for the test of performance +ERROR_RATE=20 +CUT_RATE=30 +DELAY=0 +JITTER=0 +LOSS_RATE=25 + +if [ ! -d "tests_logs/performance_tests/" ]; then + mkdir 'tests_logs/performance_tests/' +fi diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 5cf92ee3fbbaddc9875011efa45898e1a9400160..e3317a9a2a1b760f5bd31b05013337a5b27fa908 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -1,41 +1,65 @@ #!/bin/bash +BROWN='\033[0;33m' +NC='\033[0m' + # Note that this assumes to be called from the Makefile, you may want to adapt it. FILESIZE="" -TEST_FILES_DIR=./test_files/ +TEST_FILES_DIR=./tests_files/ +if [ ! -d ./tests_logs/ ]; then + mkdir ./tests_logs +fi # Remove everything -rm -rf ./unwanted_logs/* +rm -rf ./tests_logs/* + +if [ -d ./unwanted_logs/ ]; then + rm -rdf ./unwanted_logs +fi + +simple_test_files=( + 'tests_files/greeting.txt' + 'tests_files/long_message.txt' + 'tests_files/smile.png' + 'tests_files/thumbs-up-nod.gif' +) + +advanced_test_files=( + 'tests_files/greeting.txt' + 'tests_files/long_message.txt' + 'tests_files/noice.gif' +) -# We want our tests to always use valgrind -#echo -e "\nStarting simple tests ...\n" -#for FILENAME in "$TEST_FILES_DIR"/* -#do -# FILESIZE=$(stat -c%s "$FILENAME") -# echo -e "Sending $FILENAME \t\t [$FILESIZE bytes], \t\t with Valgrind" -# ./tests/simple_test.sh $FILENAME +# echo -e "\nStarting simple tests ...\n" +# i=1 +# for FILENAME in "${simple_test_files[@]}"; do +# FILESIZE=$(stat -c%s "$FILENAME") +# echo -e "${BROWN}($i/${#simple_test_files[@]}) Sending \"$FILENAME\" \t[$FILESIZE bytes] with Valgrind${NC}" +# ./tests/simple_test.sh $FILENAME -# if [ $? -ne 0 ]; then -# echo "Tests terminated cause of a failed test" -# exit 0 -# fi +# if [ $? -ne 0 ]; then +# echo "Tests terminated cause of a failed test" +# exit 0 +# fi +# let i++ -#done -#echo -e "Finished Simple tests." +# done +echo -e "Finished Simple tests." if [ -d linksimulator/ ]; then echo -e "\nStarting advanced tests ...\n" # Now we ran the advanced tests - for FILENAME in "$TEST_FILES_DIR"/* - do + i=1 + for FILENAME in "${advanced_test_files[@]}"; do FILESIZE=$(stat -c%s "$FILENAME") - echo -e "Sending $FILENAME \t\t [$FILESIZE bytes], \t\t with linksimulator and Valgrind" + echo -e "\n${BROWN}($i/${#advanced_test_files[@]}) Sending the file \"$FILENAME\" \t[$FILESIZE bytes] with linksimulator and Valgrind${NC}" ./tests/advanced_test.sh $FILENAME if [ $? -ne 0 ]; then echo "Tests terminated cause of a failed test" exit 0 - fi + fi + let i++ done echo "Finished Advanced tests." else diff --git a/tests/simple_test.sh b/tests/simple_test.sh index 46ffb8a4aa660b273aeb20d5cfe0ce5c4dcfbeb3..db3611d979edb6d89785badc2b4439a60c647bc0 100755 --- a/tests/simple_test.sh +++ b/tests/simple_test.sh @@ -9,13 +9,13 @@ FILENAME=$1 BASENAME=$(basename $FILENAME) BSNM_PRE="${BASENAME%.*}" BSNM_EXT="${BASENAME##*.}" -TEST_OUTPUT_FILES="unwanted_logs/simple_tests/${BSNM_PRE}" +TEST_OUTPUT_FILES="tests_logs/simple_tests/${BSNM_PRE}" GREEN='\033[0;32m' NC='\033[0m' # If the directory does not exist, we create it -if [ ! -d "unwanted_logs/simple_tests/" ]; then - mkdir 'unwanted_logs/simple_tests/' 2>/dev/null +if [ ! -d "tests_logs/simple_tests/" ]; then + mkdir 'tests_logs/simple_tests/' 2>/dev/null fi mkdir "${TEST_OUTPUT_FILES}/" 2>/dev/null @@ -32,7 +32,7 @@ port=$(comm -23 <(seq 65000 65200 | sort) <(ss -Htan | awk '{print $4}' | cut -d # We launch the receiver and capture its output valgrind --leak-check=full --log-file=${TEST_OUTPUT_FILES}/valgrind_${BSNM_PRE}_receiver.log \ - ./receiver ::1 $port 1> ${TEST_OUTPUT_FILES}/${BSNM_PRE}_received_file.${BSNM_EXT} \ + ./receiver ::1 $port -s ${TEST_OUTPUT_FILES}/${BSNM_PRE}_receiver_stats.csv 1> ${TEST_OUTPUT_FILES}/${BSNM_PRE}_received_file.${BSNM_EXT} \ 2> ${TEST_OUTPUT_FILES}/${BSNM_PRE}_receiver.log & receiver_pid=$! cleanup() @@ -45,7 +45,7 @@ trap cleanup SIGINT # Kill the background procces in case of ^-C # We start the transfer if ! valgrind --leak-check=full --log-file=${TEST_OUTPUT_FILES}/valgrind_${BSNM_PRE}_sender.log \ - ./sender -f ${FILENAME} ::1 $port 2> ${TEST_OUTPUT_FILES}/${BSNM_PRE}_sender.log ; then + ./sender -f ${FILENAME} ::1 $port -s ${TEST_OUTPUT_FILES}/${BSNM_PRE}_sender_stats.csv 2> ${TEST_OUTPUT_FILES}/${BSNM_PRE}_sender.log ; then echo "The sender crashed!" cat ${TEST_OUTPUT_FILES}/${BSNM_PRE}_sender.log err=1 # We record the error diff --git a/test_files/greeting.txt b/tests_files/greeting.txt similarity index 100% rename from test_files/greeting.txt rename to tests_files/greeting.txt diff --git a/test_files/long_message.txt b/tests_files/long_message.txt similarity index 70% rename from test_files/long_message.txt rename to tests_files/long_message.txt index 9a4178c362cdafd845cf5179691e0f1c6dccca0e..a18b03ec08fdc53c1f0a5e0fc9187b5a0f18568f 100644 --- a/test_files/long_message.txt +++ b/tests_files/long_message.txt @@ -6,14 +6,14 @@ ahead. So good luck have a nice day of testing and remember : Tough Times Never PS: Drink a beer when you pass the test. Share your knowledge to others, computer science isn't a field for selfishness. If you want to switch to Law, Economy, some shit -Hi Vany I love reading from you. As you know I'm working sometime a bit late in the night. -In a few hour you'll be in the famous "Salle Intel" to be really productive as you are. +Hi Vany I'm pleased to read some news from you. As you know I work sometime a bit late in the night. +In a few hour you'll show up in the famous "Salle Intel" to be really productive as you are. Best regards, Samuel -Sorry to be lazy but I'll past down what is above +Sorry to be lazy but I'll past down what is above :') Hello I'm a good friend, I've seen that you are writing a protocol named TRTP. How is it going ? Hard ? Good for you. See life is full of hard things just like your @@ -23,9 +23,7 @@ ahead. So good luck have a nice day of testing and remember : Tough Times Never PS: Drink a beer when you pass the test. Share your knowledge to others, computer science isn't a field for selfishness. If you want to switch to Law, Economy, some shit -Hi Vany I love reading from you. As you know I'm working sometime a bit late in the night. -In a few hour you'll be in the famous "Salle Intel" to be really productive as you are. +Hi Vany I'm pleased to read some news from you. As you know I work sometime a bit late in the night. +In a few hour you'll show up in the famous "Salle Intel" to be really productive as you are. Best regards, - -Samuel diff --git a/test_files/noice.gif b/tests_files/noice.gif similarity index 100% rename from test_files/noice.gif rename to tests_files/noice.gif diff --git a/test_files/smile.png b/tests_files/smile.png similarity index 100% rename from test_files/smile.png rename to tests_files/smile.png diff --git a/test_files/thumbs-up-nod.gif b/tests_files/thumbs-up-nod.gif similarity index 100% rename from test_files/thumbs-up-nod.gif rename to tests_files/thumbs-up-nod.gif