/*
 * ksocket.c - kernel socket API implementation.
 *
 * Authors  Alain Greiner   (2016,2017,2018,2019,2020)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH.; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <kernel_config.h>
#include <hal_kernel_types.h>
#include <hal_remote.h>
#include <hal_uspace.h>
#include <shared_socket.h>
#include <process.h>
#include <remote_buf.h>
#include <bits.h>
#include <printk.h>
#include <kmem.h>
#include <thread.h>
#include <vfs.h>
#include <ksocket.h>
#include <dev_nic.h>

//////////////////////////////////////////////////////////////////////////////////////
// Extern global variables
//////////////////////////////////////////////////////////////////////////////////////

extern chdev_directory_t  chdev_dir;         // allocated in kernel_init.c

///////////////////////////////////////////
char * socket_domain_str( uint32_t domain )
{
    switch( domain )
    {
        case AF_INET         : return "INET";
        case AF_LOCAL        : return "LOCAL";

        default              : return "undefined";
    }
}
   
///////////////////////////////////////
char * socket_type_str( uint32_t type )
{
    switch( type )
    {
        case SOCK_DGRAM         : return "UDP";
        case SOCK_STREAM        : return "TCP";

        default                 : return "undefined";
    }
}
   
/////////////////////////////////////////
char * socket_state_str( uint32_t state )
{
    switch( state )
    {
        case UDP_STATE_UNBOUND    : return "UDP_UNBOUND";
        case UDP_STATE_BOUND      : return "UDP_BOUND";
        case UDP_STATE_ESTAB      : return "UDP_ESTAB";

        case TCP_STATE_UNBOUND    : return "TCP_UNBOUND";
        case TCP_STATE_BOUND      : return "TCP_BOUND";
        case TCP_STATE_LISTEN     : return "TCP_LISTEN";
        case TCP_STATE_SYN_SENT   : return "TCP_SYN_SENT";
        case TCP_STATE_SYN_RCVD   : return "TCP_SYN_RCVD";
        case TCP_STATE_ESTAB      : return "TCP_ESTAB";
        case TCP_STATE_FIN_WAIT1  : return "TCP_FIN_WAIT1";
        case TCP_STATE_FIN_WAIT2  : return "TCP_FIN_WAIT2";
        case TCP_STATE_CLOSING    : return "TCP_CLOSING";
        case TCP_STATE_TIME_WAIT  : return "TCP_TIME_WAIT";
        case TCP_STATE_CLOSE_WAIT : return "TCP_CLOSE_WAIT";
        case TCP_STATE_LAST_ACK   : return "TCP_LAST_ACK";
        case TCP_STATE_CLOSED     : return "TCP_CLOSED";

        default                   : return "undefined";
    }
}

///////////////////////////////////////////
char * socket_cmd_type_str( uint32_t type )
{
    switch( type )
    {
        case CMD_TX_CONNECT  : return "TX_CONNECT";
        case CMD_TX_ACCEPT   : return "TX_ACCEPT";
        case CMD_TX_CLOSE    : return "TX_CLOSE";
        case CMD_TX_SEND     : return "TX_SEND";

        case CMD_RX_ACCEPT   : return "RX_ACCEPT";
        case CMD_RX_RECV     : return "RX_RECV";
        
        default                 : return "undefined";
    }
}
   
///////////////////////////////////////////
char * socket_cmd_sts_str( uint32_t sts )
{
    switch( sts )
    {
        case CMD_STS_SUCCESS  : return "TX_CONNECT";
        case CMD_STS_EOF      : return "EOF";
        case CMD_STS_RST      : return "RST";
        case CMD_STS_BADACK   : return "BADACK";
        case CMD_STS_BADSTATE : return "BADSTATE";
        case CMD_STS_BADCMD   : return "BADCMD";
        
        default               : return "undefined";
    }
}

/////////////////////////////////////////////////////////////////////////////////////////
// This static function registers the socket defined by the <socket_xp> argument into
// the lists of sockets attached to the relevant NIC_TX and NIC_TX chdevs identified
// by the <channel> argument, and update the channel field in socket descriptor.
/////////////////////////////////////////////////////////////////////////////////////////
// @ socket_xp   : [in]  extended pointer on socket descriptor.
// @ channel     : [in]  NIC channel index.
/////////////////////////////////////////////////////////////////////////////////////////
static void socket_link_to_servers( xptr_t   socket_xp,
                                    uint32_t channel )
{
    cxy_t      socket_cxy = GET_CXY( socket_xp );
    socket_t * socket_ptr = GET_PTR( socket_xp );

#if DEBUG_SOCKET_LINK
thread_t  * this        = CURRENT_THREAD;
process_t * process     = this->process;
pid_t       socket_pid  = hal_remote_l32( XPTR( socket_cxy , &socket_ptr->pid ));
fdid_t      socket_fdid = hal_remote_l32( XPTR( socket_cxy , &socket_ptr->pid ));
uint32_t   cycle        = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_LINK < cycle )
printk("\n[%s] thread[%x,%x] enter for socket[%x,%d] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, socket_pid, socket_fdid, cycle ); 
#endif

    // get pointers on NIC_TX[channel] chdev
    xptr_t    tx_chdev_xp  = chdev_dir.nic_tx[channel];
    chdev_t * tx_chdev_ptr = GET_PTR( tx_chdev_xp );
    cxy_t     tx_chdev_cxy = GET_CXY( tx_chdev_xp );

    // build various TX extended pointers 
    xptr_t    tx_root_xp = XPTR( tx_chdev_cxy , &tx_chdev_ptr->wait_root );
    xptr_t    tx_lock_xp = XPTR( tx_chdev_cxy , &tx_chdev_ptr->wait_lock );
    xptr_t    tx_list_xp = XPTR( socket_cxy   , &socket_ptr->tx_list );

    // get pointers on NIC_RX[channel] chdev
    xptr_t    rx_chdev_xp  = chdev_dir.nic_rx[channel];
    chdev_t * rx_chdev_ptr = GET_PTR( rx_chdev_xp );
    cxy_t     rx_chdev_cxy = GET_CXY( rx_chdev_xp );

    // build various RX extended pointers
    xptr_t    rx_root_xp = XPTR( rx_chdev_cxy , &rx_chdev_ptr->wait_root );
    xptr_t    rx_lock_xp = XPTR( rx_chdev_cxy , &rx_chdev_ptr->wait_lock );
    xptr_t    rx_list_xp = XPTR( socket_cxy   , &socket_ptr->rx_list );

    // register socket in the NIC_TX[channel] chdev clients queue
    remote_busylock_acquire( tx_lock_xp );
    xlist_add_last( tx_root_xp , tx_list_xp );
    remote_busylock_release( tx_lock_xp );

    // register socket in the NIC_RX[channel] chdev clients queue
    remote_busylock_acquire( rx_lock_xp );
    xlist_add_last( rx_root_xp , rx_list_xp );
    remote_busylock_release( rx_lock_xp );

#if DEBUG_SOCKET_LINK
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_LINK < cycle )
printk("\n[%s] thread[%x,%x] linked socket[%x,%d] to channel %d / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, socket_pid, socket_fdid, channel, cycle );
#endif

}  // end socket_link_to_servers()

/////////////////////////////////////////////////////////////////////////////////////////
// This function removes the socket defined by the <socket_xp> argument from the
// lists of sockets attached to the relevant NIC_TX and NIC_TX chdevs.
/////////////////////////////////////////////////////////////////////////////////////////
// @ socket_xp   : [in]  extended pointer on socket descriptor
/////////////////////////////////////////////////////////////////////////////////////////
static void socket_unlink_from_servers( xptr_t socket_xp )
{
    cxy_t      socket_cxy = GET_CXY( socket_xp );
    socket_t * socket_ptr = GET_PTR( socket_xp );

#if DEBUG_SOCKET_LINK
thread_t  * this        = CURRENT_THREAD;
process_t * process     = this->process;
pid_t       socket_pid  = hal_remote_l32( XPTR( socket_cxy , &socket_ptr->pid ));
fdid_t      socket_fdid = hal_remote_l32( XPTR( socket_cxy , &socket_ptr->pid ));
uint32_t   cycle        = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_LINK < cycle )
printk("\n[%s] thread[%x,%x] enter for socket[%x,%d] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, socket_pid, socket_fdid, cycle ); 
#endif

    // get NIC channel
    uint32_t channel = hal_remote_l32( XPTR( socket_cxy , &socket_ptr->nic_channel ));

    // get pointers on NIC_TX[channel] chdev
    xptr_t    tx_chdev_xp  = chdev_dir.nic_tx[channel];
    chdev_t * tx_chdev_ptr = GET_PTR( tx_chdev_xp );
    cxy_t     tx_chdev_cxy = GET_CXY( tx_chdev_xp );

    // build various TX extended pointers 
    xptr_t    tx_lock_xp = XPTR( tx_chdev_cxy , &tx_chdev_ptr->wait_lock );
    xptr_t    tx_list_xp = XPTR( socket_cxy   , &socket_ptr->tx_list );

    // get pointers on NIC_RX[channel] chdev
    xptr_t    rx_chdev_xp  = chdev_dir.nic_rx[channel];
    chdev_t * rx_chdev_ptr = GET_PTR( rx_chdev_xp );
    cxy_t     rx_chdev_cxy = GET_CXY( rx_chdev_xp );

    // build various RX extended pointers
    xptr_t    rx_lock_xp = XPTR( rx_chdev_cxy , &rx_chdev_ptr->wait_lock );
    xptr_t    rx_list_xp = XPTR( socket_cxy   , &socket_ptr->rx_list );

    // remove socket from the NIC_TX[channel] chdev clients queue
    remote_busylock_acquire( tx_lock_xp );
    xlist_unlink( tx_list_xp );
    remote_busylock_release( tx_lock_xp );

    // remove socket from the NIC_RX[channel] chdev clients queue
    remote_busylock_acquire( rx_lock_xp );
    xlist_unlink( rx_list_xp );
    remote_busylock_release( rx_lock_xp );

#if DEBUG_SOCKET_LINK
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_LINK < cycle )
printk("\n[%s] thread[%x,%x] unlinked socket [%x,%d] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, socket_pid, socket_fdid, cycle ); 
#endif

}  // end socket_unlink_from_servers()
        
/////////////////////////////////////////////////////////////////////////////////////////
// This static function is called by the socket_build() and socket_accept() functions.
// It allocates memory in cluster defined by the <cxy> argument for all structures
// associated to a socket: file descriptor, socket descriptor, RX buffer, R2T queue,
// and CRQ queue. It allocates an fdid, and register it in the process fd_array.
// It initialise the  the socket desccriptor static fields, other than local_addr, 
// local_port, remote_addr, remote_port), and set the socket state to UNBOUND.
// It returns the local pointer on socket descriptor and the fdid value in buffers
// defined by the <socket_ptr> & <fdid_ptr> arguments.
/////////////////////////////////////////////////////////////////////////////////////////
// @ cxy        : [in]  target cluster fo socket & file descriptors.
// @ domain     : [in]  socket domain.
// @ type       : [in]  socket type.
// @ socket_ptr : [out] local pointer on buffer for socket pointer.
// @ fdid_ptr   : [out] local pointer on buffer for fdid value.
// # return 0 if success / return -1 if no memory.
/////////////////////////////////////////////////////////////////////////////////////////
static error_t socket_create( cxy_t       cxy,
                              uint32_t    domain,
                              uint32_t    type,
                              socket_t ** socket_ptr,
                              uint32_t  * fdid_ptr )
{
    uint32_t    fdid;

    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

    kmem_req_t     req;
    socket_t     * socket;
    vfs_file_t   * file;
    uint32_t       state;
    error_t        error;

#if DEBUG_SOCKET_CREATE
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CREATE < cycle )
printk("\n[%s] thread[%x,%x] enter / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, cycle ); 
#endif
   
    // allocate memory for socket descriptor 
    req.type   = KMEM_KCM;
    req.order  = bits_log2( sizeof(socket_t) );
    req.flags  = AF_ZERO;
    socket     = kmem_remote_alloc( cxy , &req );

    if( socket == NULL )
    {
        printk("\n[ERROR] in %s : cannot allocate socket descriptor / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        return -1;
    }

    // allocate memory for rx_buf buffer
    error = remote_buf_init( XPTR( cxy , &socket->rx_buf ),
                             NIC_RX_BUF_SIZE );

    if( error )
    {
        printk("\n[ERROR] in %s : cannot allocate rx_buf / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        req.type = KMEM_KCM;
        req.ptr  = socket;
        kmem_remote_free( cxy , &req );
        return -1;
    }

    // allocate memory for r2tq queue 
    error = remote_buf_init( XPTR( cxy , &socket->r2tq ),
                             NIC_R2T_QUEUE_SIZE );
    if( error )
    {
        printk("\n[ERROR] in %s : cannot allocate R2T queue / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        remote_buf_destroy( XPTR( cxy , &socket->rx_buf ) );
        req.type = KMEM_KCM;
        req.ptr  = socket;
        kmem_remote_free( cxy , &req );
        return -1;
    }

    // don't allocate memory for crqq queue, as it is done by the socket_listen function

    //  allocate memory for file descriptor
	req.type  = KMEM_KCM;
	req.order = bits_log2( sizeof(vfs_file_t) );
    req.flags = AF_ZERO;
	file      = kmem_remote_alloc( cxy , &req );

    if( file == NULL ) 
    {
        printk("\n[ERROR] in %s : cannot allocate file descriptor / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        remote_buf_destroy( XPTR( cxy , &socket->r2tq ) );
        remote_buf_destroy( XPTR( cxy , &socket->rx_buf ) );
        req.type = KMEM_KCM;
        req.ptr  = socket;
        kmem_remote_free( cxy , &req );
        return -1;
    }
    
    // get an fdid value, and register file descriptor in fd_array[]
    error = process_fd_register( process->ref_xp,
                                 XPTR( cxy , file ),
                                 &fdid );
    if ( error ) 
    {
        printk("\n[ERROR] in %s : cannot register file descriptor / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        req.type = KMEM_KCM;
        req.ptr  = file;
        kmem_free( &req );
        remote_buf_destroy( XPTR( cxy , &socket->r2tq ) );
        remote_buf_destroy( XPTR( cxy , &socket->rx_buf ) );
        req.ptr  = socket;
        kmem_free( &req );
        return -1;
    }

    state = (type == SOCK_STREAM) ? TCP_STATE_UNBOUND : UDP_STATE_UNBOUND;

    // initialise socket descriptor 
    hal_remote_s32( XPTR( cxy , &socket->pid         ) , process->pid );
    hal_remote_s32( XPTR( cxy , &socket->fdid        ) , fdid );
    hal_remote_s32( XPTR( cxy , &socket->domain      ) , domain );
    hal_remote_s32( XPTR( cxy , &socket->type        ) , type );
    hal_remote_s32( XPTR( cxy , &socket->state       ) , state );
    hal_remote_s64( XPTR( cxy , &socket->tx_client   ) , XPTR_NULL );
    hal_remote_s64( XPTR( cxy , &socket->rx_client   ) , XPTR_NULL );
    hal_remote_s32( XPTR( cxy , &socket->tx_valid    ) , false );
    hal_remote_s32( XPTR( cxy , &socket->rx_valid    ) , false );
    hal_remote_s32( XPTR( cxy , &socket->nic_channel ) , 0 );

    // initialize file descriptor 
    hal_remote_s32( XPTR( cxy , &file->type        ) , INODE_TYPE_SOCK );
    hal_remote_spt( XPTR( cxy , &file->socket      ) , socket );
    hal_remote_s32( XPTR( cxy , &file->refcount    ) , 1 );

    // initialize socket lock
    remote_queuelock_init( XPTR( cxy , &socket->lock ) , LOCK_SOCKET_STATE );

#if DEBUG_SOCKET_CREATE
if( DEBUG_SOCKET_CREATE < cycle )
printk("\n[%s] thread[%x,%x] exit / socket[%x,%d] / xptr[%x,%x] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cxy, socket, cycle );
#endif
   
    // return success
    *socket_ptr = socket;
    *fdid_ptr   = fdid;

    return 0;

}  // end socket_create

/////////////////////////////////////////////////////////////////////////////////////////
// This static function is called by the socket_close() function to destroy a socket
// identified by the <file_xp> argument.
// It remove the associated file from the reference process fd_array. It unlink the
// socket from the NIC_TX [k] and NIC_RX[k] chdevs. It release all memory allocated 
// for the structures associated to the target socket socket : file descriptor,
// socket descriptor, RX buffer, R2T queue, CRQ queue.
/////////////////////////////////////////////////////////////////////////////////////////
// @ file_xp  : extended pointer on the file descriptor.
/////////////////////////////////////////////////////////////////////////////////////////
static void socket_destroy( xptr_t file_xp )
{
    kmem_req_t          req;

    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

// check file_xp argument 
assert( (file_xp != XPTR_NULL), "illegal argument\n" );

    // get cluster & local pointer for file descriptor
    vfs_file_t * file_ptr = GET_PTR( file_xp );
    cxy_t        file_cxy = GET_CXY( file_xp );

#if DEBUG_SOCKET_DESTROY
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_DESTROY < cycle )
printk("\n[%s] thread[%x,%x] enter / file[%x,%x] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, file_cxy, file_ptr, cycle );
#endif

    // get local pointer for socket and file type
    socket_t * socket_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );
    uint32_t   file_type  = hal_remote_l32( XPTR( file_cxy , &file_ptr->type ) );
    
// check file descriptor type
assert( (file_type == INODE_TYPE_SOCK), "illegal file type\n" );

    // get socket nic_channel and fdid
    uint32_t channel = hal_remote_l32( XPTR( file_cxy , &socket_ptr->nic_channel ));
    uint32_t fdid    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->fdid ));

    // remove socket from NIC_TX & NIC_RX chdev queues when socket is connected
    if( channel < LOCAL_CLUSTER->nb_nic_channels )
    {
        socket_unlink_from_servers( XPTR( file_cxy , socket_ptr ) );
    }

    // remove the file descriptor from the fd_array
    process_fd_remove( process->owner_xp , fdid );

    // release memory allocated for file descriptor
    req.type = KMEM_KCM;
    req.ptr  = file_ptr;
    kmem_remote_free( file_cxy , &req );

    // release memory allocated for buffers attached to socket descriptor
    remote_buf_destroy( XPTR( file_cxy , &socket_ptr->crqq ) );
    remote_buf_destroy( XPTR( file_cxy , &socket_ptr->r2tq ) );
    remote_buf_destroy( XPTR( file_cxy , &socket_ptr->rx_buf ) );

    // release memory allocated for socket descriptor
    req.type = KMEM_KCM;
    req.ptr  = socket_ptr;
    kmem_remote_free( file_cxy , &req );

#if DEBUG_SOCKET_DESTROY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_DESTROY < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, cycle );
#endif

}  // end socket_destroy()

////////////////////////////////////////////////
void socket_put_r2t_request( xptr_t    queue_xp,
                             uint32_t  flags,
                             uint32_t  channel )
{
    xptr_t     chdev_xp;
    cxy_t      chdev_cxy;
    chdev_t  * chdev_ptr;
    thread_t * server_ptr;
    xptr_t     server_xp;

    while( 1 )
    {
        // try to register R2T request
        error_t error = remote_buf_put_from_kernel( queue_xp,
                                                    (uint8_t *)(&flags),
                                                    1 );
        if( error )
        {
            // queue full => wait and retry
            sched_yield( "waiting R2T queue" );
        }
        else
        {
            // get NIC_TX chdev pointers
            chdev_xp = chdev_dir.nic_tx[channel];
            chdev_cxy = GET_CXY( chdev_xp );
            chdev_ptr = GET_PTR( chdev_xp );
 
            // get NIC_TX server thread pointers
            server_ptr = hal_remote_lpt( XPTR( chdev_cxy , &chdev_ptr->server ) );
            server_xp  = XPTR( chdev_cxy , server_ptr );

            // unblocks NIC_TX server thread
            thread_unblock( server_xp , THREAD_BLOCKED_CLIENT );

            return;
        }
    }
}  // end socket_put_r2t_request()
  
///////////////////////////////////////////////////
error_t socket_put_crq_request( xptr_t    queue_xp,
                                uint32_t  remote_addr,
                                uint32_t  remote_port,
                                uint32_t  remote_iss,
                                uint32_t  remote_window )
{
    connect_request_t   req;

    // build request
    req.addr   = remote_addr;
    req.port   = remote_port;
    req.iss    = remote_iss;
    req.window = remote_window;

    // try to register request in CRQ
    return remote_buf_put_from_kernel( queue_xp,
                                       (uint8_t *)(&req),
                                       sizeof(connect_request_t) );
}  // end socket_put_crq_request()
  
////////////////////////////////////////////////////
error_t socket_get_crq_request( xptr_t     queue_xp,
                                uint32_t * remote_addr,
                                uint32_t * remote_port,
                                uint32_t * remote_iss,
                                uint32_t * remote_window )
{
    connect_request_t   req;
    error_t             error;

    // get request from CRQ
    error = remote_buf_get_to_kernel( queue_xp,
                                      (uint8_t *)(&req),
                                      sizeof(connect_request_t) );
    // extract request arguments
    *remote_addr   = req.addr;
    *remote_port   = req.port;
    *remote_iss    = req.iss;
    *remote_window = req.window;

    return error;

}  // end socket_get_crq_request()
  

/////////////////////////////////////////////////////////////////////////////////////////
//                 Functions implementing the SOCKET related syscalls
/////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////
int socket_build( uint32_t   domain,
                  uint32_t   type )
{
    uint32_t    fdid;
    socket_t  * socket;
    error_t     error;

#if DEBUG_SOCKET_BUILD
uint32_t    cycle   = (uint32_t)hal_get_cycles();
thread_t  * this    = CURRENT_THREAD;
process_t * process = this->process;
if( DEBUG_SOCKET_BUILD < cycle )
printk("\n[%s] thread[%x,%x] enter / %s / %s / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, 
socket_domain_str(domain), socket_type_str(type), cycle );
#endif


    // allocate memory for the file descriptor and for the socket
    error = socket_create( local_cxy,
                           domain,
                           type,
                           &socket, 
                           &fdid );

#if DEBUG_SOCKET_BUILD
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_BUILD < cycle )
printk("\n[%s] thread[%x,%x] exit / socket %x / fdid %d / %s / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, socket, fdid, 
socket_state_str(hal_remote_l32(XPTR(local_cxy , &socket->state))),
cycle );
#endif

    if( error ) return -1;
    return fdid;
}

////////////////////////////////
int socket_bind( uint32_t  fdid,
                 uint32_t  addr,
                 uint16_t  port )
{
    vfs_inode_type_t    file_type;
    socket_t          * socket;
    uint32_t            socket_type;
    uint32_t            socket_state;

    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

#if DEBUG_SOCKET_BIND
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_BIND < cycle )
printk("\n[%s] thread[%x,%x] enter / socket[%x,%d] / addr %x / port %x / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, addr, port, cycle );
#endif

    // get pointers on file descriptor
    xptr_t       file_xp  = process_fd_get_xptr_from_local( process , fdid );
    vfs_file_t * file_ptr = GET_PTR( file_xp );
    cxy_t        file_cxy = GET_CXY( file_xp );

    // check file_xp
    if( file_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : undefined fdid %d / thread[%x,%x]\n",
        __FUNCTION__, fdid, process->pid, this->trdid );
        return -1;
    }

    file_type = hal_remote_l32( XPTR( file_cxy , &file_ptr->type ) );
    socket    = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );

    // check file descriptor type
    if( file_type != INODE_TYPE_SOCK )
    {
        printk("\n[ERROR] in %s : illegal file type %s / thread[%x,%x]",
        __FUNCTION__, vfs_inode_type_str( file_type ), process->pid, this->trdid );
        return -1;
    }

    // get socket type
    socket_type = hal_remote_l32(XPTR( file_cxy , &socket->type ));

    // compute socket state
    socket_state = (socket_type == SOCK_STREAM) ? TCP_STATE_BOUND : UDP_STATE_BOUND;

    // update the socket descriptor
    hal_remote_s32( XPTR( file_cxy , &socket->local_addr ) , addr );
    hal_remote_s32( XPTR( file_cxy , &socket->local_port ) , port );
    hal_remote_s32( XPTR( file_cxy , &socket->state      ) , socket_state );

#if DEBUG_SOCKET_BIND
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_BIND < cycle )
printk("\n[%s] thread[%x,%x] exit / socket[%x,%d] / %s / addr %x / port %x / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid,
socket_state_str(hal_remote_l32( XPTR( file_cxy , &socket->state ))),
hal_remote_l32( XPTR( file_cxy , &socket->local_addr )),
hal_remote_l32( XPTR( file_cxy , &socket->local_port )),
cycle );
#endif

    return 0;

}  // end socket_bind()

//////////////////////////////////
int socket_listen( uint32_t fdid,
                   uint32_t crq_depth )
{
    xptr_t              file_xp;
    vfs_file_t        * file_ptr;
    cxy_t               file_cxy;
    vfs_inode_type_t    file_type;
    socket_t          * socket_ptr;
    uint32_t            socket_type;
    uint32_t            socket_state;
    uint32_t            socket_local_addr;
    uint32_t            socket_local_port;
    error_t             error;

    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

#if DEBUG_SOCKET_LISTEN
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_LISTEN < cycle )
printk("\n[%s] thread[%x,%x] enter / socket[%x,%d] / crq_depth %x / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, crq_depth, cycle );
#endif

    // get pointers on file descriptor
    file_xp  = process_fd_get_xptr_from_local( process , fdid );
    file_ptr = GET_PTR( file_xp );
    file_cxy = GET_CXY( file_xp );

    // check file_xp
    if( file_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : undefined fdid %d / thread[%x,%x]\n",
        __FUNCTION__, fdid, process->pid, this->trdid );
        return -1;
    }

    file_type  = hal_remote_l32( XPTR( file_cxy , &file_ptr->type ) );
    socket_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );

    // check file descriptor type
    if( file_type != INODE_TYPE_SOCK )
    {
        printk("\n[ERROR] in %s : illegal file type %s / thread[%x,%x]\n",
        __FUNCTION__, vfs_inode_type_str(file_type), process->pid, this->trdid );
        return -1;
    }

    // get relevant infos from <fdid> socket descriptor
    socket_type       = hal_remote_l32( XPTR( file_cxy , &socket_ptr->type )); 
    socket_state      = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state )); 
    socket_local_addr = hal_remote_l32( XPTR( file_cxy , &socket_ptr->local_addr )); 
    socket_local_port = hal_remote_l32( XPTR( file_cxy , &socket_ptr->local_port )); 

    // check socket type 
    if( socket_type != SOCK_STREAM )
    {
        printk("\n[ERROR] in %s : illegal socket type %s / thread[%x,%x]\n",
        __FUNCTION__, socket_type_str(socket_type), process->pid, this->trdid );
        return -1;
    }
    
    // check socket state
    if( socket_state != TCP_STATE_BOUND )
    {
        printk("\n[ERROR] in %s : illegal socket state %s / thread[%x,%x]\n",
        __FUNCTION__, socket_state_str(socket_state), process->pid, this->trdid );
        return -1;
    }
    
    // compute CRQ queue depth : max( crq_depth , NIC_CRQ_QUEUE_SIZE )
    uint32_t depth = ( crq_depth > NIC_CRQ_QUEUE_SIZE ) ? crq_depth : NIC_CRQ_QUEUE_SIZE;

    // allocate memory for the CRQ queue
    error = remote_buf_init( XPTR( file_cxy , &socket_ptr->crqq ),
                                   depth * sizeof(connect_request_t) );
    if( error )
    {
        printk("\n[ERROR] in %s : cannot allocate CRQ queue / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        return -1;
    }

    // update socket.state
    hal_remote_s32( XPTR( file_cxy , &socket_ptr->state ) , TCP_STATE_LISTEN );

    // get pointers on NIC_RX[0] chdev
    xptr_t    rx0_chdev_xp  = chdev_dir.nic_rx[0];
    chdev_t * rx0_chdev_ptr = GET_PTR( rx0_chdev_xp );
    cxy_t     rx0_chdev_cxy = GET_CXY( rx0_chdev_xp );
   
    // build extended pointers on list of listening sockets
    xptr_t    rx0_root_xp = XPTR( rx0_chdev_cxy , &rx0_chdev_ptr->ext.nic.root );
    xptr_t    rx0_lock_xp = XPTR( rx0_chdev_cxy , &rx0_chdev_ptr->ext.nic.lock );

    // build extended pointer on socket rx_list field
    xptr_t    list_entry_xp = XPTR( file_cxy , &socket_ptr->rx_list );

    // register  <fdid> socket in listening sockets list
    remote_busylock_acquire( rx0_lock_xp );
    xlist_add_last( rx0_root_xp , list_entry_xp );
    remote_busylock_release( rx0_lock_xp );

#if DEBUG_SOCKET_LISTEN
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_LISTEN < cycle )
printk("\n[%s] thread[%x,%x] exit / socket[%x,%d] / %s / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid,
socket_state_str(socket_state), cycle );
#endif

    return 0;

}  // end socket_listen()

///////////////////////////////////
int socket_accept( uint32_t   fdid,
                   uint32_t * remote_addr,
                   uint16_t * remote_port )
{
    xptr_t              file_xp;             // extended pointer on remote file
    vfs_file_t        * file_ptr;
    cxy_t               file_cxy;
    vfs_inode_type_t    file_type;           // file descriptor type
    socket_t          * socket_ptr;          // local pointer on remote waiting socket
    uint32_t            socket_type;         // listening socket type    
    uint32_t            socket_state;        // listening socket state
    uint32_t            socket_domain;       // listening socket domain
    uint32_t            socket_local_addr;   // listening socket local IP address
    uint32_t            socket_local_port;   // listening socket local port
    uint32_t            socket_tx_nxt;       // listening socket tx_nxt
    bool_t              socket_tx_valid;     // listening socket tx_valid
    xptr_t              socket_tx_client;    // listening socket tx_client thread
    bool_t              socket_rx_valid;     // listening socket rx_valid
    xptr_t              socket_rx_client;    // listening socket rx_client thread
    xptr_t              socket_lock_xp;      // listening socket lock
    xptr_t              crq_xp;              // listening socket CRQ queue
    uint32_t            crq_status;          // number of bytes in CRQ
    cxy_t               new_socket_cxy;      // new socket cluster identifier
    socket_t          * new_socket_ptr;      // local pointer on new socket
    xptr_t              new_socket_xp;       // extended pointer on new socket
    volatile uint32_t   new_state;           // new socket state (modified by NIC_RX thread)
    uint32_t            new_fdid;            // new socket file descriptor index
    uint32_t            new_remote_addr;     // new socket remote IP address
    uint32_t            new_remote_port;     // new socket remote port
    uint32_t            new_remote_iss;      // new socket remote iss
    uint32_t            new_remote_window;   // new socket receive window
    xptr_t              tx_server_xp;        // extended pointer on TX server thread
    thread_t          * tx_server_ptr;       // local pointer on TX server thread
    uint32_t            cmd_status;          // command status (rx_sts or tx_sts)
    bool_t              cmd_valid;           // valid command (rx_valid or tx_valid)
    error_t             error;

    thread_t  * this      = CURRENT_THREAD;
    xptr_t      client_xp = XPTR( local_cxy , this );
    process_t * process   = this->process;

#if DEBUG_SOCKET_ACCEPT
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] enter for socket[%x,%d] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif

    // 1) get pointers on file descriptor
    file_xp  = process_fd_get_xptr_from_local( process , fdid );
    file_ptr = GET_PTR( file_xp );
    file_cxy = GET_CXY( file_xp );

    // check file_xp
    if( file_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : undefined fdid %d",
        __FUNCTION__, fdid );
        return -1;
    }
  
    file_type  = hal_remote_l32( XPTR( file_cxy , &file_ptr->type ) );
    socket_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );

    // check file descriptor type
    if( file_type != INODE_TYPE_SOCK )
    {
        printk("\n[ERROR] in %s : illegal file type %s / thread[%x,%x]\n",
        __FUNCTION__, vfs_inode_type_str(file_type), process->pid, this->trdid );
        return -1;
    }

    // build extended pointer on listening socket lock
    socket_lock_xp = XPTR( file_cxy , &socket_ptr->lock );

    // acquire listening socket lock
    remote_queuelock_acquire( socket_lock_xp );
                    
    // get listening socket type, domain, state, local_addr, local_port & tx_nxt 
    socket_type       = hal_remote_l32( XPTR( file_cxy , &socket_ptr->type )); 
    socket_state      = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state )); 
    socket_domain     = hal_remote_l32( XPTR( file_cxy , &socket_ptr->domain )); 
    socket_local_addr = hal_remote_l32( XPTR( file_cxy , &socket_ptr->local_addr )); 
    socket_local_port = hal_remote_l32( XPTR( file_cxy , &socket_ptr->local_port )); 
    socket_tx_nxt     = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_nxt ));
    socket_tx_valid   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_valid )); 
    socket_tx_client  = hal_remote_l64( XPTR( file_cxy , &socket_ptr->tx_client ));
    socket_rx_valid   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->rx_valid )); 
    socket_rx_client  = hal_remote_l64( XPTR( file_cxy , &socket_ptr->rx_client ));

    // check socket type 
    if( socket_type != SOCK_STREAM )
    {
        // release listening socket lock
        remote_queuelock_release( socket_lock_xp );
                    
        printk("\n[ERROR] in %s : illegal socket type %s / thread[%x,%x]\n",
        __FUNCTION__, socket_type_str(socket_type), process->pid , this->trdid );
        return -1;
    }
    
    // check socket state
    if( socket_state != TCP_STATE_LISTEN ) 
    {
        // release listening socket lock
        remote_queuelock_release( socket_lock_xp );
                    
        printk("\n[ERROR] in %s : illegal socket state %s / thread[%x,%x]\n",
        __FUNCTION__, socket_state_str(socket_state), process->pid, this->trdid );
        return -1;
    }
    
    // check no previous RX command
    if( (socket_rx_valid == true) || (socket_rx_client != XPTR_NULL) )
    { 
        // release listening socket lock
        remote_queuelock_release( socket_lock_xp );
                    
        printk("\n[ERROR] in %s : previous RX cmd on socket[%x,%d] / thread[%x,%x]\n",
        __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
        return -1;
    }

    // check no previous TX command
    if( (socket_tx_valid == true) || (socket_tx_client != XPTR_NULL) )
    { 
        // release socket lock
        remote_queuelock_release( socket_lock_xp );
                    
        printk("\n[ERROR] in %s : previous TX cmd on socket[%x,%d] / thread[%x,%x]\n",
        __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
        return -1;
    }

    // 2) build extended pointer on listening socket.crq
    crq_xp  = XPTR( file_cxy , &socket_ptr->crqq );

    // get CRQ status
    crq_status = remote_buf_status( crq_xp );

    // block & deschedule when CRQ empty
    if( crq_status == 0 )
    {
        // register command arguments in listening socket
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->rx_cmd    ), CMD_RX_ACCEPT );
        hal_remote_s64( XPTR( file_cxy , &socket_ptr->rx_client ), client_xp );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->rx_valid  ), true );

        // release listening socket lock
        remote_queuelock_release( socket_lock_xp );

#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] / CRQ empty => blocks on <IO> / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
        // block & deschedule when CRQQ empty
        thread_block( XPTR( local_cxy , this ) , THREAD_BLOCKED_IO );
        sched_yield( "CRQ queue empty");

#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] / resumes / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
        // take listening socket lock
        remote_queuelock_acquire( socket_lock_xp );

        // get CRQ status & command status
        cmd_valid    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->rx_valid ) );
        cmd_status   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->rx_sts ) );
        crq_status   = remote_buf_status( crq_xp );

assert( (((crq_status > 0) || (cmd_status!= CMD_STS_SUCCESS)) && (cmd_valid == false)),
"illegal socket state when client thread resumes after RX_ACCEPT" );

        // reset socket.rx_client
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->rx_client ) , XPTR_NULL );

        if( cmd_status != CMD_STS_SUCCESS )
        {
            // release socket lock
            remote_queuelock_release( socket_lock_xp );

            printk("\n[ERROR] in %s for RX_ACCEPT command / socket[%x,%d] / thread[%x,%x]\n",
            __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
            return -1;
        }

        // extract first request from the listening socket CRQ
        error = socket_get_crq_request( crq_xp,
                                    &new_remote_addr,
                                    &new_remote_port,
                                    &new_remote_iss,
                                    &new_remote_window );

assert( (error == 0),
"cannot get a connection request from a non-empty CRQ" ); 

        // reset listening socket rx_client
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->rx_client ) , XPTR_NULL );

        // release socket lock
        remote_queuelock_release( socket_lock_xp );

    }  // end blocking on CRQ status 

    // from this point, we can create a new socket 
    // and ask the NIC_TX to send a SYN-ACK segment

#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] / got a CRQ request / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif

    // 3) select a cluster for the new socket
    new_socket_cxy = cluster_random_select();

    // allocate memory for the new socket descriptor 
    error = socket_create( new_socket_cxy,
                           socket_domain,
                           socket_type,
                           &new_socket_ptr,
                           &new_fdid );
    if( error )
    {
        printk("\n[ERROR] in %s : cannot allocate new socket / thread[%x,%x]\n",
        __FUNCTION__, process->pid, this->trdid );
        return -1;
    }
   
    // build extended poiner on new socket
    new_socket_xp = XPTR( new_socket_cxy , new_socket_ptr );

#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] created new socket[%x,%d] / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, new_fdid, cycle );
#endif
        
    // compute NIC channel index from remote_addr and remote_port
    uint32_t new_nic_channel = dev_nic_get_key( new_remote_addr , new_remote_port );

    // update new socket descriptor 
    hal_remote_s32(XPTR(new_socket_cxy , &new_socket_ptr->local_addr ) , socket_local_addr );
    hal_remote_s32(XPTR(new_socket_cxy , &new_socket_ptr->local_port ) , socket_local_port );
    hal_remote_s32(XPTR(new_socket_cxy , &new_socket_ptr->remote_addr) , new_remote_addr );
    hal_remote_s32(XPTR(new_socket_cxy , &new_socket_ptr->remote_port) , new_remote_port );
    hal_remote_s32(XPTR(new_socket_cxy , &new_socket_ptr->nic_channel) , new_nic_channel );
    hal_remote_s32(XPTR(new_socket_cxy , &new_socket_ptr->state      ) , TCP_STATE_SYN_RCVD );

    // set new socket TCB : increment tx_nxt / initialize rx_nxt, rx_irs, rx_wnd
    hal_remote_s32( XPTR( new_socket_cxy , &new_socket_ptr->tx_nxt ), socket_tx_nxt + 1 );
    hal_remote_s32( XPTR( new_socket_cxy , &new_socket_ptr->rx_nxt ), new_remote_iss + 1 );
    hal_remote_s32( XPTR( new_socket_cxy , &new_socket_ptr->rx_irs ), new_remote_iss );
    hal_remote_s32( XPTR( new_socket_cxy , &new_socket_ptr->rx_wnd ), new_remote_window );

    // link new socket to chdev servers
    socket_link_to_servers( new_socket_xp , new_nic_channel );

    // 3) get pointers on NIC_TX[channel] chdev
    xptr_t    tx_chdev_xp  = chdev_dir.nic_tx[new_nic_channel];
    chdev_t * tx_chdev_ptr = GET_PTR( tx_chdev_xp );
    cxy_t     tx_chdev_cxy = GET_CXY( tx_chdev_xp );

    // get pointers on NIC_TX[channel] server thread
    tx_server_ptr = hal_remote_lpt( XPTR( tx_chdev_cxy , &tx_chdev_ptr->server ));
    tx_server_xp  = XPTR( tx_chdev_cxy , tx_server_ptr );

    // register command arguments in new socket to request a SYN_ACK segment
    hal_remote_s32( XPTR( new_socket_cxy , &new_socket_ptr->tx_cmd    ), CMD_TX_ACCEPT );
    hal_remote_s64( XPTR( new_socket_cxy , &new_socket_ptr->tx_client ), client_xp );
    hal_remote_s32( XPTR( new_socket_cxy , &new_socket_ptr->tx_valid  ), true );

    // unblock NIC_TX server thread
    thread_unblock( tx_server_xp , THREAD_BLOCKED_CLIENT );
 
#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] new_socket[%x,%d] blocks on <IO> waiting ESTAB / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, new_fdid, cycle );
#endif

    // client thread blocks & deschedules 
    thread_block( XPTR( local_cxy , this ) , THREAD_BLOCKED_IO );
    sched_yield( "waiting new socket connection");

#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] new_socket[%x,%d] resumes  / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, new_fdid, cycle );
#endif

    // get new socket state, tx_valid and tx_sts
    new_state  = hal_remote_l32( XPTR( new_socket_cxy , &new_socket_ptr->state ));
    cmd_valid  = hal_remote_l32( XPTR( new_socket_cxy , &new_socket_ptr->tx_valid ));
    cmd_status = hal_remote_l32( XPTR( new_socket_cxy , &new_socket_ptr->tx_sts ));

assert( (((new_state == TCP_STATE_ESTAB) || (cmd_status != CMD_STS_SUCCESS))
        && (cmd_valid == false)), 
"illegal socket state when client thread resumes after TX_ACCEPT" ); 

    // reset socket.tx_client
    hal_remote_s64( XPTR( new_socket_cxy , &new_socket_ptr->tx_client ) , XPTR_NULL );

    if( cmd_status != CMD_STS_SUCCESS ) 
    {
        printk("\n[ERROR] in %s for TX_ACCEPT command / socket[%x,%d] / thread[%x,%x]\n",
        __FUNCTION__, process->pid, new_fdid, process->pid, this->trdid );
        return -1;
    }
    else
    {

#if DEBUG_SOCKET_ACCEPT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_ACCEPT < cycle )
printk("\n[%s] thread[%x,%x] new_socket[%x,%d] / state %s / addr %x / port %x / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, new_fdid,
socket_state_str(new_state), new_remote_addr, new_remote_port, cycle );
#endif

        // return success
        *remote_addr = new_remote_addr;
        *remote_port = new_remote_port;
        return new_fdid;
    }
 
}  // end socket_accept()

//////////////////////////////////
int socket_connect( uint32_t fdid,
                    uint32_t remote_addr,
                    uint16_t remote_port )
{
    vfs_inode_type_t    file_type;
    socket_t          * socket_ptr;       // local pointer on thread descriptor
    volatile uint32_t   socket_state;     // socket state (modified by the NIC_TX thread)
    uint32_t            socket_type;      // socket type  
    uint32_t            local_addr;       // local IP address
    uint32_t            local_port;       // local port
    xptr_t              tx_server_xp;     // extended pointer on TX server thread
    thread_t          * tx_server_ptr;    // local pointer on TX server thread
    uint32_t            nic_channel;      // NIC channel index
    uint32_t            cmd_status;       // command status (tx_sts field)
    bool_t              cmd_valid;        // command valid (tx_valid field)

    thread_t  * this      = CURRENT_THREAD;
    xptr_t      client_xp = XPTR( local_cxy , this );

    // get pointers on file descriptor
    xptr_t       file_xp  = process_fd_get_xptr_from_local( this->process , fdid );
    vfs_file_t * file_ptr = GET_PTR( file_xp );
    cxy_t        file_cxy = GET_CXY( file_xp );

    // check file_xp
    if( file_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : undefined fdid %d",
        __FUNCTION__, fdid );
        return -1;
    }

    file_type  = hal_remote_l32( XPTR( file_cxy , &file_ptr->type ) );
    socket_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );

#if DEBUG_SOCKET_CONNECT
uint32_t cycle = (uint32_t)hal_get_cycles();
pid_t    pid   = this->process->pid;
trdid_t  trdid = this->trdid;
if( DEBUG_SOCKET_CONNECT < cycle )
printk("\n[%s] thread[%x,%x] enter for socket[%x,%d] / addr %x / port %d / cycle %d\n",
__FUNCTION__,  pid, trdid, pid, fdid, remote_addr, remote_port, cycle );
#endif

    // check file descriptor type
    if( file_type != INODE_TYPE_SOCK )
    {
        printk("\n[ERROR] in %s : illegal file type %s",
        __FUNCTION__, vfs_inode_type_str( file_type ) );
        return -1;
    }

    // get relevant socket infos
    socket_type   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->type ) );
    socket_state  = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state ) );
    local_addr    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->local_addr ) );
    local_port    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->local_port ) );

    if( socket_type == SOCK_DGRAM )       // UDP
    {
        if( socket_state != UDP_STATE_BOUND )
        {
            printk("\n[ERROR] in %s : illegal socket state %s for type %s",
            __FUNCTION__, socket_state_str(socket_state), socket_type_str(socket_type) );
            return -1;
        }
    }
    else if( socket_type == SOCK_STREAM )  // TCP
    {
        if( socket_state != TCP_STATE_BOUND )
        {
            printk("\n[ERROR] in %s : illegal socket state %s for type %s",
            __FUNCTION__, socket_state_str(socket_state), socket_type_str(socket_type) );
            return -1;
        }
    }
    else
    {
        printk("\n[ERROR] in %s : illegal socket type %s",
        __FUNCTION__,  socket_type_str(socket_type) );
        return -1;
    }

    // compute nic_channel index from remote_addr and remote_port
    nic_channel = dev_nic_get_key( remote_addr , remote_port );

    // link socket to chdev servers
    socket_link_to_servers( XPTR( file_cxy , socket_ptr ), nic_channel );

    // update the socket descriptor
    hal_remote_s32( XPTR( file_cxy , &socket_ptr->remote_addr ) , remote_addr  );
    hal_remote_s32( XPTR( file_cxy , &socket_ptr->remote_port ) , remote_port  );
    hal_remote_s32( XPTR( file_cxy , &socket_ptr->nic_channel ) , nic_channel  );

    // the actual connection mechanism depends on socket type
    // UDP : client thread updates the local socket state without blocking
    // TCP : client thread request TX server thread to start the 3 steps handshake

    if( socket_type == SOCK_DGRAM )  // UDP
    {
        // directly update the local socket state
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->state ) , UDP_STATE_ESTAB );

        return 0;
    }
    else                             // TCP
    {
        // get pointers on NIC_TX[channel] chdev
        xptr_t    tx_chdev_xp  = chdev_dir.nic_tx[nic_channel];
        chdev_t * tx_chdev_ptr = GET_PTR( tx_chdev_xp );
        cxy_t     tx_chdev_cxy = GET_CXY( tx_chdev_xp );

        // get pointers on NIC_TX[channel] server thread
        tx_server_ptr = hal_remote_lpt( XPTR( tx_chdev_cxy , &tx_chdev_ptr->server ));
        tx_server_xp  = XPTR( tx_chdev_cxy , tx_server_ptr );

        // register command arguments in socket descriptor for a SYN segment
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_cmd    ), CMD_TX_CONNECT );
        hal_remote_s64( XPTR( file_cxy , &socket_ptr->tx_client ), client_xp );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_valid  ), true );

        // unblock NIC_TX server thread
        thread_unblock( tx_server_xp , THREAD_BLOCKED_CLIENT );
 
#if DEBUG_SOCKET_CONNECT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CONNECT < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] blocks on <IO> waiting connexion / cycle %d \n",
__FUNCTION__, pid, trdid, pid, fdid, cycle );
#endif
        // block itself and deschedule
        thread_block( XPTR( local_cxy , this ) , THREAD_BLOCKED_IO );
        sched_yield( "waiting connection" );

#if DEBUG_SOCKET_CONNECT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CONNECT < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] / resumes / cycle %d \n",
__FUNCTION__, pid, trdid, pid, fdid, cycle );
#endif

        // get socket state, tx_valid and tx_sts
        cmd_valid    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_valid ));
        cmd_status   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_sts ));
        socket_state = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state ));

assert( (((socket_state == TCP_STATE_ESTAB) || (cmd_status != CMD_STS_SUCCESS))
        && (cmd_valid == false)),
"illegal socket state when client thread resumes after TX_CONNECT" );

        // reset socket.tx_client
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_client ) , XPTR_NULL );

        if( cmd_status != CMD_STS_SUCCESS )
        {
            printk("\n[ERROR] in %s : for command TX_CONNECT / socket[%x,%d] / thread[%x,%x]\n",
            __FUNCTION__, pid, fdid, pid, trdid );
            return -1;
        }
        else
        {

#if DEBUG_SOCKET_CONNECT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CONNECT < cycle )
printk("\n[%s] thread[%x,%x] exit for socket[%x,%d] / %s / cycle %d \n",
__FUNCTION__, pid, trdid, pid, fdid, socket_state_str(socket_state),cycle );
#endif
             return 0;
        }
    }  // end TCP

}  // end socket_connect()

///////////////////////////////////
int socket_close( xptr_t   file_xp,
                  uint32_t fdid )
{
    uint32_t     socket_type;
    uint32_t     socket_state;
    uint32_t     nic_channel;
    uint32_t     cmd_status;      // socket.tx_sts
    bool_t       cmd_valid;       // socket.tx_valid
    thread_t   * tx_server_ptr;   // local pointer on NIC_TX server thread
    xptr_t       tx_server_xp;    // extended pointer on NIC_TX server thread
    xptr_t       socket_lock_xp;  // extended pointer on socket lock

    thread_t   * this      = CURRENT_THREAD;
    xptr_t       client_xp = XPTR( local_cxy , this );
    process_t  * process   = this->process;

    // get pointer on socket descriptor
    cxy_t        file_cxy    = GET_CXY( file_xp );
    vfs_file_t * file_ptr    = GET_PTR( file_xp );
    socket_t   * socket_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );

assert( (hal_remote_l32( XPTR( file_cxy , &socket_ptr->fdid )) == fdid),
"unconsistent file_xp & fdid arguments");

#if DEBUG_SOCKET_CLOSE
uint32_t cycle = (uint32_t)hal_get_cycles();
pid_t    pid   = this->process->pid;
if (DEBUG_SOCKET_CLOSE < cycle )
printk("\n[%s] thread[%x,%x] enters for socket[%x,%d] / cycle %d\n",
__FUNCTION__, pid, this->trdid, pid, fdid, cycle );
#endif

    // build extended pointer on lock protecting socket 
    socket_lock_xp = XPTR( file_cxy , &socket_ptr->lock );

    // take socket lock
    remote_queuelock_acquire( socket_lock_xp );

    // check no previous TX command
    if( (hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_valid )) == true) || 
        (hal_remote_l64( XPTR( file_cxy , &socket_ptr->tx_client)) != XPTR_NULL) )
    { 
        // release socket lock
        remote_queuelock_release( socket_lock_xp );
                    
        printk("\n[ERROR] in %s : previous TX cmd on socket[%x,%d] / thread[%x,%x]\n",
        __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
        return -1;
    }

    // get relevant socket infos
    socket_type   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->type ));
    nic_channel   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->nic_channel ));
    socket_state  = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state ));


    // the actual close mechanism depends on socket type and state:
    // UDP or TCP not connected : client thread directly destroy the socket descriptor
    // TCP connected : client thread request TX server thread to make the TCP close handshake

    if( socket_type == SOCK_DGRAM )                   // UDP 
    {

#if DEBUG_SOCKET_CLOSE
cycle = (uint32_t)hal_get_cycles();
if( cycle > DEBUG_DEV_NIC_TX )
printk("\n[%s] thread[%x,%x] socket[%x,%d] %s / destroy socket / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, pid, fdid,
socket_state_str( socket_state ), cycle );
#endif
        // directly destroy socket
        socket_destroy( file_xp );

        return 0;
    }
    else if( (socket_state == TCP_STATE_BOUND) ||
             (socket_state == TCP_STATE_LISTEN) ||
             (socket_state == TCP_STATE_SYN_SENT) )   // TCP not connected
    {

#if DEBUG_SOCKET_CLOSE
cycle = (uint32_t)hal_get_cycles();
if( cycle > DEBUG_DEV_NIC_TX )
printk("\n[%s] thread[%x,%x] socket[%x,%d] %s / destroy socket / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, pid, fdid,
socket_state_str( socket_state ), cycle );
#endif
        // directly destroy socket
        socket_destroy( file_xp );

        return 0;
    }
    else                                             // TCP connected
    {
        // get pointers on NIC_TX[index] chdev
        xptr_t    tx_chdev_xp  = chdev_dir.nic_tx[nic_channel];
        chdev_t * tx_chdev_ptr = GET_PTR( tx_chdev_xp );
        cxy_t     tx_chdev_cxy = GET_CXY( tx_chdev_xp );

        // get pointers on NIC_TX[channel] server thread
        tx_server_ptr = hal_remote_lpt( XPTR( tx_chdev_cxy , &tx_chdev_ptr->server ));
        tx_server_xp  = XPTR( tx_chdev_cxy , tx_server_ptr );

        // register command arguments in socket descriptor
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_cmd    ), CMD_TX_CLOSE );
        hal_remote_s64( XPTR( file_cxy , &socket_ptr->tx_client ), client_xp );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_valid  ), true );
        
        // unblock NIC_TX server thread
        thread_unblock( tx_server_xp , THREAD_BLOCKED_CLIENT );
 
        // release socket lock
        remote_queuelock_release( socket_lock_xp );

#if DEBUG_SOCKET_CLOSE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CLOSE < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] blocks on <IO> waiting close / cycle %d \n",
__FUNCTION__, pid, this->trdid, pid, fdid, cycle );
#endif
        // block itself and deschedule
        thread_block( client_xp , THREAD_BLOCKED_IO );
        sched_yield( "blocked in close" );

#if DEBUG_SOCKET_CLOSE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CLOSE < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] / resumes / cycle %d \n",
__FUNCTION__, pid, this->trdid, pid, fdid, cycle );
#endif
        // take socket lock
        remote_queuelock_acquire( socket_lock_xp );

        // get socket state & command status
        socket_state = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state ) );
        cmd_status   = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_sts) );
        cmd_valid    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_valid ) );

assert( (((socket_state == TCP_STATE_CLOSED) || (cmd_status != CMD_STS_SUCCESS))
         && (cmd_valid == false)),
"illegal socket state when client thread resumes after TX_CLOSE\n"
" socket_state = %s / cmd_status = %d / cmd_valid = %d\n",
socket_state_str(socket_state), cmd_status, cmd_valid );

        // reset socket.tx_client
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_client ) , XPTR_NULL );

        if( cmd_status != CMD_STS_SUCCESS )  // error reported
        {
            printk("\n[ERROR] in %s for command TX_CLOSE / socket[%x,%d] / thread[%x,%x]\n",
            __FUNCTION__, pid, fdid, pid, this->trdid );
            return -1;
        }
        else                                 // success
        {

#if DEBUG_SOCKET_CLOSE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_CLOSE < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] / destroy socket / cycle %d\n",
__FUNCTION__, pid, this->trdid, pid, fdid, socket_state_str(socket_state) , cycle );
#endif
            // destroy socket
            socket_destroy( file_xp );

            return 0;
        }
    }   // end if TCP
}  // end socket_close()

////////////////////////////////////////////////////////////////////////////////////////
// This static and blocking function is executed by an user thread calling one of the
// four functions: socket_send() / socket_recv() / socket_sendto() / socket_recvfrom()
// It can be used for both UDP and TCP sockets.
////////////////////////////////////////////////////////////////////////////////////////
// @ is_send   : send when true / receive when false.
// @ fdid      : socket identifier.
// @ u_buf     : pointer on user buffer in user space.
// @ length    : number of bytes.
// @ explicit  : explicit remote IP address and port when true.
////////////////////////////////////////////////////////////////////////////////////////
// Implementation note : The behavior is different for SEND & RECV
// - For a SEND, the client thread checks that there is no TX command registered
//   in the socket. It registers the command arguments in the socket descriptor
//   (tx_client, tx_cmd, tx_buf, tx_len). Then the client thread unblocks the 
//   TX server thread from the BLOCKED_CLIENT condition, blocks itself on the
//   BLOCKED_IO condition, and deschedules. It is unblocked by the TX server thread
//   when the last byte has been sent (for UDP) or acknowledged (for TCP).
//   When the client thread resumes, it reset the command in socket, and returns.
// - For a RECV, the client thread checks that there is no RX command registered
//   in the socket. It registers itself in socket (rx_client). It checks the status
//   of the receive buffer. It the rx_buf is empty, it blocks on the BLOCKED_IO
//   condition, and deschedules. It is unblocked by the RX server thread when an UDP
//   packet or TCP segment has been writen in the rx_buf. When it resumes, it moves
//   the available data from the rx_buf to the user buffer, reset its registration
//   in socket (reset the rx_buf for an UDP socket), and returns.
////////////////////////////////////////////////////////////////////////////////////////
int socket_move_data( bool_t     is_send,
                      uint32_t   fdid,
                      uint8_t  * u_buf,
                      uint32_t   length,
                      bool_t     explicit,
                      uint32_t   explicit_addr,
                      uint32_t   explicit_port )
{
    vfs_inode_type_t    file_type;       // file descriptor type
    socket_t          * socket_ptr;      // local pointer on socket descriptor
    uint32_t            socket_state;    // current socket state
    uint32_t            socket_type;     // socket type (UDP/TCP)
    uint32_t            nic_channel;     // NIC channel for this socket
    xptr_t              socket_lock_xp;  // extended pointer on socket lock
    xptr_t              file_xp;         // extended pointer on file descriptor
    vfs_file_t        * file_ptr;
    cxy_t               file_cxy;
    xptr_t              chdev_xp;        // extended pointer on NIC_TX[channel] chdev
    chdev_t           * chdev_ptr;
    cxy_t               chdev_cxy;
    uint32_t            remote_addr;
    uint32_t            remote_port;
    uint32_t            buf_status;      // number of bytes in rx_buf
    int32_t             moved_bytes;     // total number of moved bytes (fot return)
    xptr_t              server_xp;       // extended pointer on NIC_TX / NIC_RX server thread
    thread_t          * server_ptr;      // local pointer on NIC_TX / NIC_RX server thread
    kmem_req_t          req;             // KCM request for TX kernel buffer
    uint8_t           * tx_buf;          // kernel buffer for TX transfer
    bool_t              cmd_valid;       // from socket descriptor
    uint32_t            cmd_status;      // from socket descriptor
    uint32_t            tx_todo;         // from socket descriptor

    thread_t  * this    = CURRENT_THREAD;
    process_t * process = this->process;

    // build extended pointer on client thread
    xptr_t client_xp = XPTR( local_cxy , this );

    // get pointers on file descriptor identifying the socket
    file_xp  = process_fd_get_xptr_from_local( process , fdid );
    file_ptr = GET_PTR( file_xp );
    file_cxy = GET_CXY( file_xp );

    if( file_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : undefined fdid %d / thread%x,%x]\n",
        __FUNCTION__, fdid , process->pid, this->trdid );
        return -1;
    }
 
    // get file type and socket pointer
    file_type  = hal_remote_l32( XPTR( file_cxy , &file_ptr->type ) );

    // get local pointer on socket
    socket_ptr = hal_remote_lpt( XPTR( file_cxy , &file_ptr->socket ) );

    // check file descriptor type
    if( file_type != INODE_TYPE_SOCK )
    {
        printk("\n[ERROR] in %s : illegal file type %s / socket[%x,%d]\n",
        __FUNCTION__, vfs_inode_type_str(file_type), process->pid, fdid );
        return -1;
    }

    // build extended pointer on lock protecting socket 
    socket_lock_xp = XPTR( file_cxy , &socket_ptr->lock );

    // take the socket lock
    remote_queuelock_acquire( socket_lock_xp );

    // get socket type, state, and channel
    socket_type  = hal_remote_l32( XPTR( file_cxy , &socket_ptr->type ));
    socket_state = hal_remote_l32( XPTR( file_cxy , &socket_ptr->state ));
    nic_channel  = hal_remote_l32( XPTR( file_cxy , &socket_ptr->nic_channel ));

    // handle the explicit remote address and port
    if( socket_type == SOCK_DGRAM )                  // UDP socket
    {
        if( socket_state == UDP_STATE_UNBOUND )
        {
            // release socket lock
            remote_queuelock_release( socket_lock_xp );
                    
            printk("\n[ERROR] in %s : SEND/RECV for socket[%x,%d] in state %s\n",
            __FUNCTION__, process->pid, fdid, socket_state_str(socket_state) );
            return -1;
        }

        if( explicit )
        {
            // update remote IP address and port into socket descriptor
            hal_remote_s32( XPTR( file_cxy , &socket_ptr->remote_addr ), explicit_addr );
            hal_remote_s32( XPTR( file_cxy , &socket_ptr->remote_port ), explicit_port );

            // update socket state if required
            if( socket_state == UDP_STATE_BOUND )
            {
                hal_remote_s32( XPTR( file_cxy , &socket_ptr->state ), UDP_STATE_ESTAB );
            }
        }
    }
    else                                            // TCP socket
    {
        if( explicit )
        {
            // get remote IP address and port from socket descriptor
            remote_addr = hal_remote_l32( XPTR( file_cxy , &socket_ptr->remote_addr ));
            remote_port = hal_remote_l32( XPTR( file_cxy , &socket_ptr->remote_port ));

            if( (remote_addr != explicit_addr) || (remote_port != explicit_port) )
            {
                // release socket lock
                remote_queuelock_release( socket_lock_xp );
                    
                printk("\n[ERROR] in %s : wrong expliciy access for socket[%x,%d]\n",
                __FUNCTION__, process->pid, fdid );
                return -1;
            }
        }
    }

    ///////////////////////////////////////////////////////
    if( is_send )                       // TX_SEND command
    {

#if DEBUG_SOCKET_SEND
uint32_t    cycle = (uint32_t)hal_get_cycles();
if (DEBUG_SOCKET_SEND < cycle )
printk("\n[%s] thread[%x,%x] received SEND command for socket[%x,%d] / length %d / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, length, cycle );
#endif
        // check no previous TX command
        if( (hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_valid )) == true) || 
            (hal_remote_l64( XPTR( file_cxy , &socket_ptr->tx_client)) != XPTR_NULL) )
        { 
            // release socket lock
            remote_queuelock_release( socket_lock_xp );
                    
            printk("\n[ERROR] in %s : previous TX command / socket[%x,%d] / thread[%x,%x]\n",
            __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
            return -1;
        }

        // allocate a temporary kernel buffer
        req.type  = KMEM_KCM;
        req.order = bits_log2( length );
        req.flags = AF_NONE;
        tx_buf    = kmem_alloc( &req );  

        if( tx_buf == NULL )
        {
            // release socket lock
            remote_queuelock_release( socket_lock_xp );
                    
            printk("\n[ERROR] in %s : no memory for tx_buf / socket[%x,%d] / thread[%x,%x]\n",
            __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
            return -1;
        }

        // copy data from user u_buf to kernel tx_buf   
        hal_copy_from_uspace( XPTR( local_cxy , tx_buf ),
                              u_buf,
                              length );

        // register command in socket descriptor
        hal_remote_s64( XPTR( file_cxy , &socket_ptr->tx_client ) , client_xp );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_cmd    ) , CMD_TX_SEND );
        hal_remote_spt( XPTR( file_cxy , &socket_ptr->tx_buf    ) , tx_buf );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_len    ) , length );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_todo   ) , length );
        hal_remote_s32( XPTR( file_cxy , &socket_ptr->tx_valid  ) , true );

        // release socket lock
        remote_queuelock_release( socket_lock_xp );
                    
        // get pointers on relevant chdev
        chdev_xp  = chdev_dir.nic_tx[nic_channel];
        chdev_ptr = GET_PTR( chdev_xp );
        chdev_cxy = GET_CXY( chdev_xp );

        // get pointers on NIC_TX[channel] server thread
        server_ptr = hal_remote_lpt( XPTR( chdev_cxy , &chdev_ptr->server ));
        server_xp  = XPTR( chdev_cxy , server_ptr );

        // unblocks the NIC_TX server thread
        thread_unblock( server_xp , THREAD_BLOCKED_CLIENT );

#if DEBUG_SOCKET_SEND   
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_SEND < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] register SEND => blocks on <IO> / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
        // client thread blocks itself and deschedules
        thread_block( XPTR( local_cxy , this ) , THREAD_BLOCKED_IO );
        sched_yield( "blocked in nic_io" );

#if DEBUG_SOCKET_SEND   
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_SEND < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] for SEND resumes / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
        // take socket lock
        remote_queuelock_acquire( socket_lock_xp );
      
        // get tx_valid, tx_todo, and tx_sts
        tx_todo    = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_todo ));
        cmd_valid  = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_valid ));
        cmd_status = hal_remote_l32( XPTR( file_cxy , &socket_ptr->tx_sts ));

        // reset tx_client in socket descriptor
        hal_remote_s64( XPTR( file_cxy , &socket_ptr->tx_client  ) , XPTR_NULL );

        // release socket lock
        remote_queuelock_release( socket_lock_xp );
      
// check SEND command completed when TX client thread resumes
assert( (((tx_todo == 0) || (cmd_status != CMD_STS_SUCCESS)) && (cmd_valid == false)),
"illegal socket state when client thread resumes after TX_SEND\n"
" tx_todo = %d / tx_status = %d / tx_valid = %d\n",
tx_todo, cmd_status, cmd_valid );

        // release the tx_buf
        req.ptr = tx_buf;
        kmem_free( &req );

        if( cmd_status != CMD_STS_SUCCESS )
        {

#if DEBUG_SOCKET_SEND
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_RECV < cycle )
printk("\n[%s] error %s for TX_SEND / socket[%x,%d] / thread[%x,%x]\n",
__FUNCTION__, socket_cmd_sts_str(cmd_status), process->pid, fdid, process->pid, this->trdid );
#endif
            return -1;
        }
        else
        {

#if DEBUG_SOCKET_SEND
cycle = (uint32_t)hal_get_cycles();
if (DEBUG_SOCKET_SEND < cycle )
printk("\n[%s] thread[%x,%x] success for SEND / socket[%x,%d] / length %d / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, length, cycle );
#endif
            return length;
        }

    }  // end TX_SEND command

    ////////////////////////////////////////////////////////
    else                                 // RX_RECV command
    {

#if DEBUG_SOCKET_RECV
uint32_t    cycle = (uint32_t)hal_get_cycles();
if (DEBUG_SOCKET_SEND < cycle )
printk("\n[%s] thread[%x,%x] received RECV command for socket[%x,%d] / length %d / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, length, cycle );
#endif
        // check no previous RX command
        if( (hal_remote_l32( XPTR( file_cxy , &socket_ptr->rx_valid )) == true) || 
            (hal_remote_l64( XPTR( file_cxy , &socket_ptr->rx_client)) != XPTR_NULL) )
        {
            // release socket lock
            remote_queuelock_release( socket_lock_xp );
                    
            printk("\n[ERROR] in %s : previous RX command on socket[%x,%d] / thread[%x,%x]\n",
            __FUNCTION__, process->pid, fdid, process->pid, this->trdid );
            return -1;
        }

        // return EOF for a TCP socket not in ESTAB state
        if( (socket_type == SOCK_STREAM ) && (socket_state != TCP_STATE_ESTAB) )
        { 
            // release socket lock
            remote_queuelock_release( socket_lock_xp );
                    
#if DEBUG_SOCKET_RECV  
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_RECV < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] TCP connection closed / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
            return 0;
        }
        // build extended pointer on socket.rx_buf
        xptr_t rx_buf_xp   = XPTR( file_cxy , &socket_ptr->rx_buf );

        // get rx_buf status
        buf_status = remote_buf_status( rx_buf_xp );

        if( buf_status == 0 )
        {
            // registers RX_RECV command in socket descriptor
            hal_remote_s32( XPTR( file_cxy , &socket_ptr->rx_cmd    ) , CMD_RX_RECV );
            hal_remote_s64( XPTR( file_cxy , &socket_ptr->rx_client ) , client_xp );
            hal_remote_s32( XPTR( file_cxy , &socket_ptr->rx_valid  ) , true );

            // release socket lock
            remote_queuelock_release( socket_lock_xp );

#if DEBUG_SOCKET_RECV  
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_RECV < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] rx_buf empty => blocks on <IO> / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
            // client thread blocks itself and deschedules
            thread_block( XPTR( local_cxy , this ) , THREAD_BLOCKED_IO );
            sched_yield( "blocked in nic_io" );

#if DEBUG_SOCKET_RECV  
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_RECV < cycle )
printk("\n[%s] thread[%x,%x] socket[%x,%d] for RECV resumes / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, cycle );
#endif
            // take socket lock
            remote_queuelock_acquire( socket_lock_xp );

            // get rx_sts and rx_buf status
            cmd_valid  = hal_remote_l32( XPTR( file_cxy , &socket_ptr->rx_valid ));
            cmd_status = hal_remote_l32( XPTR( file_cxy , &socket_ptr->rx_sts ));
            buf_status = remote_buf_status( rx_buf_xp );
        
assert( (((buf_status != 0) || (cmd_status != CMD_STS_SUCCESS)) && (cmd_valid == false)),
"illegal socket state when client thread resumes after RX_RECV\n"
" buf_status = %d / rx_sts = %d / rx_valid = %d\n",
buf_status , cmd_status , cmd_valid );

            // reset rx_client in socket descriptor
            hal_remote_s64( XPTR( file_cxy , &socket_ptr->rx_client  ) , XPTR_NULL );

            // reset rx_buf for an UDP socket
            if( socket_type == SOCK_DGRAM ) remote_buf_reset( rx_buf_xp );

            // release socket lock
            remote_queuelock_release( socket_lock_xp );

            if( cmd_status == CMD_STS_EOF )           // EOF (remote close) reported
            {

#if DEBUG_SOCKET_RECV
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_RECV < cycle )
printk("\n[%s] EOF for RX_RECV / socket[%x,%d] / thread[%x,%x]\n",
__FUNCTION__, process->pid, fdid, process->pid, this->trdid );
#endif
                return 0;
            }
            else if( cmd_status != CMD_STS_SUCCESS )   // other error reported
            {

#if DEBUG_SOCKET_RECV
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_SOCKET_RECV < cycle )
printk("\n[%s] error %s for RX_RECV / socket[%x,%d] / thread[%x,%x]\n",
__FUNCTION__, socket_cmd_sts_str(cmd_status), process->pid, fdid, process->pid, this->trdid );
#endif
                return -1;
            }

        }

        // number of bytes extracted from rx_buf cannot be larger than u_buf size
        moved_bytes = ( length < buf_status ) ? length : buf_status;

        // move data from kernel rx_buf to user u_buf
        remote_buf_get_to_user( rx_buf_xp,
                                u_buf,
                                moved_bytes );
#if DEBUG_SOCKET_SEND
cycle = (uint32_t)hal_get_cycles();
if (DEBUG_SOCKET_SEND < cycle )
printk("\n[%s] thread[%x,%x] success for RECV / socket[%x,%d] / length %d / cycle %d\n",
__FUNCTION__, process->pid, this->trdid, process->pid, fdid, moved_bytes, cycle );
#endif
        return moved_bytes;

    }  // end RX_RECV command
} // end socket_move_data()


///////////////////////////////////
int socket_send( uint32_t    fdid,
                 uint8_t   * u_buf,
                 uint32_t    length )
{
    int nbytes = socket_move_data( true,           // SEND
                                   fdid,
                                   u_buf,
                                   length,
                                   false, 0, 0 );  // no explicit remote socket
    return nbytes;

}  // end socket_send()

/////////////////////////////////////
int socket_sendto( uint32_t    fdid,
                   uint8_t   * u_buf,
                   uint32_t    length,
                   uint32_t    remote_addr,
                   uint32_t    remote_port )
{
    int nbytes = socket_move_data( true,          // SEND
                                   fdid,
                                   u_buf,
                                   length,
                                   true,          // explicit remote socket
                                   remote_addr,
                                   remote_port );
    return nbytes;

}  // end socket_sendto()

///////////////////////////////////
int socket_recv( uint32_t    fdid,
                 uint8_t   * u_buf,
                 uint32_t    length )
{
    int nbytes = socket_move_data( false,          // RECV
                                   fdid,
                                   u_buf,
                                   length,
                                   false, 0, 0 );  // no explicit remote socket
    return nbytes;

} // end socket_recv()


///////////////////////////////////////
int socket_recvfrom( uint32_t    fdid,
                     uint8_t   * u_buf,
                     uint32_t    length,
                     uint32_t    remote_addr,
                     uint32_t    remote_port )
{
    int nbytes = socket_move_data( false,         // RECV
                                   fdid,
                                   u_buf,
                                   length,
                                   true,          // explicit remote socket
                                   remote_addr,
                                   remote_port );
    return nbytes;

}  // end socket_recvfrom()

////////////////////////////////////////////
void socket_display( xptr_t       socket_xp,
                     const char * func_str )
{
    socket_t * socket = GET_PTR( socket_xp );
    cxy_t      cxy    = GET_CXY( socket_xp );

    pid_t      pid         = hal_remote_l32( XPTR( cxy , &socket->pid ));
    fdid_t     fdid        = hal_remote_l32( XPTR( cxy , &socket->fdid ));
    uint32_t   state       = hal_remote_l32( XPTR( cxy , &socket->state ));
    uint32_t   channel     = hal_remote_l32( XPTR( cxy , &socket->nic_channel ));
    uint32_t   local_addr  = hal_remote_l32( XPTR( cxy , &socket->local_addr ));
    uint32_t   local_port  = hal_remote_l32( XPTR( cxy , &socket->local_port ));
    uint32_t   remote_addr = hal_remote_l32( XPTR( cxy , &socket->remote_addr ));
    uint32_t   remote_port = hal_remote_l32( XPTR( cxy , &socket->remote_port ));
    uint32_t   tx_valid    = hal_remote_l32( XPTR( cxy , &socket->tx_valid ));
    uint32_t   tx_cmd      = hal_remote_l32( XPTR( cxy , &socket->tx_cmd ));
    uint32_t   tx_sts      = hal_remote_l32( XPTR( cxy , &socket->tx_sts ));
    uint32_t   tx_len      = hal_remote_l32( XPTR( cxy , &socket->tx_len ));
    uint32_t   tx_todo     = hal_remote_l32( XPTR( cxy , &socket->tx_todo ));
    uint32_t   tx_una      = hal_remote_l32( XPTR( cxy , &socket->tx_una ));          
    uint32_t   tx_nxt      = hal_remote_l32( XPTR( cxy , &socket->tx_nxt ));          
    uint32_t   tx_wnd      = hal_remote_l32( XPTR( cxy , &socket->tx_wnd ));          
    uint32_t   rx_valid    = hal_remote_l32( XPTR( cxy , &socket->rx_valid ));
    uint32_t   rx_cmd      = hal_remote_l32( XPTR( cxy , &socket->rx_cmd ));
    uint32_t   rx_sts      = hal_remote_l32( XPTR( cxy , &socket->rx_sts ));
    uint32_t   rx_nxt      = hal_remote_l32( XPTR( cxy , &socket->rx_nxt ));          
    uint32_t   rx_wnd      = hal_remote_l32( XPTR( cxy , &socket->rx_wnd ));          
    uint32_t   rx_irs      = hal_remote_l32( XPTR( cxy , &socket->rx_irs ));          

    if( func_str == NULL )
    {
        printk("\n****** socket[%x,%d] / xptr[%x,%x]*****\n",
        pid, fdid, cxy, socket );
    }
    else
    {
        printk("\n***** socket[%x,%d] / xptr[%x,%x] / from %s *****\n",
        pid, fdid, cxy, socket, func_str );
    }
    printk(" - state %s / channel %d\n"
           " - local_addr %x / local_port %x\n"
           " - remote_addr %x / remote_port %x\n"
           " - tx_valid %d (%s) / tx_sts %d / tx_len %x / tx_todo %x\n"
           " - tx_una %x / tx_nxt %x / tx_wnd %x\n"
           " - rx_valid %d (%s) / rx_sts %d\n"
           " - rx_nxt %x / rx_wnd %x / rx_irs %x\n",
           socket_state_str(state), channel ,
           local_addr, local_port,
           remote_addr, remote_port,
           tx_valid, socket_cmd_type_str(tx_cmd), tx_sts, tx_len, tx_todo,
           tx_una, tx_nxt, tx_wnd,
           rx_valid, socket_cmd_type_str(rx_cmd), rx_sts,
           rx_nxt, rx_wnd, rx_irs );

}  // end socket_display()





