/*
 * Copyright (c) 2008 Lukas Mejdrech
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup udp
 *  @{
 */

/** @file
 *  UDP module implementation.
 *  @see udp.h
 */

#include <async.h>
#include <fibril_synch.h>
#include <malloc.h>
#include <stdio.h>

#include <ipc/ipc.h>
#include <ipc/services.h>

#include "../../err.h"
#include "../../messages.h"
#include "../../modules.h"

#include "../../structures/dynamic_fifo.h"
#include "../../structures/packet/packet_client.h"

#include "../../include/checksum.h"
#include "../../include/in.h"
#include "../../include/in6.h"
#include "../../include/inet.h"
#include "../../include/ip_client.h"
#include "../../include/ip_interface.h"
#include "../../include/ip_protocols.h"
#include "../../include/icmp_client.h"
#include "../../include/icmp_interface.h"
#include "../../include/net_interface.h"
#include "../../include/socket_codes.h"
#include "../../include/socket_errno.h"

#include "../../socket/socket_core.h"
#include "../../socket/socket_messages.h"

#include "../tl_common.h"
#include "../tl_messages.h"

#include "udp.h"
#include "udp_header.h"
#include "udp_module.h"

/** Default UDP checksum computing.
 */
#define NET_DEFAULT_UDP_CHECKSUM_COMPUTING	true

/** Default UDP autobind when sending via unbound sockets.
 */
#define NET_DEFAULT_UDP_AUTOBINDING	true

/** Maximum UDP fragment size.
 */
#define MAX_UDP_FRAGMENT_SIZE	65535

/** Free ports pool start.
 */
#define UDP_FREE_PORTS_START	1025

/** Free ports pool end.
 */
#define UDP_FREE_PORTS_END		65535

/** Processes the received UDP packet queue.
 *  Is used as an entry point from the underlying IP module.
 *  Locks the global lock and calls udp_process_packet() function.
 *  @param[in] device_id The receiving device identifier.
 *  @param[in,out] packet The received packet queue.
 *  @param receiver The target service. Ignored parameter.
 *  @param[in] error The packet error reporting service. Prefixes the received packet.
 *  @returns EOK on success.
 *  @returns Other error codes as defined for the udp_process_packet() function.
 */
int udp_received_msg(device_id_t device_id, packet_t packet, services_t receiver, services_t error);

/** Processes the received UDP packet queue.
 *  Notifies the destination socket application.
 *  Releases the packet on error or sends an ICMP error notification..
 *  @param[in] device_id The receiving device identifier.
 *  @param[in,out] packet The received packet queue.
 *  @param[in] error The packet error reporting service. Prefixes the received packet.
 *  @returns EOK on success.
 *  @returns EINVAL if the packet is not valid.
 *  @returns EINVAL if the stored packet address is not the an_addr_t.
 *  @returns EINVAL if the packet does not contain any data.
 *  @returns NO_DATA if the packet content is shorter than the user datagram header.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns EADDRNOTAVAIL if the destination socket does not exist.
 *  @returns Other error codes as defined for the ip_client_process_packet() function.
 */
int udp_process_packet(device_id_t device_id, packet_t packet, services_t error);

/** Releases the packet and returns the result.
 *  @param[in] packet The packet queue to be released.
 *  @param[in] result The result to be returned.
 *  @return The result parameter.
 */
int udp_release_and_return(packet_t packet, int result);

/** @name Socket messages processing functions
 */
/*@{*/

/** Processes the socket client messages.
 *  Runs until the client module disconnects.
 *  @param[in] callid The message identifier.
 *  @param[in] call The message parameters.
 *  @returns EOK on success.
 *  @see socket.h
 */
int udp_process_client_messages(ipc_callid_t callid, ipc_call_t call);

/** Sends data from the socket to the remote address.
 *  Binds the socket to a free port if not already connected/bound.
 *  Handles the NET_SOCKET_SENDTO message.
 *  Supports AF_INET and AF_INET6 address families.
 *  @param[in,out] local_sockets The application local sockets.
 *  @param[in] socket_id Socket identifier.
 *  @param[in] addr The destination address.
 *  @param[in] addrlen The address length.
 *  @param[in] fragments The number of data fragments.
 *  @param[out] data_fragment_size The data fragment size in bytes.
 *  @param[in] flags Various send flags.
 *  @returns EOK on success.
 *  @returns EAFNOTSUPPORT if the address family is not supported.
 *  @returns ENOTSOCK if the socket is not found.
 *  @returns EINVAL if the address is invalid.
 *  @returns ENOTCONN if the sending socket is not and cannot be bound.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns Other error codes as defined for the socket_read_packet_data() function.
 *  @returns Other error codes as defined for the ip_client_prepare_packet() function.
 *  @returns Other error codes as defined for the ip_send_msg() function.
 */
int udp_sendto_message(socket_cores_ref local_sockets, int socket_id, const struct sockaddr * addr, socklen_t addrlen, int fragments, size_t * data_fragment_size, int flags);

/** Receives data to the socket.
 *  Handles the NET_SOCKET_RECVFROM message.
 *  Replies the source address as well.
 *  @param[in] local_sockets The application local sockets.
 *  @param[in] socket_id Socket identifier.
 *  @param[in] flags Various receive flags.
 *  @param[out] addrlen The source address length.
 *  @returns The number of bytes received.
 *  @returns ENOTSOCK if the socket is not found.
 *  @returns NO_DATA if there are no received packets or data.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns EINVAL if the received address is not an IP address.
 *  @returns Other error codes as defined for the packet_translate() function.
 *  @returns Other error codes as defined for the data_reply() function.
 */
int udp_recvfrom_message(socket_cores_ref local_sockets, int socket_id, int flags, size_t * addrlen);

/*@}*/

/** UDP global data.
 */
udp_globals_t	udp_globals;

int udp_initialize(async_client_conn_t client_connection){
	ERROR_DECLARE;

	measured_string_t names[] = {{str_dup("UDP_CHECKSUM_COMPUTING"), 22}, {str_dup("UDP_AUTOBINDING"), 15}};
	measured_string_ref configuration;
	size_t count = sizeof(names) / sizeof(measured_string_t);
	char * data;

	fibril_rwlock_initialize(&udp_globals.lock);
	fibril_rwlock_write_lock(&udp_globals.lock);
	udp_globals.icmp_phone = icmp_connect_module(SERVICE_ICMP, ICMP_CONNECT_TIMEOUT);
	udp_globals.ip_phone = ip_bind_service(SERVICE_IP, IPPROTO_UDP, SERVICE_UDP, client_connection, udp_received_msg);
	if(udp_globals.ip_phone < 0){
		return udp_globals.ip_phone;
	}
	// read default packet dimensions
	ERROR_PROPAGATE(ip_packet_size_req(udp_globals.ip_phone, -1, &udp_globals.packet_dimension));
	ERROR_PROPAGATE(socket_ports_initialize(&udp_globals.sockets));
	if(ERROR_OCCURRED(packet_dimensions_initialize(&udp_globals.dimensions))){
		socket_ports_destroy(&udp_globals.sockets);
		return ERROR_CODE;
	}
	udp_globals.packet_dimension.prefix += sizeof(udp_header_t);
	udp_globals.packet_dimension.content -= sizeof(udp_header_t);
	udp_globals.last_used_port = UDP_FREE_PORTS_START - 1;
	// get configuration
	udp_globals.checksum_computing = NET_DEFAULT_UDP_CHECKSUM_COMPUTING;
	udp_globals.autobinding = NET_DEFAULT_UDP_AUTOBINDING;
	configuration = &names[0];
	ERROR_PROPAGATE(net_get_conf_req(udp_globals.net_phone, &configuration, count, &data));
	if(configuration){
		if(configuration[0].value){
			udp_globals.checksum_computing = (configuration[0].value[0] == 'y');
		}
		if(configuration[1].value){
			udp_globals.autobinding = (configuration[1].value[0] == 'y');
		}
		net_free_settings(configuration, data);
	}
	fibril_rwlock_write_unlock(&udp_globals.lock);
	return EOK;
}

int udp_received_msg(device_id_t device_id, packet_t packet, services_t receiver, services_t error){
	int result;

	fibril_rwlock_write_lock(&udp_globals.lock);
	result = udp_process_packet(device_id, packet, error);
	if(result != EOK){
		fibril_rwlock_write_unlock(&udp_globals.lock);
	}

	return result;
}

int udp_process_packet(device_id_t device_id, packet_t packet, services_t error){
	ERROR_DECLARE;

	size_t length;
	size_t offset;
	int result;
	udp_header_ref header;
	socket_core_ref socket;
	packet_t next_packet;
	size_t total_length;
	uint32_t checksum;
	int fragments;
	packet_t tmp_packet;
	icmp_type_t type;
	icmp_code_t code;
	ip_pseudo_header_ref ip_header;
	struct sockaddr * src;
	struct sockaddr * dest;
	packet_dimension_ref packet_dimension;

	if(error){
		switch(error){
			case SERVICE_ICMP:
				// ignore error
				// length = icmp_client_header_length(packet);
				// process error
				result = icmp_client_process_packet(packet, &type, &code, NULL, NULL);
				if(result < 0){
					return udp_release_and_return(packet, result);
				}
				length = (size_t) result;
				if(ERROR_OCCURRED(packet_trim(packet, length, 0))){
					return udp_release_and_return(packet, ERROR_CODE);
				}
				break;
			default:
				return udp_release_and_return(packet, ENOTSUP);
		}
	}
	// TODO process received ipopts?
	result = ip_client_process_packet(packet, NULL, NULL, NULL, NULL, NULL);
	if(result < 0){
		return udp_release_and_return(packet, result);
	}
	offset = (size_t) result;

	length = packet_get_data_length(packet);
	if(length <= 0){
		return udp_release_and_return(packet, EINVAL);
	}
	if(length < UDP_HEADER_SIZE + offset){
		return udp_release_and_return(packet, NO_DATA);
	}

	// trim all but UDP header
	if(ERROR_OCCURRED(packet_trim(packet, offset, 0))){
		return udp_release_and_return(packet, ERROR_CODE);
	}

	// get udp header
	header = (udp_header_ref) packet_get_data(packet);
	if(! header){
		return udp_release_and_return(packet, NO_DATA);
	}
	// find the destination socket
	socket = socket_port_find(&udp_globals.sockets, ntohs(header->destination_port), SOCKET_MAP_KEY_LISTENING, 0);
	if(! socket){
		if(tl_prepare_icmp_packet(udp_globals.net_phone, udp_globals.icmp_phone, packet, error) == EOK){
			icmp_destination_unreachable_msg(udp_globals.icmp_phone, ICMP_PORT_UNREACH, 0, packet);
		}
		return EADDRNOTAVAIL;
	}

	// count the received packet fragments
	next_packet = packet;
	fragments = 0;
	total_length = ntohs(header->total_length);
	// compute header checksum if set
	if(header->checksum && (! error)){
		result = packet_get_addr(packet, (uint8_t **) &src, (uint8_t **) &dest);
		if(result <= 0){
			return udp_release_and_return(packet, result);
		}
		if(ERROR_OCCURRED(ip_client_get_pseudo_header(IPPROTO_UDP, src, result, dest, result, total_length, &ip_header, &length))){
			return udp_release_and_return(packet, ERROR_CODE);
		}else{
			checksum = compute_checksum(0, ip_header, length);
			// the udp header checksum will be added with the first fragment later
			free(ip_header);
		}
	}else{
		header->checksum = 0;
		checksum = 0;
	}

	do{
		++ fragments;
		length = packet_get_data_length(next_packet);
		if(length <= 0){
			return udp_release_and_return(packet, NO_DATA);
		}
		if(total_length < length){
			if(ERROR_OCCURRED(packet_trim(next_packet, 0, length - total_length))){
				return udp_release_and_return(packet, ERROR_CODE);
			}
			// add partial checksum if set
			if(header->checksum){
				checksum = compute_checksum(checksum, packet_get_data(packet), packet_get_data_length(packet));
			}
			// relese the rest of the packet fragments
			tmp_packet = pq_next(next_packet);
			while(tmp_packet){
				next_packet = pq_detach(tmp_packet);
				pq_release(udp_globals.net_phone, packet_get_id(tmp_packet));
				tmp_packet = next_packet;
			}
			// exit the loop
			break;
		}
		total_length -= length;
		// add partial checksum if set
		if(header->checksum){
			checksum = compute_checksum(checksum, packet_get_data(packet), packet_get_data_length(packet));
		}
	}while((next_packet = pq_next(next_packet)) && (total_length > 0));

	// check checksum
	if(header->checksum){
		if(flip_checksum(compact_checksum(checksum)) != IP_CHECKSUM_ZERO){
			if(tl_prepare_icmp_packet(udp_globals.net_phone, udp_globals.icmp_phone, packet, error) == EOK){
				// checksum error ICMP
				icmp_parameter_problem_msg(udp_globals.icmp_phone, ICMP_PARAM_POINTER, ((size_t) ((void *) &header->checksum)) - ((size_t) ((void *) header)), packet);
			}
			return EINVAL;
		}
	}

	// queue the received packet
	if(ERROR_OCCURRED(dyn_fifo_push(&socket->received, packet_get_id(packet), SOCKET_MAX_RECEIVED_SIZE))
		|| ERROR_OCCURRED(tl_get_ip_packet_dimension(udp_globals.ip_phone, &udp_globals.dimensions, device_id, &packet_dimension))){
		return udp_release_and_return(packet, ERROR_CODE);
	}

	// notify the destination socket
	fibril_rwlock_write_unlock(&udp_globals.lock);
	async_msg_5(socket->phone, NET_SOCKET_RECEIVED, (ipcarg_t) socket->socket_id, packet_dimension->content, 0, 0, (ipcarg_t) fragments);
	return EOK;
}

int udp_message(ipc_callid_t callid, ipc_call_t * call, ipc_call_t * answer, int * answer_count){
	ERROR_DECLARE;

	packet_t packet;

	*answer_count = 0;
	switch(IPC_GET_METHOD(*call)){
		case NET_TL_RECEIVED:
			if(! ERROR_OCCURRED(packet_translate(udp_globals.net_phone, &packet, IPC_GET_PACKET(call)))){
				ERROR_CODE = udp_received_msg(IPC_GET_DEVICE(call), packet, SERVICE_UDP, IPC_GET_ERROR(call));
			}
			return ERROR_CODE;
		case IPC_M_CONNECT_TO_ME:
			return udp_process_client_messages(callid, * call);
	}
	return ENOTSUP;
}

int udp_process_client_messages(ipc_callid_t callid, ipc_call_t call){
	int res;
	bool keep_on_going = true;
	socket_cores_t local_sockets;
	int app_phone = IPC_GET_PHONE(&call);
	struct sockaddr * addr;
	size_t addrlen;
	ipc_call_t answer;
	int answer_count;
	packet_dimension_ref packet_dimension;

	/*
	 * Accept the connection
	 *  - Answer the first IPC_M_CONNECT_TO_ME call.
	 */
	res = EOK;
	answer_count = 0;

	// The client connection is only in one fibril and therefore no additional locks are needed.

	socket_cores_initialize(&local_sockets);

	while(keep_on_going){

		// answer the call
		answer_call(callid, res, &answer, answer_count);

		// refresh data
		refresh_answer(&answer, &answer_count);

		// get the next call
		callid = async_get_call(&call);

		// process the call
		switch(IPC_GET_METHOD(call)){
			case IPC_M_PHONE_HUNGUP:
				keep_on_going = false;
				res = EHANGUP;
				break;
			case NET_SOCKET:
				*SOCKET_SET_SOCKET_ID(answer) = SOCKET_GET_SOCKET_ID(call);
				res = socket_create(&local_sockets, app_phone, NULL, SOCKET_SET_SOCKET_ID(answer));
				if(res == EOK){
					if(tl_get_ip_packet_dimension(udp_globals.ip_phone, &udp_globals.dimensions, DEVICE_INVALID_ID, &packet_dimension) == EOK){
						*SOCKET_SET_DATA_FRAGMENT_SIZE(answer) = packet_dimension->content;
					}
//					*SOCKET_SET_DATA_FRAGMENT_SIZE(answer) = MAX_UDP_FRAGMENT_SIZE;
					*SOCKET_SET_HEADER_SIZE(answer) = UDP_HEADER_SIZE;
					answer_count = 3;
				}
				break;
			case NET_SOCKET_BIND:
				res = data_receive((void **) &addr, &addrlen);
				if(res == EOK){
					fibril_rwlock_write_lock(&udp_globals.lock);
					res = socket_bind(&local_sockets, &udp_globals.sockets, SOCKET_GET_SOCKET_ID(call), addr, addrlen, UDP_FREE_PORTS_START, UDP_FREE_PORTS_END, udp_globals.last_used_port);
					fibril_rwlock_write_unlock(&udp_globals.lock);
					free(addr);
				}
				break;
			case NET_SOCKET_SENDTO:
				res = data_receive((void **) &addr, &addrlen);
				if(res == EOK){
					fibril_rwlock_write_lock(&udp_globals.lock);
					res = udp_sendto_message(&local_sockets, SOCKET_GET_SOCKET_ID(call), addr, addrlen, SOCKET_GET_DATA_FRAGMENTS(call), SOCKET_SET_DATA_FRAGMENT_SIZE(answer), SOCKET_GET_FLAGS(call));
					if(res != EOK){
						fibril_rwlock_write_unlock(&udp_globals.lock);
					}else{
						answer_count = 2;
					}
					free(addr);
				}
				break;
			case NET_SOCKET_RECVFROM:
				fibril_rwlock_write_lock(&udp_globals.lock);
				res = udp_recvfrom_message(&local_sockets, SOCKET_GET_SOCKET_ID(call), SOCKET_GET_FLAGS(call), &addrlen);
				fibril_rwlock_write_unlock(&udp_globals.lock);
				if(res > 0){
					*SOCKET_SET_READ_DATA_LENGTH(answer) = res;
					*SOCKET_SET_ADDRESS_LENGTH(answer) = addrlen;
					answer_count = 3;
					res = EOK;
				}
				break;
			case NET_SOCKET_CLOSE:
				fibril_rwlock_write_lock(&udp_globals.lock);
				res = socket_destroy(udp_globals.net_phone, SOCKET_GET_SOCKET_ID(call), &local_sockets, &udp_globals.sockets, NULL);
				fibril_rwlock_write_unlock(&udp_globals.lock);
				break;
			case NET_SOCKET_GETSOCKOPT:
			case NET_SOCKET_SETSOCKOPT:
			default:
				res = ENOTSUP;
				break;
		}
	}

	// release the application phone
	ipc_hangup(app_phone);

	// release all local sockets
	socket_cores_release(udp_globals.net_phone, &local_sockets, &udp_globals.sockets, NULL);

	return res;
}

int udp_sendto_message(socket_cores_ref local_sockets, int socket_id, const struct sockaddr * addr, socklen_t addrlen, int fragments, size_t * data_fragment_size, int flags){
	ERROR_DECLARE;

	socket_core_ref socket;
	packet_t packet;
	packet_t next_packet;
	udp_header_ref header;
	int index;
	size_t total_length;
	int result;
	uint16_t dest_port;
	uint32_t checksum;
	ip_pseudo_header_ref ip_header;
	size_t headerlen;
	device_id_t device_id;
	packet_dimension_ref packet_dimension;

	ERROR_PROPAGATE(tl_get_address_port(addr, addrlen, &dest_port));

	socket = socket_cores_find(local_sockets, socket_id);
	if(! socket){
		return ENOTSOCK;
	}

	if((socket->port <= 0) && udp_globals.autobinding){
		// bind the socket to a random free port if not bound
//		do{
			// try to find a free port
//			fibril_rwlock_read_unlock(&udp_globals.lock);
//			fibril_rwlock_write_lock(&udp_globals.lock);
			// might be changed in the meantime
//			if(socket->port <= 0){
				if(ERROR_OCCURRED(socket_bind_free_port(&udp_globals.sockets, socket, UDP_FREE_PORTS_START, UDP_FREE_PORTS_END, udp_globals.last_used_port))){
//					fibril_rwlock_write_unlock(&udp_globals.lock);
//					fibril_rwlock_read_lock(&udp_globals.lock);
					return ERROR_CODE;
				}
				// set the next port as the search starting port number
				udp_globals.last_used_port = socket->port;
//			}
//			fibril_rwlock_write_unlock(&udp_globals.lock);
//			fibril_rwlock_read_lock(&udp_globals.lock);
			// might be changed in the meantime
//		}while(socket->port <= 0);
	}

	if(udp_globals.checksum_computing){
		if(ERROR_OCCURRED(ip_get_route_req(udp_globals.ip_phone, IPPROTO_UDP, addr, addrlen, &device_id, &ip_header, &headerlen))){
			return udp_release_and_return(packet, ERROR_CODE);
		}
		// get the device packet dimension
//		ERROR_PROPAGATE(tl_get_ip_packet_dimension(udp_globals.ip_phone, &udp_globals.dimensions, device_id, &packet_dimension));
	}
//	}else{
		// do not ask all the time
		ERROR_PROPAGATE(ip_packet_size_req(udp_globals.ip_phone, -1, &udp_globals.packet_dimension));
		packet_dimension = &udp_globals.packet_dimension;
//	}

	// read the first packet fragment
	result = tl_socket_read_packet_data(udp_globals.net_phone, &packet, UDP_HEADER_SIZE, packet_dimension, addr, addrlen);
	if(result < 0){
		return result;
	}
	total_length = (size_t) result;
	if(udp_globals.checksum_computing){
		checksum = compute_checksum(0, packet_get_data(packet), packet_get_data_length(packet));
	}else{
		checksum = 0;
	}
	// prefix the udp header
	header = PACKET_PREFIX(packet, udp_header_t);
	if(! header){
		return udp_release_and_return(packet, ENOMEM);
	}
	bzero(header, sizeof(*header));
	// read the rest of the packet fragments
	for(index = 1; index < fragments; ++ index){
		result = tl_socket_read_packet_data(udp_globals.net_phone, &next_packet, 0, packet_dimension, addr, addrlen);
		if(result < 0){
			return udp_release_and_return(packet, result);
		}
		if(ERROR_OCCURRED(pq_add(&packet, next_packet, index, 0))){
			return udp_release_and_return(packet, ERROR_CODE);
		}
		total_length += (size_t) result;
		if(udp_globals.checksum_computing){
			checksum = compute_checksum(checksum, packet_get_data(next_packet), packet_get_data_length(next_packet));
		}
	}
	// set the udp header
	header->source_port = htons((socket->port > 0) ? socket->port : 0);
	header->destination_port = htons(dest_port);
	header->total_length = htons(total_length + sizeof(*header));
	header->checksum = 0;
	if(udp_globals.checksum_computing){
		// update the pseudo header
		if(ERROR_OCCURRED(ip_client_set_pseudo_header_data_length(ip_header, headerlen, total_length + UDP_HEADER_SIZE))){
			free(ip_header);
			return udp_release_and_return(packet, ERROR_CODE);
		}
		// finish the checksum computation
		checksum = compute_checksum(checksum, ip_header, headerlen);
		checksum = compute_checksum(checksum, (uint8_t *) header, sizeof(*header));
		header->checksum = htons(flip_checksum(compact_checksum(checksum)));
		free(ip_header);
	}else{
		device_id = DEVICE_INVALID_ID;
	}
	// prepare the first packet fragment
	if(ERROR_OCCURRED(ip_client_prepare_packet(packet, IPPROTO_UDP, 0, 0, 0, 0))){
		return udp_release_and_return(packet, ERROR_CODE);
	}
	// send the packet
	fibril_rwlock_write_unlock(&udp_globals.lock);
	ip_send_msg(udp_globals.ip_phone, device_id, packet, SERVICE_UDP, 0);
	return EOK;
}

int udp_recvfrom_message(socket_cores_ref local_sockets, int socket_id, int flags, size_t * addrlen){
	ERROR_DECLARE;

	socket_core_ref socket;
	int packet_id;
	packet_t packet;
	udp_header_ref header;
	struct sockaddr * addr;
	size_t length;
	uint8_t * data;
	int result;

	// find the socket
	socket = socket_cores_find(local_sockets, socket_id);
	if(! socket){
		return ENOTSOCK;
	}
	// get the next received packet
	packet_id = dyn_fifo_value(&socket->received);
	if(packet_id < 0){
		return NO_DATA;
	}
	ERROR_PROPAGATE(packet_translate(udp_globals.net_phone, &packet, packet_id));
	// get udp header
	data = packet_get_data(packet);
	if(! data){
		pq_release(udp_globals.net_phone, packet_id);
		return NO_DATA;
	}
	header = (udp_header_ref) data;

	// set the source address port
	result = packet_get_addr(packet, (uint8_t **) &addr, NULL);
	if(ERROR_OCCURRED(tl_set_address_port(addr, result, ntohs(header->source_port)))){
		pq_release(udp_globals.net_phone, packet_id);
		return ERROR_CODE;
	}
	*addrlen = (size_t) result;
	// send the source address
	ERROR_PROPAGATE(data_reply(addr, * addrlen));

	// trim the header
	ERROR_PROPAGATE(packet_trim(packet, UDP_HEADER_SIZE, 0));

	// reply the packets
	ERROR_PROPAGATE(socket_reply_packets(packet, &length));

	// release the packet
	dyn_fifo_pop(&socket->received);
	pq_release(udp_globals.net_phone, packet_get_id(packet));
	// return the total length
	return (int) length;
}

int udp_release_and_return(packet_t packet, int result){
	pq_release(udp_globals.net_phone, packet_get_id(packet));
	return result;
}

/** @}
 */
