/*
 * 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 tcp
 *  @{
 */

/** @file
 *  TCP module implementation.
 *  @see tcp.h
 *  \todo
 */

#include <assert.h>
#include <async.h>
#include <fibril_sync.h>
#include <malloc.h>
//TODO remove stdio
#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 "../../include/tcp_codes.h"

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

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

#include "tcp.h"
#include "tcp_header.h"
#include "tcp_module.h"

/** The TCP window default value.
 */
#define NET_DEFAULT_TCP_WINDOW	10240

/** \todo
 */
#define NET_DEFAULT_TCP_INITIAL_TIMEOUT	3000000L

/** \todo
 */
#define NET_DEFAULT_TCP_TIME_WAIT_TIMEOUT	2000L

/** The initial outgoing sequence number.
 */
#define TCP_INITIAL_SEQUENCE_NUMBER		2999

/** Maximum TCP fragment size.
 */
#define MAX_TCP_FRAGMENT_SIZE	65535

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

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

/** \todo
 */
#define TCP_SYN_SENT_TIMEOUT	1000000L

/** The maximum number of timeouts in a row before singaling connection lost.
 */
#define TCP_MAX_TIMEOUTS		8

/** The number of acknowledgements before retransmit.
 */
#define TCP_FAST_RETRANSMIT_COUNT	3

/** \todo
 */
#define IS_IN_INTERVAL_OVERFLOW( lower, value, higher_equal )	(((( lower ) < ( value )) && ((( value ) <= ( higher_equal )) || (( higher_equal ) < ( lower )))) || ((( value ) <= ( higher_equal )) && (( higher_equal ) < ( lower ))))

/** Type definition of the TCP timeout.
 *  @see tcp_timeout
 */
typedef struct tcp_timeout	tcp_timeout_t;

/** Type definition of the TCP timeout pointer.
 *  @see tcp_timeout
 */
typedef tcp_timeout_t *	tcp_timeout_ref;

/** TCP reply timeout data.
 *  Used as a timeouting fibril argument.
 *  @see tcp_timeout()
 */
struct tcp_timeout{
	/** TCP global data are going to be read only.
	 */
	int					globals_read_only;
	/** Socket port.
	 */
	int					port;
	/** Local sockets.
	 */
	socket_cores_ref	local_sockets;
	/** Socket identifier.
	 */
	int					socket_id;
	/** Socket state.
	 */
	tcp_socket_state_t	state;
	/** Sent packet sequence number.
	 */
	int					sequence_number;
	/** Timeout in microseconds.
	 */
	suseconds_t			timeout;
	/** Port map key.
	 */
	char *				key;
	/** Port map key length.
	 */
	size_t				key_length;
};

/** 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	tcp_release_and_return( packet_t packet, int result );

int	tcp_get_packet_dimension( device_id_t device_id, packet_dimension_ref * packet_dimension );
void	tcp_prepare_operation_header( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, int synchronize, int finalize );
int	tcp_prepare_timeout( int ( * timeout_function )( void * tcp_timeout_t ), socket_core_ref socket, tcp_socket_data_ref socket_data, size_t sequence_number, tcp_socket_state_t state, suseconds_t timeout, int globals_read_only );
void	tcp_free_socket_data( socket_core_ref socket );
int	tcp_timeout( void * data );
int	tcp_release_after_timeout( void * data );
int	tcp_process_packet( device_id_t device_id, packet_t packet, services_t error );
int	tcp_connect_core( socket_core_ref socket, socket_cores_ref local_sockets, struct sockaddr * addr, socklen_t addrlen );
int	tcp_queue_prepare_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length );
int	tcp_queue_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length );
packet_t	tcp_get_packets_to_send( socket_core_ref socket, tcp_socket_data_ref socket_data );
void	tcp_send_packets( device_id_t device_id, packet_t packet );
void	tcp_process_acknowledgement( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header );
packet_t	tcp_send_prepare_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length, size_t sequence_number );
packet_t	tcp_prepare_copy( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length, size_t sequence_number );
void	tcp_retransmit_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, size_t sequence_number );
int	tcp_create_notification_packet( packet_t * packet, socket_core_ref socket, tcp_socket_data_ref socket_data, int synchronize, int finalize );
void	tcp_refresh_socket_data( tcp_socket_data_ref socket_data );
void	tcp_initialize_socket_data( tcp_socket_data_ref socket_data );
int	tcp_process_listen( socket_core_ref listening_socket, tcp_socket_data_ref listening_socket_data, tcp_header_ref header, packet_t packet, struct sockaddr * src, struct sockaddr * dest, size_t addrlen );
int	tcp_process_syn_sent( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, packet_t packet );
int	tcp_process_syn_received( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, packet_t packet );
int	tcp_process_established( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, packet_t packet, int fragments, size_t total_length );
int	tcp_queue_received_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, int fragments, size_t total_length );

int	tcp_received_msg( device_id_t device_id, packet_t packet, services_t receiver, services_t error );
int	tcp_process_client_messages( ipc_callid_t callid, ipc_call_t call );
int	tcp_listen_message( socket_cores_ref local_sockets, int socket_id, int backlog );
int	tcp_connect_message( socket_cores_ref local_sockets, int socket_id, struct sockaddr * addr, socklen_t addrlen );
int	tcp_recvfrom_message( socket_cores_ref local_sockets, int socket_id, int flags, size_t * addrlen );
int	tcp_send_message( socket_cores_ref local_sockets, int socket_id, int fragments, size_t data_fragment_size, int flags );
int	tcp_accept_message( socket_cores_ref local_sockets, int socket_id, size_t * addrlen );
int tcp_close_message( socket_cores_ref local_sockets, int socket_id );

DEVICE_MAP_IMPLEMENT( packet_dimensions, packet_dimension_t );

/** TCP global data.
 */
tcp_globals_t	tcp_globals;

/**	Initializes the module.
 */
int tcp_initialize( async_client_conn_t client_connection ){
	ERROR_DECLARE;

	assert( client_connection );
	fibril_rwlock_initialize( & tcp_globals.lock );
	fibril_rwlock_write_lock( & tcp_globals.lock );
	tcp_globals.icmp_phone = icmp_connect_module( SERVICE_ICMP );
	if( tcp_globals.icmp_phone < 0 ){
		return tcp_globals.icmp_phone;
	}
	tcp_globals.ip_phone = ip_bind_service( SERVICE_IP, IPPROTO_TCP, SERVICE_TCP, client_connection, tcp_received_msg );
	if( tcp_globals.ip_phone < 0 ){
		return tcp_globals.ip_phone;
	}
	ERROR_PROPAGATE( socket_ports_initialize( & tcp_globals.sockets ));
	if( ERROR_OCCURRED( packet_dimensions_initialize( & tcp_globals.dimensions ))){
		socket_ports_destroy( & tcp_globals.sockets );
		return ERROR_CODE;
	}
	tcp_globals.last_used_port = TCP_FREE_PORTS_START - 1;
	fibril_rwlock_write_unlock( & tcp_globals.lock );
	return EOK;
}

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

	if( receiver != SERVICE_TCP ) return EREFUSED;
	fibril_rwlock_write_lock( & tcp_globals.lock );
	if( ERROR_OCCURRED( tcp_process_packet( device_id, packet, error ))){
		fibril_rwlock_write_unlock( & tcp_globals.lock );
	}
	printf( "receive %d \n", ERROR_CODE );

	return ERROR_CODE;
}

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

	size_t				length;
	size_t				offset;
	int					result;
	tcp_header_ref		header;
	socket_core_ref 	socket;
	tcp_socket_data_ref	socket_data;
	packet_t			next_packet;
	size_t				total_length;
	uint32_t			checksum;
	int					fragments;
	icmp_type_t			type;
	icmp_code_t			code;
	struct sockaddr *	src;
	struct sockaddr *	dest;
	size_t				addrlen;

	printf( "p1 \n" );
	if( error ){
		switch( error ){
			case SERVICE_ICMP:
				// process error
				result = icmp_client_process_packet( packet, & type, & code, NULL, NULL );
				if( result < 0 ){
					return tcp_release_and_return( packet, result );
				}
				length = ( size_t ) result;
				if( ERROR_OCCURRED( packet_trim( packet, length, 0 ))){
					return tcp_release_and_return( packet, ERROR_CODE );
				}
				break;
			default:
				return tcp_release_and_return( packet, ENOTSUP );
		}
	}

	// TODO process received ipopts?
	result = ip_client_process_packet( packet, NULL, NULL, NULL, NULL, NULL );
//	printf("ip len %d\n", result );
	if( result < 0 ){
		return tcp_release_and_return( packet, result );
	}
	offset = ( size_t ) result;

	length = packet_get_data_length( packet );
//	printf("packet len %d\n", length );
	if( length <= 0 ){
		return tcp_release_and_return( packet, EINVAL );
	}
	if( length < sizeof( tcp_header_t ) + offset ){
		return tcp_release_and_return( packet, NO_DATA );
	}

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

	// get tcp header
	header = ( tcp_header_ref ) packet_get_data( packet );
	if( ! header ){
		return tcp_release_and_return( packet, NO_DATA );
	}
//	printf( "header len %d, port %d \n", TCP_HEADER_LENGTH( header ), ntohs( header->destination_port ));

	result = packet_get_addr( packet, ( uint8_t ** ) & src, ( uint8_t ** ) & dest );
	if( result <= 0 ){
		return tcp_release_and_return( packet, result );
	}
	addrlen = ( size_t ) result;

	if( ERROR_OCCURRED( tl_set_address_port( src, addrlen, ntohs( header->source_port )))){
		return tcp_release_and_return( packet, ERROR_CODE );
	}

	// find the destination socket
	socket = socket_port_find( & tcp_globals.sockets, ntohs( header->destination_port ), ( const char * ) src, addrlen );
	if( ! socket ){
//		printf("listening?\n");
		// find the listening destination socket
		socket = socket_port_find( & tcp_globals.sockets, ntohs( header->destination_port ), SOCKET_MAP_KEY_LISTENING, 0 );
		if( ! socket ){
			if( tl_prepare_icmp_packet( tcp_globals.net_phone, tcp_globals.icmp_phone, packet, error ) == EOK ){
				icmp_destination_unreachable_msg( tcp_globals.icmp_phone, ICMP_PORT_UNREACH, 0, packet );
			}
			return EADDRNOTAVAIL;
		}
	}
	printf("socket id %d\n", socket->socket_id );
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;
	assert( socket_data );

	// some data received, clear the timeout counter
	socket_data->timeout_count = 0;

	// count the received packet fragments
	next_packet = packet;
	fragments = 0;
	checksum = 0;
	total_length = 0;
	do{
		++ fragments;
		length = packet_get_data_length( next_packet );
		if( length <= 0 ){
			return tcp_release_and_return( packet, NO_DATA );
		}
		total_length += length;
		// add partial checksum if set
		if( ! error ){
			checksum = compute_checksum( checksum, packet_get_data( packet ), packet_get_data_length( packet ));
		}
	}while(( next_packet = pq_next( next_packet )));
//	printf( "fragments %d of %d bytes\n", fragments, total_length );

//	printf("lock?\n");
	fibril_rwlock_write_lock( socket_data->local_lock );
//	printf("locked\n");
	if( ! error ){
		if( socket_data->state == TCP_SOCKET_LISTEN ){
			if( socket_data->pseudo_header ){
				free( socket_data->pseudo_header );
				socket_data->pseudo_header = NULL;
				socket_data->headerlen = 0;
			}
			if( ERROR_OCCURRED( ip_client_get_pseudo_header( IPPROTO_TCP, src, addrlen, dest, addrlen, total_length, & socket_data->pseudo_header, & socket_data->headerlen ))){
				fibril_rwlock_write_unlock( socket_data->local_lock );
				return tcp_release_and_return( packet, ERROR_CODE );
			}
		}else if( ERROR_OCCURRED( ip_client_set_pseudo_header_data_length( socket_data->pseudo_header, socket_data->headerlen, total_length ))){
			fibril_rwlock_write_unlock( socket_data->local_lock );
			return tcp_release_and_return( packet, ERROR_CODE );
		}
		checksum = compute_checksum( checksum, socket_data->pseudo_header, socket_data->headerlen );
		if( flip_checksum( compact_checksum( checksum ))){
			printf( "checksum err %x -> %x\n", header->checksum, flip_checksum( compact_checksum( checksum )));
			fibril_rwlock_write_unlock( socket_data->local_lock );
			if( ! ERROR_OCCURRED( tl_prepare_icmp_packet( tcp_globals.net_phone, tcp_globals.icmp_phone, packet, error ))){
				// checksum error ICMP
				icmp_parameter_problem_msg( tcp_globals.icmp_phone, ICMP_PARAM_POINTER, (( size_t ) (( void * ) & header->checksum )) - (( size_t ) (( void * ) header )), packet );
			}
			return EINVAL;
		}
	}

	fibril_rwlock_read_unlock( & tcp_globals.lock );

	// TODO error reporting/handling
//	printf( "st %d\n", socket_data->state );
	switch( socket_data->state ){
		case TCP_SOCKET_LISTEN:
			ERROR_CODE = tcp_process_listen( socket, socket_data, header, packet, src, dest, addrlen );
			break;
		case TCP_SOCKET_SYN_RECEIVED:
			ERROR_CODE = tcp_process_syn_received( socket, socket_data, header, packet );
			break;
		case TCP_SOCKET_SYN_SENT:
			ERROR_CODE = tcp_process_syn_sent( socket, socket_data, header, packet );
			break;
		case TCP_SOCKET_FIN_WAIT_1:
			// ack changing the state to FIN_WAIT_2 gets processed later
		case TCP_SOCKET_FIN_WAIT_2:
			// fin changing state to LAST_ACK gets processed later
		case TCP_SOCKET_LAST_ACK:
			// ack releasing the socket get processed later
		case TCP_SOCKET_CLOSING:
			// ack releasing the socket gets processed later
		case TCP_SOCKET_ESTABLISHED:
			ERROR_CODE = tcp_process_established( socket, socket_data, header, packet, fragments, total_length );
			break;
		default:
			pq_release( tcp_globals.net_phone, packet_get_id( packet ));
	}

	if( ERROR_CODE != EOK ){
		printf( "process %d\n", ERROR_CODE );
		fibril_rwlock_write_unlock( socket_data->local_lock );
	}
	return EOK;
}

int tcp_process_established( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, packet_t packet, int fragments, size_t total_length ){
	ERROR_DECLARE;

	packet_t	next_packet;
	packet_t	tmp_packet;
	uint32_t	old_incoming;
	size_t		order;
	uint32_t	sequence_number;
	size_t		length;
	size_t		offset;
	uint32_t	new_sequence_number;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	assert( header );
	assert( packet );

	new_sequence_number = ntohl( header->sequence_number );
	old_incoming = socket_data->next_incoming;

	if( header->finalize ){
		socket_data->fin_incoming = new_sequence_number;
	}

//	printf( "pe %d < %d <= %d\n", new_sequence_number, socket_data->next_incoming, new_sequence_number + total_length );
	// trim begining if containing expected data
	if( IS_IN_INTERVAL_OVERFLOW( new_sequence_number, socket_data->next_incoming, new_sequence_number + total_length )){
		// get the acknowledged offset
		if( socket_data->next_incoming < new_sequence_number ){
			offset = new_sequence_number - socket_data->next_incoming;
		}else{
			offset = socket_data->next_incoming - new_sequence_number;
		}
//		printf( "offset %d\n", offset );
		new_sequence_number += offset;
		total_length -= offset;
		length = packet_get_data_length( packet );
		// trim the acknowledged data
		while( length <= offset ){
			// release the acknowledged packets
			next_packet = pq_next( packet );
			pq_release( tcp_globals.net_phone, packet_get_id( packet ));
			packet = next_packet;
			offset -= length;
			length = packet_get_data_length( packet );
		}
		if(( offset > 0 )
		&& ( ERROR_OCCURRED( packet_trim( packet, offset, 0 )))){
			return tcp_release_and_return( packet, ERROR_CODE );
		}
		assert( new_sequence_number == socket_data->next_incoming );
	}

	// release if overflowing the window
//	if( IS_IN_INTERVAL_OVERFLOW( socket_data->next_incoming + socket_data->window, new_sequence_number, new_sequence_number + total_length )){
//		return tcp_release_and_return( packet, EOVERFLOW );
//	}

/*
	// trim end if overflowing the window
	if( IS_IN_INTERVAL_OVERFLOW( new_sequence_number, socket_data->next_incoming + socket_data->window, new_sequence_number + total_length )){
		// get the allowed data length
		if( socket_data->next_incoming + socket_data->window < new_sequence_number ){
			offset = new_sequence_number - socket_data->next_incoming + socket_data->window;
		}else{
			offset = socket_data->next_incoming + socket_data->window - new_sequence_number;
		}
		next_packet = packet;
		// trim the overflowing data
		while( next_packet && ( offset > 0 )){
			length = packet_get_data_length( packet );
			if( length <= offset ){
				next_packet = pq_next( next_packet );
			}else if( ERROR_OCCURRED( packet_trim( next_packet, 0, length - offset ))){
				return tcp_release_and_return( packet, ERROR_CODE );
			}
			offset -= length;
			total_length -= length - offset;
		}
		// release the overflowing packets
		next_packet = pq_next( next_packet );
		if( next_packet ){
			tmp_packet = next_packet;
			next_packet = pq_next( next_packet );
			pq_insert_after( tmp_packet, next_packet );
			pq_release( tcp_globals.net_phone, packet_get_id( tmp_packet ));
		}
		assert( new_sequence_number + total_length == socket_data->next_incoming + socket_data->window );
	}
*/
	// the expected one arrived?
	if( new_sequence_number == socket_data->next_incoming ){
		printf("expected\n");
		// process acknowledgement
		tcp_process_acknowledgement( socket, socket_data, header );

		// remove the header
		total_length -= TCP_HEADER_LENGTH( header );
		if( ERROR_OCCURRED( packet_trim( packet, TCP_HEADER_LENGTH( header ), 0 ))){
			return tcp_release_and_return( packet, ERROR_CODE );
		}

		if( total_length ){
			ERROR_PROPAGATE( tcp_queue_received_packet( socket, socket_data, packet, fragments, total_length ));
		}else{
			total_length = 1;
		}
		socket_data->next_incoming = old_incoming + total_length;
		packet = socket_data->incoming;
		while( packet ){
			if( ERROR_OCCURRED( pq_get_order( socket_data->incoming, & order, NULL ))){
				// remove the corrupted packet
				next_packet = pq_detach( packet );
				if( packet == socket_data->incoming ){
					socket_data->incoming = next_packet;
				}
				pq_release( tcp_globals.net_phone, packet_get_id( packet ));
				packet = next_packet;
				continue;
			}
			sequence_number = ( uint32_t ) order;
			if( IS_IN_INTERVAL_OVERFLOW( sequence_number, old_incoming, socket_data->next_incoming )){
				// move to the next
				packet = pq_next( packet );
			// coninual data?
			}else if( IS_IN_INTERVAL_OVERFLOW( old_incoming, sequence_number, socket_data->next_incoming )){
				// detach the packet
				next_packet = pq_detach( packet );
				if( packet == socket_data->incoming ){
					socket_data->incoming = next_packet;
				}
				// get data length
				length = packet_get_data_length( packet );
				new_sequence_number = sequence_number + length;
				if( length <= 0 ){
					// remove the empty packet
					pq_release( tcp_globals.net_phone, packet_get_id( packet ));
					packet = next_packet;
					continue;
				}
				// exactly following
				if( sequence_number == socket_data->next_incoming ){
					// queue received data
					ERROR_PROPAGATE( tcp_queue_received_packet( socket, socket_data, packet, 1, packet_get_data_length( packet )));
					socket_data->next_incoming = new_sequence_number;
					packet = next_packet;
					continue;
				// at least partly following data?
				}else if( IS_IN_INTERVAL_OVERFLOW( sequence_number, socket_data->next_incoming, new_sequence_number )){
					if( socket_data->next_incoming < new_sequence_number ){
						length = new_sequence_number - socket_data->next_incoming;
					}else{
						length = socket_data->next_incoming - new_sequence_number;
					}
					if( ! ERROR_OCCURRED( packet_trim( packet, length, 0 ))){
						// queue received data
						ERROR_PROPAGATE( tcp_queue_received_packet( socket, socket_data, packet, 1, packet_get_data_length( packet )));
						socket_data->next_incoming = new_sequence_number;
						packet = next_packet;
						continue;
					}
				}
				// remove the duplicit or corrupted packet
				pq_release( tcp_globals.net_phone, packet_get_id( packet ));
				packet = next_packet;
				continue;
			}else{
				break;
			}
		}
	}else if( IS_IN_INTERVAL( socket_data->next_incoming, new_sequence_number, socket_data->next_incoming + socket_data->window )){
		printf("in window\n");
		// process acknowledgement
		tcp_process_acknowledgement( socket, socket_data, header );

		// remove the header
		total_length -= TCP_HEADER_LENGTH( header );
		if( ERROR_OCCURRED( packet_trim( packet, TCP_HEADER_LENGTH( header ), 0 ))){
			return tcp_release_and_return( packet, ERROR_CODE );
		}

		next_packet = pq_detach( packet );
		length = packet_get_data_length( packet );
		tmp_packet = pq_add( socket_data->incoming, packet, new_sequence_number, length );
		if( ! tmp_packet ){
			// remove the corrupted packets
			pq_release( tcp_globals.net_phone, packet_get_id( packet ));
			pq_release( tcp_globals.net_phone, packet_get_id( next_packet ));
		}else{
			socket_data->incoming = tmp_packet;
			while( next_packet ){
				new_sequence_number += length;
				tmp_packet = pq_detach( next_packet );
				length = packet_get_data_length( next_packet );
				if( ERROR_OCCURRED( pq_set_order( next_packet, new_sequence_number, length ))
				|| ERROR_OCCURRED( pq_insert_after( packet, next_packet ))){
					pq_release( tcp_globals.net_phone, packet_get_id( next_packet ));
				}
				next_packet = tmp_packet;
			}
		}
	}else{
		printf("unexpected\n");
		// release duplicite or restricted
		pq_release( tcp_globals.net_phone, packet_get_id( packet ));
	}

	// change state according to the acknowledging incoming fin
	if( IS_IN_INTERVAL_OVERFLOW( old_incoming, socket_data->fin_incoming, socket_data->next_incoming )){
		switch( socket_data->state ){
			case TCP_SOCKET_FIN_WAIT_1:
			case TCP_SOCKET_FIN_WAIT_2:
			case TCP_SOCKET_CLOSING:
				socket_data->state = TCP_SOCKET_CLOSING;
				break;
			//case TCP_ESTABLISHED:
			default:
				socket_data->state = TCP_SOCKET_CLOSE_WAIT;
				break;
		}
	}

	packet = tcp_get_packets_to_send( socket, socket_data );
	if( ! packet ){
		// create the notification packet
		ERROR_PROPAGATE( tcp_create_notification_packet( & packet, socket, socket_data, 0, 0 ));
		ERROR_PROPAGATE( tcp_queue_prepare_packet( socket, socket_data, packet, 1 ));
		packet = tcp_send_prepare_packet( socket, socket_data, packet, 1, socket_data->last_outgoing + 1 );
	}
	fibril_rwlock_write_unlock( socket_data->local_lock );
	// send the packet
	tcp_send_packets( socket_data->device_id, packet );
	return EOK;
}

int tcp_queue_received_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, int fragments, size_t total_length ){
	ERROR_DECLARE;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	assert( packet );
	assert( fragments >= 1 );
	assert( socket_data->window > total_length );

	// queue the received packet
	if( ERROR_OCCURRED( dyn_fifo_push( & socket->received, packet_get_id( packet ), SOCKET_MAX_RECEIVED_SIZE ))){
		return tcp_release_and_return( packet, ERROR_CODE );
	}

	// decrease the window size
	socket_data->window -= total_length;

	// notify the destination socket
	async_msg_5( socket->phone, NET_SOCKET_RECEIVED, ( ipcarg_t ) socket->socket_id, 0, 0, 0, ( ipcarg_t ) fragments );
	return EOK;
}

int tcp_process_syn_sent( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, packet_t packet ){
	ERROR_DECLARE;

	packet_t	next_packet;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	assert( header );
	assert( packet );

	if( header->synchronize ){
		// process acknowledgement
		tcp_process_acknowledgement( socket, socket_data, header );

		socket_data->next_incoming = ntohl( header->sequence_number ) + 1;
		// release additional packets
		next_packet = pq_detach( packet );
		if( next_packet ){
			pq_release( tcp_globals.net_phone, packet_get_id( next_packet ));
		}
		// trim if longer than the header
		if(( packet_get_data_length( packet ) > sizeof( * header ))
		&& ERROR_OCCURRED( packet_trim( packet, 0, packet_get_data_length( packet ) - sizeof( * header )))){
			return tcp_release_and_return( packet, ERROR_CODE );
		}
		tcp_prepare_operation_header( socket, socket_data, header, 0, 0 );
		fibril_mutex_lock( & socket_data->operation.mutex );
		socket_data->operation.result = tcp_queue_packet( socket, socket_data, packet, 1 );
		if( socket_data->operation.result == EOK ){
			socket_data->state = TCP_SOCKET_ESTABLISHED;
			packet = tcp_get_packets_to_send( socket, socket_data );
			if( packet ){
				fibril_rwlock_write_unlock( socket_data->local_lock );
				// send the packet
				tcp_send_packets( socket_data->device_id, packet );
				// signal the result
				fibril_condvar_signal( & socket_data->operation.condvar );
				fibril_mutex_unlock( & socket_data->operation.mutex );
				return EOK;
			}
		}
		fibril_mutex_unlock( & socket_data->operation.mutex );
	}
	return tcp_release_and_return( packet, EINVAL );
}

int tcp_process_listen( socket_core_ref listening_socket, tcp_socket_data_ref listening_socket_data, tcp_header_ref header, packet_t packet, struct sockaddr * src, struct sockaddr * dest, size_t addrlen ){
	ERROR_DECLARE;

	packet_t			next_packet;
	socket_core_ref		socket;
	tcp_socket_data_ref	socket_data;
	int					socket_id;
	int					listening_socket_id = listening_socket->socket_id;
	int					listening_port = listening_socket->port;

	assert( listening_socket );
	assert( listening_socket_data );
	assert( listening_socket->specific_data == listening_socket_data );
	assert( header );
	assert( packet );

//	printf( "syn %d\n", header->synchronize );
	if( header->synchronize ){
		socket_data = ( tcp_socket_data_ref ) malloc( sizeof( * socket_data ));
		if( ! socket_data ){
			return tcp_release_and_return( packet, ENOMEM );
		}else{
			tcp_initialize_socket_data( socket_data );
			socket_data->local_lock = listening_socket_data->local_lock;
			socket_data->local_sockets = listening_socket_data->local_sockets;
			socket_data->listening_socket_id = listening_socket->socket_id;

			socket_data->next_incoming = ntohl( header->sequence_number );
			socket_data->treshold = socket_data->next_incoming + ntohs( header->window );

			socket_data->addrlen = addrlen;
			socket_data->addr = malloc( socket_data->addrlen );
			if( ! socket_data->addr ){
				free( socket_data );
				return tcp_release_and_return( packet, ENOMEM );
			}
			memcpy( socket_data->addr, src, socket_data->addrlen );

			socket_data->dest_port = ntohs( header->source_port );
			if( ERROR_OCCURRED( tl_set_address_port( socket_data->addr, socket_data->addrlen, socket_data->dest_port ))){
				free( socket_data->addr );
				free( socket_data );
				pq_release( tcp_globals.net_phone, packet_get_id( packet ));
				return ERROR_CODE;
			}

//			printf( "addr %p\n", socket_data->addr, socket_data->addrlen );
			// create a socket
			if( ERROR_OCCURRED( socket_create( socket_data->local_sockets, listening_socket->phone, socket_data, & socket_id ))){
				free( socket_data->addr );
				free( socket_data );
				return tcp_release_and_return( packet, ERROR_CODE );
			}

			printf("new_sock %d\n", socket_id);
			socket_data->pseudo_header = listening_socket_data->pseudo_header;
			socket_data->headerlen = listening_socket_data->headerlen;
			listening_socket_data->pseudo_header = NULL;
			listening_socket_data->headerlen = 0;

			fibril_rwlock_write_unlock( socket_data->local_lock );
//			printf("list lg\n");
			fibril_rwlock_write_lock( & tcp_globals.lock );
//			printf("list locked\n");
			// find the destination socket
			listening_socket = socket_port_find( & tcp_globals.sockets, listening_port, SOCKET_MAP_KEY_LISTENING, 0 );
			if(( ! listening_socket ) || ( listening_socket->socket_id != listening_socket_id )){
				fibril_rwlock_write_unlock( & tcp_globals.lock );
				// a shadow may remain until app hangs up
				return tcp_release_and_return( packet, EOK/*ENOTSOCK*/ );
			}
//			printf("port %d\n", listening_socket->port );
			listening_socket_data = ( tcp_socket_data_ref ) listening_socket->specific_data;
			assert( listening_socket_data );

//			printf("list ll\n");
			fibril_rwlock_write_lock( listening_socket_data->local_lock );
//			printf("list locked\n");

			socket = socket_cores_find( listening_socket_data->local_sockets, socket_id );
			if( ! socket ){
				// where is the socket?!?
				fibril_rwlock_write_unlock( & tcp_globals.lock );
				return ENOTSOCK;
			}
			socket_data = ( tcp_socket_data_ref ) socket->specific_data;
			assert( socket_data );

//			uint8_t * data = socket_data->addr;
//			printf( "addr %d of %x %x %x %x-%x %x %x %x-%x %x %x %x-%x %x %x %x\n", socket_data->addrlen, data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ], data[ 4 ], data[ 5 ], data[ 6 ], data[ 7 ], data[ 8 ], data[ 9 ], data[ 10 ], data[ 11 ], data[ 12 ], data[ 13 ], data[ 14 ], data[ 15 ] );

			ERROR_CODE = socket_port_add( & tcp_globals.sockets, listening_port, socket, ( const char * ) socket_data->addr, socket_data->addrlen );
			assert( socket == socket_port_find( & tcp_globals.sockets, listening_port, ( const char * ) socket_data->addr, socket_data->addrlen ));
			//ERROR_CODE = socket_bind_free_port( & tcp_globals.sockets, socket, TCP_FREE_PORTS_START, TCP_FREE_PORTS_END, tcp_globals.last_used_port );
			//tcp_globals.last_used_port = socket->port;
//			printf("bound %d\n", socket->port );
			fibril_rwlock_write_unlock( & tcp_globals.lock );
			if( ERROR_CODE != EOK ){
				socket_destroy( tcp_globals.net_phone, socket->socket_id, socket_data->local_sockets, & tcp_globals.sockets, tcp_free_socket_data );
				return tcp_release_and_return( packet, ERROR_CODE );
			}

			socket_data->state = TCP_SOCKET_LISTEN;
			socket_data->next_incoming = ntohl( header->sequence_number ) + 1;
			// release additional packets
			next_packet = pq_detach( packet );
			if( next_packet ){
				pq_release( tcp_globals.net_phone, packet_get_id( next_packet ));
			}
			// trim if longer than the header
			if(( packet_get_data_length( packet ) > sizeof( * header ))
			&& ERROR_OCCURRED( packet_trim( packet, 0, packet_get_data_length( packet ) - sizeof( * header )))){
				socket_destroy( tcp_globals.net_phone, socket->socket_id, socket_data->local_sockets, & tcp_globals.sockets, tcp_free_socket_data );
				return tcp_release_and_return( packet, ERROR_CODE );
			}
			tcp_prepare_operation_header( socket, socket_data, header, 1, 0 );
			if( ERROR_OCCURRED( tcp_queue_packet( socket, socket_data, packet, 1 ))){
				socket_destroy( tcp_globals.net_phone, socket->socket_id, socket_data->local_sockets, & tcp_globals.sockets, tcp_free_socket_data );
				return ERROR_CODE;
			}
			packet = tcp_get_packets_to_send( socket, socket_data );
//			printf("send %d\n", packet_get_id( packet ));
			if( ! packet ){
				socket_destroy( tcp_globals.net_phone, socket->socket_id, socket_data->local_sockets, & tcp_globals.sockets, tcp_free_socket_data );
				return EINVAL;
			}else{
				socket_data->state = TCP_SOCKET_SYN_RECEIVED;
//				printf("unlock\n");
				fibril_rwlock_write_unlock( socket_data->local_lock );
				// send the packet
				tcp_send_packets( socket_data->device_id, packet );
				return EOK;
			}
		}
	}
	return tcp_release_and_return( packet, EINVAL );
}

int	tcp_process_syn_received( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, packet_t packet ){
	ERROR_DECLARE;

	socket_core_ref		listening_socket;
	tcp_socket_data_ref	listening_socket_data;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	assert( header );
	assert( packet );

	printf("syn_rec\n");
	if( header->acknowledge ){
		// process acknowledgement
		tcp_process_acknowledgement( socket, socket_data, header );

		socket_data->next_incoming = ntohl( header->sequence_number );// + 1;
		pq_release( tcp_globals.net_phone, packet_get_id( packet ));
		socket_data->state = TCP_SOCKET_ESTABLISHED;
		listening_socket = socket_cores_find( socket_data->local_sockets, socket_data->listening_socket_id );
		if( listening_socket ){
			listening_socket_data = ( tcp_socket_data_ref ) listening_socket->specific_data;
			assert( listening_socket_data );

			// queue the received packet
			if( ! ERROR_OCCURRED( dyn_fifo_push( & listening_socket->accepted, socket->socket_id, listening_socket_data->backlog ))){
				// notify the destination socket
				async_msg_5( socket->phone, NET_SOCKET_ACCEPTED, ( ipcarg_t ) listening_socket->socket_id, 0, 0, 0, ( ipcarg_t ) socket->socket_id );
				fibril_rwlock_write_unlock( socket_data->local_lock );
				return EOK;
			}
		}
		// send FIN
		socket_data->state = TCP_SOCKET_FIN_WAIT_1;

		// create the notification packet
		ERROR_PROPAGATE( tcp_create_notification_packet( & packet, socket, socket_data, 0, 1 ));

		// send the packet
		ERROR_PROPAGATE( tcp_queue_packet( socket, socket_data, packet, 1 ));

		// flush packets
		packet = tcp_get_packets_to_send( socket, socket_data );
		fibril_rwlock_write_unlock( socket_data->local_lock );
		if( packet ){
			// send the packet
			tcp_send_packets( socket_data->device_id, packet );
		}
		return EOK;
	}else{
		return tcp_release_and_return( packet, EINVAL );
	}
	return EINVAL;
}

void tcp_process_acknowledgement( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header ){
	size_t		number;
	size_t		length;
	packet_t	packet;
	packet_t	next;
	packet_t	acknowledged = NULL;
	packet_t	first;
	uint32_t	old;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	assert( header );

	if( header->acknowledge ){
		number = ntohl( header->acknowledgement_number );
		// if more data acknowledged
		if( number != socket_data->expected ){
			old = socket_data->expected;
			if( IS_IN_INTERVAL_OVERFLOW( old, socket_data->fin_outgoing, number )){
				switch( socket_data->state ){
					case TCP_SOCKET_FIN_WAIT_1:
						socket_data->state = TCP_SOCKET_FIN_WAIT_2;
						break;
					case TCP_SOCKET_LAST_ACK:
					case TCP_SOCKET_CLOSING:
						// fin acknowledged - release the socket in another fibril
						tcp_prepare_timeout( tcp_release_after_timeout, socket, socket_data, 0, TCP_SOCKET_TIME_WAIT, NET_DEFAULT_TCP_TIME_WAIT_TIMEOUT, true );
						break;
					default:
						break;
				}
			}
			// update the treshold if higher than set
			if( number + ntohs( header->window ) > socket_data->expected + socket_data->treshold ){
				socket_data->treshold = number + ntohs( header->window ) - socket_data->expected;
			}
			// set new expected sequence number
			socket_data->expected = number;
			socket_data->expected_count = 1;
			packet = socket_data->outgoing;
			while( pq_get_order( packet, & number, & length ) == EOK ){
				if( IS_IN_INTERVAL_OVERFLOW(( uint32_t ) old, ( uint32_t )( number + length ), ( uint32_t ) socket_data->expected )){
					next = pq_detach( packet );
					if( packet == socket_data->outgoing ){
						socket_data->outgoing = next;
					}
					// add to acknowledged or release
					first = pq_add( acknowledged, packet, 0, 0 );
					if( first ){
						acknowledged = first;
					}else{
						pq_release( tcp_globals.net_phone, packet_get_id( packet ));
					}
					packet = next;
				}else if( old < socket_data->expected ){
					break;
				}
			}
			// release acknowledged
			if( acknowledged ){
				pq_release( tcp_globals.net_phone, packet_get_id( acknowledged ));
			}
			return;
		// if the same as the previous time
		}else if( number == socket_data->expected ){
			// increase the counter
			++ socket_data->expected_count;
			if( socket_data->expected_count == TCP_FAST_RETRANSMIT_COUNT ){
				socket_data->expected_count = 1;
				// TODO retransmit lock
				//tcp_retransmit_packet( socket, socket_data, number );
			}
		}
	}
}

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

	packet_t	packet;

	assert( call );
	assert( answer );
	assert( answer_count );

	* answer_count = 0;
	switch( IPC_GET_METHOD( * call )){
		case NET_TL_RECEIVED:
			//fibril_rwlock_read_lock( & tcp_globals.lock );
			if( ! ERROR_OCCURRED( packet_translate( tcp_globals.net_phone, & packet, IPC_GET_PACKET( call )))){
				ERROR_CODE = tcp_received_msg( IPC_GET_DEVICE( call ), packet, SERVICE_TCP, IPC_GET_ERROR( call ));
			}
			//fibril_rwlock_read_unlock( & tcp_globals.lock );
			return ERROR_CODE;
		case IPC_M_CONNECT_TO_ME:
			return tcp_process_client_messages( callid, * call );
	}
	return ENOTSUP;
}

void tcp_refresh_socket_data( tcp_socket_data_ref socket_data ){
	assert( socket_data );

	bzero( socket_data, sizeof( * socket_data ));
	socket_data->state = TCP_SOCKET_INITIAL;
	socket_data->device_id = -1;
	socket_data->window = NET_DEFAULT_TCP_WINDOW;
	socket_data->treshold = socket_data->window;
	socket_data->last_outgoing = TCP_INITIAL_SEQUENCE_NUMBER;
	socket_data->timeout = NET_DEFAULT_TCP_INITIAL_TIMEOUT;
	socket_data->acknowledged = socket_data->last_outgoing;
	socket_data->next_outgoing = socket_data->last_outgoing + 1;
	socket_data->expected = socket_data->next_outgoing;
}

void tcp_initialize_socket_data( tcp_socket_data_ref socket_data ){
	assert( socket_data );

	tcp_refresh_socket_data( socket_data );
	fibril_mutex_initialize( & socket_data->operation.mutex );
	fibril_condvar_initialize( & socket_data->operation.condvar );
}

int tcp_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;
	tcp_socket_data_ref		socket_data;
	socket_core_ref			socket;

	/*
	 * 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:
				socket_data = ( tcp_socket_data_ref ) malloc( sizeof( * socket_data ));
				if( ! socket_data ){
					res = ENOMEM;
				}else{
					tcp_initialize_socket_data( socket_data );
					socket_data->local_lock = & lock;
					socket_data->local_sockets = & local_sockets;
					fibril_rwlock_write_lock( & lock );
					res = socket_create( & local_sockets, app_phone, socket_data, SOCKET_SET_SOCKET_ID( answer ));
					fibril_rwlock_write_unlock( & lock );
					if( res == EOK ){
						* SOCKET_SET_HEADER_SIZE( answer ) = sizeof( tcp_header_t );
						* SOCKET_SET_DATA_FRAGMENT_SIZE( answer ) = MAX_TCP_FRAGMENT_SIZE;
						answer_count = 3;
					}else{
						free( socket_data );
					}
				}
				break;
			case NET_SOCKET_BIND:
				res = data_receive(( void ** ) & addr, & addrlen );
				if( res == EOK ){
					fibril_rwlock_write_lock( & tcp_globals.lock );
					fibril_rwlock_write_lock( & lock );
					res = socket_bind( & local_sockets, & tcp_globals.sockets, SOCKET_GET_SOCKET_ID( call ), addr, addrlen, TCP_FREE_PORTS_START, TCP_FREE_PORTS_END, tcp_globals.last_used_port );
					if( res == EOK ){
						socket = socket_cores_find( & local_sockets, SOCKET_GET_SOCKET_ID( call ));
						if( socket ){
							socket_data = ( tcp_socket_data_ref ) socket->specific_data;
							assert( socket_data );
							socket_data->state = TCP_SOCKET_LISTEN;
						}
					}
					fibril_rwlock_write_unlock( & lock );
					fibril_rwlock_write_unlock( & tcp_globals.lock );
					free( addr );
				}
				break;
			case NET_SOCKET_LISTEN:
				fibril_rwlock_read_lock( & tcp_globals.lock );
//				fibril_rwlock_write_lock( & tcp_globals.lock );
				fibril_rwlock_write_lock( & lock );
				res = tcp_listen_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), SOCKET_GET_BACKLOG( call ));
				fibril_rwlock_write_unlock( & lock );
//				fibril_rwlock_write_unlock( & tcp_globals.lock );
				fibril_rwlock_read_unlock( & tcp_globals.lock );
				break;
			case NET_SOCKET_CONNECT:
				res = data_receive(( void ** ) & addr, & addrlen );
				if( res == EOK ){
					// the global lock may released in the tcp_connect_message() function
					fibril_rwlock_write_lock( & tcp_globals.lock );
					fibril_rwlock_write_lock( & lock );
					res = tcp_connect_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), addr, addrlen );
					if( res != EOK ){
						fibril_rwlock_write_unlock( & lock );
						fibril_rwlock_write_unlock( & tcp_globals.lock );
						free( addr );
					}
				}
				break;
			case NET_SOCKET_ACCEPT:
				fibril_rwlock_read_lock( & tcp_globals.lock );
				fibril_rwlock_write_lock( & lock );
				res = tcp_accept_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), & addrlen );
				fibril_rwlock_write_unlock( & lock );
				fibril_rwlock_read_unlock( & tcp_globals.lock );
				if( res > 0 ){
					* SOCKET_SET_SOCKET_ID( answer ) = res;
					* SOCKET_SET_ADDRESS_LENGTH( answer ) = addrlen;
					answer_count = 2;
				}
				break;
			case NET_SOCKET_SEND:
				fibril_rwlock_read_lock( & tcp_globals.lock );
				fibril_rwlock_write_lock( & lock );
				res = tcp_send_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), SOCKET_GET_DATA_FRAGMENTS( call ), SOCKET_GET_DATA_FRAGMENT_SIZE( call ), SOCKET_GET_FLAGS( call ));
				if( res != EOK ){
					fibril_rwlock_write_unlock( & lock );
					fibril_rwlock_read_unlock( & tcp_globals.lock );
				}
				break;
			case NET_SOCKET_SENDTO:
				res = data_receive(( void ** ) & addr, & addrlen );
				if( res == EOK ){
					fibril_rwlock_read_lock( & tcp_globals.lock );
					fibril_rwlock_write_lock( & lock );
					res = tcp_send_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), SOCKET_GET_DATA_FRAGMENTS( call ), SOCKET_GET_DATA_FRAGMENT_SIZE( call ), SOCKET_GET_FLAGS( call ));
					if( res != EOK ){
						fibril_rwlock_write_unlock( & lock );
						fibril_rwlock_read_unlock( & tcp_globals.lock );
					}
					free( addr );
				}
				break;
			case NET_SOCKET_RECV:
				fibril_rwlock_read_lock( & tcp_globals.lock );
				fibril_rwlock_write_lock( & lock );
				res = tcp_recvfrom_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), SOCKET_GET_FLAGS( call ), NULL );
				fibril_rwlock_write_unlock( & lock );
				fibril_rwlock_read_unlock( & tcp_globals.lock );
				if( res > 0 ){
					* SOCKET_SET_READ_DATA_LENGTH( answer ) = res;
					answer_count = 1;
					res = EOK;
				}
				break;
			case NET_SOCKET_RECVFROM:
				fibril_rwlock_read_lock( & tcp_globals.lock );
				fibril_rwlock_write_lock( & lock );
				res = tcp_recvfrom_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ), SOCKET_GET_FLAGS( call ), & addrlen );
				fibril_rwlock_write_unlock( & lock );
				fibril_rwlock_read_unlock( & tcp_globals.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( & tcp_globals.lock );
				fibril_rwlock_write_lock( & lock );
				res = tcp_close_message( & local_sockets, SOCKET_GET_SOCKET_ID( call ));
				if( res != EOK ){
					fibril_rwlock_write_unlock( & lock );
					fibril_rwlock_write_unlock( & tcp_globals.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 );
	}

	printf("release\n");
	// release all local sockets
	socket_cores_release( tcp_globals.net_phone, & local_sockets, & tcp_globals.sockets, tcp_free_socket_data );

	return EOK;
}

int tcp_timeout( void * data ){
	tcp_timeout_ref		timeout = data;
	int					keep_write_lock = false;
	socket_core_ref		socket;
	tcp_socket_data_ref	socket_data;

	assert( timeout );

	// sleep the given timeout
	async_usleep( timeout->timeout );
	// lock the globals
	if( timeout->globals_read_only ){
		fibril_rwlock_read_lock( & tcp_globals.lock );
	}else{
		fibril_rwlock_write_lock( & tcp_globals.lock );
	}
	// find the pending operation socket
	socket = socket_port_find( & tcp_globals.sockets, timeout->port, timeout->key, timeout->key_length );
	if( socket && ( socket->socket_id == timeout->socket_id )){
		socket_data = ( tcp_socket_data_ref ) socket->specific_data;
		assert( socket_data );
		if( socket_data->local_sockets == timeout->local_sockets ){
			fibril_rwlock_write_lock( socket_data->local_lock );
			if( timeout->sequence_number ){
				// increase the timeout counter;
				++ socket_data->timeout_count;
				if( socket_data->timeout_count == TCP_MAX_TIMEOUTS ){
					// TODO release as connection lost
					//tcp_refresh_socket_data( socket_data );
				}
				// retransmit
				// TODO enable retransmit
				//tcp_retransmit_packet( socket, socket_data, timeout->sequence_number );
				fibril_rwlock_write_unlock( socket_data->local_lock );
			}else{
				fibril_mutex_lock( & socket_data->operation.mutex );
				// set the timeout operation result if state not changed
				if( socket_data->state == timeout->state ){
					socket_data->operation.result = ETIMEOUT;
					// notify the main fibril
					fibril_condvar_signal( & socket_data->operation.condvar );
					// keep the global write lock
					keep_write_lock = true;
				}else{
					// operation is ok, do nothing
					// unlocking from now on, so the unlock order does not matter...
					fibril_rwlock_write_unlock( socket_data->local_lock );
				}
				fibril_mutex_unlock( & socket_data->operation.mutex );
			}
		}
	}
	// unlock only if no socket
	if( timeout->globals_read_only ){
		fibril_rwlock_read_unlock( & tcp_globals.lock );
	}else if( ! keep_write_lock ){
		// release if not desired
		fibril_rwlock_write_unlock( & tcp_globals.lock );
	}
	// release the timeout structure
	free( timeout );
	return EOK;
}

int tcp_release_after_timeout( void * data ){
	tcp_timeout_ref		timeout = data;
	socket_core_ref		socket;
	tcp_socket_data_ref	socket_data;
	fibril_rwlock_t *	local_lock;

	assert( timeout );

	// sleep the given timeout
	async_usleep( timeout->timeout );
	// lock the globals
	fibril_rwlock_write_lock( & tcp_globals.lock );
	// find the pending operation socket
	socket = socket_port_find( & tcp_globals.sockets, timeout->port, timeout->key, timeout->key_length );
	if( socket && ( socket->socket_id == timeout->socket_id )){
		socket_data = ( tcp_socket_data_ref ) socket->specific_data;
		assert( socket_data );
		if( socket_data->local_sockets == timeout->local_sockets ){
			local_lock = socket_data->local_lock;
			fibril_rwlock_write_lock( local_lock );
			socket_destroy( tcp_globals.net_phone, timeout->socket_id, timeout->local_sockets, & tcp_globals.sockets, tcp_free_socket_data );
			fibril_rwlock_write_unlock( local_lock );
		}
	}
	// unlock the globals
	fibril_rwlock_write_unlock( & tcp_globals.lock );
	// release the timeout structure
	free( timeout );
	return EOK;
}

void tcp_retransmit_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, size_t sequence_number ){
	packet_t	packet;
	packet_t	copy;
	size_t		data_length;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	// sent packet?
	packet = pq_find( socket_data->outgoing, sequence_number );
	printf("retransmit %d\n", packet_get_id( packet ));
	if( packet ){
		pq_get_order( packet, NULL, & data_length );
		copy = tcp_prepare_copy( socket, socket_data, packet, data_length, sequence_number );
		fibril_rwlock_write_unlock( socket_data->local_lock );
//		printf( "r send %d\n", packet_get_id( packet ));
		if( copy ){
			tcp_send_packets( socket_data->device_id, copy );
		}
	}else{
		fibril_rwlock_write_unlock( socket_data->local_lock );
	}
}

int tcp_listen_message( socket_cores_ref local_sockets, int socket_id, int backlog ){
	socket_core_ref		socket;
	tcp_socket_data_ref	socket_data;

	assert( local_sockets );

	if( backlog < 0 ) return EINVAL;
	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	// get the socket specific data
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;
	assert( socket_data );
	// set the backlog
	socket_data->backlog = backlog;
	return EOK;
}

int tcp_connect_message( socket_cores_ref local_sockets, int socket_id, struct sockaddr * addr, socklen_t addrlen ){
	ERROR_DECLARE;

	socket_core_ref			socket;

	assert( local_sockets );
	assert( addr );
	assert( addrlen > 0 );

	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	if( ERROR_OCCURRED( tcp_connect_core( socket, local_sockets, addr, addrlen ))){
		tcp_free_socket_data( socket );
		// unbind if bound
		if( socket->port > 0 ){
			socket_ports_exclude( & tcp_globals.sockets, socket->port );
			socket->port = 0;
		}
	}
	return ERROR_CODE;
}

int tcp_connect_core( socket_core_ref socket, socket_cores_ref local_sockets, struct sockaddr * addr, socklen_t addrlen ){
	ERROR_DECLARE;

	tcp_socket_data_ref		socket_data;
	packet_t				packet;

	assert( socket );
	assert( addr );
	assert( addrlen > 0 );

	// get the socket specific data
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	if(( socket_data->state != TCP_SOCKET_INITIAL )
	&& (( socket_data->state != TCP_SOCKET_LISTEN ) || ( socket->port <= 0 ))){
		return EINVAL;
	}
	// get the destination port
	ERROR_PROPAGATE( tl_get_address_port( addr, addrlen, & socket_data->dest_port ));
	if( socket->port <= 0 ){
		// try to find a free port
		ERROR_PROPAGATE( socket_bind_free_port( & tcp_globals.sockets, socket, TCP_FREE_PORTS_START, TCP_FREE_PORTS_END, tcp_globals.last_used_port ));
		// set the next port as the search starting port number
		tcp_globals.last_used_port = socket->port;
	}
	ERROR_PROPAGATE( ip_get_route_req( tcp_globals.ip_phone, IPPROTO_TCP, addr, addrlen, & socket_data->device_id, & socket_data->pseudo_header, & socket_data->headerlen ));

	// create the notification packet
	ERROR_PROPAGATE( tcp_create_notification_packet( & packet, socket, socket_data, 1, 0 ));

	// unlock the globals and wait for an operation
	fibril_rwlock_write_unlock( & tcp_globals.lock );

	socket_data->addr = addr;
	socket_data->addrlen = addrlen;
	// send the packet
	if( ERROR_OCCURRED( tcp_queue_packet( socket, socket_data, packet, 1 ))
	|| ERROR_OCCURRED( tcp_prepare_timeout( tcp_timeout, socket, socket_data, 0, TCP_SOCKET_INITIAL, NET_DEFAULT_TCP_INITIAL_TIMEOUT, false ))){
		socket_data->addr = NULL;
		socket_data->addrlen = 0;
		fibril_rwlock_write_lock( & tcp_globals.lock );
	}else{
		packet = tcp_get_packets_to_send( socket, socket_data );
		if( packet ){
			fibril_mutex_lock( & socket_data->operation.mutex );
			fibril_rwlock_write_unlock( socket_data->local_lock );
			// send the packet
			printf( "connecting %d\n", packet_get_id( packet ));
			tcp_send_packets( socket_data->device_id, packet );
			// wait for a reply
			fibril_condvar_wait( & socket_data->operation.condvar, & socket_data->operation.mutex );
			ERROR_CODE = socket_data->operation.result;
			if( ERROR_CODE != EOK ){
				socket_data->addr = NULL;
				socket_data->addrlen = 0;
			}
		}else{
			socket_data->addr = NULL;
			socket_data->addrlen = 0;
			ERROR_CODE = EINTR;
		}
	}

	fibril_mutex_unlock( & socket_data->operation.mutex );

	// return the result
	return ERROR_CODE;
}

int tcp_queue_prepare_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length ){
	ERROR_DECLARE;

	tcp_header_ref	header;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	// get tcp header
	header = ( tcp_header_ref ) packet_get_data( packet );
	if( ! header ) return NO_DATA;
	header->destination_port = htons( socket_data->dest_port );
	header->source_port = htons( socket->port );
	header->sequence_number = htonl( socket_data->next_outgoing );
	if( ERROR_OCCURRED( packet_set_addr( packet, NULL, ( uint8_t * ) socket_data->addr, socket_data->addrlen ))){
		return tcp_release_and_return( packet, EINVAL );
	}
	// remember the outgoing FIN
	if( header->finalize ){
		socket_data->fin_outgoing = socket_data->next_outgoing;
	}
	return EOK;
}

int	tcp_queue_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length ){
	ERROR_DECLARE;
	packet_t		first;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	ERROR_PROPAGATE( tcp_queue_prepare_packet( socket, socket_data, packet, data_length ));

	first = pq_add( socket_data->outgoing, packet, socket_data->next_outgoing, data_length );
	if( ! first ){
		return tcp_release_and_return( packet, EINVAL );
	}
	socket_data->outgoing = first;
	socket_data->next_outgoing += data_length;
	return EOK;
}

packet_t tcp_get_packets_to_send( socket_core_ref socket, tcp_socket_data_ref socket_data ){
	ERROR_DECLARE;

	packet_t		packet;
	packet_t		copy;
	packet_t		sending = NULL;
	packet_t		previous = NULL;
	size_t			data_length;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	packet = pq_find( socket_data->outgoing, socket_data->last_outgoing + 1 );
	while( packet ){
		pq_get_order( packet, NULL, & data_length );
		// send only if fits into the window
		// respecting the possible overflow
		if( IS_IN_INTERVAL_OVERFLOW(( uint32_t ) socket_data->last_outgoing, ( uint32_t )( socket_data->last_outgoing + data_length ), ( uint32_t )( socket_data->expected + socket_data->treshold ))){
			copy = tcp_prepare_copy( socket, socket_data, packet, data_length, socket_data->last_outgoing + 1 );
			if( ! copy ){
				return sending;
			}
			if( ! sending ){
				sending = copy;
			}else{
				if( ERROR_OCCURRED( pq_insert_after( previous, copy ))){
					pq_release( tcp_globals.net_phone, packet_get_id( copy ));
					return sending;
				}
			}
			previous = copy;
			packet = pq_next( packet );
			// overflow occurred ?
			if(( ! packet ) && ( socket_data->last_outgoing > socket_data->next_outgoing )){
				printf("gpts overflow\n");
				// continue from the beginning
				packet = socket_data->outgoing;
			}
			socket_data->last_outgoing += data_length;
		}else{
			break;
		}
	}
	return sending;
}

packet_t tcp_send_prepare_packet( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length, size_t sequence_number ){
	ERROR_DECLARE;

	tcp_header_ref	header;
	uint32_t		checksum;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	// adjust the pseudo header
	if( ERROR_OCCURRED( ip_client_set_pseudo_header_data_length( socket_data->pseudo_header, socket_data->headerlen, packet_get_data_length( packet )))){
		pq_release( tcp_globals.net_phone, packet_get_id( packet ));
		return NULL;
	}

	// get the header
	header = ( tcp_header_ref ) packet_get_data( packet );
	if( ! header ){
		pq_release( tcp_globals.net_phone, packet_get_id( packet ));
		return NULL;
	}
	assert( ntohl( header->sequence_number ) == sequence_number );

	// adjust the header
	if( socket_data->next_incoming ){
		header->acknowledgement_number = htonl( socket_data->next_incoming );
		header->acknowledge = 1;
	}
	header->window = htons( socket_data->window );

	// checksum
	header->checksum = 0;
	checksum = compute_checksum( 0, socket_data->pseudo_header, socket_data->headerlen );
	checksum = compute_checksum( checksum, ( uint8_t * ) packet_get_data( packet ), packet_get_data_length( packet ));
	header->checksum = htons( flip_checksum( compact_checksum( checksum )));
	// prepare the packet
	if( ERROR_OCCURRED( ip_client_prepare_packet( packet, IPPROTO_TCP, 0, 0, 0, 0 ))
	// prepare the timeout
	|| ERROR_OCCURRED( tcp_prepare_timeout( tcp_timeout, socket, socket_data, sequence_number, socket_data->state, socket_data->timeout, true ))){
		pq_release( tcp_globals.net_phone, packet_get_id( packet ));
		return NULL;
	}
	return packet;
}

packet_t tcp_prepare_copy( socket_core_ref socket, tcp_socket_data_ref socket_data, packet_t packet, size_t data_length, size_t sequence_number ){
	packet_t		copy;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	// make a copy of the packet
	copy = packet_get_copy( tcp_globals.net_phone, packet );
	if( ! copy ) return NULL;

	return tcp_send_prepare_packet( socket, socket_data, copy, data_length, sequence_number );
}

void tcp_send_packets( device_id_t device_id, packet_t packet ){
	packet_t	next;

	while( packet ){
		next = pq_detach( packet );
		ip_send_msg( tcp_globals.ip_phone, device_id, packet, SERVICE_TCP, 0 );
		packet = next;
	}
}

int tcp_get_packet_dimension( device_id_t device_id, packet_dimension_ref * packet_dimension ){
	ERROR_DECLARE;

	assert( packet_dimension );

	* packet_dimension = packet_dimensions_find( & tcp_globals.dimensions, device_id );
	if( ! * packet_dimension ){
		// ask for and remember them if not found
		* packet_dimension = malloc( sizeof( ** packet_dimension ));
		if( ! * packet_dimension ) return ENOMEM;
		if( ERROR_OCCURRED( ip_packet_size_req( tcp_globals.ip_phone, device_id, & ( ** packet_dimension ).addr_len, & ( ** packet_dimension ).prefix, & ( ** packet_dimension ).content, & ( ** packet_dimension ).suffix ))){
			free( * packet_dimension );
			return ERROR_CODE;
		}
		ERROR_CODE = packet_dimensions_add( & tcp_globals.dimensions, device_id, * packet_dimension );
		if( ERROR_CODE < 0 ){
			free( * packet_dimension );
			return ERROR_CODE;
		}
	}
	return EOK;
}

void tcp_prepare_operation_header( socket_core_ref socket, tcp_socket_data_ref socket_data, tcp_header_ref header, int synchronize, int finalize ){
	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );
	assert( header );

	bzero( header, sizeof( * header ));
	header->source_port = htons( socket->port );
	header->source_port = htons( socket_data->dest_port );
	header->header_length = TCP_COMPUTE_HEADER_LENGTH( sizeof( * header ));
	header->synchronize = synchronize;
	header->finalize = finalize;
}

int tcp_prepare_timeout( int ( * timeout_function )( void * tcp_timeout_t ), socket_core_ref socket, tcp_socket_data_ref socket_data, size_t sequence_number, tcp_socket_state_t state, suseconds_t timeout, int globals_read_only ){
	tcp_timeout_ref	operation_timeout;
	fid_t			fibril;

	assert( socket );
	assert( socket_data );
	assert( socket->specific_data == socket_data );

	// prepare the timeout with key bundle structure
	operation_timeout = malloc( sizeof( * operation_timeout ) + socket->key_length + 1 );
	if( ! operation_timeout ) return ENOMEM;
	bzero( operation_timeout, sizeof( * operation_timeout ));
	operation_timeout->globals_read_only = globals_read_only;
	operation_timeout->port = socket->port;
	operation_timeout->local_sockets = socket_data->local_sockets;
	operation_timeout->socket_id = socket->socket_id;
	operation_timeout->timeout = timeout;
	operation_timeout->sequence_number = sequence_number;
	operation_timeout->state = state;

	// copy the key
	operation_timeout->key = (( char * ) operation_timeout ) + sizeof( * operation_timeout );
	operation_timeout->key_length = socket->key_length;
	memcpy( operation_timeout->key, socket->key, socket->key_length );
	operation_timeout->key[ operation_timeout->key_length ] = '\0';

	// prepare the timeouting thread
	fibril = fibril_create( timeout_function, operation_timeout );
	if( ! fibril ){
		free( operation_timeout );
		return EPARTY;
	}
//	fibril_mutex_lock( & socket_data->operation.mutex );
	// start the timeouting fibril
	fibril_add_ready( fibril );
	//socket_data->state = state;
	return EOK;
}

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

	socket_core_ref		socket;
	tcp_socket_data_ref	socket_data;
	int					packet_id;
	packet_t			packet;
	size_t				length;

	assert( local_sockets );

	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	// get the socket specific data
	if( ! socket->specific_data ) return NO_DATA;
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;

	// check state
	if(( socket_data->state != TCP_SOCKET_ESTABLISHED ) && ( socket_data->state != TCP_SOCKET_CLOSE_WAIT )){
		return ENOTCONN;
	}

	// send the source address if desired
	if( addrlen ){
		ERROR_PROPAGATE( data_reply( socket_data->addr, socket_data->addrlen ));
		* addrlen = socket_data->addrlen;
	}

	// get the next received packet
	packet_id = dyn_fifo_value( & socket->received );
	if( packet_id < 0 ) return NO_DATA;
	ERROR_PROPAGATE( packet_translate( tcp_globals.net_phone, & packet, packet_id ));

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

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

int tcp_send_message( socket_cores_ref local_sockets, int socket_id, int fragments, size_t data_fragment_size, int flags ){
	ERROR_DECLARE;

	socket_core_ref			socket;
	tcp_socket_data_ref		socket_data;
	packet_dimension_ref	packet_dimension;
	packet_t				packet;
	size_t					total_length;
	tcp_header_ref			header;
	int						index;
	int						result;

	assert( local_sockets );

	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	// get the socket specific data
	if( ! socket->specific_data ) return NO_DATA;
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;

	// check state
	if(( socket_data->state != TCP_SOCKET_ESTABLISHED ) && ( socket_data->state != TCP_SOCKET_CLOSE_WAIT )){
		return ENOTCONN;
	}

	ERROR_PROPAGATE( tcp_get_packet_dimension( socket_data->device_id, & packet_dimension ));

	// TODO return the device_id + data_fragment_size if different - the client should send it again
	// ( two messages are better than ip fragmentation )

	for( index = 0; index < fragments; ++ index ){
		// read the data fragment
		result = tl_socket_read_packet_data( tcp_globals.net_phone, & packet, sizeof( tcp_header_t ), packet_dimension, socket_data->addr, socket_data->addrlen );
		if( result < 0 ) return result;
		total_length = ( size_t ) result;
		// prefix the tcp header
		header = PACKET_PREFIX( packet, tcp_header_t );
		if( ! header ){
			return tcp_release_and_return( packet, ENOMEM );
		}
		tcp_prepare_operation_header( socket, socket_data, header, 0, 0 );
		ERROR_PROPAGATE( tcp_queue_packet( socket, socket_data, packet, 0 ));
	}

	// flush packets
	packet = tcp_get_packets_to_send( socket, socket_data );
	fibril_rwlock_write_unlock( socket_data->local_lock );
	fibril_rwlock_read_unlock( & tcp_globals.lock );
	if( packet ){
		// send the packet
		tcp_send_packets( socket_data->device_id, packet );
	}

	return EOK;
}

int tcp_close_message( socket_cores_ref local_sockets, int socket_id ){
	ERROR_DECLARE;

	socket_core_ref			socket;
	tcp_socket_data_ref		socket_data;
	packet_t				packet;

	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	// get the socket specific data
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;
	assert( socket_data );

	// check state
	switch( socket_data->state ){
		case TCP_SOCKET_ESTABLISHED:
			socket_data->state = TCP_SOCKET_FIN_WAIT_1;
			break;
		case TCP_SOCKET_CLOSE_WAIT:
			socket_data->state = TCP_SOCKET_LAST_ACK;
			break;
//		case TCP_SOCKET_LISTEN:
		default:
			// just destroy
			if( ! ERROR_OCCURRED( socket_destroy( tcp_globals.net_phone, socket_id, local_sockets, & tcp_globals.sockets, tcp_free_socket_data ))){
				fibril_rwlock_write_unlock( socket_data->local_lock );
				fibril_rwlock_write_unlock( & tcp_globals.lock );
			}
			return ERROR_CODE;
	}
	// send FIN
	// TODO should I wait to complete?

	// create the notification packet
	ERROR_PROPAGATE( tcp_create_notification_packet( & packet, socket, socket_data, 0, 1 ));

	// send the packet
	ERROR_PROPAGATE( tcp_queue_packet( socket, socket_data, packet, 1 ));

	// flush packets
	packet = tcp_get_packets_to_send( socket, socket_data );
	fibril_rwlock_write_unlock( socket_data->local_lock );
	fibril_rwlock_write_unlock( & tcp_globals.lock );
	if( packet ){
		// send the packet
		tcp_send_packets( socket_data->device_id, packet );
	}
	return EOK;
}

int tcp_create_notification_packet( packet_t * packet, socket_core_ref socket, tcp_socket_data_ref socket_data, int synchronize, int finalize ){
	ERROR_DECLARE;

	packet_dimension_ref	packet_dimension;
	tcp_header_ref			header;

	assert( packet );

	// get the device packet dimension
	ERROR_PROPAGATE( tcp_get_packet_dimension( socket_data->device_id, & packet_dimension ));
	// get a new packet
	* packet = packet_get_4( tcp_globals.net_phone, sizeof( tcp_header_t ), packet_dimension->addr_len, packet_dimension->prefix, packet_dimension->suffix );
	if( ! * packet ) return ENOMEM;
	// allocate space in the packet
	header = PACKET_SUFFIX( * packet, tcp_header_t );
	if( ! header ){
		tcp_release_and_return( * packet, ENOMEM );
	}

	tcp_prepare_operation_header( socket, socket_data, header, synchronize, finalize );
	return EOK;
}

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

	socket_core_ref		accepted;
	socket_core_ref		socket;
	tcp_socket_data_ref	socket_data;

	assert( local_sockets );
	assert( addrlen );

	// find the socket
	socket = socket_cores_find( local_sockets, socket_id );
	if( ! socket ) return ENOTSOCK;
	// get the socket specific data
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;
	assert( socket_data );

	// check state
	if( socket_data->state != TCP_SOCKET_LISTEN ){
		return EINVAL;
	}

	do{
		socket_id = dyn_fifo_value( & socket->accepted );
		if( socket_id < 0 ) return ENOTSOCK;

		accepted = socket_cores_find( local_sockets, socket_id );
		if( ! accepted ) return ENOTSOCK;
		// get the socket specific data
		socket_data = ( tcp_socket_data_ref ) accepted->specific_data;
		assert( socket_data );
		if( socket_data->state == TCP_SOCKET_ESTABLISHED ){
			ERROR_PROPAGATE( data_reply( socket_data->addr, socket_data->addrlen ));
			* addrlen = socket_data->addrlen;
		}
		dyn_fifo_pop( & socket->accepted );
	}while( socket_data->state != TCP_SOCKET_ESTABLISHED );
	printf("ret accept %d\n", accepted->socket_id );
	return accepted->socket_id;
}

void tcp_free_socket_data( socket_core_ref socket ){
	tcp_socket_data_ref		socket_data;

	assert( socket );

	printf( "destroy_socket %d\n", socket->socket_id );

	// get the socket specific data
	socket_data = ( tcp_socket_data_ref ) socket->specific_data;
	assert( socket_data );
	//free the pseudo header
	if( socket_data->pseudo_header ){
		if( socket_data->headerlen ){
			printf("d pseudo\n");
			free( socket_data->pseudo_header );
			socket_data->headerlen = 0;
		}
		socket_data->pseudo_header = NULL;
	}
	socket_data->headerlen = 0;
	// free the address
	if( socket_data->addr ){
		if( socket_data->addrlen ){
			printf("d addr\n");
			free( socket_data->addr );
			socket_data->addrlen = 0;
		}
		socket_data->addr = NULL;
	}
	socket_data->addrlen = 0;
}

int	tcp_release_and_return( packet_t packet, int result ){
	pq_release( tcp_globals.net_phone, packet_get_id( packet ));
	return result;
}

/** @}
 */
