/**
 * @file service_replication.c
 *
 * @brief Service replication.
 *
 * The replication protocol is as follows: Node A initiates replication to node
 * B by sending a SERVICE REPLICATION REQUEST packet via RUDP. If node B is
 * capable of host the service, it sends SERVICE REPLICATION ACK packets to
 * node A requesting segments of the service data of the service to be
 * replicated. Node A responds to these ACKs by sending the SERVICE REPLICATION
 * DATA packets with the requested segments. Once all service data segments
 * have been received by node B, it sends a SERVICE REPLICATION ACK with
 * segment size set to 0. Node A can then either proceed to shutdown the
 * service in case it was migrating it.
 *
 * Service migration works two phases: In the first phase the service is
 * replicated to the destination node, in the second phase the old host
 * redirects service request to the new host. The second phase terminates
 * after a timeout and the service is shutdown.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: service_replication.c 2685 2008-06-25 16:38:56Z wittenbu $
 */

#include "../types.h"
#include "../vars.h"
#include "../interface.h"
#include "../interface_internal.h"
#include "../util/debug.h"
#include "../util/callback.h"
#include "../util/net.h"
#include "../rudp-light/rudp-light.h"
#include "service.h"
#include "service_types.h"
#include "service_vars.h"
#include "service_replication.h"
#include "service_management.h"
#include "service_util.h"

/**
 * @brief Timeout for service replication in seconds.
 */
#define SERVICE_REPLICATION_TIMEOUT	(C_ROUTE_ESTABLISHMENT_TIME + C_EXPECTED_MULTI_HOP_RTT * service->data_size / RUDP_max_payload_size())

/**
 * @brief Timeout for service redirection in seconds.
 *
 * Since redirection information is piggy-backed on replies to service request,
 * we should allow for the clients to make at least one requests. Otherwise,
 * the clients have to broadcast SLP queries to locate the new host.
 */
#define SERVICE_REDIRECTION_TIMEOUT	(C_EXPECTED_MULTI_HOP_RTT * 5)

/*
 * Packet handling functions.
 */

/**
 * @brief RUDP callback for a SERVICE REPLICATION REQUEST packet.
 *
 * @param parameter Pointer to RUDP transmission result.
 */
static void send_SERVICE_SERVER_REPLICATION_REQUEST_callback(callback_parameter_t parameter) {
	RUDP_transmission_result_t* result = (RUDP_transmission_result_t*) parameter;
	SERVICE_outgoing_replication_t* replication = (SERVICE_outgoing_replication_t*) result->parameter;

	if(list_contains(C_SERVICE_outgoing_replications, replication)){
		SERVICE_service_t* service = (SERVICE_service_t*) replication->service;
		if(result->error_code == C_ERROR_OK) {
			// Nothing to do, segments will be requested by new server.
		} else if(result->error_code == C_ERROR_TIMEOUT) {
			C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: REPLICATION REQUEST timed out, replication of service \"%s\" failed.\n", service->id);
			service->state = SERVICE_STATE_RUNNING;
			service->outgoing_replication = NULL;
			replication->result->error_code = C_ERROR_COMM_ERROR;
			replication->callback(replication->result);
			list_remove(C_SERVICE_outgoing_replications, replication);
			callback_cancel(replication->callback_id);
			FREE(replication);
			C_SERVICE_failed_replications++;
		} else if(result->error_code == C_ERROR_BUSY) {
			C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: REPLICATION REQUEST hit full queue, replication of service \"%s\" failed.\n", service->id);
			service->state = SERVICE_STATE_RUNNING;
			service->outgoing_replication = NULL;
			replication->result->error_code = C_ERROR_BUSY;
			replication->callback(replication->result);
			list_remove(C_SERVICE_outgoing_replications, replication);
			callback_cancel(replication->callback_id);
			FREE(replication);
			C_SERVICE_failed_replications++;
		} else {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Unhandled error while sending REPLICATION REQUEST with RUDP (%d).\n", result->error_code);
			abort();
		}
	} else {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Callback on REPLICATION REQUEST for previously cancelled replication.\n");
	}
	FREE(result);
}

/**
 * @brief Send a SERVICE REPLICATION REQUEST packet.
 *
 * @param replication Pointer to outgoing replication information.
 */
static void send_SERVICE_SERVER_REPLICATION_REQUEST(SERVICE_outgoing_replication_t* replication) {
	SERVICE_REPLICATION_REQUEST_packet_t request;
	RUDP_transmission_result_t* result = NULL;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Sending REPLICATION REQUEST for %d byte(s) of service \"%s\" to node %s...\n", replication->service->data_size, replication->service->id, addr_to_str(replication->destination));
	copy_service_id(request.service_id, replication->service->id);
	request.service_data_size = replication->service->data_size;
	MALLOC(result, RUDP_transmission_result_t);
	result->parameter = replication;
	RUDP_sendto(replication->destination, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_REPLICATION_REQUEST, &request, sizeof(SERVICE_REPLICATION_REQUEST_packet_t), send_SERVICE_SERVER_REPLICATION_REQUEST_callback, result);
}

/**
 * @brief RUDP callback for a SERVICE REPLICATION DATA packet.
 *
 * @param parameter Pointer to RUDP transmission result.
 */
static void send_SERVICE_REPLICATION_DATA_callback(callback_parameter_t parameter) {
	RUDP_transmission_result_t* result = (RUDP_transmission_result_t*) parameter;
	SERVICE_outgoing_replication_t* replication = (SERVICE_outgoing_replication_t*)result->parameter;

	if(list_contains(C_SERVICE_outgoing_replications, replication)){
		SERVICE_service_t* service = replication->service;
		if(result->error_code == C_ERROR_OK) {
			// Nothing to do, more segments will be requested by new server.
		} else if(result->error_code == C_ERROR_TIMEOUT) {
			C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: REPLICATION DATA timed out, replication of service \"%s\" failed.\n", service->id);
			service->state = SERVICE_STATE_RUNNING;
			service->outgoing_replication = NULL;
			replication->result->error_code = C_ERROR_COMM_ERROR;
			replication->callback(replication->result);
			list_remove(C_SERVICE_outgoing_replications, replication);
			callback_cancel(replication->callback_id);
			FREE(replication);
			C_SERVICE_failed_replications++;
		} else if(result->error_code == C_ERROR_BUSY) {
			C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: REPLICATION DATA hit full queue, replication of service \"%s\" failed.\n", service->id);
			service->state = SERVICE_STATE_RUNNING;
			service->outgoing_replication = NULL;
			replication->result->error_code = C_ERROR_BUSY;
			replication->callback(replication->result);
			list_remove(C_SERVICE_outgoing_replications, replication);
			callback_cancel(replication->callback_id);
			FREE(replication);
			C_SERVICE_failed_replications++;
		} else {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Unhandled error while sending REPLICATION DATA with RUDP (%d).\n", result->error_code);
			abort();
		}
	} else {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Callback on REPLICATION DATA for previously cancelled replication.\n");
	}
	FREE(result);
}

/**
 * @brief Send a SERVICE REPLICATION DATA packet.
 *
 * @param replication Pointer to outgoing replication information.
 * @param segment Pointer to segment to send.
 */
static void send_SERVICE_REPLICATION_DATA(SERVICE_outgoing_replication_t* replication, const SERVICE_segment_t* segment) {
	SERVICE_service_t* service = replication->service;
	SERVICE_REPLICATION_DATA_packet_t data_header;
	RUDP_transmission_result_t* result = NULL;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Sending REPLICATION DATA for %d byte(s) at offset 0x%X of service \"%s\" to node %s...\n", segment->size, segment->offset, service->id, addr_to_str(replication->destination));
	copy_service_id(data_header.service_id, service->id);
	data_header.segment_offset = segment->offset;
	MALLOC(result, RUDP_transmission_result_t);
	result->parameter = replication;
	RUDP_sendto2(replication->destination, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_REPLICATION_DATA, &data_header, sizeof(SERVICE_REPLICATION_DATA_packet_t), (service->data + segment->offset), segment->size, send_SERVICE_REPLICATION_DATA_callback, result);
}

static void send_SERVICE_REPLICATION_NACK(C_addr_t to, const char* service_id, C_errno_t error_code);

/**
 * @brief RUDP callback for a SERVICE REPLICATION ACK packet.
 *
 * @param parameter Pointer to RUDP transmission result.
 */
static void send_SERVICE_REPLICATION_ACK_callback(callback_parameter_t parameter) {
	RUDP_transmission_result_t* result = (RUDP_transmission_result_t*) parameter;
	SERVICE_incoming_replication_t* replication = (SERVICE_incoming_replication_t*) result->parameter;

	if(list_contains(C_SERVICE_incoming_replications, replication)){
		SERVICE_service_t* service = replication->service;
		if(result->error_code == C_ERROR_OK) {
			// Nothing to do, next segment will be sent by old server.
		} else if(result->error_code == C_ERROR_TIMEOUT) {
			C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: REPLICATION ACK timed out, replication of service \"%s\" failed.\n", service->id);
			send_SERVICE_REPLICATION_NACK(service->incoming_replication->source, service->id, C_ERROR_TIMEOUT);
			free_service(service);
			list_free(&replication->missing_segments);
			list_remove(C_SERVICE_incoming_replications, replication);
			callback_cancel(replication->callback_id);
			FREE(replication);
		} else if(result->error_code == C_ERROR_BUSY) {
			C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: REPLICATION ACK hit full queue, replication of service \"%s\" failed.\n", service->id);
			free_service(service);
			list_free(&replication->missing_segments);
			list_remove(C_SERVICE_incoming_replications, replication);
			callback_cancel(replication->callback_id);
			FREE(replication);
		} else {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Unhandled error while sending REPLICATION ACK with RUDP (%d).\n", result->error_code);
			abort();
		}
	} else {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Callback on REPLICATION ACK for previously cancelled replication.\n");
	}
	FREE(result);
}

/**
 * @brief Send a SERVICE REPLICATION ACK packet.
 *
 * @param replication Pointer to incoming replication information.
 * @param segment Pointer to segment to request.
 */
static void send_SERVICE_REPLICATION_ACK(SERVICE_incoming_replication_t* replication, const SERVICE_segment_t* segment) {
	SERVICE_service_t* service = replication->service;
	SERVICE_REPLICATION_ACK_packet_t ack;
	RUDP_transmission_result_t* result = NULL;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Sending REPLICATION ACK requesting %d byte(s) at offset 0x%X of service \"%s\" to node %s...\n", segment->size, segment->offset, service->id, addr_to_str(replication->source));
	copy_service_id(ack.service_id, service->id);
	memcpy(&ack.next_segment, segment, sizeof(SERVICE_segment_t));
	MALLOC(result, RUDP_transmission_result_t);
	result->parameter = replication;
	RUDP_sendto(replication->source, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_REPLICATION_ACK, &ack, sizeof(SERVICE_REPLICATION_ACK_packet_t), send_SERVICE_REPLICATION_ACK_callback, result);
}

/**
 * @brief Send a SERVICE REPLICATION NACK packet.
 *
 * @param to Address of destination node.
 * @param service_id ID of the service for which the replication has failed.
 * @param error_code Error code.
 */
static void send_SERVICE_REPLICATION_NACK(C_addr_t to, const SERVICE_id_t* service_id, C_errno_t error_code) {
	SERVICE_REPLICATION_NACK_packet_t nack;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Sending REPLICATION NACK for service \"%s\" to node %s...\n", service_id, addr_to_str(to));
	copy_service_id(nack.service_id, service_id);
	nack.error_code = error_code;
	RUDP_sendto(to, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_REPLICATION_NACK, &nack, sizeof(SERVICE_REPLICATION_NACK_packet_t), NULL, NULL);
}

/**
 * @brief Timeout for handled incoming SERVICE REPLICATION REQUEST packet.
 *
 * If the service is does not exist anymore or is still initializing then we
 * abort the replication and free the resources.
 *
 * @param parameter Incoming replication information.
 */
void handle_SERVICE_REPLICATION_REQUEST_timeout(callback_parameter_t parameter) {
	SERVICE_incoming_replication_t* replication = (SERVICE_incoming_replication_t*) parameter;
	SERVICE_service_t* service = replication->service;

	C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Replication of service \"%s\" from node %s timed out, freeing resources...\n", service->id, addr_to_str(service->incoming_replication->source));
	free_service(service);
	list_free(&replication->missing_segments);
	list_remove(C_SERVICE_incoming_replications, replication);
	FREE(replication);
}

/**
 * @brief Handle incoming SERVICE REPLICATION REQUEST packet.
 *
 * If this node can host the service then setup a list of missing segments and
 * start requesting them by sending a first SERVICE REPLICATION ACK.
 *
 * @param header Header of incoming packet.
 * @param request Replication request information.
 */
void handle_SERVICE_REPLICATION_REQUEST(const packet_header_t* header, const SERVICE_REPLICATION_REQUEST_packet_t* request) {
	size_t max_segment_size = RUDP_max_payload_size() - sizeof(SERVICE_REPLICATION_DATA_packet_t);
	SERVICE_incoming_replication_t* replication = NULL;
	uint16_t offset = 0;
	char* service_id = NULL;

	SERVICE_service_t* service = alloc_service(request->service_id, request->service_data_size);
	if(service == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION REQUEST for service \"%s\" from node %s, but can't host new service...\n", request->service_id, addr_to_str(header->from));
		send_SERVICE_REPLICATION_NACK(header->from, request->service_id, C_ERROR_BUSY);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Received REPLICATION REQUEST for service \"%s\" from node %s...\n", request->service_id, addr_to_str(header->from));
	MALLOC(replication, SERVICE_incoming_replication_t);
	replication->service = service;
	replication->source = header->from;
	service->incoming_replication = replication;
	list_add(C_SERVICE_incoming_replications, replication);

	// Split the service into segments for transmission and tag these
	// segments as missing.
	list_alloc(&replication->missing_segments);
	for(offset = 0x0000; offset < service->data_size; offset += max_segment_size) {
		SERVICE_segment_t* segment;
		MALLOC(segment, SERVICE_segment_t);
		segment->offset = offset;
		segment->size = service->data_size - offset;
		if(segment->size > max_segment_size)
			segment->size = max_segment_size;
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Adding segment with offset 0x%X and size %d to missing segments list of service \"%s\"...\n", segment->offset, segment->size, service->id);
		list_add(replication->missing_segments, segment);
	}

	send_SERVICE_REPLICATION_ACK(replication, (SERVICE_segment_t*) list_head(replication->missing_segments));
	MALLOC_ARRAY(service_id, char, SERVICE_ID_LENGTH);
	copy_service_id(service_id, service->id);
	replication->callback_id = callback_add(handle_SERVICE_REPLICATION_REQUEST_timeout, replication, SERVICE_REPLICATION_TIMEOUT);
}

/**
 * @brief Check whether two service data segments match.
 *
 * @param item Segment to check.
 * @param cmp_item Reference segment.
 * @return <code>true</code> if the two segments match, <code>false</code>
 * otherwise.
 */
static bool matches_segment(const void* item, const void* cmp_item) {
	SERVICE_segment_t* segment = (SERVICE_segment_t*) item;
	SERVICE_segment_t* cmp_segment = (SERVICE_segment_t*) cmp_item;
	return (
		(segment->offset == cmp_segment->offset)
		&& (segment->size == cmp_segment->size)
	);
}

/**
 * @brief Handle incoming SERVICE REPLICATION DATA packet.
 *
 * @param header Header of the incoming packet.
 * @param data_header REPLICATION DATA header.
 * @param segment_data Segement data.
 * @param segment_size Size of segment data.
 */
void handle_SERVICE_REPLICATION_DATA(const packet_header_t* header, const SERVICE_REPLICATION_DATA_packet_t* data_header, const char* segment_data, size_t segment_size) {
	SERVICE_segment_t segment;
	SERVICE_service_t* service = NULL;
	SERVICE_incoming_replication_t* replication = NULL;
	SERVICE_segment_t* missing_segment = NULL;

	segment.size = segment_size;
	segment.offset = data_header->segment_offset;
	service = get_service(data_header->service_id);
	if(service == NULL) {	// May occur if replication timed out, but source didn't receive NACK.
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION DATA packet for %d byte(s) at offset 0x%X of service \"%s\" from node %s, but service is unknown...\n", segment.size, segment.offset, data_header->service_id, addr_to_str(header->from));
		return;
	} else if(service->state != SERVICE_STATE_INIT) {	// May occur on retransmitted REPLICATION DATA packets.
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION DATA packet for %d byte(s) at offset 0x%X of service \"%s\" from node %s, but service is not initializing...\n", segment.size, segment.offset, data_header->service_id, addr_to_str(header->from));
		return;
	} else if(header->from != service->incoming_replication->source) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Received REPLICATION DATA packet for %d byte(s) at offset 0x%X of service \"%s\" from node %s, but service replication was initialized by node %s...\n", segment.size, segment.offset, data_header->service_id, addr_to_str(header->from), addr_to_str(service->incoming_replication->source));
		return;
	}

	replication = service->incoming_replication;
	missing_segment = (SERVICE_segment_t*) list_find(replication->missing_segments, &segment, matches_segment);
	if(missing_segment != NULL) {
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Received REPLICATION DATA packet for %d byte(s) at offset 0x%X of service \"%s\" from node %s...\n", segment.size, segment.offset, data_header->service_id, addr_to_str(header->from));
		update_service_data(service, segment.offset, segment.size, segment_data);
		list_remove(replication->missing_segments, missing_segment);
		FREE(missing_segment);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION DATA packet for %d byte(s) at offset 0x%X of service \"%s\" from node %s, but segment was not missing...\n", segment.size, segment.offset, data_header->service_id, addr_to_str(header->from));
	}

	if(!list_empty(replication->missing_segments)) {
		send_SERVICE_REPLICATION_ACK(replication, (SERVICE_segment_t*) list_head(replication->missing_segments));
	} else {
		// All segments have been transfered, send ACK with segment.size = 0.
		SERVICE_segment_t segment;
		segment.offset = 0x0000;
		segment.size = 0;
		send_SERVICE_REPLICATION_ACK(replication, &segment);
		service->incoming_replication = NULL;
		list_free(&replication->missing_segments);
		list_remove(C_SERVICE_incoming_replications, replication);
		callback_cancel(replication->callback_id);
		FREE(replication);
		start_service(service);
	}
}

/**
 * @brief Handle incoming SERVICE REPLICATION ACK packet.
 *
 * @param header Header of incoming packet.
 * @param ack Acknowledgement information either requesting another segment of
 * service data or confirming the successful replication.
 */
void handle_SERVICE_REPLICATION_ACK(const packet_header_t* header, const SERVICE_REPLICATION_ACK_packet_t* ack) {
	SERVICE_outgoing_replication_t* replication = NULL;

	SERVICE_service_t* service = get_service(ack->service_id);
	if(service == NULL) {	// May occur on delayed REPLICATION ACKs.
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION ACK requesting %d byte(s) offset 0x%X of service \"%s\" from node %s, but service is unknown...\n", ack->next_segment.size, ack->next_segment.offset, ack->service_id, addr_to_str(header->from));
		return;
	} else if(service->state != SERVICE_STATE_REPLICATING) {	// May occur on delayed REPLICATION ACKs.
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION ACK requesting %d byte(s) offset 0x%X of service \"%s\" from node %s, but service is not replicating...\n", ack->next_segment.size, ack->next_segment.offset, ack->service_id, addr_to_str(header->from));
		return;
	} else if((ack->next_segment.offset + ack->next_segment.size) > service->data_size) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Received REPLICATION ACK requesting %d byte(s) offset 0x%X of service \"%s\" from node %s, but service data size is only %d...\n", ack->next_segment.size, ack->next_segment.offset, ack->service_id, addr_to_str(header->from), service->data_size);
		return;
	} else if(header->from != service->outgoing_replication->destination) {	// May occur on delayed REPLICATION ACKs.
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION ACK requesting %d byte(s) offset 0x%X of service \"%s\" from node %s, but service replication was initialized to node %s...\n", ack->next_segment.size, ack->next_segment.offset, ack->service_id, addr_to_str(header->from), addr_to_str(service->outgoing_replication->destination));
		return;
	}

	replication = service->outgoing_replication;
	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Received REPLICATION ACK requesting %d byte(s) offset 0x%X of service \"%s\" from node %s...\n", ack->next_segment.size, ack->next_segment.offset, ack->service_id, addr_to_str(header->from));
	if(ack->next_segment.size != 0) {
		send_SERVICE_REPLICATION_DATA(replication, &ack->next_segment);
	} else {
		// All segments have been transfered.
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Replication of service \"%s\" to node %s is complete.\n", service->id, addr_to_str(header->from));
		service->state = SERVICE_STATE_RUNNING;
		service->outgoing_replication = NULL;
		replication->result->error_code = C_ERROR_OK;
		replication->callback(replication->result);
		list_remove(C_SERVICE_outgoing_replications, replication);
		callback_cancel(replication->callback_id);
		FREE(replication);
		C_SERVICE_successful_replications++;
	}
}

/**
 * @brief Handle incoming SERVICE REPLICATION NACK packet.
 *
 * @param header Header of the incoming packet.
 * @param nack Information about why the replication failed.
 */
void handle_SERVICE_REPLICATION_NACK(const packet_header_t* header, const SERVICE_REPLICATION_NACK_packet_t* nack) {
	SERVICE_outgoing_replication_t* replication = NULL;

	SERVICE_service_t* service = get_service(nack->service_id);
	if(service == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION NACK for service \"%s\" from node %s, but that service is not hosted on this node...\n", nack->service_id, addr_to_str(header->from));
		return;
	} else if(service->state != SERVICE_STATE_REPLICATING) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION NACK for service \"%s\" from node %s, but that service is not replicating...\n", nack->service_id, addr_to_str(header->from));
		return;
	}

	replication = service->outgoing_replication;
	if(nack->error_code == C_ERROR_BUSY) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION NACK for service \"%s\" from node %s. The node has refused to host the service.\n", service->id, addr_to_str(header->from));
		service->state = SERVICE_STATE_RUNNING;
		service->outgoing_replication = NULL;
		replication->result->error_code = C_ERROR_REMOTE_BUSY;
		replication->callback(replication->result);
		list_remove(C_SERVICE_outgoing_replications, replication);
		callback_cancel(replication->callback_id);
		FREE(replication);
		C_SERVICE_failed_replications++;
	} else if(nack->error_code == C_ERROR_TIMEOUT) {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received REPLICATION NACK for service \"%s\" from node %s. The replication timed out.\n", service->id, addr_to_str(header->from));
		service->state = SERVICE_STATE_RUNNING;
		service->outgoing_replication = NULL;
		replication->result->error_code = C_ERROR_COMM_ERROR;
		replication->callback(replication->result);
		list_remove(C_SERVICE_outgoing_replications, replication);
		callback_cancel(replication->callback_id);
		FREE(replication);
		C_SERVICE_failed_replications++;
	} else {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Received REPLICATION NACK for service \"%s\" from node %s with illegal error code (%d).\n", service->id, addr_to_str(header->from), nack->error_code);
		abort();
	}
}



/*
 * Control function.
 */

/**
 * @brief Timeout function for service replication.
 *
 * @param parameter Pointer to outgoing replication information.
 */
static void replicate_service_timeout(callback_parameter_t parameter) {
	SERVICE_outgoing_replication_t* replication = (SERVICE_outgoing_replication_t*) parameter;
	SERVICE_service_t* service = (SERVICE_service_t*) replication->service;

	C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Replication of service \"%s\" timed out.\n", service->id);
	service->state = SERVICE_STATE_RUNNING;
	service->outgoing_replication = NULL;
	replication->result->error_code = C_ERROR_TIMEOUT;
	replication->callback(replication->result);
	list_remove(C_SERVICE_outgoing_replications, replication);
	FREE(replication);
	C_SERVICE_failed_replications++;
}

/**
 * @brief Replicate a service from this node to another node.
 *
 * @param service Pointer to the service to replicate.
 * @param to Address of the destination node.
 * @param callback Callback function.
 * @param result Pointer to replication result.
 */
void replicate_service(SERVICE_service_t* service, C_addr_t to, callback_function_t callback, SERVICE_replication_result_t* result) {
	SERVICE_outgoing_replication_t* replication = NULL;

	if(service->state == SERVICE_STATE_REPLICATING) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Trying to replicate service \"%s\" to node %s, but service is not running (%d).\n", service->id, addr_to_str(to), service->state);
		result->error_code = C_ERROR_IN_PROGRESS;
		callback(result);
		return;
	} else if(service->state != SERVICE_STATE_RUNNING) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Trying to replicate service \"%s\" to node %s, but service is not running (%d).\n", service->id, addr_to_str(to), service->state);
		result->error_code = C_ERROR_PARAMETER;
		callback(result);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Replicating service \"%s\" to node %s...\n", service->id, addr_to_str(to));
	service->state = SERVICE_STATE_REPLICATING;
	C_SERVICE_total_replications++;

	MALLOC(replication, SERVICE_outgoing_replication_t);
	replication->service = service;
	replication->destination = to;
	replication->callback = callback;
	replication->result = result;
	replication->result->error_code = C_ERROR_NOT_SET;
	service->outgoing_replication = replication;
	list_add(C_SERVICE_outgoing_replications, replication);

	send_SERVICE_SERVER_REPLICATION_REQUEST(replication);
	replication->callback_id = callback_add(replicate_service_timeout, replication, SERVICE_REPLICATION_TIMEOUT);
}

/**
 * @brief Redirect client requests to a service to another node.
 *
 * @param service Pointer to the service for which requests are to be
 * redirected.
 * @param to Address of the alternative server.
 */
static void redirect_clients(SERVICE_service_t* service, C_addr_t to) {
	if(service->state != SERVICE_STATE_RUNNING) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Trying to redirect clients of service \"%s\" to node %s, but service is not running (%d).\n", service->id, addr_to_str(to), service->state);
		abort();
	}

	// Update local cache.
	cache_set(service->id, to);

	service->state = SERVICE_STATE_REDIRECTING;
	service->redirect_addr = to;

	// TODO: Migrate active streams (possibly cancelling their current callbacks).
}

/**
 * @brief Service migration callback after the redirection phase.
 *
 * @param parameter Service migration information.
 */
static void migrate_service_redirection_callback(callback_parameter_t parameter) {
	SERVICE_migration_t* migration = (SERVICE_migration_t*) parameter;

	C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Redirection of clients of service \"%s\" complete, stopping service...\n", migration->service->id);
	migration->service->redirect_addr = C_BROADCAST_ADDR;
	stop_service(migration->service);
	free_service(migration->service);
	migration->result->error_code = C_ERROR_OK;
	migration->callback(migration->result);
	C_SERVICE_successful_migrations++;
	FREE(migration);
}

/**
 * @brief Service migration callback for the replication phase.
 *
 * @param parameter Service replication result.
 */
static void migrate_service_replication_callback(callback_parameter_t parameter) {
	SERVICE_replication_result_t* result = (SERVICE_replication_result_t*) parameter;
	SERVICE_migration_t* migration = (SERVICE_migration_t*) result->parameter;
	if(result->error_code == C_ERROR_OK) {
		C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Replication of service \"%s\" successful, proceeding with redirection of clients...\n", migration->service->id);

		// Update local SLP cache entry.
		cache_set(migration->service->id, migration->destination);

		redirect_clients(migration->service, migration->destination);
		callback_add(migrate_service_redirection_callback, migration, SERVICE_REDIRECTION_TIMEOUT);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Replication of service \"%s\" failed (%d), aborting service migration.\n", migration->service->id, result->error_code);
		migration->result->error_code = result->error_code;
		migration->callback(migration->result);
		C_SERVICE_failed_migrations++;
		FREE(migration);
	}
	FREE(result);
}

/**
 * @brief Migrate a service from this node to another node.
 *
 * @param service Pointer to the service to migrate.
 * @param to Address of the destination node.
 * @param callback Callback function.
 * @param result Pointer to migration result.
 */
void migrate_service(SERVICE_service_t* service, C_addr_t to, callback_function_t callback, SERVICE_migration_result_t* result) {
	SERVICE_migration_t* migration = NULL;
	SERVICE_replication_result_t* replication_result = NULL;

	if(service->state == SERVICE_STATE_REPLICATING) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Trying to migrate service \"%s\" to node %s, but service is not running (%d).\n", service->id, addr_to_str(to), service->state);
		result->error_code = C_ERROR_IN_PROGRESS;
		callback(result);
		return;
	} else if(service->state != SERVICE_STATE_RUNNING) {
		C_DEBUG(C_DEBUG_LEVEL_ERROR, "SERVICE: Trying to migrate service \"%s\" to node %s, but service is not running (%d).\n", service->id, service->state, addr_to_str(to));
		result->error_code = C_ERROR_PARAMETER;
		callback(result);
		return;
	}

	C_DEBUG(C_DEBUG_LEVEL_NOTE, "SERVICE: Migrating service \"%s\" to node %s...\n", service->id, addr_to_str(to));
	C_SERVICE_total_migrations++;

	MALLOC(migration, SERVICE_migration_t);
	migration->service = service;
	migration->destination = to;
	migration->callback = callback;
	migration->result = result;
	copy_service_id(migration->result->service_id, service->id);
	migration->result->error_code = C_ERROR_NOT_SET;

	MALLOC(replication_result, SERVICE_replication_result_t);
	replication_result->parameter = migration;
	replicate_service(service, to, migrate_service_replication_callback, replication_result);
}
