/*
    NetSocket.c, Copyright 2000 (c) by Claudio Jeker,
    Swiss Federal Institute of Technology,
    Computer Engineering and Networks Laboratory.
 
    TOPSY -- A Teachable Operating System.
             Implementation of a tiny and simple
             micro kernel for teaching purposes.
 
    For further information, please visit http://www.tik.ee.ethz.ch/~topsy
 
    This software is provided under the terms of the GNU General Public 
    Licence. A full copy of the GNU GPL is provided in the file COPYING 
    found in the development root of Topsy.
*/
/*
	
    File:                  $Source: /home/ruf/cvs_Topsy/cvs/Topsy/Net/NetSocket.c,v $
    Author(s):             
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Creation Date:         
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $  by: $Author: baderma $
 
*/
#include "IO.h"
#include "IODevice.h"
#include "NetSocket.h"
#include "NetProtocols.h"
#include "NetBuf.h"
#include "NetAttr.h"
#include "NetQueue.h"
#include "NetError.h"
#include "NetSocket.h"
#include "NetConfigMngr.h"
#include "NetInQueueMngr.h"
#include "TMIPC.h"
#include "NetDebug.h"
#include "Syscall.h"
#include "Socket.h"
#include "Support.h"


IODeviceDesc socketDesc = {
                            NULL, NULL,              /* pointers for doubly linked list */
                            "socket",                /* device name */
                            NULL,                    /* device base address */
                            NULL,                    /* buffer must be allocated by driver */
                            0,                       /* interrupt */
                            NULL,                    /* interrupt handling function */
                            0,                       /* thread id filled in after start */
                            socket_read,             /* device read function */
                            socket_write,            /* device write function */
                            socket_close,            /* device close function */
                            socket_init,             /* device init function */
                            socket_handleMsg,        /* private msg handler */
                            FALSE,                   /* initialisation state, normally FALSE */
                            NULL                     /* data extension */
                          };

static int getNetQType( short so_type )
{
  switch ( so_type )
  {
  case SOCK_DGRAM:
    return TYPE_DGRAM;
  case SOCK_STREAM:
    return TYPE_STREAM;
  case SOCK_RAW:
    return TYPE_DGRAM;
  default:
    printf("socket_init: unknown so_type \"%i\"\n", so_type );
    return -1;
  }
}

Error socket_init(IODevice this)
{
  int type = 0;
  SocketDescPtr so = (SocketDescPtr) this->extension;


  // XXX alloc socketdesc ptr
  if ( this->isInitialised == TRUE )
    return IO_INITOK;

  if ( (type = getNetQType(so->so_type) ) == -1 )
    return IO_INITFAILED;

  if ( ( so->so_rcv = netQueueNew( type ) ) == NULL )
  {
    return IO_INITFAILED;
  }
  if ( ( so->so_snd = netQueueNew( type ) ) == NULL )
  {
    netQueueFree(so->so_rcv);
    return IO_INITFAILED;
  }

  so->subscribed = 0;
  so->so_error = 0;

  so->so_state = ISINITIALIZED;
  /* XXX it is not possible to REQ a PCB here because we are still running
     in the IOThread and therfore the "back pointer" to this socket (the
     threadId will be wrong. So we create the PCB now outmagicaly when 
     BIND or CONNECT is called. This is also correct because it is not
     possible to write to a unbinded or not connected socket.
     The only problem is the SETSOCKOPT call but we will find a solution */
#if 0

  msg.id = NET_CONF_PCBREQ;
  msg.msg.netConf.SocketReq.domain = so->so_domain;
  msg.msg.netConf.SocketReq.type = so->so_type;
  msg.msg.netConf.SocketReq.protocol = so->so_protocol;

  t = netConfigThread;
  if ( tmMsgSend(netConfigThread, &msg) != TM_MSGSENDOK )
  {
    goto failed;
  }
  /* wait for OK */
  if ( tmMsgRecv(&t, NET_CONF_REPLY, &msg, INFINITY ) !=
       TM_MSGRECVOK || msg.msg.netConfReply.errno == NET_CONF_FAILED )
    goto failed;

  this->isInitialised = TRUE;
  return IO_INITOK;

failed:
  netQueueFree(so->so_rcv);
  netQueueFree(so->so_snd);
  return IO_INITFAILED;
#endif

  this->isInitialised = TRUE;
  return IO_INITOK;

}


Error socket_read(IODevice this, ThreadId threadId,
                  char *buffer, long int *size)
{
  int 						type;
  NetBuf					meta;


  SocketDescPtr so = (SocketDescPtr) this->extension;

  if (! this->isInitialised )
    return IO_READFAILED;
  if ( (type = getNetQType(so->so_type) ) == -1 )
    return IO_READFAILED;

  if ( netQueueRead( so->so_rcv, buffer, (unsigned int *) size, NULL, 0 )
       == NETQ_FAILED )
  {
    return IO_READFAILED;
  }

  return IO_READOK;
}

Error socket_getPort(IODevice this, ThreadId threadId, unsigned short *port)
{
  int             type;
  NetBuf          meta;


  SocketDescPtr so = (SocketDescPtr) this->extension;

  if (! this->isInitialised )
    return IO_READFAILED;
  if ( (type = getNetQType(so->so_type) ) == -1 )
    return IO_READFAILED;

  if ( netQueueRead( so->so_rcv, NULL, 0L, &meta, DONT_SEEK)
       == NETQ_FAILED )
  {
    return IO_READFAILED;
  }
  netattrGet((NetAttrs) meta, NETATTR_UDP_LOCAL, port);

  return IO_READOK;
}

Error socket_write(IODevice this, ThreadId threadId,
                   char *buffer, long int *size)
{
  NetBuf buf = NULL;
  Message msg;
  SocketDescPtr so = (SocketDescPtr) this->extension;

  if (! this->isInitialised )
    return IO_WRITEFAILED;
  /*	if ( (type = getNetQType(so->so_type) ) == -1 ) return IO_WRITEFAILED; */
  if ( !( (so->so_type == SOCK_STREAM || so->so_type == SOCK_DGRAM) && so->so_state == ISCONNECTED) )
    return IO_WRITEFAILED; /* write works only for connected SOCK_STREAM */

  if (! netbufWrite(&buf, buffer, *size, 0, SEEK_SET ) )
    goto fail;

  msg.from = this->threadId;
  msg.id = NET_QUEUE_DATA;
  msg.msg.netqMsg.netData = buf;
  if ( netattrNew((NetAttrs *) &(msg.msg.netqMsg.netMeta)) != 1)
  {
    netdbgPrintf (NETDEBUG_SOCKET,
                  "NetSocket: failed to allocate attributes\n");
    return IO_WRITEFAILED;
  }
  netattrSet (msg.msg.netqMsg.netMeta, NETATTR_SOCKET_THREADLOW, (this->threadId & 0xFFFF));
  netattrSet (msg.msg.netqMsg.netMeta, NETATTR_SOCKET_THREADHIGH, ((this->threadId>>16) & 0xFFFF));
  switch (so->so_protocol)
  {
  case PROTO_TCP:
    if ( ! (netattrSet (msg.msg.netqMsg.netMeta,
                        NETATTR_IN_TYPE, NETATTR_TYPE_TCP) &&
            netattrSet (msg.msg.netqMsg.netMeta,
                        NETATTR_OSI_4_TYPE, NETATTR_TYPE_TCP) ))
    {
      netdbgPrintf (NETDEBUG_SOCKET,
                    "NetSocket: failed to set attribute\n");
      goto fail;
    }
    break;
  case PROTO_UDP:
    if ( ! (netattrSet (msg.msg.netqMsg.netMeta,
                        NETATTR_IN_TYPE, NETATTR_TYPE_UDP) &&
            netattrSet (msg.msg.netqMsg.netMeta,
                        NETATTR_OSI_4_TYPE, NETATTR_TYPE_UDP) ))
    {
      netdbgPrintf (NETDEBUG_SOCKET,
                    "NetSocket: failed to set attribute\n");
      goto fail;
    }
    break;
    /*        case PROTO_UDP6:
    if ( ! (netattrSet (msg.msg.netqMsg.netMeta,
    				NETATTR_IN_TYPE, NETATTR_TYPE_UDP) &&
    			netattrSet (msg.msg.netqMsg.netMeta,
    				NETATTR_OSI_4_TYPE, NETATTR_TYPE_UDP) &&
    	                netattrSet (msg.msg.netqMsg.netMeta,
    				NETATTR_OSI_3_TYPE, NETATTR_TYPE_IPv6)
    	)) {
    	netdbgPrintf (NETDEBUG_SOCKET,
    			"NetSocket: failed to set attribute\n");
    	goto fail;
  }
    break;
    */

  default:
    netdbgPrintf (NETDEBUG_SOCKET,
                  "NetSocket: can't dertermine protocol\n");
    goto fail;
  }
  if ( tmMsgSend(netInqueueThread, &msg) != TM_MSGSENDOK )
    goto fail;
  /* no reply needed */
  netdbgPrintf (NETDEBUG_SOCKET, "NetSocket: sent %i bytes to inqueue\n", *size);
  return IO_WRITEOK;

fail:
  netdbgPrintf (NETDEBUG_SOCKET, "NetSocket: write failed\n");
  netbufFree(buf);
  return IO_WRITEFAILED;
}

Error socket_close(IODevice this)
{
  SocketDescPtr so = (SocketDescPtr) this->extension;
  SocketDescPtr s, t;
  Message msg;

  msg.from = this->threadId;
  msg.id = NET_CONF_PCBDEL;
  /* no more data neccessary */
  tmMsgSend(netConfigThread, &msg); /* error codes are ignored */

  if ( --(this->count) == 0 )
  {
    /* free all data */
    netQueueFree(so->so_rcv);
    netQueueFree(so->so_snd);
    if ( so->so_qlen )
    {
      /* free accept queue */
      s = so->so_q;
      while ( s )
      {
        t = s;
        s = s->so_q;
        vmFree(t);
      }
      /* so->so_q = NULL;
       * so->so_qlen = 0; */
    }
    hmFree(so);
    ioRemoveDriver(this);
    hmFree(this);
  }

  return IO_CLOSEOK;
}

void socket_handleMsg(IODevice this, Message * msg)
{
  SocketDescPtr so = (SocketDescPtr) this->extension;
  Message reply, send;
  short d;
  NetBuf data, dummy;

  if ( this->isInitialised != TRUE )
  {
    netdbgPrintf (NETDEBUG_SOCKET,
                  "NetSocket: socket not initialized! domain = %i  type = %i\n",
                  so->so_domain, so->so_type);
    reply.id = UNKNOWN_SYSCALL;
    goto fail;
  }

  netdbgPrintf (NETDEBUG_SOCKET,
                "NetSocket: got msg from %i (id %i)\n", msg->from, msg->id);
  switch ( msg->id )
  {
    /*		case LISTEN:
    		case ACCEPT:
    		case RECVMSG:
    		case RECVFROM:
    		case SENDMSG:
    		case SENDTO:
    		case SHUTDOWN:
    		case GETSOCKOPT:
    		case SETSOCKOPT:
    		case GETSOCKNAME:
    		case SETSOCKNAME:
    */			/* XXX ignored, need netConfigure Thread for most functions */
    /*			break; */
  case CONNECT:
  case BIND:
    reply.id = NET_SOCKET_CONNECTED;
    send.id = msg->id;
    send.msg.netSockBindConnect.attrVec =
      msg->msg.netSockBindConnect.attrVec;
    send.msg.netSockBindConnect.len =
      msg->msg.netSockBindConnect.len;
    send.msg.netSockBindConnect.protocol = so->so_protocol;
    send.msg.netSockBindConnect.domain = so->so_domain;
    if ( genericSyscall(netConfigThread, &send, &reply) == TM_MSGSENDFAILED)
    {
      netdbgPrintf (NETDEBUG_SOCKET,
                    "NetSocket: failed to send syscall!\n");
      reply.msg.netConfReply.errno = SOCKET_FAILED;
    }
    if ( reply.msg.netConfReply.errno == NET_CONF_OK)
    {
      switch (so->so_type)
      {
      case SOCK_STREAM:
        so->so_state = ISCONNECTING;
        break;
      case SOCK_DGRAM:
        so->so_state = ISCONNECTED;
        break;
      default:
        netdbgPrintf (NETDEBUG_SOCKET,
                      "NetSocket: unexpected so-type!\n");
        so->so_state = ISINVALID;
        break;
      }
    }
    else
    {
      netdbgPrintf (NETDEBUG_SOCKET,
                    "NetSocket: failed to connect!\n");
    }
    break;
  case NET_QUEUE_DATA:
    /* XXX should check from address !! */
    switch ( so->so_type )
    {
    case SOCK_STREAM:
      if ( netattrGet(msg->msg.netqMsg.netMeta, NETATTR_OOBDATA, &d) )
      {
        /* out-of-bound data */
        if ( netQueueAdd( so->so_rcv,
                          NULL,
                          msg->msg.netqMsg.netData )==NETQ_FAILED )
        {
          /* XXX exeption code */
        }
      }
      else
      {
        if ( netQueueAdd( so->so_rcv,
                          msg->msg.netqMsg.netData,
                          NULL ) == NETQ_FAILED )
        {
          /* XXX exeption code */
        }
      }
      break;
    case SOCK_DGRAM:
    case SOCK_RAW:
      if ( netQueueAdd( so->so_rcv,
                        msg->msg.netqMsg.netData,
                        msg->msg.netqMsg.netMeta ) == NETQ_FAILED )
      {
        /* XXX exeption code */
      }
      break;
    default:
      /* XXX exeption code */
      break;
    }
  case IO_NOTIFY:
    /* need to send me a IO notify if I got a subscribe
       after I received a packet */
    if (so->subscribed != 0)
    {
      /* Send notify message to subscribed thread. */
      send.from = this->threadId;
      send.id = IO_NOTIFY;
      send.msg.ioNotify.deviceNumber = this->major;
      if ( netQueuePeek(so->so_rcv, &data, &dummy) != NETQ_OK )
      {
        netdbgPrintf (NETDEBUG_SOCKET,
                      "NetSocket Notify: failed to get packet\n");
      }
      send.msg.ioNotify.length = netbufLen(data);
      netdbgPrintf (NETDEBUG_SOCKET, "NetSocket Notify: to %i\n",
                    so->subscribed);
      if (tmMsgSend (so->subscribed, &send) != TM_MSGSENDOK)
        netdbgPrintf (NETDEBUG_SOCKET, "NetSocket Notify: tmMsgSend failed.\n");
    }
    /* no reply needed */
    return;
  case IO_SUBSCRIBE:
    reply.id = IO_SUBSCRIBEREPLY;
    if (so->subscribed == 0)
    {
      reply.msg.ioSubscribeReply.errorCode = IO_SUBSCRIBEOK;
      so->subscribed = msg->from;
      if ( !netQueueIsEmpty( so->so_rcv ) )
      {
        /* send me a NOTIFY so I inform the requestor just after
           I have sent him the reply */
        send.from = this->threadId;
        send.id = IO_NOTIFY;
        if (tmMsgSend (this->threadId, &send) != TM_MSGSENDOK)
          netdbgPrintf (NETDEBUG_SOCKET, "NetSocket Notify: tmMsgSend failed.\n");
      }
    }
    else
    {
      reply.msg.ioSubscribeReply.errorCode = IO_SUBSCRIBEFAILED;
    }
    break;
  case IO_UNSUBSCRIBE:
    reply.id = IO_UNSUBSCRIBEREPLY;
    if (so->subscribed == msg->from)
    {
      reply.msg.ioUnSubscribeReply.errorCode = IO_UNSUBSCRIBEOK;
      so->subscribed = 0;
    }
    else
    {
      reply.msg.ioUnSubscribeReply.errorCode = IO_UNSUBSCRIBEFAILED;
    }
    break;
    /*		case IO_GETADDR:
    			// XXX
    			reply.id = IO_GETADDRREPLY;
    			reply.msg.ioGetAddrReply.addr[0] = 0;
    			reply.msg.ioGetAddrReply.addr[1] = 1;
    			reply.msg.ioGetAddrReply.addr[2] = 2;
    			reply.msg.ioGetAddrReply.addr[3] = 3;
    			reply.msg.ioGetAddrReply.addr[4] = 4;
    			reply.msg.ioGetAddrReply.addr[5] = 5;
    			break;
     XXX NO LONGER NEEDED */
  case SOCK_GET_PORT:
    reply.id = NET_SOCKET_PORT;
    reply.msg.netSockPort.errorCode = socket_getPort(this, this->threadId, &(reply.msg.netSockPort.port) );
    break;
  case UNKNOWN_SYSCALL:
    /* if we get a UNKNOWN_SYSCALL don't resend a UNKNOWN_SYSCALL msg
       or we will get nice loops */
    return;
  default:
    reply.id = UNKNOWN_SYSCALL;
    break;
  }
fail:
  reply.from = this->threadId;
  tmMsgSend (msg->from, &reply);
}
