/*
    Copyright 2000 (c) by Boris Lutz,
    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/NetBootP.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2922 $
    Last Date of Change:   $Date: 2007-03-19 17:48:52 +0100 (Mon, 19 Mär 2007) $  by: $Author: baderma $
 
*/


#include "Topsy.h"
#include "IODevice.h"
#include "Syscall.h"
#include "Support.h"
#include "NetByteorder.h"
#include "NetDevice.h"
#include "NetError.h"
#include "NetConfigMngr.h"
#include "NetARP.h"
#include "NetDebug.h"
#include "Socket.h"
#include "NetSocket.h"
#include "TMTimerNG.h"
#include "TMTime.h"
#include "NetBootP.h"
#include "NetUDPTest.h"

u8							gatesOfHell[4];		// Gateway address
int							gBootPFinished = 0;


void netBootP (ThreadId arg)
{
  int						nextState,
  notQuit,
  i,
  myDevice,
  defaultDevice,
  moreDevToConfig,
  configuredDefaultDevice;
  ThreadId					mySock,
  from;
  Message					msg,
  reply;
  NetBootPHdr				firstReply,
  secondReply;
  u8						myHWAddress[6];		// for ethernet
  long						sId = 999,			// XXX should be random
                  loopCntStateStarted,
                  loopCntStateVerifyComplete,
                  hwLen;
  NetAttr					*myAddrAttr,
  *mySockAttr;
  ThreadId  t;

  extern ThreadId			netConfigThread;
  extern ThreadId			arp_main_thread;
  extern ThreadId			netInit;


  defaultDevice = 0;
  myDevice = defaultDevice;
  configuredDefaultDevice = FALSE;
  moreDevToConfig = TRUE;
  if ( (mySock = socketXXX (AF_INET, SOCK_DGRAM, 0) ) == 0 )
  {
    netdbgPrintf (NETDEBUG_CONFIG, "BootP: failed to get a socket\n");
    tmExit();
  }

  vmAlloc ((Address*) &(mySockAttr), sizeof(NetAttr) * 14);	// should be big enough
  vmAlloc ((Address*) &(myAddrAttr), sizeof(NetAttr) * 14);	// should be big enough
  // from address is set to 0.0.0.0 in device automatically
  // Set gateway address (is 0.0.0.0 for request to defaultDevice and will
  // be set to the Address of the defaultDevice as soon as it is known
  for (i = 0; i < 4; i++)
  {
    gatesOfHell[i] = 0;
  }
  // Set to address
  mySockAttr[0].key =   NETATTR_IP_REMOTE_0;
  mySockAttr[1].key =   NETATTR_IP_REMOTE_1;
  mySockAttr[2].key =   NETATTR_IP_REMOTE_2;
  mySockAttr[3].key =   NETATTR_IP_REMOTE_3;
  for (i = 0; i < 4; i++)
  {
    mySockAttr[i].data = 255;
  }
  // Set server port
  mySockAttr[4].key =   NETATTR_UDP_REMOTE;
  mySockAttr[4].data =  BOOTP_SERVER_PORT;
  // Set client port
  mySockAttr[5].key =   NETATTR_UDP_LOCAL;
  mySockAttr[5].data =  BOOTP_CLIENT_PORT;
  // Set OSI_2 type
  mySockAttr[6].key =  NETATTR_OSI_2_TYPE;
  mySockAttr[6].data = NETATTR_TYPE_ETHERNET;
  // Set OSI_3 type
  mySockAttr[7].key =  NETATTR_OSI_3_TYPE;
  mySockAttr[7].data = NETATTR_TYPE_IPv4;
  // Set BROADCAST
  mySockAttr[8].key =  NETATTR_BROADCAST;
  mySockAttr[8].data = 1;
  // Set Interface XXX this is a hack or probably not
  mySockAttr[9].key = NETATTR_IF_TO;
  mySockAttr[9].data = defaultDevice;

  if (connectXXX(mySock,mySockAttr, 10) )
  {
    printf ("BootP: failed to connect!\n");
    vmFree (myAddrAttr);
    vmFree (mySockAttr);
    tmExit();
  }

  // Configure all devices...
  while (moreDevToConfig)
  {
    // make sure that the default devices has been configured before
    // trying to configure other devices via the default devoce
    if (myDevice > defaultDevice && configuredDefaultDevice == FALSE)
    {
      printf ("BootP: failed to configure default Device! Can't configure other devices, exiting...\n");
      vmFree (mySockAttr);
      vmFree (myAddrAttr);
      tmExit();
    }
    // init states for BootP
    notQuit = TRUE;
    nextState = STATE_STARTED;
    loopCntStateStarted = 0;
    loopCntStateVerifyComplete = 0;
    sId ++;
    // get myHWAddress
    msg.id = NET_CONF_GETHWADDR;
    msg.msg.netConf.HWAddrGet.deviceNr = myDevice;
    msg.msg.netConf.HWAddrGet.attrVec = myAddrAttr;
    hwLen = 10;
    msg.msg.netConf.HWAddrGet.len = &hwLen;
    if (tmMsgSend (netConfigThread, &msg) != TM_MSGSENDOK)
    {
      printf ("BootP: failed to get hwaddress (tmMsgSend)!\n");
      vmFree (mySockAttr);
      vmFree (myAddrAttr);
      tmExit();
    }
    from = netConfigThread;
    if (tmMsgRecv (&from, NET_CONF_REPLY, &msg, INFINITY) != TM_MSGRECVOK)
    {
      printf ("BootP: failed to get hwaddress (tmMsgRecv)!\n");
      vmFree (mySockAttr);
      vmFree (myAddrAttr);
      tmExit();
    }
    if (msg.msg.netConfReply.errno == NET_CONF_FAILED)
    {
      printf ("BootP: No more devices left to configure: devices %i - %i have been configured\n",
              defaultDevice, (myDevice - 1) > 0 ? (myDevice - 1) : 0);
      moreDevToConfig = FALSE;
      continue;
      if (myDevice == defaultDevice)
      {
        printf ("BootP: failed to get hwaddress for default device... exiting!\n");
        tmExit();
      }
    }
    else
    {
      for (i = 0; i < hwLen; i++)
      {
        switch (myAddrAttr[i].key)
        {
        case NETATTR_ETH_LOCAL_0:
          myHWAddress[0] = myAddrAttr[i].data>>8 & 0xFF;
          myHWAddress[1] = myAddrAttr[i].data & 0xFF;
          break;
        case NETATTR_ETH_LOCAL_1:
          myHWAddress[2] = myAddrAttr[i].data>>8 & 0xFF;
          myHWAddress[3] = myAddrAttr[i].data & 0xFF;
          break;
        case NETATTR_ETH_LOCAL_2:
          myHWAddress[4] = myAddrAttr[i].data>>8 & 0xFF;
          myHWAddress[5] = myAddrAttr[i].data & 0xFF;
          break;
        default:
          break;
        }
      }
    }

    // get my IP
    while (notQuit)
    {
      switch (nextState)
      {
      case STATE_STARTED:
        netdbgPrintf (NETDEBUG_CONFIG, "BootP: entering STATE_STARTED\n");
        // Send request
        if (loopCntStateStarted > MAX_LOOP)
        {	// prevent looping
          notQuit = FALSE;
          printf ("BootP: somehow, through phenomens not yet fully understood, something went completly wrong...\n");
          continue;
        }
        if (SendBootPRequest (mySock, sId, myHWAddress) == RETVAL_OK)
          nextState = STATE_FIRST_REQUEST_SENT;
        else
          nextState = STATE_STARTED;
        loopCntStateStarted++;
        break;
      case STATE_FIRST_REQUEST_SENT:
        netdbgPrintf (NETDEBUG_CONFIG, "BootP: entering STATE_FIRST_REQUEST_SENT\n");
        // Wait for answer or timeout
        if (ReadBootPReply (mySock, &firstReply) == RETVAL_OK)
          nextState = STATE_FIRST_ANSWER_RECEIVED;
        else
          nextState = STATE_STARTED;
        break;
      case STATE_FIRST_ANSWER_RECEIVED:
        netdbgPrintf (NETDEBUG_CONFIG, "BootP: entering STATE_FIRST_ANSWER_RECEIVED\n");
        // Verify (check if hwaddres and id matches)
        if ( ! VerifyBootPReply(&firstReply, sId, myHWAddress) == RETVAL_OK)
        {
          printf("BootP: VerifyBootPReply failed\n");
          nextState = STATE_STARTED;
          break;
        }
        // do ARP to check that this IP is not already used
        msg.id = NET_ARP_REQ;
        msg.msg.netARPReq.proto = ARP_TYPE_IPv4;
        msg.msg.netARPReq.device = myDevice;
        msg.msg.netARPReq.pa = firstReply.yiaddr;
        msg.msg.netARPReq.pa_len = 4;
        if (tmMsgSend (arp_main_thread, &msg) != TM_MSGSENDOK )
        {
          printf ("BootP: failed to send ARP-Request!\n");
          nextState = STATE_STARTED;
          break;
        }
        from = arp_main_thread;
        if (tmMsgRecv (&from, ANYMSGTYPE, &reply, INFINITY) != TM_MSGRECVOK )
        {
          printf ("BootP: failed to receive ARP-Reply (Request 1)!\n");
          nextState = STATE_STARTED;
          break;
        }
        if ( reply.msg.netARPReply.status != ARP_NOTFOUND )
        {
          printf ("BootP-ARP: address already in use, next try...!\n");
          nextState = STATE_STARTED;
          break;
        }
        // set source IP to the one obtained from first request
        msg.msg.netConf.AddrSet.deviceNr = myDevice;
        myAddrAttr[0].key = NETATTR_IP_LOCAL_0;
        myAddrAttr[1].key = NETATTR_IP_LOCAL_1;
        myAddrAttr[2].key = NETATTR_IP_LOCAL_2;
        myAddrAttr[3].key = NETATTR_IP_LOCAL_3;
        myAddrAttr[4].key = NETATTR_TYPE_IPv4; // must know the type
        for (i = 0; i < 4; i++)
        {
          myAddrAttr[i].data = firstReply.yiaddr[i];
        }
        myAddrAttr[4].data = 1;
        msg.id = NET_CONF_ADDRSET;
        msg.msg.netConf.AddrSet.attrVec = (Address) myAddrAttr;
        msg.msg.netConf.AddrSet.deviceNr = myDevice;
        msg.msg.netConf.AddrSet.len = 5;
        msg.msg.netConf.AddrSet.index = 0;
        if (tmMsgSend (netConfigThread, &msg) != TM_MSGSENDOK)
        {
          printf ("BootP: failed to set address (tmMsgSend)!\n");
          nextState = STATE_STARTED;
          break;
        }
        from = netConfigThread;
        if (tmMsgRecv (&from, NET_CONF_REPLY, &msg, INFINITY) != TM_MSGRECVOK)
        {
          printf ("BootP: failed to set address (tmMsgRecv)!\n");
          nextState = STATE_STARTED;
          break;
        }
        if (msg.msg.netConfReply.errno == NET_CONF_FAILED)
        {
          printf ("BootP: failed to set address!\n");
          nextState = STATE_STARTED;
          break;
        }
        else
        {
          netdbgPrintf (NETDEBUG_CONFIG, "BootP: Address for device %i has been set: %i.%i.%i.%i\n",
                        myDevice, firstReply.yiaddr[0], firstReply.yiaddr[1],
                        firstReply.yiaddr[2], firstReply.yiaddr[3] );
        }
        // ARP is done automatically here (after deleting the invalid entry)
        nextState = STATE_VERIFY_COMPLETE;
        break;
      case STATE_VERIFY_COMPLETE:
        netdbgPrintf (NETDEBUG_CONFIG, "BootP: entering STATE_VERIFY_COMPLETE\n");
        // Send request again (but this time the obtained IP has been
        // set)
        if (loopCntStateVerifyComplete > MAX_LOOP)
        {	// prevent looping
          notQuit = FALSE;
          printf ("BootP: somehow, through phenomens not yet fully understood, something went completly wrong...\n");
          continue;
        }
        if (SendBootPRequest (mySock, sId, myHWAddress) == RETVAL_OK)
          nextState = STATE_SECOND_REQUEST_SENT;
        else
          nextState = STATE_VERIFY_COMPLETE;
        loopCntStateVerifyComplete++;
        break;
      case STATE_SECOND_REQUEST_SENT:
        netdbgPrintf (NETDEBUG_CONFIG, "BootP: entering STATE_SECOND_REQUEST_SENT\n");
        // Wait for the answer (trash old/duplicate offers) or timeout
        if (ReadBootPReply (mySock, &secondReply) == RETVAL_OK)
        {
          nextState = STATE_SECOND_ANSWER_RECEIVED;
        }
        else
        {
          notQuit = FALSE;
          printf ("BootP: PANIC IP has been set but then someting bad happened... Configuration might be invalid!\n");
          continue;
        }
        break;
      case STATE_SECOND_ANSWER_RECEIVED:
        netdbgPrintf (NETDEBUG_CONFIG, "BootP: entering STATE_SECOND_ANSWER_RECEIVED\n");
        // Compare
        if (CompareBootReply (&firstReply, &secondReply) == RETVAL_OK)
        {
          notQuit = FALSE;
          configuredDefaultDevice = TRUE;
          printf ("BootP: Configuration for device %i complete!\n", myDevice);
          if (myDevice == defaultDevice)
          {
            netdbgPrintf (NETDEBUG_CONFIG, "BootP: setting Gateway to default device (%i)\n",
                          defaultDevice);
            for (i = 0; i < 4; i++)
            {
              gatesOfHell[i] = secondReply.yiaddr[i];
            }
            // reconnect Socket since we need to change the listening port
            // this is beacause the requests sent to a gateway are sent
            // to the BootPServer port
            mySockAttr[5].key =   NETATTR_UDP_LOCAL;
            mySockAttr[5].data =  BOOTP_SERVER_PORT;
            if (connectXXX(mySock,mySockAttr, 10) )
            {
              printf ("BootP: failed to re-connect!\n");
              vmFree (myAddrAttr);
              vmFree (mySockAttr);
              tmExit();
            }

          }
          continue;
        }
        else
        {
          notQuit = FALSE;
          printf ("BootP: PANIC IP has been set but then someting bad happened... Configuration might be invalid!\n");
          continue;
        }
        break;
      default:
        printf ("BootP: PANIC, unknown STATE entered... resetting!\n");
        nextState = STATE_STARTED;
        break;
      }
    }
    myDevice++;
  }
  ioClose (mySock);   //(jb, 30.5.01) ioClose does not work on ia32 ???

  vmFree (mySockAttr);
  vmFree(myAddrAttr);
  gBootPFinished = 1;
  printf ("BootP: finished!\n");

#if 0
  /******************************************************************************/
  /*  only for test and demonstrastion use                                      */
  netdbgPrintf(NETDEBUG_UDP, "netInit: NetUDP Test Starting\n");
  if ( tmStart(&t, NetUDPTest, (ThreadArg) 0,"NetUDPTest") != TM_STARTOK )
  {
    printf(0, "netInit: Failed to start NetUDPTest!\n");
  }
  netdbgPrintf(NETDEBUG_UDP, "netInit: NetUDPTest done!\n=================\n");

  /*  	netdbgPrintf(NETDEBUG_UDP, "netInit: NetUDP Test Starting\n"); */
  /*  	if ( tmStart(&t, NetUDPTest, (ThreadArg) 1,"NetUDPTest") != TM_STARTOK ) { */
  /*  		printf(0, "netInit: Failed to start NetUDPTest!\n"); */
  /*  	} */
  /*  	netdbgPrintf(NETDEBUG_UDP, "netInit: NetUDPTest done!\n================\n");*/
#endif


  tmExit();		// do not remove this unless you know what you are doing
}


int SendBootPRequest (ThreadId sock, long sessionID, u8 *myHWAddr)
{
  NetBootPHdr		myRequest;
  long			len;
  int				i;


  myRequest.op			= NETBOOTP_OPCODE_REQUEST;
  myRequest.htype			= NETBOOTP_HTYPE_ENET;
  myRequest.hlen			= NETBOOTP_HLEN_ENET;
  myRequest.hops			= 0;			// needs to be zero
  myRequest.xid			= htonl(sessionID);
  myRequest.secs			= htons(secs(k_now()));
  myRequest.flags			= 0;			// ignored in BootP
  for (i = 0; i < 4; i++)
  {
    myRequest.ciaddr[i]		= 0;		// needs to be zero
    myRequest.yiaddr[i]		= 0;		// needs to be zero
    myRequest.siaddr[i]		= 0;		// needs to be zero
    myRequest.giaddr[i]		= gatesOfHell[i];
  }
  for (i = 0; i < 6; i++)
  {
    (myRequest.chaddr)[i]	= myHWAddr[i];
  }
  for (i = 6; i < 16; i++)
  {
    (myRequest.chaddr)[i] = 0;		// just to be sure
  }
  for (i = 0; i < 64; i++)
  {
    (myRequest.sname)[i]	= 0;		// we don't use so zero it
  }
  for (i = 0; i < 128; i++)
  {
    (myRequest.file)[i]		= 0;		// dito :)
  }

  len = sizeof(NetBootPHdr);
  if (ioWrite (sock, (char*) &myRequest, &len) != IO_WRITEOK || len != sizeof(NetBootPHdr))
  {
    printf ("BootP: failed to write to socket!\n");
    return RETVAL_FAILED;
  }
  else
  {
    return RETVAL_OK;
  }
}


int ReadBootPReply (ThreadId sock, NetBootPHdr* myHdr)
{
  long	rLen;
  char	myBuf[BOOTP_MAX_SIZE];

  switch (selectDevice (sock, SELECT_TIMEOUT ) )
  {
  case NETSELECT_READY:
    rLen = BOOTP_MAX_SIZE;
    if (ioRead (sock, myBuf, &rLen) != IO_READOK
        || rLen < sizeof(NetBootPHdr) )
    {
      printf ("BootP: failed to read from socket!\n");
      return RETVAL_FAILED;
    }
    else
    {
      byteCopy (myHdr, myBuf, sizeof(NetBootPHdr) );
      return RETVAL_OK;
    }
    break;
  case NETSELECT_TIMEOUT:
    printf ("BootP: read from socket timed out!\n");
    return RETVAL_FAILED;
  case NETSELECT_ERROR:
    printf ("BootP: failed to read from socket!\n");
    return RETVAL_FAILED;
  default:
    printf ("BootP: should never get here!\n");
    return RETVAL_FAILED;
  }
}


int selectDevice (ThreadId sock, long timeout)
{
  ThreadId		fromId,
  selfId,
  parentId;
  int				retCode;
  Message			myMsg;
  unsigned long	timerId;


  if (tmGetInfo(SELF, &selfId, &parentId) != TM_INFOOK)
  {
    printf ("Select: PANIC failed to get thread info\n");
    return NETSELECT_ERROR;
  }
  if (ioSubscribe(sock, selfId) != IO_SUBSCRIBEOK)
  {
    printf ("Select: PANIC failed to subscribe to IO-Device\n");
    return NETSELECT_ERROR;
  }
  if (tmSetTimer (timeout, &timerId) != TM_TIMERSETOK)
  {
    printf ("BootP-Select: failed to set timer\n");
    return NETSELECT_ERROR;
  }
  fromId = ANY;
  //-CJ	printf ("BootP-Select: waiting for message...\n");
  if (tmMsgRecv (&fromId, ANYMSGTYPE, &myMsg, INFINITY) != TM_MSGRECVOK)
  {
    printf ("BootP-Select: failed to receive message\n");
    return NETSELECT_ERROR;
  }
  //-BL printf ("BootP-Select: received mesg from %i (id: %i)\n", myMsg.from, myMsg.id);
  switch (myMsg.id)
  {
  case IO_NOTIFY:
    tmClearTimer (timerId);
    retCode = NETSELECT_READY;
    break;
  case TM_TIMEREXPIRED:
    retCode = NETSELECT_TIMEOUT;
    break;
  default:
    printf ("Select: received unknown message (%i)!\n", myMsg.id);
    tmClearTimer (timerId);
    retCode = NETSELECT_ERROR;
    break;
  }
  if (ioUnSubscribe(sock, selfId) != IO_UNSUBSCRIBEOK)
  {
    printf ("Select: PANIC failed to unsubscribe from IO-Device\n");
    return NETSELECT_ERROR;
  }
  return retCode;
}


int VerifyBootPReply (NetBootPHdr* myHdr, long sessionID, u8 *myHWAddr)
{
  /*
  printf("myHwAddr: %02x:%02x:%02x:%02x:%02x:%02x\n",
    myHWAddr[0], myHWAddr[1], myHWAddr[2], myHWAddr[3], 
    myHWAddr[4], myHWAddr[5]);
  printf("HeaderAddr: %02x:%02x:%02x:%02x:%02x:%02x\n",
    (myHdr->chaddr)[0], (myHdr->chaddr)[1], (myHdr->chaddr)[2],
    (myHdr->chaddr)[3], (myHdr->chaddr)[4], (myHdr->chaddr)[5]); 
  printf("HeaderSecs: %i\n", ntohs(myHdr->secs));
  */
  if ( ntohl(myHdr->xid) == sessionID &&
       !mem_cmp(myHdr->chaddr, myHWAddr, 6) )
    return RETVAL_OK;
  else
    return RETVAL_FAILED;
}


int CompareBootReply (NetBootPHdr* myHdrA, NetBootPHdr* myHdrB)
{
  /*
  printf("HeaderSecs: old %i new %i\n", ntohs(myHdrA->secs), ntohs(myHdrB->secs));
  printf("HeaderOp: old %i new %i\n", myHdrA->op, myHdrB->op);
  printf("HeaderXID: old %i new %i\n", ntohl(myHdrA->xid), ntohl(myHdrB->xid) );
  printf("HeaderAddrA: %02x:%02x:%02x:%02x:%02x:%02x\n",
    (myHdrA->chaddr)[0], (myHdrA->chaddr)[1], (myHdrA->chaddr)[2],
    (myHdrA->chaddr)[3], (myHdrA->chaddr)[4], (myHdrA->chaddr)[5]); 
  printf("HeaderAddrB: %02x:%02x:%02x:%02x:%02x:%02x\n",
    (myHdrB->chaddr)[0], (myHdrB->chaddr)[1], (myHdrB->chaddr)[2],
    (myHdrB->chaddr)[3], (myHdrB->chaddr)[4], (myHdrB->chaddr)[5]); 
  */
  if (	myHdrA->op == myHdrB->op &&
       myHdrA->xid == myHdrB->xid &&
       ! mem_cmp(myHdrA->yiaddr, myHdrB->yiaddr, 12 + myHdrA->hlen) )
    return RETVAL_OK;
  else
    return RETVAL_FAILED;
}
