/**************************************************************
 *
 *  VALTION TEKNILLINEN TUTKIMUSKESKUS (VTT/TEL/TIV)
 *  Otakaari 7B, 02150 Espoo, Finland
 *
 *
 *
 *  OTSO Source Code Control System, 12.7.89
 *
 *  Name:	/home/users/otso/official/otso/protocols/3/cons/all/SCCS/s.nettcp.cx
 *  Version:	1.2
 *  Time:	91/07/19, 14:13:37
 *
 **************************************************************/

#ifdef SCCS_ID
/* for Unix 'what' command */
static char sccs_id[] = "@(#)nettcp.cx	1.2 91/07/19";
#endif

/**********************************************************************
* NAME:	nettcp.cx
*
* PURPOSE:
*  Connection Oriented Network Service (CONS)
*
* DESCRIPTION:
*
**********************************************************************/


#include <stream.h>

#ifndef OTSO_HXX
#include "OTSO.hxx"	/* standard OTSO include files */
#endif
#ifndef ISOADDRS_HXX
#include "isoaddrs.hxx"
#endif

/* include the interface/implementation headers */
#define CONS_SERV_HXX		1
#define CONS_PEER_HXX		1
#define CONS_HXX		1
#define NETTCP_HXX              1
#define OTSO_PROTOCOL_INCLUDES	1
#ifndef PROTOCOL_HXX
#include "PROTOCOL.hxx"
#endif


extern int errno;

#if COMPILER_HPUX       /*put this to some header file!?*/
  extern "C" {
    //char* alloca(int);
    char* index(char*, char);
  } ;
#endif

#define CERRNO(s)  dout << "Error:" << s << ". errno = " << int(::errno) << "\n"
#define CERROR(s)  dout << "Error:" << s << "\n"


////////////////////////////////////////////////////////////////
//      Derived Class : NetTCP - network layer on top of TCP/IP 
////////////////////////////////////////////////////////////////


/***********************************************************
 NOTES:

   The NetTCP Layer EFSA actually uses an underlying Berkley
 Socket subnetwork.  It is assumed that there is perfect data 
 transmission through the underlying Socket connection.

   In theory, both styles of BSD sockets can be used 
 (i.e. either UNIX or TCP/IP domain).  In practice, however,
 TCP/IP sockets are more desirable for interconnecting different
 host computers.
***********************************************************/



AUTOMATON (NetTCP, netState) {

//
// STATE	INPUTS		ACTIONS
//


  DEFAULT	DEFAULT()	{
    CERROR  ("Net EFSA - DEFAULT action,  primitive ignored");
  }

  net_idle	n_cr(-)		{ 
    // NOTE: 'Frame data' parameter is probably not needed at all.

    // n_cr is really a socket connect (activeOpen) to a remote socket.

    if (this -> activeOpen (source_addr, dest_addr, qos, data) == false) {
      up -> n_di (ORIG_LOCAL_PPM, DISC_RSN_BAD_ADDR, data, dest_addr);
    }
    else {
      //
      // n_ci was successfully sent to Responder.
      // Normally we would wait for n_cc, but it is implicitly
      // understood with sockets that if the activeOpen (connect)
      // succeeded, this is equivalent to a n_cc.
      //

      up -> n_cc (dest_addr, qos, data);
      netState      = net_connected;	// we are connected now.
      connectRole   = CON_INITIATOR;	// we started this connection.
    }
  }

  net_wfcresp	n_cresp(-)	{
    netState = net_connected;
    //
    // The N_CC NPDU is implicitily generated in the initiator.
    // peer -> N_CC(resp_addr, qos, data);
    //
  }

  net_connected	n_dtr(-)	{ this -> transmit(data); }

  net_connected	n_dr(-)		{ 
    //
    // Upon disconnect, this layer entity is free again.
    //

    this -> disconnect();	
    netState     = net_idle;
    connectRole  = CON_FREE;

    up -> n_di(originator, reason, data, resp_addr);
  }



  //
  // DUMMY STATE/INPUTS
  //

  //
  //   This layer is written for BSD sockets.  Some of the "state +
  // input" pairs below can never occur in practice for this implementation.
  // They are here purely for historical reasons and for 
  // bulletproofing purposes.
  //

  net_wfcc	N_CC(-)		{
    netState = net_connected;
    up -> n_cc(resp_addr, qos, data);
  }

  net_idle	N_CR(-)		{
    netState = net_wfcresp;
    up -> n_ci(source_addr, dest_addr, qos, data);
  }

  net_connected	N_DR(-)		{
    netState = net_idle;
    this -> disconnect();
    up -> n_di(originator, reason, data, resp_addr);
  }

  net_connected	N_DT(-)		{ up -> n_dti(data); }
} ;



/////////////////////////////////////
//  NetTCP Class member functions  //
/////////////////////////////////////

NetTCP::NetTCP(NSAPaddr* addr, Group *n, uint16 tport) : Net(addr, n) {

  this -> connectRole  = CON_FREE;

  //// Initially, we haven't started accumulating NPDU's ////
  this -> accumulating = ACCUM_START;
  this -> desiredNPDUsize = 0;

  char	hostname[128];

  this -> netSubtype = NTYPE_TCP;

  //////////////////////////////////////////
  //// Set up BSD sockets for later use ////
  //////////////////////////////////////////
  Bytes	*tmpB=NULL;
  ::gethostname(hostname, 128);
  NETaddr	*na = NULL;
  int		portnum = tport;
    
  this -> soService = NULL;
  this -> soClient  = new ClientSocket(ev_read, -1, -1, AF_INET);

  //// QUESTION: should really use localAddr ? ////
  this -> soServer  = new ServerSocket(
    (Device**)(&this -> soService), ev_read, hostname, portnum, AF_INET
  );
  if (tmpB != NULL) delete tmpB;	// hostname freed

  //
  // - Net runs so it can watch for incoming events 
  // on it's Socket Devices (n_ci, n_dti, n_di).
  // - Someone else, i.e. main(), must put the Net 
  // instance onto a runnable scheduler queue.
  //
  this -> runStatus = RunStatus::enabled;
  //? setPriority(lowRunnerPriority);

}

void NetTCP::resetAccumulator(){
  this -> desiredNPDUsize = 0;
  this -> npdu.reset();
  this -> accumulating = ACCUM_START;
}


//
// TPKT Header as defined in RFC-1006
//
#define TPKT_VERSION 3
#define TPKT_HDR_LENGTH 4

union Poke {
  short x;
  char b[sizeof(short)];
} ;

static void encode_tpkt_hdr(Frame& f) {
  Poke poke;
  poke.x = (short)(f.length() + TPKT_HDR_LENGTH);
  f.putPrefix(poke.b[1]);		// TPKT_HDR[3]	- Len low byte
  f.putPrefix(poke.b[0]);		// TPKT_HDR[2]  - Len high byte
  f.putPrefix(0);			// TPKT_HDR[1]
  f.putPrefix(TPKT_VERSION);		// TPKT_HDR[0]	- versioN
}

int decode_tpkt_hdr(Frame& f) {
  if (f.length() != TPKT_HDR_LENGTH) {
    return -f.length();
  } else if (f[0] != TPKT_VERSION){	// TPKT_HDR[0]
    return -1;
  } else {
    short x = f[3] + 256 * (f[2]) - TPKT_HDR_LENGTH;
	// TPKT_HDR[2]   TPKT_HDR[3]
	// b[0] - high   b[1] - low
    return ((int)x);
  }
}


void NetTCP::run() {
  Socket *psock = NULL;
  int nRead = 0, toRead = 0;
  Frame tmp;

  ///////////////////////////////
  // handle side effects first //
  ///////////////////////////////
  switch (this -> connectRole) {
  case CON_FREE:

    //// incoming connnection (n_ci) ////
    if (this -> soServer -> pending & ev_read) {
      /* re-initialize */
      this -> netState     = net_connected;
      this -> connectRole  = CON_RESPONDER;
      this -> resetAccumulator();

      this -> soServer -> pending &= ~ev_read;	// turn off pending read (n_ci)
      this -> up -> n_ci (localAddr, remoteAddr, 0/*qos*/, this -> npdu);
      netState = net_wfcresp;
    } 
    break;

  case CON_RESPONDER:
    psock = this -> soService;
    /* and fall thru */

  case CON_INITIATOR:
    if (this->connectRole == CON_INITIATOR) {
      psock = this -> soClient;
    }

    // n_dti pending ?
    if (psock -> pending & ev_read) {
      psock -> pending &= ~ev_read ;

      switch (accumulating) {

      case ACCUM_START:	//// READ TPKT HDR FIRST ////
        this -> desiredNPDUsize = TPKT_HDR_LENGTH;
        toRead                  = TPKT_HDR_LENGTH;

        if ((nRead = psock -> Read(this -> npdu, toRead)) > 0) {
          if (nRead < toRead) {
            this -> accumulating    = ACCUM_HDR;
            this -> desiredNPDUsize = TPKT_HDR_LENGTH;
          } else {
            // Entire TPKT header was read, start acc'ing TPDU
            this -> desiredNPDUsize = ::decode_tpkt_hdr(this -> npdu);	
            this -> npdu.reset();
            this -> accumulating = ACCUM_PDU;
          } 
        } else {
	  // Any Read() Error is a disconnect
	  CERRNO("Bad Read - ACCUM_START");
          this -> up -> n_di(ORIG_LOCAL_PPM, DISC_RSN_OTHER, npdu, remoteAddr);
          this -> disconnect();
          this -> resetAccumulator();
          this -> netState = net_idle;
          this -> connectRole = CON_FREE;
        }
        break;

      case ACCUM_HDR:	//// Finish READ of TPKT HDR ////
        if ((toRead = desiredNPDUsize - npdu.length()) <= 0) {
	  CERROR("Bad # of hdr bytes to read. desired=" << int(desiredNPDUsize) << ", pdu.length=" << int(npdu.length()));
          this -> up -> n_di(ORIG_LOCAL_PPM, DISC_RSN_OTHER, npdu, remoteAddr);
          this -> disconnect();
          this -> resetAccumulator();
          this -> netState = net_idle;
          this -> connectRole = CON_FREE;
	  return;
        }

        tmp = npdu;	//// old Prefix moved to tmp storage ////

        if ((nRead = psock -> Read(this -> npdu, toRead)) > 0) {

          npdu.putPrefix(tmp);	//// put old Prefix back ////

          if (nRead == toRead) {
            // Entire TPKT header was read, start acc'ing TPDU
            this -> desiredNPDUsize = ::decode_tpkt_hdr(this -> npdu);	
            this -> npdu.reset();
            this -> accumulating = ACCUM_PDU;
            /* and fall thru */
          } 
          else {
            // ACCUM_HDR should finish accumulating the hdr during 
            // next Net::run()
            break;
          }
        } else {
	  // Any Read() Error is a disconnect
	  CERRNO("Bad Read - ACCUM_PDU");
          this -> up -> n_di(ORIG_LOCAL_PPM, DISC_RSN_OTHER, npdu, remoteAddr);
          this -> disconnect();
          this -> resetAccumulator();
          this -> netState = net_idle;
          this -> connectRole = CON_FREE;

          break;
        }

        /* fall thru cause entire header was read */

      case ACCUM_PDU:	//// READ TPKT NEXT ////
        if ((toRead = desiredNPDUsize - npdu.length()) <= 0) {
	  CERROR("Bad # pdu bytes to read. desired=" << int(desiredNPDUsize) << ", pdu.length=" << int(npdu.length()));
          this -> up -> n_di(ORIG_LOCAL_PPM, DISC_RSN_OTHER, npdu, remoteAddr);
          this -> disconnect();
          this -> resetAccumulator();
          this -> netState = net_idle;
          this -> connectRole = CON_FREE;
	  return;
        }

        tmp = npdu;	//// old Prefix moved to tmp storage ////

        if ((nRead = psock -> Read(this -> npdu, toRead)) > 0) {

          npdu.putPrefix(tmp);	//// put old Prefix back ////

          if (nRead == toRead) {
            // Entire TPKT was read, send n_dti to upper layer.
            this -> up -> n_dti (npdu);
		// npdu contents are transferred to upper layer !
		// So it is okay to reset the npdu now.
            this -> resetAccumulator();
          } 
          // else
          //   ACCUM_PDU should finish accumulating TPKT during next Net::run()
        } else {
	  CERROR("Bad Read : " << int(nRead));
	  // Any Read() Error is a disconnect
          this -> up -> n_di(ORIG_LOCAL_PPM, DISC_RSN_OTHER, npdu, remoteAddr);
          this -> disconnect();
          this -> resetAccumulator();
          this -> netState = net_idle;
          this -> connectRole = CON_FREE;
        }

        break;

      }	/* End of switch(accumulating) */
    }	/* End of if(pending read) */
    break;
  }	/* End of switch(connectRole) */ 

  ///////////////////////////////////////////////
  // Handle msgs on inQ next
  ///////////////////////////////////////////////
  if (inQ() != NULL) {
    // If there is an outstanding message on inQ
    // that is not a dummy, Run it.
#if 0
    Runner* msg = NULL;
    inQ() -> get(msg);	
    if (msg != &dummyRunner) {
      inQ() -> put(msg);
      Runner::run();
    }
#else
   if (inQ() -> isEmpty() == false) {
      Runner::run();
   }
#endif
  }
  this -> runStatus = RunStatus::enabled;
  return;
}


/////////////////////////////////////////////////////////
///// Extended Finite State Automaton (EFSA) methods ////
/////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////
// Format for NSAP address of TCP/IP subnetwork
// via RFC-1006 (similar to Steven Kille`s) :
// 	afi : 54
// 	idi : 00728722
// 	dsp : portnumber@hostname
//////////////////////////////////////////////////////////

static char *rfc_1006_afi = "54";
static char *rfc_1006_idi = "00728722";

NETaddr* NetTCP::goodNetwkAddr (NSAPaddr& n) {
  uint16 i = 0;
  NETaddr *nap = 0;

  if (n.na_naddr <= 0) {
    return NULL;
  }
  else {
    for (i=0; i < n.na_naddr; i++) {
      nap = n.na_addrs[i];

      /* check afi = "54" */
      if (nap -> afi.length() != (::strlen(::rfc_1006_afi)+1)) {
        CERROR("Bad afi len: " << nap->afi.length() << ", afi: " << nap->afi);
        return NULL;
      }
      else {
	if (::strcmp (nap -> afi.st, ::rfc_1006_afi) != 0){	
          CERROR("Bad afi: " << nap->afi << " -- (RFC-1006 AFI: \"" << rfc_1006_afi << "\")");
          return NULL;
        }
        else {
          if (nap -> idi.length() != (::strlen(::rfc_1006_idi)+1)) {
            CERROR("Bad idi len: " << nap -> idi.length() << ", idi: " << nap -> idi);
            return NULL;
          }
          else {
            if (::strcmp (nap -> idi.st, ::rfc_1006_idi) != 0) {	
              CERROR("Bad idi: " << nap -> idi << " -- (RFC-1006 IDI: \"" << rfc_1006_idi << "\")");
              return NULL;
            }
            else {
              /* short circuit evaluation: at least one addr format is good */
              return nap;
            }
          }
        }
      }
    }
  }
  return NULL;	/* default */
}

boolean NetTCP::activeOpen(
  NSAPaddr& src, NSAPaddr& dst, slong qos, Frame& data
) {
  NETaddr *nap = 0;
  char *cp = 0, *hostname = 0;
  int portnum = 3102;
  

  this -> remoteAddr = dst.copy();

  if ((nap = goodNetwkAddr(dst)) == NULL) {
    CERROR ("activeOpen - Dest Addr not in RFC-1006 format, try NS+54+00728722+portnum@hostname");
    return false;
  } else {
    if ((cp = ::index(nap -> dsp.st, '@')) == NULL) {
      CERROR ("activeOpen - Net Addr bad dsp format, try portnum@hostname");
      return false;
    }
    else {
      // cp is sitting at '@' seperator
      hostname = cp + 1;

      *cp = NULL;
      portnum = ::atoi (nap -> dsp.st);
      *cp = '@';	// put the @ back

      // Connect to port@inetAddr
      return (this -> soClient -> Connect (hostname, portnum));
    }
  }
}

void NetTCP::transmit(Frame& f) {
  ::encode_tpkt_hdr(f);

  int toWrite = f.length();
  int nWritten = 0;

  switch (this -> connectRole) {
  case CON_INITIATOR:
    nWritten = this -> soClient -> Write(f);
    break;
  case CON_RESPONDER:
    nWritten = this -> soService -> Write(f);
    break;
  default:
    CERROR ("transmit - tried to write with bad Net::connectRole");
    break;
  }

#if 0
  dout << "#DBUG# toWrite =" << int(toWrite) << ", nWritten=" << int(nWritten) << "\n";
#endif
}

void NetTCP::disconnect() {
  if (this -> connectRole == CON_INITIATOR) {
    this -> soClient -> Close();
  }
  else {
    if (this -> soService != NULL) {
      this -> soService -> Close();
      delete this -> soService;
      this -> soService = NULL;
    }
  }
}
