/*
 * socket.c - 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 <shared_socket.h>
#include <process.h>
#include <remote_buf.h>
#include <printk.h>
#include <kmem.h>
#include <thread.h>
#include <vfs.h>
#include <socket.h>

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

extern chdev_directory_t  chdev_dir;         // allocated in kernel_init.c

//////////////////////////////////////
char * socket_cmd_str( uint32_t type )
{
    switch( type )
    {
        case SOCKET_TX_CONNECT  : return "CONNECT";
        case SOCKET_TX_SEND     : return "SEND";
        case SOCKET_TX_CLOSE    : return "CLOSE";

        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_CONNECT    : return "UDP_CONNECT";

        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";

        default:                    return "undefined";
    }
}

///////////////////////////////////////
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;

    // 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_create( 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_create( 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;
    }

    // allocate memory for crqq queue 
    error = remote_buf_create( XPTR( cxy , &socket->crqq ),
                               NIC_CRQ_QUEUE_SIZE * sizeof(sockaddr_t) );
    if( error )
    {
        printk("\n[ERROR] in %s : cannot allocate CRQ queue / 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;
    }

    //  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->crqq ) );
        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->crqq ) );
        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->domain    ) , domain );
    hal_remote_s32( XPTR( cxy , &socket->type      ) , type );
    hal_remote_s32( XPTR( cxy , &socket->pid       ) , process->pid );
    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 );

    // 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 );

    remote_rwlock_init( XPTR( cxy , &file->lock ) , LOCK_VFS_FILE );
   
    // return success
    *socket_ptr = socket;
    *fdid_ptr   = fdid;

    return 0;

}  // end socket_create

////////////////////////////////////
void socket_destroy( uint32_t fdid )
{
    uint32_t            type;
    socket_t          * socket;
    kmem_req_t          req;

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

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

    type   = hal_remote_l32( XPTR( cxy , &file->type ) );
    socket = hal_remote_lpt( XPTR( cxy , &file->socket ) );

// check file descriptor pointer
assert( (file_xp != XPTR_NULL), "illegal fdid\n" );

// check file descriptor type
assert( (type == INODE_TYPE_SOCK), "illegal file type\n" );

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

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

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

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

}  // end socket_destroy()

/////////////////////////////////////////////////
void socket_link_to_servers( xptr_t    socket_xp,
                             uint32_t  nic_channel ) 
{
    cxy_t      socket_cxy = GET_CXY( socket_xp );
    socket_t * socket_ptr = GET_PTR( socket_xp );

    // 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 );

    // build extended pointers on root of sockets attached to NIC_TX[channel] chdev
    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 );

    // register socket in the NIC_TX[channel] chdev clients queue
    remote_rwlock_wr_acquire( tx_lock_xp );
    xlist_add_last( tx_root_xp , XPTR( socket_cxy , &socket_ptr->tx_list ) );
    remote_rwlock_wr_release( tx_lock_xp );

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

    // build extended pointer on root of sockets attached to NIC_TX[channel] chdev
    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 );

    // register socket in the NIC_RX[channel] chdev clients queue
    remote_rwlock_wr_acquire( rx_lock_xp );
    xlist_add_last( rx_root_xp , XPTR( socket_cxy , &socket_ptr->rx_list ) );
    remote_rwlock_wr_release( rx_lock_xp );

}  // end socket_link_to_server()


