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

#include <math.h>

#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 "../slp-light/slp-light.h"
#include "service.h"
#include "service_types.h"
#include "service_vars.h"
#include "service_client.h"
#include "service_management.h"
#include "service_util.h"



/**
 * @brief Number of service instances to locate before selecting the best
 * server.
 */
#define CLIENT_MIN_SERVICE_INSTANCES	1



/*
 * Packet handling functions.
 */

/**
 * @brief Send SERVICE DATA REQUEST packet.
 *
 * @param to Address of destination node.
 * @param service_id ID of service of which data is requested.
 * @param offset Offset in service data.
 * @param size Request size in bytes.
 */
static void send_SERVICE_CLIENT_DATA_REQUEST(C_addr_t to, const SERVICE_id_t* service_id, uint16_t offset, size_t size) {
	SERVICE_CLIENT_DATA_REQUEST_packet_t request;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Sending CLIENT DATA REQUEST for %d byte(s) at 0x%X of service \"%s\" to node %s...\n", size, offset, service_id, addr_to_str(to));
	copy_service_id(request.service_id, service_id);
	request.offset = offset;
	request.size = size;
	request.start_time = C_time();
	C_sendto(to, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_CLIENT_DATA_REQUEST, &request, sizeof(SERVICE_CLIENT_DATA_REQUEST_packet_t));

	cache_increment_age(service_id);
}

/**
 * @brief RUDP callback for SERVICE DATA UPDATE packets.
 *
 * @param parameter RUDP transmission result.
 */
static void send_SERVICE_CLIENT_DATA_UPDATE_callback(callback_parameter_t parameter) {
	RUDP_transmission_result_t* result = (RUDP_transmission_result_t*) parameter;

	if(result->error_code == C_ERROR_OK) {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: CLIENT DATA UPDATE was successful.\n");
		C_SERVICE_successful_data_updates++;
	} else {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: CLIENT DATA UPDATE failed (%d).\n", result->error_code);
		C_SERVICE_failed_data_updates++;
	}

	FREE(result);
}

/**
 * @brief Send SERVICE DATA UPDATE packet.
 *
 * @param to Address of destination node.
 * @param service_id ID of service of which data is to be updated.
 * @param offset Offset in service data.
 * @param size Update size in bytes.
 * @param data Pointer to new service data.
 */
static void send_SERVICE_CLIENT_DATA_UPDATE(C_addr_t to, const SERVICE_id_t* service_id, uint16_t offset, size_t size, const char* data) {
	SERVICE_CLIENT_DATA_UPDATE_packet_t update;
	RUDP_transmission_result_t* result = NULL;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Sending CLIENT DATA UPDATE for %d byte(s) at 0x%X of service \"%s\" (0x%X...) to node %s...\n", size, offset, service_id, (uint8_t) *data, addr_to_str(to));
	copy_service_id(update.service_id, service_id);
	update.offset = offset;
	MALLOC(result, RUDP_transmission_result_t);
	RUDP_sendto2(to, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_CLIENT_DATA_UPDATE, &update, sizeof(SERVICE_CLIENT_DATA_UPDATE_packet_t), data, size, send_SERVICE_CLIENT_DATA_UPDATE_callback, result);
}

/**
 * @brief RUDP callback for SERVICE STREAM REQUEST packets.
 *
 * @param parameter RUDP transmission result.
 */
static void send_SERVICE_CLIENT_STREAM_REQUEST_callback(callback_parameter_t parameter) {
	RUDP_transmission_result_t* result = (RUDP_transmission_result_t*) parameter;

	if(result->error_code == C_ERROR_OK) {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: CLIENT STREAM REQUEST was successful.\n");
		C_SERVICE_successful_stream_requests++;
	} else {
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: CLIENT STREAM REQUEST failed (%d).\n", result->error_code);
		C_SERVICE_failed_stream_requests++;
	}

	FREE(result);
}

/**
 * @brief Send SERVICE STREAM REQUEST packet.
 *
 * @param to Address of destination node.
 * @param service_id ID of service of which data is requested.
 * @param offset Offset in service data.
 * @param size Request size in bytes.
 * @param interval Interval between streamed packets in seconds.
 * @param duration Time the stream is to be active in seconds.
 */
static void send_SERVICE_CLIENT_STREAM_REQUEST(C_addr_t to, const SERVICE_id_t* service_id, uint16_t offset, size_t size, C_time_t interval, C_time_t duration) {
	SERVICE_CLIENT_STREAM_REQUEST_packet_t request;
	RUDP_transmission_result_t* result = NULL;

	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Sending CLIENT STREAM REQUEST for %d byte(s) at 0x%X of service \"%s\" at interval %.3fs for a duration of %.2fs to node %s...\n", size, offset, service_id, interval, duration, addr_to_str(to));
	copy_service_id(request.service_id, service_id);
	request.offset = offset;
	request.size = size;
	request.interval = interval;
	request.duration = duration;
	MALLOC(result, RUDP_transmission_result_t);
	RUDP_sendto(to, APP_TYPE_SERVICE, SERVICE_PACKET_TYPE_CLIENT_STREAM_REQUEST, &request, sizeof(SERVICE_CLIENT_STREAM_REQUEST_packet_t), send_SERVICE_CLIENT_STREAM_REQUEST_callback, result);

	cache_increment_age(service_id);
}

/**
 * @brief Handle incoming SERVICE DATA ITEM packet.
 *
 * Possibly redirect future requests to this service if server provided
 * corresponding information
 *
 * @param header Header of the incoming packet.
 * @param data_item SERVER DATA ITEM header.
 * @param data Requested data.
 * @param data_size Size of requested data.
 */
void handle_SERVICE_SERVER_DATA_ITEM(const packet_header_t* header, const SERVICE_SERVER_DATA_ITEM_packet_t* data_item, const char* data, size_t data_size) {
	C_time_t delay = C_time() - data_item->start_time;
	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Received %d byte(s) of SERVER DATA ITEM for service \"%s\" (0x%X...) from node %s after %.2fs.\n", data_size, data_item->service_id, (uint8_t) *data, addr_to_str(header->from), delay);

	if(data_item->redirect_addr == C_BROADCAST_ADDR) {
		cache_set(data_item->service_id, header->from);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Redirecting future request for service \"%s\" to node %s.\n", data_item->service_id, addr_to_str(data_item->redirect_addr));
		cache_set(data_item->service_id, data_item->redirect_addr);
	}

	C_SERVICE_successful_data_requests++;
	C_SERVICE_total_successful_data_request_delay += delay;
}

/**
 * @brief Handle incoming SERVICE STREAM ITEM packet.
 *
 * Possibly redirect future requests to this service if server provided
 * corresponding information
 *
 * @param header Header of the incoming packet.
 * @param stream_item SERVER DATA ITEM header.
 * @param data Requested data.
 * @param data_size Size of requested data.
 */
void handle_SERVICE_SERVER_STREAM_ITEM(const packet_header_t* header, const SERVICE_SERVER_STREAM_ITEM_packet_t* stream_item, const char* data, size_t data_size) {
	C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Received %d byte(s) of SERVER STREAM ITEM (0x%X...) from node %s.\n", header->payload_length, (uint8_t) *data, addr_to_str(header->from));

	if(stream_item->redirect_addr == C_BROADCAST_ADDR) {
		cache_set(stream_item->service_id, header->from);
	} else {
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Redirecting future request to service \"%s\" to node %s.\n", stream_item->service_id, addr_to_str(stream_item->redirect_addr));
		cache_set(stream_item->service_id, stream_item->redirect_addr);
	}

	C_SERVICE_received_stream_packets++;
}

/**
 * @brief Handle incoming SERVICE NOT AVAILABLE packet.
 *
 * Possibly redirect future requests to this service if server provided
 * corresponding information
 *
 * @param header Header of incoming packet.
 * @param not_available Data structure with redirection information.
 */
void handle_SERVICE_SERVER_NOT_AVAILABLE(const packet_header_t* header, const SERVICE_SERVER_NOT_AVAILABLE_packet_t* not_available) {
	C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Received of SERVER NOT AVAILABLE for service \"%s\" from node %s.\n", not_available->service_id, addr_to_str(header->from));

	if(not_available->redirect_addr != C_BROADCAST_ADDR) {
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Redirecting future requests to service \"%s\" to node %s.\n", not_available->service_id, addr_to_str(not_available->redirect_addr));
		cache_set(not_available->service_id, not_available->redirect_addr);
	} else {
		C_addr_t server;
		switch(cache_lookup(not_available->service_id, &server)) {
		case C_ERROR_OK:
			if(server == header->from) {
				C_DEBUG(C_DEBUG_LEVEL_WARN, "SERVICE: Removing cache entry for service \"%s\" pointing to node %s.\n", not_available->service_id, addr_to_str(server));
				cache_remove(not_available->service_id);
			}
			break;
		case C_ERROR_NOT_FOUND:
		case C_ERROR_TIMEOUT:
			break;
		default:
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
			abort();
		}
	}
}



/*
 * Control function.
 */

/**
 * @brief Callback for retried data request if SLP query was in progress.
 *
 * Proceed with data request if query was successful, abort otherwise
 *
 * @param parameter SLP query result.
 */
static void request_data_retry(callback_parameter_t parameter) {
	SLP_query_result_t* slp_query_result = (SLP_query_result_t*) parameter;
	SERVICE_data_request_t* request = (SERVICE_data_request_t*) slp_query_result->parameter;
	C_addr_t server;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Retrying request for %d byte(s) of service \"%s\"...\n", request->size, request->service_id);
	switch(cache_lookup(request->service_id, &server)) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Reusing cached server for service \"%s\"...\n", request->service_id);
		send_SERVICE_CLIENT_DATA_REQUEST(server, request->service_id, request->offset, request->size);
		break;
	case C_ERROR_NOT_FOUND:
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping data request...\n", request->service_id);
		C_SERVICE_failed_data_requests++;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
		abort();
	}
	FREE(request);
	list_free(&slp_query_result->service_instances);
	FREE(slp_query_result);
}

/**
 * @brief Data request callback function for SLP queries.
 *
 * Proceed with data request if query was succesful, schedule retry if query is
 * in progress, abort otherwise.
 *
 * @param parameter SLP query result
 */
static void request_data_callback(callback_parameter_t parameter) {
	C_addr_t server;
	SLP_query_result_t* slp_query_result = (SLP_query_result_t*) parameter;
	SERVICE_data_request_t* request = (SERVICE_data_request_t*) slp_query_result->parameter;

	switch(slp_query_result->error_code) {
	case C_ERROR_OK:
		server = selectBestServer(slp_query_result->service_instances);
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Using node %s as server for service \"%s\"...\n", addr_to_str(server), request->service_id);
		cache_set(request->service_id, server);
		send_SERVICE_CLIENT_DATA_REQUEST(server, request->service_id, request->offset, request->size);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping data request...\n", request->service_id);
		cache_tag_not_reachable(request->service_id);
		C_SERVICE_failed_data_requests++;
		break;
	case C_ERROR_IN_PROGRESS:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Query for service \"%s\" is already in progress, scheduling retry in %.2fs...\n", request->service_id, C_TIMEOUT);
		callback_add(request_data_retry, slp_query_result, C_TIMEOUT);
		return;	// Don't free the resources at the end of this function.
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: SERVICE_request_data() callback with error code %d.\n", slp_query_result->error_code);
		abort();
	}

	FREE(request);
	list_free(&slp_query_result->service_instances);
	FREE(slp_query_result);
}

/**
 * @brief Request specific service data of a service.
 *
 * Perform SLP query for service in case it is not contained in the service
 * cache.
 *
 * @param service_id ID of the service from which to request data.
 * @param offset Offset in service data.
 * @param size Size of requested service data.
 */
void request_data(const SERVICE_id_t* service_id, uint16_t offset, size_t size) {
	C_addr_t server;
	SLP_query_result_t* slp_query_result = NULL;
	SERVICE_data_request_t* request = NULL;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Requesting %d byte(s) at 0x%X of service \"%s\"...\n", size, offset, service_id);
	C_SERVICE_total_data_requests++;

	switch(cache_lookup(service_id, &server)) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Reusing cached server for service \"%s\"...\n", service_id);
		send_SERVICE_CLIENT_DATA_REQUEST(server, service_id, offset, size);
		break;
	case C_ERROR_NOT_FOUND:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: No server in cache for service \"%s\", starting SLP query...\n", service_id);
		MALLOC(slp_query_result, SLP_query_result_t);
		list_alloc(&slp_query_result->service_instances);
		MALLOC(request, SERVICE_data_request_t);
		copy_service_id(request->service_id, service_id);
		request->offset = offset;
		request->size = size;
		slp_query_result->parameter = request;
		SLP_locate_service(service_id, CLIENT_MIN_SERVICE_INSTANCES, request_data_callback, slp_query_result);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping data request...\n", service_id);
		C_SERVICE_failed_data_requests++;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
		abort();
	}
}

/**
 * @brief Callback for retried data update if SLP query was in progress.
 *
 * Proceed with data update if query was successful, abort otherwise
 *
 * @param parameter SLP query result.
 */
static void update_data_retry(callback_parameter_t parameter) {
	SLP_query_result_t* slp_query_result = (SLP_query_result_t*) parameter;
	SERVICE_data_update_t* update = (SERVICE_data_update_t*) slp_query_result->parameter;
	C_addr_t server;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Retrying update of %d byte(s) of service \"%s\"...\n", update->size, update->service_id);
	switch(cache_lookup(update->service_id, &server)) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Reusing cached server for service \"%s\"...\n", update->service_id);
		send_SERVICE_CLIENT_DATA_UPDATE(server, update->service_id, update->offset, update->size, update->data);
		break;
	case C_ERROR_NOT_FOUND:
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping data update...\n", update->service_id);
		C_SERVICE_failed_data_updates++;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
		abort();
	}
	FREE(update);
	list_free(&slp_query_result->service_instances);
	FREE(slp_query_result);
}

/**
 * @brief Data update callback function for SLP queries.
 *
 * Proceed with data update if query was succesful, schedule retry if query is
 * in progress, abort otherwise.
 *
 * @param parameter SLP query result
 */
static void update_data_callback(callback_parameter_t parameter) {
	C_addr_t server;
	SLP_query_result_t* slp_query_result = (SLP_query_result_t*) parameter;
	SERVICE_data_update_t* update = (SERVICE_data_update_t*) slp_query_result->parameter;

	switch(slp_query_result->error_code) {
	case C_ERROR_OK:
		server = selectBestServer(slp_query_result->service_instances);
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Using node %s as server for service \"%s\"...\n", addr_to_str(server), update->service_id);
		cache_set(update->service_id, server);
		send_SERVICE_CLIENT_DATA_UPDATE(server, update->service_id, update->offset, update->size, update->data);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping data update...\n", update->service_id);
		cache_tag_not_reachable(update->service_id);
		C_SERVICE_failed_data_updates++;
		break;
	case C_ERROR_IN_PROGRESS:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Query for service \"%s\" is already in progress, scheduling retry in %.2fs...\n", update->service_id, C_TIMEOUT);
		callback_add(update_data_retry, slp_query_result, C_TIMEOUT);
		return;	// Don't free the resources at the end of this function.
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: SERVICE_request_data() callback with error code %d.\n", slp_query_result->error_code);
		abort();
	}

	FREE(update);
	list_free(&slp_query_result->service_instances);
	FREE(slp_query_result);
}

/**
 * @brief Update specific service data of a service.
 *
 * Perform SLP query for service in case it is not contained in the service
 * cache.
 *
 * @param service_id ID of the service of which to update data.
 * @param offset Offset in service data.
 * @param size Size of service data to update.
 * @param data Pointer to new service data.
 */
void update_data(const SERVICE_id_t* service_id, uint16_t offset, size_t size, const char* data) {
	C_addr_t server;
	SLP_query_result_t* slp_query_result = NULL;
	SERVICE_data_update_t* update = NULL;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Updating %d byte(s) at 0x%X of service \"%s\"...\n", size, offset, service_id);
	C_SERVICE_total_data_updates++;

	switch(cache_lookup(service_id, &server)) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Reusing cached server for service \"%s\"...\n", service_id);
		send_SERVICE_CLIENT_DATA_UPDATE(server, service_id, offset, size, data);
		break;
	case C_ERROR_NOT_FOUND:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: No server in cache for service \"%s\", starting SLP query...\n", service_id);
		MALLOC(slp_query_result, SLP_query_result_t);
		list_alloc(&slp_query_result->service_instances);
		MALLOC(update, SERVICE_data_update_t);
		copy_service_id(update->service_id, service_id);
		update->offset = offset;
		update->size = size;
		update->data = data;
		slp_query_result->parameter = update;
		SLP_locate_service(service_id, CLIENT_MIN_SERVICE_INSTANCES, update_data_callback, slp_query_result);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping data update...\n", service_id);
		C_SERVICE_failed_data_updates++;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
		abort();
	}
}

/**
 * @brief Callback for retried stream request if SLP query was in progress.
 *
 * Proceed with stream request if query was successful, abort otherwise
 *
 * @param parameter SLP query result.
 */
static void request_stream_retry(callback_parameter_t parameter) {
	SLP_query_result_t* slp_query_result = (SLP_query_result_t*) parameter;
	SERVICE_stream_request_t* request = (SERVICE_stream_request_t*) slp_query_result->parameter;
	C_addr_t server;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Retrying request for %d byte(s) of service \"%s\" at interval %.2fs for a duration of %.2fs...\n", request->size, request->service_id, request->interval, request->duration);
	switch(cache_lookup(request->service_id, &server)) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Reusing cached server for service \"%s\"...\n", request->service_id);
		send_SERVICE_CLIENT_STREAM_REQUEST(server, request->service_id, request->offset, request->size, request->interval, request->duration);
		break;
	case C_ERROR_NOT_FOUND:
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping stream request...\n", request->service_id);
		C_SERVICE_failed_stream_requests++;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
		abort();
	}
	FREE(request);
	list_free(&slp_query_result->service_instances);
	FREE(slp_query_result);
}

/**
 * @brief Stream request callback function for SLP queries.
 *
 * Proceed with stream request if query was succesful, schedule retry if query
 * is in progress, abort otherwise.
 *
 * @param parameter SLP query result
 */
static void request_stream_callback(callback_parameter_t parameter) {
	C_addr_t server;
	SLP_query_result_t* slp_query_result = (SLP_query_result_t*) parameter;
	SERVICE_stream_request_t* request = (SERVICE_stream_request_t*) slp_query_result->parameter;

	switch(slp_query_result->error_code) {
	case C_ERROR_OK:
		server = selectBestServer(slp_query_result->service_instances);
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Using node %s as server for service \"%s\"...\n", addr_to_str(server), request->service_id);
		cache_set(request->service_id, server);
		send_SERVICE_CLIENT_STREAM_REQUEST(server, request->service_id, request->offset, request->size, request->interval, request->duration);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping stream request...\n", request->service_id);
		cache_tag_not_reachable(request->service_id);
		C_SERVICE_failed_stream_requests++;
		break;
	case C_ERROR_IN_PROGRESS:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Query for service \"%s\" is already in progress, scheduling retry in %.2fs...\n", request->service_id, C_TIMEOUT);
		callback_add(request_stream_retry, slp_query_result, C_TIMEOUT);
		return;	// Don't free the resources at the end of this function.
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: SERVICE_request_data() callback with error code %d.\n", slp_query_result->error_code);
		abort();
	}

	FREE(request);
	list_free(&slp_query_result->service_instances);
	FREE(slp_query_result);
}

/**
 * @brief Request a stream of specific service data of a service.
 *
 * Perform SLP query for service in case it is not contained in the service
 * cache.
 *
 * @param service_id ID of the service from which to request data.
 * @param offset Offset in service data.
 * @param size Size of requested service data.
 * @param interval Interval at which to send stream packets in seconds.
 * @param duration Time during which the stream is to be active in seconds.
 */
void request_stream(const SERVICE_id_t* service_id, uint16_t offset, size_t size, C_time_t interval, C_time_t duration) {
	C_addr_t server;
	SLP_query_result_t* slp_query_result = NULL;
	SERVICE_stream_request_t* request = NULL;

	C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Requesting a stream of %d byte(s) at 0x%X of service \"%s\" at interval %.2fs for a duration of %.2fs...\n", size, offset, service_id, interval, duration);
	C_SERVICE_total_stream_requests++;

	// NOTE: With the current implementation, we cannot tell different
	// streams apart. As far as counting packets is concerned, this
	// shouldn't be a problem because RUDP allows us to tell apart whether
	// we have lost single packets or a stream request was never received.
	C_SERVICE_expected_stream_packets = ((uint32_t) (duration / interval)) + 1;

	switch(cache_lookup(service_id, &server)) {
	case C_ERROR_OK:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: Reusing cached server for service \"%s\"...\n", service_id);
		send_SERVICE_CLIENT_STREAM_REQUEST(server, service_id, offset, size, interval, duration);
		break;
	case C_ERROR_NOT_FOUND:
		C_DEBUG(C_DEBUG_LEVEL_DEBUG, "SERVICE: No server in cache for service \"%s\", starting SLP query...\n", service_id);
		MALLOC(slp_query_result, SLP_query_result_t);
		list_alloc(&slp_query_result->service_instances);
		MALLOC(request, SERVICE_stream_request_t);
		copy_service_id(request->service_id, service_id);
		request->offset = offset;
		request->size = size;
		request->interval = interval;
		request->duration = duration;
		slp_query_result->parameter = request;
		SLP_locate_service(service_id, CLIENT_MIN_SERVICE_INSTANCES, request_stream_callback, slp_query_result);
		break;
	case C_ERROR_TIMEOUT:
		C_DEBUG(C_DEBUG_LEVEL_INFO, "SERVICE: Service \"%s\" is not reachable, dropping stream request...\n", service_id);
		C_SERVICE_failed_stream_requests++;
		break;
	default:
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "SERVICE: Cache lookup returned invalid error code.\n");
		abort();
	}
}
