/**
 * @file net.c
 *
 * @brief Network utility functions.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: net.c 2685 2008-06-25 16:38:56Z wittenbu $
 */

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

#ifdef ARCH_LINUX
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#elif defined(ARCH_WIN32)
#include <winsock2.h>
#endif	// ARCH_*

#include "../types.h"
#include "../vars.h"
#include "../interface.h"
#include "../interface_internal.h"
#include "debug.h"
#include "list.h"
#include "callback.h"
#include "net.h"



/**
 * @brief Convert a numeric node address to a string.
 *
 * This function may be called several times in function parameters, so we have
 * to be careful to use different buffers every time.
 *
 * @param addr Numeric node address.
 * @return String representation of the node address.
 */
char* addr_to_str(C_addr_t addr) {
	int index = ++C_addr_to_str_buffer_index % C_ADDR_TO_STR_BUFFER_SIZE;
#ifdef ARCH_NS2
	snprintf(C_addr_to_str_buffer[index], C_ADDR_TO_STR_BUFFER_STR_LEN, "#%d", addr);
#else
	struct in_addr tmp_addr;
	char* str = NULL;
	tmp_addr.s_addr = addr;
	str = inet_ntoa(tmp_addr);
#ifdef ARCH_WIN32
	strncpy_s(C_addr_to_str_buffer[index], C_ADDR_TO_STR_BUFFER_STR_LEN, str, strlen(str));
#else
	strncpy(C_addr_to_str_buffer[index], str, C_ADDR_TO_STR_BUFFER_STR_LEN);
#endif	// ARCH_WIN32
#endif	// ARCH_*
	return C_addr_to_str_buffer[index];
}



/**
 * @brief Initialize a packet header.
 *
 * @param header Pointer to packet header to initialize.
 * @param to Address of destination node.
 * @param app Application identifier.
 * @param type Packet type identifier.
 */
void init_header(packet_header_t* header, C_addr_t to, uint8_t app, uint8_t type) {
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Initializing packet header at %p to node %s...\n", header, addr_to_str(to));
	header->from = C_LOCAL_ADDR;
	header->to = to;
	header->hops = 0;
	header->ttl = C_DEFAULT_TTL;
	header->seqno = C_sequence_number++;
	header->app = app;
	header->type = type;
	header->payload_length = 0;
}

/**
 * @brief Send a packet 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.
 */
void C_sendto(C_addr_t to, uint8_t app, uint8_t type, const void* payload, size_t payload_length) {
	C_sendto2(to, app, type, payload, payload_length, NULL, 0);
}

/**
 * @brief Send a packet 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.
 */
void C_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) {
	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, but payload parameters are inconsistent.\n", addr_to_str(to));
		abort();
	}

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

/**
 * @brief Maximal randomized delay before forwarding a packet in seconds.
 *
 * Uncommenting this define causes packet to be forwarded immediatly.
 */
//#define RUDP_BROADCAST_FORWARD_DELAY	0
#define RUDP_BROADCAST_FORWARD_DELAY	C_EXPECTED_MULTI_HOP_RTT

/**
 * @brief Packet forwarding callback to support delayed forwarding.
 *
 * @param parameter Pointer to packet to be forwarded.
 */
static void forward_callback(callback_parameter_t parameter) {
	C_packet_t* packet = (C_packet_t*) parameter;
	C_send(packet->header, packet->payload);
	if(packet->header->payload_length > 0)
		FREE(packet->payload);
	FREE(packet->header);
	FREE(packet);
}

/**
 * @brief Forward a packet.
 *
 * Re-sends packets with non-zero TTL. Decreases TTL before sending.
 *
 * @param header Pointer to header of packet to be forwarded.
 * @param payload Pointer to payload of packet to be forwarded.
 */
void forward(const packet_header_t* header, const void* payload) {
	packet_header_t new_header;
	if(header->to == C_LOCAL_ADDR) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "Dropping packet instead of forwarding it to the local node...\n");
	} else if(header->ttl > 1) {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Forwarding packet...\n");
		memcpy(&new_header, header, sizeof(packet_header_t));
		new_header.ttl--;
		if(RUDP_BROADCAST_FORWARD_DELAY == 0) {
			C_send(&new_header, payload);
		} else {
			if(new_header.to != C_BROADCAST_ADDR) {
				C_send(&new_header, payload);
			} else {		// Add delay to broadcast packets.
				C_packet_t* packet;
				MALLOC(packet, C_packet_t);
				MALLOC(packet->header, packet_header_t);
				memcpy(packet->header, &new_header, sizeof(packet_header_t));
				if(packet->header->payload_length > 0) {
					void* p;
					MALLOC_ARRAY(p, char, packet->header->payload_length);
					memcpy(p, payload, packet->header->payload_length);
					packet->payload = p;
				} else {
					packet->payload = NULL;
				}
				callback_add(forward_callback, packet, C_random(RUDP_BROADCAST_FORWARD_DELAY));
			}
		}
	} else {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Dropping packet with expired TTL...\n");
	}
}



/**
 * @brief Check for reception of duplicate packets.
 *
 * Check whether a packet has already been received recently and add it to the
 * corresponding data structure for future checks.
 *
 * @param cache Pointer to the packet cache array.
 * @param cache_size Size of the packet cache array.
 * @param header Pointer to header of a recently received packet.
 * @return <code>true</code> if the packet is a duplicate based on its
 * fingerprint, <code>false</code> otherwise.
 */
bool check_for_duplicate(packet_fingerprint_t* cache, uint16_t cache_size, const packet_header_t* header) {
	int i, LRU_index = -1;
	C_time_t current_time = C_time();
	C_time_t time_of_oldest_item = current_time;

	if(cache == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Pointer to packet cache is NULL.\n");
		abort();
	} else if(cache_size == 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Packet cache size is 0.\n");
		abort();
	} else if(header == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to add packet header at NULL to packet cache.\n");
		abort();
	}

	// Do a reverse loop to use the beginning of the cache first and ease debugging.
	for(i = cache_size - 1; i >= 0; i--) {
		// Check whether we have recently seen a similar packet.
		if(
			(cache[i].time != 0)
			&& (cache[i].from == header->from)
			&& (cache[i].seqno == header->seqno)
		) {
			C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Duplicate packet fingerprint (time=%.2f, from=%s, seqno=%d) found at index %d.\n", current_time, addr_to_str(header->from), header->seqno, i);
			cache[i].time = current_time;
			return true;
		}
		// Find the least recently used (LRU) entry in the data
		// structure for storing information about this packet.
		if(
			(cache[i].time == 0)
			|| (cache[i].time <= time_of_oldest_item)
		) {
			time_of_oldest_item = cache[i].time;
			LRU_index = i;
		}
	}
	if(LRU_index < 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Failed to find slot for packet fingerprint (time=%f, from=%s, seqno=%d).\n", current_time, addr_to_str(header->from), header->seqno);
		abort();
	}
	cache[LRU_index].time = current_time;
	cache[LRU_index].from = header->from;
	cache[LRU_index].seqno = header->seqno;
	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "Added packet fingerprint (time=%.2f, from=%s, seqno=%d) at index %d.\n", cache[LRU_index].time, addr_to_str(cache[LRU_index].from), cache[LRU_index].seqno, LRU_index);
	return false;
}



/**
 * @brief Initialize a node structure.
 *
 * @param node Pointer to node structure to initialize.
 * @param address Address of the node.
 */
void init_node(node_t* node, C_addr_t address) {
	node->address = address;
	node->hops = C_EXPECTED_NETWORK_DIAMETER + 1;
	node->last_hop = C_BROADCAST_ADDR;
	node->packets = 0;
}

/**
 * @brief Check whether two nodes have matching addresses.
 *
 * @param item Node to check.
 * @param cmp_item Reference node.
 * @return <code>true</code> if the two nodes have the same address,
 * <code>false</code> otherwise.
 */
bool matches_node_addr(const void* item, const void* cmp_item) {
	node_t* node = (node_t*) item;
	node_t* cmp_node = (node_t*) cmp_item;
	return (node->address == cmp_node->address);
}

/**
 * @brief Compare the addresses of two nodes.
 *
 * @param item Node to check.
 * @param cmp_item Reference node.
 * @return <code>true</code> if the node has a smaller address than the
 * reference node, <code>false</code> otherwise.
 */
bool node_addr_less_than(const void* item, const void* cmp_item) {
	node_t* node = (node_t*) item;
	node_t* cmp_node = (node_t*) cmp_item;
	return (node->address < cmp_node->address);
}

/**
 * @brief Print the address of a node.
 *
 * @param item Node whose address is to be printed.
 */
void print_node_addr(const void* item) {
	node_t* node = (node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_NONE, "%s", addr_to_str(node->address));
}

/**
 * @brief Print the address and distance in hops of a node.
 *
 * @param item Node whose address and distance is to be printed.
 */
void print_node_addr_and_distance(const void* item) {
	node_t* node = (node_t*) item;
	C_DEBUG(C_DEBUG_LEVEL_NONE, "%s @ %d hops", addr_to_str(node->address), node->hops);
}

/**
 * @brief Adds or updates a node in the network map.
 *
 * @param address Address of the node.
 * @param hops Distance to the node.
 * @param last_hop Address of the node that is the last hop on the route.
 */
void add_node_to_network_map(C_addr_t address, uint8_t hops, C_addr_t last_hop) {
	node_t cmp_node;
	node_t* node;

	if(address == C_LOCAL_ADDR)
		return;

	cmp_node.address = address;
	node = (node_t*) list_find(C_network_map, &cmp_node, matches_node_addr);
	if(node != NULL) {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Updating node %s at %d hop(s) with last hop %s in network map...\n", addr_to_str(address), hops, addr_to_str(last_hop));
	} else {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Adding node %s at %d hop(s) with last hop %s to network map...\n", addr_to_str(address), hops, addr_to_str(last_hop));
		MALLOC(node, node_t);
		init_node(node, address);
		list_add(C_network_map, node);
	}
	node->hops = hops;
	node->last_hop = last_hop;
	node->packets++;
}

/**
 * @brief Update the aggregate network statistics counters.
 */
void calculate_network_statistics() {
	uint32_t direct_neighbors = 0;
	uint32_t longest_route = 0;
	double total_route_length = 0;
	unsigned int i;
	node_t* node;
	for(i = 0; i < list_size(C_network_map); i++) {
		node = (node_t*) list_get(C_network_map, i);
		if(node->hops == 0) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "Node %s has hop count 0, last node was %s.\n", addr_to_str(node->address), addr_to_str(node->last_hop));
			abort();
		} else if(node->hops == 1) {
			direct_neighbors++;
		}
		if(node->hops > longest_route)
			longest_route = node->hops;
		total_route_length += node->hops;
	}
	C_direct_neighbors = direct_neighbors;
	C_longest_route = longest_route;
	if(list_size(C_network_map) > 0)
		C_average_route = total_route_length / list_size(C_network_map);
}
