/*
    Copyright 2001 (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/Attic/NetTftp.c,v $
    Affiliation:           ETH Zuerich, TIK
    Version:               $Revision: 2936 $
    Last Date of Change:   $Date: 2007-03-21 15:23:42 +0100 (Mit, 21 Mär 2007) $  by: $Author: baderma $
 
*/


#include "Topsy.h"
#include "Syscall.h"
#include "Support.h"
#include "NetDebug.h"
#include "Socket.h"
#include "NetSocket.h"
#include "TMTimerNG.h"
#include "NetTftp.h"
#include "NetByteorder.h"


#define DO_ABORT()			do {						\
								ioClose(mySock);		\
								vmFree(mySockAttr);		\
								vmFree(myAddrAttr);		\
								vmFree(DataBuffer);		\
								vmFree(AckBuffer);		\
							} while (0)


void netTftp(ThreadId arg)
{
  ThreadId        	mySock,
  fromId;
  NetAttr         	*myAddrAttr,
  *mySockAttr;
  Tftp_Ack_Ptr_t	AckBuffer;
  Tftp_Data_Ptr_t	DataBuffer;
  address_t			*FileBuffer;
  char				*RreqBuffer,
  *FileName;
  char				XmitMode[] = "octet";
  long				length,
  DataSize;
  int				err,
  NumOfRcvTry,
  NumOfOutOfSync;
  uint16			ExpectedSeqNr;
  extern int		gBootPFinished;
  unsigned short	port;
  Message			CmdMsg,
  SizeMsg;



  while (!gBootPFinished)
  {
    tmYield();
  }
  netdbgPrintf(NETDEBUG_TFTPC, "TFTP: here we go...\n");
  if ( (mySock = socketXXX (AF_INET, SOCK_DGRAM, 0) ) == 0 )
  {
    netdbgPrintf(NETDEBUG_CONFIG, "TFTP: 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
  // Set to address XXX fixed for now
  mySockAttr[0].key =   NETATTR_IP_REMOTE_0;
  mySockAttr[0].data =	192;
  mySockAttr[1].key =   NETATTR_IP_REMOTE_1;
  mySockAttr[1].data =	168;
  mySockAttr[2].key =   NETATTR_IP_REMOTE_2;
  mySockAttr[2].data =	2;
  mySockAttr[3].key =   NETATTR_IP_REMOTE_3;
  mySockAttr[3].data =	1;
  // Set server port
  mySockAttr[4].key =   NETATTR_UDP_REMOTE;
  mySockAttr[4].data =  TFTP_SERVER_PORT;
  // Set client port
  mySockAttr[5].key =   NETATTR_UDP_LOCAL;
  mySockAttr[5].data =  TFTP_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 Interface XXX this is a hack or probably not
  mySockAttr[8].key =		NETATTR_IF_TO;
  mySockAttr[8].data =	DEFAULT_DEVICE;

  if (connectXXX(mySock,mySockAttr, 9) )
  {
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to connect!\n");
    vmFree(myAddrAttr);
    vmFree(mySockAttr);
    tmExit();
  }

  // allocate buffers
  vmAlloc( (address_t*)&DataBuffer, TFTP_MAX_SIZE + 2L);
  vmAlloc( (address_t*)&AckBuffer, sizeof(Tftp_Ack_Head_t) );

main_loop:
  // Wait for Command (what file should I get and where should I write it to)
  do
  {
    fromId = ANY;
    err = tmMsgRecv (&fromId, NET_TFTP_GETCMD, &CmdMsg, INFINITY);
    if (err != TM_MSGRECVOK)
    {
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: error in MsgRecv (from: %i, err: %i)\n", (int)fromId, err);
    }
  }
  while (err != TM_MSGRECVOK);

  FileName = CmdMsg.msg.netTftpGet.file;
  FileBuffer = CmdMsg.msg.netTftpGet.buff;
  vmAlloc( (address_t*)&RreqBuffer, 4 + str_len(FileName) + str_len(XmitMode) );
  netdbgPrintf(NETDEBUG_TFTPC, "TFTP: got command from %i to fetch file \"%s\" and store it at %p\n",
               (int)CmdMsg.from, FileName, FileBuffer);

  // assemble RRQ
  *((uint16*)RreqBuffer) = htons(NETTFTP_OPCODE_RREQUEST);
  str_cpy(RreqBuffer + 2, FileName);
  // add 1 to strlen since '\0' must not be overwritten
  str_cpy(RreqBuffer + 2 + str_len(FileName) + 1, XmitMode);
  // send RRQ
  if (sndTftpPacket(mySock, RreqBuffer, 4 + str_len(FileName) + str_len(XmitMode)) != RETVAL_OK)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to send receive request, terminating...\n");
    vmFree(RreqBuffer);
    DO_ABORT();
    tmExit();
  }
  vmFree(RreqBuffer);

  //-BL netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: after send RRQ call\n");
  // initialize status
  NumOfRcvTry = 0;
  NumOfOutOfSync = 0;
  ExpectedSeqNr = 1;	// first data packet has SeqNr = 1
  AckBuffer->opc = htons(NETTFTP_OPCODE_ACK);
  do
  {		// while data is not completly received
    do
    {	// untill we get a valid packet
      // receive and try again if failed or timeout occured
      err = rcvTftpPacket(mySock, (char*)DataBuffer, &length, &port);
      NumOfRcvTry++;
    }
    while (err != RETVAL_OK && NumOfRcvTry < MAX_TRY);
    NumOfRcvTry = 0;
    if (err != RETVAL_OK)
    {
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to receive, terminating...\n");
      DO_ABORT();
      tmExit();
    }
    if (ExpectedSeqNr == 1 && ntohs(DataBuffer->opc) == NETTFTP_OPCODE_DAT
        && ntohs(DataBuffer->block) == 1)
    {
      // first data packet contains size information
      DataSize =  ntohl( *((uint32*)(DataBuffer + 4L)) );
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: expected data size: %li Bytes\n", DataSize);
      // let the laoder know how big my data region needs to be
      SizeMsg.id = NET_TFTP_SIZE_REPLY;
      SizeMsg.msg.netTftpSize.size = DataSize;
      tmMsgSend(CmdMsg.from, &SizeMsg);
      // have to remap remote-port
      netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: remapping remote port to %i\n", port);
      mySockAttr[0].key =   NETATTR_UDP_REMOTE;
      mySockAttr[0].data =  port;
      if (connectXXX(mySock,mySockAttr, 1) )
      {
        netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to remap port!\n");
        DO_ABORT();
        tmExit();
      }
    }
    // check reply opcode (valid answers can only be ERR or DATA)
    switch (ntohs(DataBuffer->opc) )
    {
    case NETTFTP_OPCODE_DAT:
      // process data
      // check if seq-nr matches expected
      if (ntohs(DataBuffer->block) == ExpectedSeqNr)
      {
        // send ack
        AckBuffer->block = htons(ExpectedSeqNr);
        if (sndTftpPacket(mySock, (char*)AckBuffer, 4) != RETVAL_OK)
        {
          netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to send ack, terminating...\n");
          DO_ABORT();
          tmExit();
        }
        netdbgPrintf(NETDEBUG_TFTPC, "TFTP: received and acknowledged data packet (block: %i)\n",
                     ExpectedSeqNr);
        // copy data to buffer (if 0 Bytes byteCopy does nothing and next if traps)
        //-BL netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: byteCopy wants to copy %li Bytes to offset %li\n",
        //-BL		length - 4L, (ExpectedSeqNr - 1L) * 512L);
        byteCopy((address_t)FileBuffer + (ExpectedSeqNr - 1L) * 512L,
                 &(DataBuffer->data) + 4L, length - 4L);
        if ( length - 4L < 512L)
        {
          netdbgPrintf(NETDEBUG_TFTPC, "TFTP: got final data packet!\n");
          // wait and send ACK again if neccessary
          err = rcvTftpPacket(mySock, (char*)DataBuffer, &length, &port);
          switch (err)
          {
          case RETVAL_OK:
            netdbgPrintf(NETDEBUG_TFTPC, "TFTP: received more data eventough receive is complete => send ack again & finish\n");
            sndTftpPacket(mySock, (char*)AckBuffer, 4);
            break;
          case RETVAL_TIMEOUT:
            netdbgPrintf(NETDEBUG_TFTPC, "TFTP: received no more data => finish\n");
            break;
          default:
            netdbgPrintf(NETDEBUG_TFTPC, "TFTP: unknown error after terminal listen => send ack again & finish\n");
            sndTftpPacket(mySock, (char*)AckBuffer, 4);
            break;
          }
          // release (don't free) FileBuffer
          goto main_loop;
        }
        // update SeqNr
        ExpectedSeqNr++;
        NumOfOutOfSync = 0;
      }
      else
      {
        // otherwise ignore it and send ack for last valid packet or terminate
        if (NumOfOutOfSync >= MAX_OUT_OF_SYNC)
        {
          netdbgPrintf(NETDEBUG_TFTPC, "TFTP: too many out of sync packets. Expected block numer %i, received: %i\nAborting this transfer...\n",	ExpectedSeqNr, ntohs(DataBuffer->block) );
          // XXX have to tell my commander
          goto main_loop;
        }
        AckBuffer->block = htons(ExpectedSeqNr);
        if (sndTftpPacket(mySock, (char*) AckBuffer, 4) != RETVAL_OK)
        {
          netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to send ack, terminating...\n");
          DO_ABORT();
          tmExit();
        }
        //-BL netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: after send of ack (2) call\n");
        NumOfOutOfSync++;
      }
      break;
    case NETTFTP_OPCODE_ERROR:
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: Error occured! Err_Num = %i Cause: %s\nAborting this transfer...\n",
                   ntohs(DataBuffer->block), &(DataBuffer->data) );
      switch (ntohs(DataBuffer->block) )
      {
      case NETTFTP_UNDEF_ERR:
      case NETTFTP_NOTFOUND_ERR:
      case NETTFTP_ACCESS_ERR:
      case NETTFTP_DISK_ERR:
      case NETTFTP_ILL_ERR:
      case NETTFTP_EXIST_ERR:
      case NETTFTP_USER_ERR:
        // XXX should tell my commander
        goto main_loop;
      case NETTFTP_ID_ERR:
        // ignoring wrong TID Errors
        netdbgPrintf(NETDEBUG_TFTPC, "TFTP: got TID-Error, which should not occur when using UDP/IP, ignoring\n");
        break;
      default:
        netdbgPrintf(NETDEBUG_TFTPC, "TFTP: Unknown error!\nAborting this transfer...\n");
        // XXX should tell my commander
        goto main_loop;
      }
    case NETTFTP_OPCODE_ACK:
      // should not get any ACKs... ignore it.
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: unexpected ACK, ignoring.\n");
      break;
    default:
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: received unexpected packet type (%i), ignoring.\n",
                   DataBuffer->opc);
      break;
    }
  }
  while (1);
}


int sndTftpPacket(ThreadId sock, char* MyPack, long length)
{
  long		BytesWritten;


  BytesWritten = length;
  if (ioWrite (sock, (char*) MyPack, &BytesWritten) != IO_WRITEOK
      || BytesWritten != length)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to write to socket!\n");
    return RETVAL_FAILED;
  }
  else
  {
    return RETVAL_OK;
  }
}


int rcvTftpPacket(ThreadId sock, char* buffer, long* len, unsigned short *port)
{
  switch (tftpSelect(sock, SELECT_TIMEOUT ) )
  {
  case NETSELECT_READY:
    *len = TFTP_MAX_SIZE + 1;
    ioSockGetPort(sock, port);
    if (ioRead (sock, buffer, len) != IO_READOK || *len < TFTP_MIN_SIZE
        || *len > TFTP_MAX_SIZE)
    {
      netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to read from socket!\n");
      return RETVAL_FAILED;
    }
    else
    {
      return RETVAL_OK;
    }
    break;
  case NETSELECT_TIMEOUT:
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: read from socket timed out!\n");
    return RETVAL_TIMEOUT;
  case NETSELECT_ERROR:
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: failed to read from socket!\n");
    return RETVAL_FAILED;
  default:
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: should never get here!\n");
    return RETVAL_FAILED;
  }
}


int tftpSelect(ThreadId sock, long timeout)
{
  ThreadId   			fromId,
  						selfId,
  						parentId;
  int      				retCode = 0;
  Message     			myMsg,
  						BusyMsg;
  unsigned long			timerId;


  if (tmGetInfo(SELF, &selfId, &parentId) != TM_INFOOK)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "Select: PANIC failed to get thread info\n");
    return NETSELECT_ERROR;
  }
  //-BL netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: after tmGetInfo call\n");
  if (ioSubscribe(sock, selfId) != IO_SUBSCRIBEOK)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "Select: PANIC failed to subscribe to IO-Device\n");
    return NETSELECT_ERROR;
  }
  //-BL netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: after ioSubscribe call\n");
  if (tmSetTimer (timeout, &timerId) != TM_TIMERSETOK)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "Select: failed to set timer\n");
    return NETSELECT_ERROR;
  }
  //-BL netdbgPrintf(NETDEBUG_TFTPC, "+++ TFTP: after tmSetTimer call\n");

select_rcv:
  fromId = ANY;
  if (tmMsgRecv (&fromId, ANYMSGTYPE, &myMsg, INFINITY) != TM_MSGRECVOK)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "Select: failed to receive message\n");
    return NETSELECT_ERROR;
  }
  //-BL netdbgPrintf(NETDEBUG_TFTPC, "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;
  case NET_TFTP_GETCMD:
    // XXX this is a race condition workaround: let the sender do the job:)
    netdbgPrintf(NETDEBUG_TFTPC, "TFTP: received a command while waiting for data... Send back a busy message\n");
    BusyMsg.id = NET_TFTP_SIZE_REPLY;
    // size = 0 means the tftp client is busy
    BusyMsg.msg.netTftpSize.size = 0L;
    tmMsgSend(myMsg.from, &BusyMsg);
    goto select_rcv;
  default:
    netdbgPrintf(NETDEBUG_TFTPC, "Select: received unknown message (%i)!\n", myMsg.id);
    tmClearTimer (timerId);
    retCode = NETSELECT_ERROR;
    break;
  }
  if (ioUnSubscribe(sock, selfId) != IO_UNSUBSCRIBEOK)
  {
    netdbgPrintf(NETDEBUG_TFTPC, "Select: PANIC failed to unsubscribe from IO-Device\n");
    return NETSELECT_ERROR;
  }
  return retCode;
}
