/**
 * @file rudp-light.c
 *
 * @brief Reliable User Datagram Protocol (RUDP) - minimal implemenation.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: rudp-light.c 2685 2008-06-25 16:38:56Z wittenbu $
 */

#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "../types.h"
#include "../vars.h"
#include "../interface.h"
#include "../interface_internal.h"
#include "../util/debug.h"
#include "../util/list.h"
#include "../util/callback.h"
#include "../util/net.h"
#include "rudp-light.h"
#include "rudp-light_types.h"
#include "rudp-light_vars.h"

#define RUDP_PACKET_TYPE_DATA	0x0	///< RUDP DATA packet type.
#define RUDP_PACKET_TYPE_ACK	0x1	///< RUDP ACK packet type.

/**
 * @brief Maximal transmission queue length.
 */
#define RUDP_MAX_QUEUED_PACKETS	128

/**
 * @brief Base of the exponential backoff time function.
 */
#define RUDP_BACKOFF_TIME_BASE	2



/*
 * Interface to main module.
 */

/**
 * @brief Initialize the RUDP module.
 */
void RUDP_init() {
	list_alloc(&C_RUDP_transmission_queue);
	memset(C_RUDP_received_packet_cache, 0, C_RUDP_RECEIVED_PACKET_CACHE_SIZE * sizeof(packet_fingerprint_t));
	C_RUDP_total_transmissions = 0;
	C_RUDP_successful_transmissions = 0;
	C_RUDP_failed_transmissions = 0;
}

/**
 * @brief Start the RUDP module.
 */
void RUDP_start() {
}

/**
 * @brief Stop the RUDP module.
 */
void RUDP_stop() {
}

/**
 * @brief Finalize the RUDP module.
 */
void RUDP_finalize() {
	if(C_check_consistency_on_shutdown) {
		if(!list_empty(C_RUDP_transmission_queue)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "RUDP: There are still %d packet(s) in the transmission queue.\n", list_size(C_RUDP_transmission_queue));
			abort();
		} else if(C_RUDP_total_transmissions != (C_RUDP_successful_transmissions + C_RUDP_failed_transmissions)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "RUDP: Transmit counters don't add up (%d != (%d + %d)).\n", C_RUDP_total_transmissions, C_RUDP_successful_transmissions, C_RUDP_failed_transmissions);
			abort();
		}
	}
	list_free(&C_RUDP_transmission_queue);
}



/*
 * Packet handling functions.
 */

/**
 * @brief Send a RUDP DATA packet.
 *
 * @param orig_header Pointer to header of the packet to be transmitted.
 * @param orig_payload Pointer to payload of the packet to be transmitted.
 */
static void send_RUDP_DATA(const packet_header_t* orig_header, const void* orig_payload) {
	packet_header_t rudp_header;
	RUDP_DATA_packet_t rudp_data;
	void* rudp_payload;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Sending DATA packet to node %s with sequence number %d...\n", addr_to_str(orig_header->to), orig_header->seqno);
	memcpy(&rudp_header, orig_header, sizeof(packet_header_t));
	rudp_header.app = APP_TYPE_RUDP;
	rudp_header.type = RUDP_PACKET_TYPE_DATA;
	rudp_header.payload_length = orig_header->payload_length + sizeof(RUDP_DATA_packet_t);
	rudp_data.orig_app = orig_header->app;
	rudp_data.orig_type = orig_header->type;

	MALLOC_JOIN(rudp_payload, char, &rudp_data, sizeof(RUDP_DATA_packet_t), orig_payload, orig_header->payload_length);
	C_send(&rudp_header, rudp_payload);
	FREE(rudp_payload);
}

/**
 * @brief Send a RUDP ACK packet.
 *
 * @param to Address of the destination node.
 * @param seqno Sequence number of the packet to be acknowledged.
 */
static void send_RUDP_ACK(C_addr_t to, seqno_t seqno) {
	RUDP_ACK_packet_t ack;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Sending ACK for DATA packet with sequence number %d to node %s...\n", seqno, addr_to_str(to));
	ack.seqno = seqno;
	C_sendto(to, APP_TYPE_RUDP, RUDP_PACKET_TYPE_ACK, &ack, sizeof(RUDP_ACK_packet_t));
}

/**
 * @brief Handle incoming RUDP DATA packet.
 *
 * @param rudp_header Pointer to header of incoming RUDP DATA packet.
 * @param rudp_payload Pointer to payload of incoming RUDP DATA packet.
 */
static void handle_RUDP_DATA(const packet_header_t* rudp_header, const void* rudp_payload) {
	packet_header_t orig_header;

	if(rudp_header->payload_length < sizeof(RUDP_DATA_packet_t)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "RUDP: Received DATA packet with wrong payload length (%d != %d).\n", rudp_header->payload_length, sizeof(RUDP_ACK_packet_t));
		abort();
	} else if(check_for_duplicate(C_RUDP_received_packet_cache, C_RUDP_RECEIVED_PACKET_CACHE_SIZE, rudp_header)) {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Dropping duplicate DATA packet from node %s with sequence number %d...\n", addr_to_str(rudp_header->from), rudp_header->seqno);
		send_RUDP_ACK(rudp_header->from, rudp_header->seqno);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Received DATA from node %s with sequence number %d...\n", addr_to_str(rudp_header->from), rudp_header->seqno);
	send_RUDP_ACK(rudp_header->from, rudp_header->seqno);

	memcpy(&orig_header, rudp_header, sizeof(packet_header_t));
	orig_header.app = ((RUDP_DATA_packet_t*) rudp_payload)->orig_app;
	orig_header.type = ((RUDP_DATA_packet_t*) rudp_payload)->orig_type;
	orig_header.payload_length = rudp_header->payload_length - sizeof(RUDP_DATA_packet_t);
	if(orig_header.payload_length == 0)
		C_handle_packet(&orig_header, NULL, true);
	else
		C_handle_packet(&orig_header, ((char*) rudp_payload) + sizeof(RUDP_DATA_packet_t), true);
}

/**
 * @brief Check whether destination address and sequence number of two
 * transmission match.
 *
 * @param item Transmission to check.
 * @param cmp_item Reference transmission.
 * @return <code>true</code> if destination address and sequence number of two
 * transmissions are equal, <code>false</code> otherwise.
 */
static bool matches_transmission_packet_header_to_and_seqno(const void* item, const void* cmp_item) {
	RUDP_transmission_t* transmission = (RUDP_transmission_t*) item;
	RUDP_transmission_t* cmp_transmission = (RUDP_transmission_t*) cmp_item;
	return (
		(transmission->packet.header->to == cmp_transmission->packet.header->to)
		&& (transmission->packet.header->seqno == cmp_transmission->packet.header->seqno)
	);
}

/**
 * @brief Handle incoming RUDP ACK packet.
 *
 * @param header Pointer to header of incoming RUDP ACK packet.
 * @param ack Pointer to ACK information of incoming RUDP ACK packet.
 */
static void handle_RUDP_ACK(const packet_header_t* header, const RUDP_ACK_packet_t* ack) {
	RUDP_transmission_t cmp_transmission;
	packet_header_t cmp_header;
	RUDP_transmission_t* transmission = NULL;
	cmp_transmission.packet.header = &cmp_header;
	cmp_transmission.packet.header->to = header->from;
	cmp_transmission.packet.header->seqno = ack->seqno;
	transmission = (RUDP_transmission_t*) list_find(C_RUDP_transmission_queue, &cmp_transmission, matches_transmission_packet_header_to_and_seqno);
	if(transmission == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Ignoring ACK from node %s for packet with sequence number %d that is not in queue...\n", addr_to_str(header->from), ack->seqno);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Received ACK from node %s for packet with sequence number %d.\n", addr_to_str(header->from), ack->seqno);
	if(transmission->function != NULL) {
		transmission->result->error_code = C_ERROR_OK;
		transmission->function(transmission->result);
	}
	transmission->result = NULL;
	list_remove(C_RUDP_transmission_queue, transmission);
	callback_cancel(transmission->callback_id);
	if(transmission->packet.payload != NULL)
		FREE(transmission->packet.payload);
	FREE(transmission->packet.header);
	FREE(transmission);

	C_RUDP_successful_transmissions++;
}

/**
 * @brief RUDP packet classifier.
 *
 * @param header Pointer to header of an incoming packet.
 * @param payload Pointer to payload of an incoming packet.
 */
void RUDP_recv(const packet_header_t* header, const void* payload) {
	if(header->app != APP_TYPE_RUDP) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "RUDP: Received non-RUDP packet.\n");
		abort();
	}

	switch(header->type) {
	case RUDP_PACKET_TYPE_DATA:
		handle_RUDP_DATA(header, payload);
		break;
	case RUDP_PACKET_TYPE_ACK:
		if(header->payload_length != sizeof(RUDP_ACK_packet_t)) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "RUDP: Received ACK packet with wrong payload length (%d != %d).\n", header->payload_length, sizeof(RUDP_ACK_packet_t));
			abort();
		}
		handle_RUDP_ACK(header, (RUDP_ACK_packet_t*) payload);
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "RUDP: Received packet of unknown type 0x%X from node %s.\n", header->type, addr_to_str(header->from));
		abort();
	}
}



/*
 * Interface provided by this module.
 */

/**
 * @brief Timeout callback for sending a packet via RUDP.
 *
 * @param parameter Transmission for which the timeout ocurred.
 */
static void RUDP_send_timeout(callback_parameter_t parameter) {
	RUDP_transmission_t* transmission = (RUDP_transmission_t*) parameter;

	// Retry if the packet hasn't been removed from the queue.
	if(transmission->retries > 0) {
		int retry = C_RUDP_PACKET_RETRIES - (--transmission->retries);
		C_time_t backoff_time = C_RUDP_MIN_BACKOFF_TIME + C_random(C_RUDP_MIN_BACKOFF_TIME * pow(RUDP_BACKOFF_TIME_BASE, retry));
		if(backoff_time > C_RUDP_MAX_BACKOFF_TIME)
			backoff_time = C_RUDP_MAX_BACKOFF_TIME;
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Timeout for acknowledgement of DATA packet to node %s with sequence number %d has expired (retry #%d). Resending packet and rescheduling by %.2fs...\n", addr_to_str(transmission->packet.header->to), transmission->packet.header->seqno, retry, backoff_time);
		send_RUDP_DATA(transmission->packet.header, transmission->packet.payload);
		transmission->callback_id = callback_add(RUDP_send_timeout, transmission, backoff_time);

	// Remove from queue and drop packet if no retries are left.
	} else {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "RUDP: Timeout for acknowledgement of DATA packet to node %s with sequence number %d has expired. No retries left, dropping packet...\n", addr_to_str(transmission->packet.header->to), transmission->packet.header->seqno);
		if(transmission->function != NULL) {
			transmission->result->error_code = C_ERROR_TIMEOUT;
			transmission->function(transmission->result);
		}
		list_remove(C_RUDP_transmission_queue, transmission);
		FREE(transmission->packet.header);
		if(transmission->packet.payload != NULL)
			FREE(transmission->packet.payload);
		FREE(transmission);

		C_RUDP_failed_transmissions++;
	}
}

/**
 * @brief Send a packet via RUDP.
 *
 * @param header Pointer to the header of the packet to send.
 * @param payload Pointer to the payload of the packet to send.
 * @param function Callback function.
 * @param result Result of the transmission.
 */
void RUDP_send(const packet_header_t* header, const void* payload, callback_function_t function, RUDP_transmission_result_t* result) {
	RUDP_transmission_t* transmission = NULL;

	if((function != NULL) && (result == NULL)) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "RUDP: Trying to send packet with callback, but no memory for the results has been allocated.\n");
		return;
	} else if(header == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "RUDP: Trying to send packet without header.\n");
		if(function != NULL) {
			result->error_code = C_ERROR_PARAMETER;
			function(result);
		}
		return;
	} else if((header->payload_length > 0) && (payload == NULL)) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "RUDP: Trying to send non-empty packet without payload.\n");
		if(function != NULL) {
			result->error_code = C_ERROR_PARAMETER;
			function(result);
		}
		return;
	} else if(header->to == C_BROADCAST_ADDR) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "RUDP: Broadcasts are not supported, ignoring packet...\n");
		if(function != NULL) {
			result->error_code = C_ERROR_PARAMETER;
			function(result);
		}
		return;
	}

	C_RUDP_total_transmissions++;

	if(list_size(C_RUDP_transmission_queue) >= RUDP_MAX_QUEUED_PACKETS) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "RUDP: Queue full, dropping packet...\n");
		if(function != NULL) {
			result->error_code = C_ERROR_BUSY;
			function(result);
		}
		C_RUDP_failed_transmissions++;
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "RUDP: Reliably sending packet with header at %p and payload at %p...\n", header, payload);
	MALLOC(transmission, RUDP_transmission_t);
	MALLOC(transmission->packet.header, packet_header_t);
	memcpy(transmission->packet.header, header, sizeof(packet_header_t));
	if(header->payload_length > 0) {
		void* p;
		MALLOC_ARRAY(p, char, header->payload_length);
		memcpy(p, payload, header->payload_length);
		transmission->packet.payload = p;
	} else {
		transmission->packet.payload = NULL;
	}
	transmission->retries = C_RUDP_PACKET_RETRIES;
	if(function == NULL) {
		transmission->function = NULL;
		transmission->result = NULL;
	} else {
		transmission->function = function;
		transmission->result = result;
		transmission->result->to = header->to;
		transmission->result->error_code = C_ERROR_NOT_SET;
	}

	list_add(C_RUDP_transmission_queue, transmission);
	// NOTE: We have to add the callback before sending the packet, because
	// otherwise the callback ID is not set properly when sending to the
	// local node.
	transmission->callback_id = callback_add(RUDP_send_timeout, transmission, C_EXPECTED_MULTI_HOP_RTT);
	send_RUDP_DATA(transmission->packet.header, transmission->packet.payload);
}


/**
 * @brief Send a packet via RUDP with application parameters and payload.
 *
 * @param to Address of destination node.
 * @param app Application identifier.
 * @param type Packet type identifier.
 * @param payload Pointer to payload.
 * @param payload_length Length of payload in bytes.
 * @param function Callback function.
 * @param result Result of the transmission.
 */
void RUDP_sendto(C_addr_t to, uint8_t app, uint8_t type, const void* payload, size_t payload_length, callback_function_t function, RUDP_transmission_result_t* result) {
	RUDP_sendto2(to, app, type, payload, payload_length, NULL, 0, function, result);
}

/**
 * @brief Send a packet via RUDP with application parameters and two payloads.
 *
 * @param to Address of destination node.
 * @param app Application identifier.
 * @param type Packet type identifier.
 * @param payload1 Pointer to first payload.
 * @param payload_length1 Length of first payload in bytes.
 * @param payload2 Pointer to second payload.
 * @param payload_length2 Length of second payload in bytes.
 * @param function Callback function.
 * @param result Result of the transmission.
 */
void RUDP_sendto2(C_addr_t to, uint8_t app, uint8_t type, const void* payload1, size_t payload_length1, const void* payload2, size_t payload_length2, callback_function_t function, RUDP_transmission_result_t* result) {
	packet_header_t header;

	if(
		((payload1 == NULL) && (payload_length1 != 0))
		|| ((payload1 != NULL) && (payload_length1 == 0))
		|| ((payload2 == NULL) && (payload_length2 != 0))
		|| ((payload2 != NULL) && (payload_length2 == 0))
	) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to assemble packet for sending to node %s via RUDP, but payload parameters are inconsistent.\n", addr_to_str(to));
		abort();
	}

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Assembling packet for sending to node %s via RUDP...\n", addr_to_str(to));
	init_header(&header, to, app, type);
	header.payload_length = payload_length1 + payload_length2;
	if(header.payload_length == 0) {
		RUDP_send(&header, NULL, function, result);
	} else {
		void* payload = NULL;
		MALLOC_JOIN(payload, char, payload1, payload_length1, payload2, payload_length2);
		RUDP_send(&header, payload, function, result);
		FREE(payload);
	}
}

/**
 * @brief Return the MTU size supported by RUDP.
 *
 * @return Maximal Transmission Unit (MTU) size in bytes.
 */
size_t RUDP_max_payload_size() {
	return C_max_payload_size() - sizeof(RUDP_DATA_packet_t);
}



/*
 * Interface provided by this module for direct commands.
 */

/**
 * @brief RUDP command function.
 *
 * @param argc Number of command parameters.
 * @param argv Array of strings used as command parameters.
 * @return <code>true</code> if the command was executed successfully,
 * <code>false</code> otherwise.
 */
bool RUDP_command(int argc, const char*const* argv) {
	return false;
}
