/*
 * 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_sync.h>
#include <malloc.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/crc.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_messages.h"

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

/** Default UDP checksum computing.
 */
#define NET_DEFAULT_UDP_CHECKSUM_COMPUTING	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.
 *  Notifies the destination socket application.
 *  Releases the packet on error or send an ICMP error notification..
 *  @param device_id The device identifier. Ignored parameter.
 *  @param packet The received packet queue. Input/output parameter.
 *  @param receiver The target service. Ignored parameter.
 *  @param error The packet error reporting service. Prefixes the received packet. Input parameter.
 *  @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_received_msg( device_id_t device_id, packet_t packet, services_t receiver, services_t error );

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

/** Sends the port unreachable ICMP notification.
 *  Sends the first packet and releases all the others.
 *  Releases the packet queu on error.
 *  @param packet The packet to be send. Input parameter.
 *  @param error The packet error reporting service. Prefixes the received packet. Input parameter.
 */
void	udp_send_icmp_port_unreachable( packet_t packet, services_t error );

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

/** Processes the socket client messages.
 *  Runs until the client module disconnects.
 *  @param callid The message identifier. Input parameter.
 *  @param call The message parameters. Input parameter.
 *  @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 local_sockets The application local sockets. Input/output parameter.
 *  @param socket_id Socket identifier. Input parameter.
 *  @param addr The destination address. Input parameter.
 *  @param addrlen The address length. Input parameter.
 *  @param fragments The number of data fragments. Input parameter.
 *  @param flags Various send flags. Input parameter.
 *  @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, int flags );

/** Receives data to the socket.
 *  Handles the NET_SOCKET_RECVFROM message.
 *  Replies the source address as well.
 *  @param local_sockets The application local sockets. Input parameter.
 *  @param socket_id Socket identifier. Input parameter.
 *  @param flags Various receive flags. Input parameter.
 *  @param addrlen The source address length. Output parameter.
 *  @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 );

/*@}*/

/** Receives data from the socket into a packet.
 *  @param packet The new created packet. Output parameter.
 *  @param prefix Reserved packet data prefix length. Input parameter.
 *  @param addr The destination address. Input parameter.
 *  @param addrlen The address length. Input parameter.
 *  @returns Number of bytes received.
 *  @returns EINVAL if the client does not send data.
 *  @returns ENOMEM if there is not enough memory left.
 *  @returns Other error codes as defined for the ipc_data_read_finalize() function.
 */
int	socket_read_packet_data( packet_ref packet, size_t prefix, const struct sockaddr * addr, socklen_t addrlen );

/** Sets the address port.
 *  Supports AF_INET and AF_INET6 address families.
 *  @param addr The address to be updated. Input/output parameter.
 *  @param addrlen The address length. Input parameter.
 *  @param port The port to be set. Input parameter.
 *  @returns EOK on success.
 *  @returns EINVAL if the address length does not match the address family.
 *  @returns EAFNOSUPPORT if the address family is not supported.
 */
int	udp_set_address_port( struct sockaddr * addr, int addrlen, uint16_t port );

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

int udp_initialize( async_client_conn_t client_connection ){
	ERROR_DECLARE;

	measured_string_t	names[] = {{ "UDP_CHECKSUM_COMPUTING", 22 }};
	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 );
	if( udp_globals.icmp_phone < 0 ){
		return udp_globals.icmp_phone;
	}
	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;
	}
	ERROR_PROPAGATE( ip_packet_size_req( udp_globals.ip_phone, -1, & udp_globals.addr_len, & udp_globals.prefix, & udp_globals.content, & udp_globals.suffix ));
	ERROR_PROPAGATE( socket_ports_initialize( & udp_globals.sockets ));
	udp_globals.prefix += sizeof( udp_header_t );
	udp_globals.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;
	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' );
		}
		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 ){
	ERROR_DECLARE;

	size_t			length;
	size_t			offset;
	int				result;
	uint8_t *		data;
	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;

	if( error ){
		switch( error ){
			case SERVICE_ICMP:
				// process error
				// TODO remove debug dump
				// length = icmp_client_header_length( packet );
				result = icmp_client_process_packet( packet, & type, & code, NULL, NULL );
				if( result < 0 ){
					return udp_release_and_return( packet, result );
				}
				printf( "ICMP error %d (%d) in packet %d\n", type, code, packet_get_id( packet ) );
				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 < sizeof( udp_header_t ) + offset ){
		return udp_release_and_return( packet, NO_DATA );
	}
	data = packet_get_data( packet );
	if( ! data ){
		return udp_release_and_return( packet, NO_DATA );
	}
	// get udp header
	header = ( udp_header_ref )( data + offset );
	// find the destination socket
	socket = socket_ports_find( & udp_globals.sockets, ntohs( header->dest ));
	if( ! socket ){
		udp_send_icmp_port_unreachable( packet, error );
		return EADDRNOTAVAIL;
	}
	// trim after successful processing to be able to send an ICMP error message!
	ERROR_PROPAGATE( packet_trim( packet, offset, 0 ));
	// count the received packet fragments
	next_packet = packet;
	fragments = 0;
	total_length = ntohs( header->len );
	// compute header checksum if set
	if( header->check && ( ! 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->check = 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->check ){
				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->check ){
			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->check ){
		if( flip_checksum( compact_checksum( checksum ))){
			// TODO checksum error ICMP?
			// TODO remove debug dump
			printf("udp check failed %x => %x\n", header->check, flip_checksum( compact_checksum( checksum )));
			return udp_release_and_return( packet, EINVAL );
		}
	}
	// queue the received packet
	if( ERROR_OCCURRED( dyn_fifo_push( &( ** socket ).received, packet_get_id( packet ), SOCKET_MAX_RECEIVED_SIZE ))){
		return udp_release_and_return( packet, ERROR_CODE );
	}

	// notify the destination socket
	async_msg_2(( ** socket ).phone, NET_SOCKET_RECEIVED, ( ipcarg_t ) ( ** socket ).socket_id, ( 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:
			fibril_rwlock_read_lock( & udp_globals.lock );
			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 ));
			}
			fibril_rwlock_read_unlock( & udp_globals.lock );
			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;
	fibril_rwlock_t			lock;
	ipc_call_t				answer;
	int						answer_count;

	/*
	 * Accept the connection
	 *  - Answer the first IPC_M_CONNECT_ME_TO call.
	 */
	ipc_answer_0( callid, EOK );

	socket_cores_initialize( & local_sockets );
	fibril_rwlock_initialize( & lock );

	while( keep_on_going ){
		// refresh data
		refresh_answer( & answer, & answer_count );

		callid = async_get_call( & call );
//		printf( "message %d\n", IPC_GET_METHOD( * call ));

		switch( IPC_GET_METHOD( call )){
			case IPC_M_PHONE_HUNGUP:
				keep_on_going = false;
				res = EOK;
				break;
			case NET_SOCKET:
				fibril_rwlock_write_lock( & lock );
				res = socket_create( & local_sockets, app_phone, SOCKET_SET_SOCKET_ID( answer ));
				fibril_rwlock_write_unlock( & lock );
				* SOCKET_SET_HEADER_SIZE( answer ) = sizeof( udp_header_t );
				* SOCKET_SET_DATA_FRAGMENT_SIZE( answer ) = MAX_UDP_FRAGMENT_SIZE;
				answer_count = 3;
				break;
			case NET_SOCKET_BIND:
				res = data_receive(( void ** ) & addr, & addrlen );
				if( res == EOK ){
					fibril_rwlock_write_lock( & lock );
					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 );
					fibril_rwlock_write_unlock( & lock );
					free( addr );
				}
				break;
			case NET_SOCKET_SENDTO:
				res = data_receive(( void ** ) & addr, & addrlen );
				if( res == EOK ){
					fibril_rwlock_read_lock( & lock );
					fibril_rwlock_read_lock( & udp_globals.lock );
					res = udp_sendto_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), addr, addrlen, SOCKET_GET_DATA_FRAGMENTS( call ), SOCKET_GET_FLAGS( call ));
					fibril_rwlock_read_unlock( & udp_globals.lock );
					fibril_rwlock_read_unlock( & lock );
					free( addr );
				}
				break;
			case NET_SOCKET_RECVFROM:
				fibril_rwlock_read_lock( & lock );
				fibril_rwlock_read_lock( & udp_globals.lock );
				res = udp_recvfrom_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), SOCKET_GET_FLAGS( call ), & addrlen );
				fibril_rwlock_read_unlock( & udp_globals.lock );
				fibril_rwlock_read_unlock( & lock );
				if( res > 0 ){
					* SOCKET_SET_READ_DATA_LENGTH( answer ) = res;
					* SOCKET_SET_ADDRESS_LENGTH( answer ) = addrlen;
					answer_count = 2;
					res = EOK;
				}
				break;
			case NET_SOCKET_CLOSE:
				fibril_rwlock_write_lock( & lock );
				fibril_rwlock_write_lock( & udp_globals.lock );
				res = socket_destroy( udp_globals.net_phone, SOCKET_GET_SOCKET_ID( call ), & local_sockets, & udp_globals.sockets );
				fibril_rwlock_write_unlock( & udp_globals.lock );
				fibril_rwlock_write_unlock( & lock );
				break;
			case NET_SOCKET_GETSOCKOPT:
			case NET_SOCKET_SETSOCKOPT:
			default:
				res = ENOTSUP;
				break;
		}

//		printf( "res = %d\n", res );

		answer_call( callid, res, & answer, answer_count );
	}

	socket_cores_destroy( & local_sockets );

	return EOK;
}

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

	socket_core_ref			socket;
	struct sockaddr_in *	address_in;
	struct sockaddr_in6 *	address_in6;
	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;

	if( addrlen < sizeof( struct sockaddr )) return EINVAL;
	switch( addr->sa_family ){
		case AF_INET:
			if( addrlen != sizeof( struct sockaddr_in )) return EINVAL;
			address_in = ( struct sockaddr_in * ) addr;
			dest_port = address_in->sin_port;
			break;
		case AF_INET6:
			if( addrlen != sizeof( struct sockaddr_in6 )) return EINVAL;
			address_in6 = ( struct sockaddr_in6 * ) addr;
			dest_port = address_in6->sin6_port;
			break;
		default:
			return EAFNOSUPPORT;
	}

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

	// bind the socket to a random free port if not bound
	while( socket->port <= 0 ){
		// try to find a free port
		fibril_rwlock_read_unlock( & udp_globals.lock );
		fibril_rwlock_write_lock( & udp_globals.lock );
		if( socket->port <= 0 ){
			ERROR_PROPAGATE( socket_bind_free_port( & udp_globals.sockets, socket, UDP_FREE_PORTS_START, UDP_FREE_PORTS_END, udp_globals.last_used_port ));
			// 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 );
	}

	// TODO do not ask all the time
	ERROR_PROPAGATE( ip_packet_size_req( udp_globals.ip_phone, socket->device_id, & udp_globals.addr_len, & udp_globals.prefix, & udp_globals.content, & udp_globals.suffix ));

	// read the first packet fragment
	result = socket_read_packet_data( & packet, sizeof( udp_header_t ), 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 = socket_read_packet_data( & next_packet, 0, addr, addrlen );
		if( result < 0 ){
			return udp_release_and_return( packet, result );
		}
		packet = pq_add( packet, next_packet, index, 0 );
		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 = htons( socket->port );
	header->dest = htons( dest_port );
	header->len = htons( total_length + sizeof( udp_header_t ));
	header->check = 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 );
		}
		if( ERROR_OCCURRED( ip_client_set_pseudo_header_data_length( ip_header, headerlen, total_length + sizeof( udp_header_t )))){
			free( ip_header );
			return udp_release_and_return( packet, ERROR_CODE );
		}
/*//		TODO remove debug dump:
		uint8_t *	data;
		data = ip_header;
		printf( "ip_header:\tlength\t= %d\n\tdata\t= %.2hhX %.2hhX %.2hhX %.2hhX:%.2hhX %.2hhX %.2hhX %.2hhX:%.2hhX %.2hhX %.2hhX %.2hhX\n", headerlen, data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ], data[ 8 ], data[ 9 ], data[ 10 ], data[ 11 ] );
*/		checksum = compute_checksum( checksum, ip_header, headerlen );
		checksum = compute_checksum( checksum, ( uint8_t * ) header, sizeof( * header ));
		header->check = htons( flip_checksum( compact_checksum( checksum )));
		free( ip_header );
	}else{
		device_id = socket->device_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
	return ip_send_msg( udp_globals.ip_phone, device_id, packet, SERVICE_UDP, 0 );
}

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;
	packet_t		next_packet;
	uint8_t *		data;
	size_t			fragments;
	size_t *		lengths;
	size_t			index;
	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( udp_set_address_port( addr, result, ntohs( header->source )))){
		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 ));

	next_packet = pq_next( packet );
	if( ! next_packet ){
		// write all if only one fragment
		ERROR_PROPAGATE( data_reply( data + sizeof( udp_header_t ), packet_get_data_length( packet ) - sizeof( udp_header_t )));
		// store the total length
		length = packet_get_data_length( packet ) - sizeof( udp_header_t );
	}else{
		// count the packet fragments
		fragments = 1;
		next_packet = pq_next( packet );
		while(( next_packet = pq_next( next_packet ))){
			++ fragments;
		}
		// compute and store the fragment lengths
		lengths = ( size_t * ) malloc( sizeof( size_t ) * fragments + sizeof( size_t ));
		if( ! lengths ) return ENOMEM;
		lengths[ 0 ] = packet_get_data_length( packet ) - sizeof( udp_header_t );
		lengths[ fragments ] = lengths[ 0 ];
		next_packet = pq_next( packet );
		for( index = 1; index < fragments; ++ index ){
			lengths[ index ] = packet_get_data_length( next_packet );
			lengths[ fragments ] += lengths[ index ];
			next_packet = pq_next( packet );
		}while( next_packet );
		// write the fragment lengths
		ERROR_PROPAGATE( data_reply( lengths, sizeof( int ) * ( fragments + 1 )));
		// write the first fragment
		ERROR_PROPAGATE( data_reply( data + sizeof( udp_header_t ), lengths[ 0 ] ));
		next_packet = pq_next( packet );
		// write the rest of the fragments
		for( index = 1; index < fragments; ++ index ){
			ERROR_PROPAGATE( data_reply( packet_get_data( next_packet ), lengths[ index ] ));
			next_packet = pq_next( packet );
		}while( next_packet );
		// store the total length
		length = lengths[ fragments ];
		free( lengths );
	}
	// 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 socket_read_packet_data( packet_ref packet, size_t prefix, const struct sockaddr * addr, socklen_t addrlen ){
	ERROR_DECLARE;

	ipc_callid_t	callid;
	size_t			length;
	void *			data;

	// get the data length
	if( ! ipc_data_write_receive( & callid, & length )) return EINVAL;
	// get a new packet
	* packet = packet_get_4( udp_globals.net_phone, length, udp_globals.addr_len, prefix + udp_globals.prefix, udp_globals.suffix );
	if( ! packet ) return ENOMEM;
	// allocate space in the packet
	data = packet_suffix( * packet, length );
	if( ! data ){
		return udp_release_and_return( * packet, ENOMEM );
	}
	// read the data into the packet
	if( ERROR_OCCURRED( ipc_data_write_finalize( callid, data, length ))
	// set the packet destination address
	|| ERROR_OCCURRED( packet_set_addr( * packet, NULL, ( uint8_t * ) addr, addrlen ))){
		return udp_release_and_return( * packet, ERROR_CODE );
	}
	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;
}

void udp_send_icmp_port_unreachable( packet_t packet, services_t error ){
	packet_t	next;
	uint8_t *	src;
	int			length;

	// detach the first packet and release the others
	next = pq_detach( packet );
	if( next ){
		pq_release( udp_globals.net_phone, packet_get_id( next ));
	}
	length = packet_get_addr( packet, & src, NULL );
	if(( length > 0 )
	&& ( ! error )
	&& ( udp_globals.icmp_phone >= 0 )
	// set both addresses to the source one (avoids the source address deletion before setting the destination one)
	&& ( packet_set_addr( packet, src, src, ( size_t ) length ) == EOK )){
		icmp_destination_unreachable_msg( udp_globals.icmp_phone, ICMP_PORT_UNREACH, 0, packet );
	}else{
		udp_release_and_return( packet, EINVAL );
	}
}

int udp_set_address_port( struct sockaddr * addr, int addrlen, uint16_t port ){
	struct sockaddr_in *	address_in;
	struct sockaddr_in6 *	address_in6;
	size_t					length;

	if( addrlen < 0 ) return EINVAL;
	length = ( size_t ) addrlen;
	if( length < sizeof( struct sockaddr )) return EINVAL;
	switch( addr->sa_family ){
		case AF_INET:
			if( length != sizeof( struct sockaddr_in )) return EINVAL;
			address_in = ( struct sockaddr_in * ) addr;
			address_in->sin_port = port;
			return EOK;
		case AF_INET6:
			if( length != sizeof( struct sockaddr_in6 )) return EINVAL;
			address_in6 = ( struct sockaddr_in6 * ) addr;
			address_in6->sin6_port = port;
			return EOK;
		default:
			return EAFNOSUPPORT;
	}
}

/** @}
 */
