/* 
 * All modifications in this file to the original code are
 * (C) Copyright 1992, ..., 2001 the "DOSEMU-Development-Team".
 *
 * for details see file COPYING in the DOSEMU distribution
 */

/* ipx.c for the DOS emulator
 * 		Tim Bird, tbird@novell.com
 *
 * 96/07/31 -	Add callback from ESR and procedure to call into 
 *		IPX from DOSEmu (bios.S ESRFarCall). JES
 */
#include "ipx.h"
#ifdef IPX

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/vm86.h>
#if GLIBC_VERSION_CODE >= 2000
#  include <netipx/ipx.h>
#else
#  include "Linux/ipx.h"
#endif /* GLIBC_VERSION_CODE */
#include <netinet/in.h>
#include <errno.h>

#include "timers.h"
#include "memory.h"
#include "emu.h"
#include "cpu.h"
#include "bios.h"
#include "inifile.h"
#include "doshelpers.h"
#include "dosemu_select.h"

#define MAX_PACKET_DATA		1500

#if 0
/* NOTE: the below is already defined with #include "emu.h"
 *       Must NOT redefine it, else vm86plus won't work !!!
 */
extern struct vm86_struct vm86s;
#endif

/* declare some function prototypes */
extern u_char IPXCancelEvent(far_t ECBPtr);
far_t ESRPopRegistersReturn;
far_t ESRPopRegistersIRet;
far_t ESRFarCall;

static ipx_socket_t *ipx_socket_list = NULL;
static int IPXRunning = 0;

/* DANG_FIXTHIS - get a real value for my address !! */
static unsigned char MyAddress[10] =
{0x01, 0x01, 0x00, 0xe0,
 0x00, 0x00, 0x1b, 0x33, 0x2b, 0x13};

int 
GetMyAddress( void )
{
  int sock;
  struct sockaddr_ipx ipxs;
  struct sockaddr_ipx ipxs2;
  int len;
  int i;
  
  sock=socket(AF_IPX,SOCK_DGRAM,PF_IPX);
  if(sock==-1)
  {
    n_printf("IPX: could not open socket in GetMyAddress\n");
    return(-1);
  }

  /* bind this socket to network 0 */  
  ipxs.sipx_family=AF_IPX;
  ipxs.sipx_network=0;
  ipxs.sipx_port=0;
  
  if(bind(sock,(struct sockaddr *)&ipxs,sizeof(ipxs))==-1)
  {
    n_printf("IPX: could bind to network 0 in GetMyAddress\n");
    close( sock );
    return(-1);
  }
  
  len = sizeof(ipxs2);
  if (getsockname(sock,(struct sockaddr *)&ipxs2,&len) < 0) {
    n_printf("IPX: could not get socket name in GetMyAddress\n");
    close( sock );
    return(-1);
  }

  *((unsigned long *)MyAddress) = ipxs2.sipx_network;
  for (i = 0; i < 6; i++) {
    MyAddress[4+i] = ipxs2.sipx_node[i];
  }
  n_printf("IPX: using network address %02X%02X%02X%02X\n",
    MyAddress[0], MyAddress[1], MyAddress[2], MyAddress[3] );
  n_printf("IPX: using node address of %02X%02X%02X%02X%02X%02X\n",
    MyAddress[4], MyAddress[5], MyAddress[6], MyAddress[7],
    MyAddress[8], MyAddress[9] );
  close( sock );
  return(0);
}
  
void
InitIPXFarCallHelper(void)
{
#if 0
  u_char *ptr;
#endif
  char value[40];
  int addressTemp[6];
  int numFields;
  int ccode;

  ccode = GetMyAddress();
  if( ccode ) {
    n_printf("IPX: cannot initialize MyAddress\n");
    n_printf("IPX: trying to read network and node address from DOS.INI\n");
    
    /* take some time here to read my address from the DOS.INI file */
    GetValueFromIniFile(IPX_DOS_INI_PATH, "ipx", "network", value );
    if( strlen( value )==0 ) {
      n_printf("IPX: cannot read IPX network address from dos.ini file\n");
      n_printf("IPX: please create an [IPX] section in your dos.ini file\n");
      n_printf("IPX: and add the line NETWORK=xxxxxxxx, where xxxxxxxx\n");
      n_printf("IPX: is your IPX network number.\n");
    } else {
      numFields = sscanf( value, "%2x%2x%2x%2x",
        &addressTemp[0], &addressTemp[1], &addressTemp[2], &addressTemp[3] );
      if( numFields != 4 ) {
        n_printf("IPX: invalid IPX network address read from dos.ini\n");
        n_printf("IPX: please create an [IPX] section in your dos.ini file\n");
        n_printf("IPX: and add the line NODE=xxxxxxxxxxxx, where xxxxxxxxxxxx\n");
        n_printf("IPX: is your IPX node number.\n");
      } else {
        MyAddress[0] = addressTemp[0];
        MyAddress[1] = addressTemp[1];
        MyAddress[2] = addressTemp[2];
        MyAddress[3] = addressTemp[3];
      }
    }
    
    GetValueFromIniFile(IPX_DOS_INI_PATH, "ipx", "node", value );
    if( strlen( value )==0 ) {
      n_printf("IPX: cannot read IPX node address from dos.ini file\n");
    } else {
      numFields = sscanf( value, "%2x%2x%2x%2x%2x%2x",
        &addressTemp[0], &addressTemp[1], &addressTemp[2], &addressTemp[3], 
        &addressTemp[4], &addressTemp[5] );
      if( numFields != 6 ) {
        n_printf("IPX: invalid IPX node address read from dos.ini\n");
      } else {
        MyAddress[4] = addressTemp[0];
        MyAddress[5] = addressTemp[1];
        MyAddress[6] = addressTemp[2];
        MyAddress[7] = addressTemp[3];
        MyAddress[8] = addressTemp[4];
        MyAddress[9] = addressTemp[5];
      }
    }
  
    n_printf("IPX: using network address %02X%02X%02X%02X\n",
      MyAddress[0], MyAddress[1], MyAddress[2], MyAddress[3] );
    n_printf("IPX: using node address of %02X%02X%02X%02X%02X%02X\n",
      MyAddress[4], MyAddress[5], MyAddress[6], MyAddress[7],
      MyAddress[8], MyAddress[6] );
  }
   
#if 0
  ptr = (u_char *) IPX_ADD;
  *ptr++ = 0x50;		/* push ax - FarCallHandler removes this before returning */
  *ptr++ = 0xb0;		/* mov al, 7a */
  *ptr++ = 0x7a;
  *ptr++ = 0xcd;		/* int DOS_HELPER_INT (0xe6) */
  *ptr++ = DOS_HELPER_INT;
  *ptr++ = 0xcb;		/* far ret */

  ESRPopRegistersReturn.segment = ((int) ptr) >> 4;
  ESRPopRegistersReturn.offset = ((int) ptr) & 0xf;
  *ptr++ = 0x07;		/* pop es, ds, bp, di, si, dx, cx, bx, ax */
  *ptr++ = 0x1f;
  *ptr++ = 0x5d;
  *ptr++ = 0x5f;
  *ptr++ = 0x5e;
  *ptr++ = 0x5a;
  *ptr++ = 0x59;
  *ptr++ = 0x5b;
  *ptr++ = 0x58;
  *ptr++ = 0xcb;		/* far return */

  ESRPopRegistersIRet.segment = ((int) ptr) >> 4;
  ESRPopRegistersIRet.offset = ((int) ptr) & 0xf;
  *ptr++ = 0x07;		/* pop es, ds, bp, di, si, dx, cx, bx, ax */
  *ptr++ = 0x1f;
  *ptr++ = 0x5d;
  *ptr++ = 0x5f;
  *ptr++ = 0x5e;
  *ptr++ = 0x5a;
  *ptr++ = 0x59;
  *ptr++ = 0x5b;
  *ptr++ = 0x58;
  *ptr++ = 0xcf;		/* iret */
#else
  {
    extern void bios_IPX_PopRegistersReturn(), bios_IPX_PopRegistersIRet(), bios_IPX_FarCall();
    long i = (long)bios_IPX_PopRegistersReturn - (long)bios_f000;
    i += BIOSSEG << 4;
    ESRPopRegistersReturn.segment = i >> 4;
    ESRPopRegistersReturn.offset = i & 0xf;
    i = (long)bios_IPX_PopRegistersIRet - (long)bios_f000;
    i += BIOSSEG << 4;
    ESRPopRegistersIRet.segment = i >> 4;
    ESRPopRegistersIRet.offset = i & 0xf;
    i = (long)bios_IPX_FarCall - (long)bios_f000;
    i += BIOSSEG << 4;
    ESRFarCall.segment = i >> 4;
    ESRFarCall.offset = i & 0xf;
  }
#endif
}

/*************************
 * Check for IPX - int 2f, AH=7A
 * returns AL=FF
 * 	ES:DI points to helper routine which gets to FarCallHandler
 *************************/
int
IPXInt2FHandler(void)
{
  LO(ax) = 0xff;
  _regs.edi = IPX_OFF;
  _regs.es = IPX_SEG;
  n_printf("IPX: request for IPX far call handler address %lx:%lx\n",
	   (unsigned long)_regs.es, (unsigned long)_regs.edi);
  return 1;
}

static void 
ipx_remove_socket(ipx_socket_t * sk)
{
  ipx_socket_t *s;

  s = ipx_socket_list;
  if (s == sk) {
    ipx_socket_list = s->next;
    free(s);
    return;
  }
  while (s && s->next) {
    if (s->next == sk) {
      s->next = sk->next;
      free(sk);
      return;
    }
    s = s->next;
  }
}

static void 
ipx_insert_socket(u_short socket, u_short PSP, int fd)
{
  ipx_socket_t *sk;

  sk = (ipx_socket_t *) malloc(sizeof(ipx_socket_t));

  sk->socket = socket;
  sk->listenList.segment = 0;
  sk->listenList.offset = 0;
  sk->listenCount = 0;
  sk->AESList.segment = 0;
  sk->AESList.offset = 0;
  sk->AESCount = 0;
  sk->PSP = PSP;
  sk->fd = fd;
  sk->next = ipx_socket_list;
  ipx_socket_list = sk;
}

static ipx_socket_t *
ipx_find_socket(int port)
{
  ipx_socket_t *s;

  s = ipx_socket_list;
  while (s) {
    if (s->socket == port) {
      return (s);
    }
    s = s->next;
  }
  return (NULL);
}

u_short
SwapInt(u_short value)
{
  u_short temp;

  temp = (value & 0xff) << 8;
  return (temp + (value >> 8));
}

void
dumpBytes(u_char * memptr, int count)
{
  int i, linecounter;

  linecounter = 0;
  if (count > 64) {
    /* dumping whole packet takes too long */
    count = 64;
  }
  for (i = 0; i < count; i++) {
    if (linecounter == 0) {
      n_printf("%X: ", (int)memptr);
    }
    n_printf("%02X ", *memptr++);
    if (linecounter == 7) {
      n_printf("-");
    }
    linecounter++;
    if (linecounter == 16) {
      n_printf("\n");
      linecounter = 0;
    }
  }
  n_printf("\n");
}

void
printECB(ECB_t * ECB)
{
  int i;
   
  if( d.network ) {
    n_printf("--DOS ECB (dump)--\n");
    dumpBytes((u_char *) ECB, 60);
    n_printf("--DOS ECB--\n");
    n_printf("Link             %04X:%04X\n", ECB->Link.segment, ECB->Link.offset);
    n_printf("ESR              %04X:%04X\n", ECB->ESRAddress.segment, ECB->ESRAddress.offset);
    n_printf("InUseFlag        %02x\n", ECB->InUseFlag);
    n_printf("CompletionCode   %02x\n", ECB->CompletionCode);
    n_printf("ECBSocket	     %04x\n", SwapInt(ECB->ECBSocket));
    n_printf("ImmediateAddress %02X%02X%02X%02X%02X%02X\n",
      ECB->ImmediateAddress[0],
      ECB->ImmediateAddress[1],
      ECB->ImmediateAddress[2],
      ECB->ImmediateAddress[3],
      ECB->ImmediateAddress[4],
      ECB->ImmediateAddress[5]);
    n_printf("FragmentCount	 %d\n", ECB->FragmentCount);
    for (i = 0; i < ECB->FragmentCount; i++) {
      n_printf("Frag[%d].Length   %d\n", i, ECB->FragTable[i].Length);
      n_printf("Frag[%d].Address	 %04X:%04X\n", i,
        ECB->FragTable[i].Address.segment,
        ECB->FragTable[i].Address.offset);
    }
  }
}

void
printIPXHeader(IPXPacket_t * IPXHeader)
{
  if (d.network) {
    n_printf("--IPX Header (dump)--\n");
    dumpBytes((u_char *) IPXHeader, 30);
    n_printf("--IPX Header --\n");
    n_printf("Checksum         %04X\n", IPXHeader->Checksum);
    n_printf("Length           %d\n", SwapInt(IPXHeader->Length));
    n_printf("TransportControl %d\n", IPXHeader->TransportControl);
    n_printf("PacketType       %d\n", IPXHeader->PacketType);
    n_printf("Destination      %02X%02X%02X%02X:%02X%02X%02X%02X%02X%02X:%02X%02X\n",
      IPXHeader->Destination.Network[0],
      IPXHeader->Destination.Network[1],
      IPXHeader->Destination.Network[2],
      IPXHeader->Destination.Network[3],
      IPXHeader->Destination.Node[0],
      IPXHeader->Destination.Node[1],
      IPXHeader->Destination.Node[2],
      IPXHeader->Destination.Node[3],
      IPXHeader->Destination.Node[4],
      IPXHeader->Destination.Node[5],
      IPXHeader->Destination.Socket[0],
      IPXHeader->Destination.Socket[1]);
    n_printf("Source           %02X%02X%02X%02X:%02X%02X%02X%02X%02X%02X:%02X%02X\n",
      IPXHeader->Source.Network[0],
      IPXHeader->Source.Network[1],
      IPXHeader->Source.Network[2],
      IPXHeader->Source.Network[3],
      IPXHeader->Source.Node[0],
      IPXHeader->Source.Node[1],
      IPXHeader->Source.Node[2],
      IPXHeader->Source.Node[3],
      IPXHeader->Source.Node[4],
      IPXHeader->Source.Node[5],
      IPXHeader->Source.Socket[0],
      IPXHeader->Source.Socket[1]);
  }
}

u_char
IPXOpenSocket(u_short port, u_short * newPort)
{
  PRIV_SAVE_AREA
  int sock;			/* sock here means Linux socket handle */
  int opt;
  struct sockaddr_ipx ipxs;
  int len;
  struct sockaddr_ipx ipxs2;

  /* DANG_FIXTHIS - do something with longevity flag */
  port = SwapInt(port);
  n_printf("IPX: open socket %x\n", port);
  if (port != 0 && ipx_find_socket(port) != NULL) {
    n_printf("IPX: socket %x already open.\n", port);
    /* someone already has this socket open */
    return (RCODE_SOCKET_ALREADY_OPEN);
  }
  /* DANG_FIXTHIS - kludge to support broken linux IPX stack */
  /* need to convert dynamic socket open into a real socket number */
/*  if (port == 0) {
    n_printf("IPX: using socket %x\n", nextDynamicSocket);
    port = nextDynamicSocket++;
  }
*/
  /* do a socket call, then bind to this port */
  sock = socket(AF_IPX, SOCK_DGRAM, PF_IPX);
  if (sock == -1) {
    n_printf("IPX: could not open IPX socket.\n");
    /* I can't think of anything else to return */
    return (RCODE_SOCKET_TABLE_FULL);
  }

  opt = 1;
  /* turn on socket debugging */
  if (d.network) {
    enter_priv_on();
    if (setsockopt(sock, SOL_SOCKET, SO_DEBUG, &opt, sizeof(opt)) == -1) {
      leave_priv_setting();
      n_printf("IPX: could not set socket option for debugging.\n");
      /* I can't think of anything else to return */
      return (RCODE_SOCKET_TABLE_FULL);
    }
    leave_priv_setting();
  }
  opt = 1;
  /* Permit broadcast output */
  enter_priv_on();
  if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
		 &opt, sizeof(opt)) == -1) {
    leave_priv_setting();
    /* I can't think of anything else to return */
    n_printf("IPX: could not set socket option for broadcast.\n");
    return (RCODE_SOCKET_TABLE_FULL);
  }
  /* allow setting the type field in the IPX header */
  opt = 1;
#if 0 /* this seems to be wrong: IPX_TYPE can only be set on level SOL_IPX */
  if (setsockopt(sock, SOL_SOCKET, IPX_TYPE, &opt, sizeof(opt)) == -1) {
#else
  /* the socket _is_ an IPX socket, hence it first passes ipx_setsockopt()
   * in file linux/net/ipx/af_ipx.c. This one handles SOL_IPX itself and
   * passes SOL_SOCKET-levels down to sock_setsockopt().
   * Hence I guess the below is correct (can somebody please verify this?)
   * -- Hans, June 14 1997
   */
  if (setsockopt(sock, SOL_IPX, IPX_TYPE, &opt, sizeof(opt)) == -1) {
#endif
    leave_priv_setting();
    /* I can't think of anything else to return */
    n_printf("IPX: could not set socket option for type.\n");
    return (RCODE_SOCKET_TABLE_FULL);
  }
  ipxs.sipx_family = AF_IPX;
  ipxs.sipx_network = *((unsigned int *)&MyAddress[0]);
/*  ipxs.sipx_network = htonl(MyNetwork); */
  bzero(ipxs.sipx_node, 6);	/* Please fill in my node name */
  ipxs.sipx_port = htons(port);

  /* now bind to this port */
  if (bind(sock, (struct sockaddr *) &ipxs, sizeof(ipxs)) == -1) {
    /* I can't think of anything else to return */
    n_printf("IPX: could not bind socket to address\n");
    close( sock );
    leave_priv_setting();
    return (RCODE_SOCKET_TABLE_FULL);
  }
  
  if( port==0 ) {
    len = sizeof(ipxs2);
    if (getsockname(sock,(struct sockaddr *)&ipxs2,&len) < 0) {
      /* I can't think of anything else to return */
      n_printf("IPX: could not get socket name in IPXOpenSocket\n");
      close( sock );
      leave_priv_setting();
      return(RCODE_SOCKET_TABLE_FULL);
    } else {
      port = htons(ipxs2.sipx_port);
      n_printf("IPX: opened dynamic socket %04x\n", port);
    }
  }
  leave_priv_setting();
  
  /* if we successfully bound to this port, then record it */
  ipx_insert_socket(port, /* PSP */ 0, sock);
  n_printf("IPX: successfully opened socket %04x\n", port);
  *newPort = htons(port);
  return (RCODE_SUCCESS);
}

u_char
IPXCloseSocket(u_short port)
{
  ipx_socket_t *mysock;
  far_t ECBPtr;

  /* see if this socket is actually open */
  port = SwapInt(port);
  n_printf("IPX: close socket %x\n", port);
  mysock = ipx_find_socket(port);
  if (mysock == NULL) {
    n_printf("IPX: close of unopened socket\n");
    return (RCODE_SOCKET_NOT_OPEN);
  }
  /* cancel all pending events on this socket */
  n_printf("IPX: canceling all listen events on socket %x\n", port);
  ECBPtr = mysock->listenList;
  while (ECBPtr.segment | ECBPtr.offset) {
    IPXCancelEvent(ECBPtr);
    ECBPtr = mysock->listenList;
  }
  n_printf("IPX: canceling all AES events on socket %x\n", port);
  ECBPtr = mysock->AESList;
  while (ECBPtr.segment | ECBPtr.offset) {
    IPXCancelEvent(ECBPtr);
    ECBPtr = mysock->AESList;
  }
  /* now close the file descriptor for the socket, and free it */
  n_printf("IPX: closing file descriptor on socket %x\n", port);
  close(mysock->fd);
  ipx_remove_socket(mysock);
  n_printf("IPX: successfully closed socket %x\n", port);
  return (RCODE_SUCCESS);
}

int
GatherFragmentData(char *buffer, ECB_t * ECB)
{
  int i;
  int nextFragLen, totalDataCount;
  u_char *bufptr, *memptr;

  totalDataCount = 0;
  bufptr = buffer;
  for (i = 0; i < ECB->FragmentCount; i++) {
    nextFragLen = ECB->FragTable[i].Length;
    memptr = (u_char *) ((ECB->FragTable[i].Address.segment << 4) +
			 ECB->FragTable[i].Address.offset);
    if (i == 0) {
      /* subtract off IPX header size from first fragment */
      nextFragLen -= 30;
      memptr += 30;
    }
    if (nextFragLen) {
      /* DANG_FIXTHIS - dumb use of hardcoded 1500 here */
      if (totalDataCount + nextFragLen > 1500) {
	return (-1);
      }
      memcpy(bufptr, memptr, nextFragLen);
      bufptr += nextFragLen;
      totalDataCount += nextFragLen;
    }
  }
  n_printf("IPX: gathered %d fragments for %d bytes of data\n",
	   ECB->FragmentCount, totalDataCount);
  if (d.network) {
    dumpBytes(buffer, totalDataCount);
  }
  return (totalDataCount);
}

void
PrepareForESR(int iretFlag, ECB_t * ECB, far_t ECBPtr, u_char AXVal)
{
  unsigned char *ssp;
  unsigned long sp;

  IPXRunning++;
  ssp = (unsigned char *)(REG(ss)<<4);
  sp = (unsigned long) LWORD(esp);

  /* push all registers */
  pushw(ssp, sp, LWORD(eax));
  pushw(ssp, sp, LWORD(ebx));
  pushw(ssp, sp, LWORD(ecx));
  pushw(ssp, sp, LWORD(edx));
  pushw(ssp, sp, LWORD(esi));
  pushw(ssp, sp, LWORD(edi));
  pushw(ssp, sp, LWORD(ebp));
  pushw(ssp, sp, LWORD(ds));
  pushw(ssp, sp, LWORD(es));

  /* push far return address for ESRPopRegisters... */
  if (iretFlag) {
    pushw(ssp, sp, ESRPopRegistersIRet.segment);
    pushw(ssp, sp, ESRPopRegistersIRet.offset);
  }
  else {
    pushw(ssp, sp, ESRPopRegistersReturn.segment);
    pushw(ssp, sp, ESRPopRegistersReturn.offset);
  }

  LWORD(esp) -= 22;
  
  _regs.cs = ECB->ESRAddress.segment;
  _regs.eip = ECB->ESRAddress.offset;
  _regs.es = ECBPtr.segment;
  _regs.esi = ECBPtr.offset;
  _regs.eax = AXVal;

  n_printf("IPX: setup for ESR address at %04X:%04lX\n",
	   _regs.cs, _regs.eip);
  n_printf("IPX: ES:SI = %04X:%04lX, AX = %X, VIF = %d\n",
	   _regs.es, _regs.esi, LWORD(eax), LWORD(eflags) & VIF);
}

void
IPXSendPacket(far_t ECBPtr)
{
  ECB_t *ECB;
  IPXPacket_t *IPXHeader;
  u_char data[MAX_PACKET_DATA];
  struct sockaddr_ipx ipxs;
  int dataLen;
  ipx_socket_t *mysock;

  ECB = (ECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
  printECB(ECB);
  dataLen = GatherFragmentData(data, ECB);
  if (dataLen == -1) {
    ECB->InUseFlag = IU_ECB_FREE;
    ECB->CompletionCode = CC_FRAGMENT_ERROR;
    return;
  }
  IPXHeader = (IPXPacket_t *) ((ECB->FragTable[0].Address.segment << 4)
			       + ECB->FragTable[0].Address.offset);
  /* for a complete emulation, we need to fill in fields in the */
  /* send packet header */
  /* first field is an IPX convention, not really a checksum */
  IPXHeader->Checksum = 0xffff;
  IPXHeader->Length = SwapInt(dataLen + 30);	/* in network order */
  memcpy(&IPXHeader->Source, MyAddress, 10);
  *(u_short *) & IPXHeader->Source.Socket = ECB->ECBSocket;
  printIPXHeader(IPXHeader);
  ipxs.sipx_family = AF_IPX;
  /* get destination address from IPX packet header */
  memcpy(&ipxs.sipx_network, IPXHeader->Destination.Network, 4);
  /* if destination address is 0, then send to my net */
  if (ipxs.sipx_network == 0) {
    ipxs.sipx_network = *((unsigned int *)&MyAddress[0]);
/*  ipxs.sipx_network = htonl(MyNetwork); */
  }
  memcpy(&ipxs.sipx_node, IPXHeader->Destination.Node, 6);
  memcpy(&ipxs.sipx_port, IPXHeader->Destination.Socket, 2);
  ipxs.sipx_type = 1;
  /*	ipxs.sipx_port=htons(0x452); */
  mysock = ipx_find_socket(htons(ECB->ECBSocket));
  if (mysock == NULL) {
    /* DANG_FIXTHIS - should do a bind, send, close here */
    /* DOS IPX allows sending on unopened sockets */
    n_printf("IPX: send to unopened socket %04x\n", htons( ECB->ECBSocket ));
    return;
  }
  if (sendto(mysock->fd, (void *) &data, dataLen, 0,
	     (struct sockaddr *) &ipxs, sizeof(ipxs)) == -1) {
    n_printf("IPX: error sending packet\n");
    ECB->InUseFlag = IU_ECB_FREE;
    ECB->CompletionCode = CC_HARDWARE_ERROR;
  }
  else {
    ECB->InUseFlag = IU_ECB_FREE;
    ECB->CompletionCode = CC_SUCCESS;
    n_printf("IPX: successfully sent packet!\n");
  }
  /* now call the ESR for the ECB */
  /* bit of trickiness here - I will let the ESR, if any, return to */
  /* the return address from the original far call to IPX, instead of */
  /* to my helper routine. */
  if ((ECB->ESRAddress.segment | ECB->ESRAddress.offset) != 0) {
    PrepareForESR(0 /* not an iret, just a normal return */ ,
		  ECB, ECBPtr, ESR_CALLOUT_IPX);
  }
  return;
}

u_char
IPXListenForPacket(far_t ECBPtr)
{
  ECB_t *ECB;
  ipx_socket_t *mysock;

  ECB = (ECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
  printECB(ECB);
  /* see if the socket is open for this ECB */
  mysock = ipx_find_socket(htons(ECB->ECBSocket));
  if (mysock == NULL) {
    n_printf("IPX: listen on unopened socket\n");
    ECB->InUseFlag = IU_ECB_FREE;
    ECB->CompletionCode = CC_SOCKET_NOT_OPEN;
    return (RCODE_SOCKET_NOT_OPEN);
  }

  ECB->InUseFlag = IU_ECB_LISTENING;
  ECB->CompletionCode = CC_SUCCESS;
  /* now put this ECB on the listen list for this socket */
  ECB->Link = mysock->listenList;
  mysock->listenList = ECBPtr;
  mysock->listenCount++;

  n_printf("IPX: successfully posted listen packet on socket %x!\n",
	   mysock->socket);
  return (RCODE_SUCCESS);
}

u_char
IPXScheduleEvent(far_t ECBPtr, u_char inUseCode, u_short delayTime)
{
  ECB_t *ECB;
  ipx_socket_t *mysock;

  ECB = (ECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
  printECB(ECB);
  /* see if the socket is open for this ECB */
  mysock = ipx_find_socket(htons(ECB->ECBSocket));
  if (mysock == NULL) {
    n_printf("IPX: AES event on unopened socket\n");
    ECB->InUseFlag = IU_ECB_FREE;
    ECB->CompletionCode = CC_SOCKET_NOT_OPEN;
    return (RCODE_SOCKET_NOT_OPEN);
  }

  ECB->InUseFlag = inUseCode;
  ECB->CompletionCode = CC_SUCCESS;
  /* now put this ECB on the AES list for this socket */
  ECB->Link = mysock->AESList;
  ((AESECB_t *) ECB)->TimeLeft = delayTime;
  mysock->AESList = ECBPtr;
  mysock->AESCount++;

  n_printf("IPX: successfully posted AES event on socket %x!\n",
	   mysock->socket);
  n_printf("IPX: AES delay time %d!\n", ((AESECB_t *) ECB)->TimeLeft);
  return (RCODE_SUCCESS);
}

u_char
IPXCancelEvent(far_t ECBPtr)
{
  ECB_t *ECB;
  ipx_socket_t *mysock;
  ECB_t *prevECB;
  far_t ECBList;

  ECB = (ECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
  /* if it was a listen, cancel it */
  if (ECB->InUseFlag == IU_ECB_LISTENING ||
      ECB->InUseFlag == IU_ECB_IPX_WAITING ||
      ECB->InUseFlag == IU_ECB_AES_WAITING) {
    /* look up the socket for this ECB */
    mysock = ipx_find_socket(htons(ECB->ECBSocket));
    if (mysock == NULL) {
      n_printf("IPX: cancel on unopened socket\n");
      ECB->InUseFlag = IU_ECB_FREE;
      ECB->CompletionCode = CC_SOCKET_NOT_OPEN;
      return (RCODE_SOCKET_NOT_OPEN);
    }
    n_printf("IPX: canceling event on socket %x\n",
	     mysock->socket);
    /* now see if this ECB is actually on the list for this socket */
    if (ECB->InUseFlag == IU_ECB_LISTENING) {
      /* check the listenList */
      ECBList = mysock->listenList;
      n_printf("IPX: cancel a listen event from %d events\n",
	       mysock->listenCount);
      prevECB = (ECB_t *) (&mysock->listenList);
    }
    else {
      /* check the AESList */
      ECBList = mysock->AESList;
      n_printf("IPX: cancel an AES event from %d events\n",
	       mysock->AESCount);
      prevECB = (ECB_t *) (&mysock->AESList);
    }
    /* this next cast is somewhat evil.  If the first item on the */
    /* list is the one removed, then change the head pointer for */
    /* the list as if it were a ECB.Link field. */
    n_printf("IPX: scanning ECBList for match\n");
    while (ECBList.segment | ECBList.offset) {
      /* see if the list pointer equals the ECB we are canceling */
      n_printf("IPX: ECBPtr = %x:%x, ECBList = %x:%x\n",
	       ECBPtr.segment, ECBPtr.offset,
	       ECBList.segment, ECBList.offset);
      if (ECBList.segment == ECBPtr.segment &&
	  ECBList.offset == ECBPtr.offset) {
	/* remove it from the list */
	prevECB->Link = ECB->Link;
	if (ECB->InUseFlag == IU_ECB_LISTENING) {
	  mysock->listenCount--;
	}
	else {
	  mysock->AESCount--;
	}
	ECB->InUseFlag = IU_ECB_FREE;
	ECB->CompletionCode = CC_EVENT_CANCELED;
	n_printf("IPX: successfully canceled event!\n");
	return (RCODE_SUCCESS);
      }
      prevECB = (ECB_t *) ((ECBList.segment << 4) + ECBList.offset);
      ECBList = prevECB->Link;
    }
    n_printf("IPX: ECB was not in use.\n");
    return (RCODE_ECB_NOT_IN_USE);
  }
  else {
    if (ECB->InUseFlag == IU_ECB_FREE) {
      return (RCODE_ECB_NOT_IN_USE);
    }
  }
  return (RCODE_CANNOT_CANCEL_EVENT);
}

void
AESTimerTick(void)
{
  ipx_socket_t *mysock;
  far_t ECBPtr;
  AESECB_t *ECB;
  int done;

  /* run through the socket list, and process any pending AES events */
  mysock = ipx_socket_list;
  done = 0;
  while (mysock && !done) {
    /* see if this socket has any AES events pending */
    ECBPtr = mysock->AESList;
    while ((ECBPtr.segment | ECBPtr.offset) && !done) {
      ECB = (AESECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
      if (ECB->TimeLeft > 0) {
	ECB->TimeLeft--;
	n_printf("IPX: AES timer decremented to %d on ECB at %x:%x\n",
		 ECB->TimeLeft, ECBPtr.segment, ECBPtr.offset);
      }
      ECBPtr = ECB->Link;
    }
    mysock = mysock->next;
  }
}

void
ipx_fdset(fd_set * set)
{
  ipx_socket_t *s;

  s = ipx_socket_list;
  while (s != NULL) {
    FD_SET(s->fd, set);
    s = s->next;
  }
}

int
ScatterFragmentData(int size, char *buffer, ECB_t * ECB,
		    struct sockaddr_ipx *sipx)
{
  int i;
  int nextFragLen, dataLeftCount;
  u_char *bufptr, *memptr;
  IPXPacket_t *IPXHeader;

  n_printf("received data bytes:\n");
  if (d.network) {
    dumpBytes(buffer, size);
  }
  dataLeftCount = size;
  bufptr = buffer;
  if (ECB->FragmentCount < 1 || ECB->FragTable[0].Length < 30) {
    /* posted listen has a fragment error */
    n_printf("IPX: listen packet fragment error\n");
    return (size);
  }
  i = 0;
  while (i < ECB->FragmentCount && dataLeftCount) {
    nextFragLen = ECB->FragTable[i].Length;
    memptr = (u_char *) ((ECB->FragTable[i].Address.segment << 4) +
			 ECB->FragTable[i].Address.offset);
    n_printf("IPX: filling fragment %d at %x:%x, length %d with %d data left\n",
	     i, ECB->FragTable[i].Address.segment,
	     ECB->FragTable[i].Address.offset, nextFragLen,
	     dataLeftCount);
    if (i == 0) {
      /* subtract off IPX header size from first fragment */
      nextFragLen -= 30;
      /* also, fill out IPX header */
      /* use data from sipx to fill out source address */
      IPXHeader = (IPXPacket_t *) memptr;
      IPXHeader->Checksum = 0xFFFF;
      IPXHeader->Length = SwapInt(size + 30); /* in network order */
      /* DANG_FIXTHIS - use real values to fill out IPX header here */
      IPXHeader->TransportControl = 1;
      IPXHeader->PacketType = 0;
      memcpy((u_char *) & IPXHeader->Destination, MyAddress, 10);
      *((u_short *) &IPXHeader->Destination.Socket) = ECB->ECBSocket;
      memcpy(IPXHeader->Source.Network, (char *) &sipx->sipx_network, 4);
      memcpy(IPXHeader->Source.Node, sipx->sipx_node, 6);
      *((u_short *) &IPXHeader->Source.Socket) = sipx->sipx_port;
      /* DANG_FIXTHIS - kludge for bug in linux IPX stack */
      /* IPX stack returns data count including IPX header */
      /*			dataLeftCount -= 30; */
      memptr += 30;
    }
    if (nextFragLen > dataLeftCount) {
      nextFragLen = dataLeftCount;
    }
    if (nextFragLen) {
      memcpy(memptr, bufptr, nextFragLen);
      bufptr += nextFragLen;
      dataLeftCount -= nextFragLen;
    }
    i++;
  }
  n_printf("IPX: scattered %d fragments, %d bytes left over.\n",
	   ECB->FragmentCount, dataLeftCount);
  return (dataLeftCount);
}

int
IPXReceivePacket(ipx_socket_t * s)
{
  struct sockaddr_ipx ipxs;
  char buffer[MAX_PACKET_DATA];
  ECB_t *ECB;
  far_t ECBPtr;
  int size, sz;
  int ESRFired = 0;

  sz = sizeof(ipxs);
  size = recvfrom(s->fd, buffer, sizeof(buffer), 0,
		  (struct sockaddr *) &ipxs, &sz);
  n_printf("IPX: received %d bytes of data\n", size);
  if (size > 0 && s->listenCount) {
    ECBPtr = s->listenList;
    ECB = (ECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
    sz = ScatterFragmentData(size, buffer, ECB, &ipxs);
    IPXCancelEvent(ECBPtr);
    /* if there was packet left over, then report a fragment error */
    if (sz) {
      ECB->CompletionCode = CC_FRAGMENT_ERROR;
      /* DANG_FIXTHIS - should remove this override, linux IPX stack returns bad sizes, but we ignore this here */
      ECB->CompletionCode = CC_SUCCESS;
    }
    else {
      ECB->CompletionCode = CC_SUCCESS;
    }
    /* now call the ESR for this packet */
    if ((ECB->ESRAddress.segment | ECB->ESRAddress.offset) != 0) {
      PrepareForESR(0 /* not an iret, just a normal return */ ,
		    ECB, ECBPtr, ESR_CALLOUT_IPX);
      ESRFired = 1;
    }
  }
  return (ESRFired);
}

int
IPXCheckForAESReady(void)
{
  ipx_socket_t *s;
  far_t ECBPtr;
  AESECB_t *ECB;
  int ESRFired = 0;
  u_char waitType;
  u_short rcode;

  s = ipx_socket_list;
  while (s != NULL && !ESRFired) {
    if (s->AESCount) {
      ECBPtr = s->AESList;
      while ((ECBPtr.segment | ECBPtr.offset) && !ESRFired) {
	ECB = (AESECB_t *) ((ECBPtr.segment << 4) + ECBPtr.offset);
	if (ECB->TimeLeft == 0) {
	  /* DANG_FIXTHIS - this architecture currently only supports firing of one AES event per call */
	  n_printf("IPX: AES event ready on ECB at %x:%x\n",
		   ECBPtr.segment, ECBPtr.offset);
	  printECB((ECB_t *) ECB);

	  /* first, save off the type of event, either AES or IPX */
	  waitType = ECB->InUseFlag;

	  /* now remove the ECB from the AES list */
	  rcode = IPXCancelEvent(ECBPtr);
	  if (rcode == RCODE_SUCCESS) {
	    ECB->CompletionCode = CC_SUCCESS;
	  }
	  /* now setup to call the ESR for this event */
	  if ((ECB->ESRAddress.segment | ECB->ESRAddress.offset) != 0) {
	    PrepareForESR(0 /* not an iret, just a normal return */ ,
			  (ECB_t *) ECB, ECBPtr, ESR_CALLOUT_AES);
	    ESRFired = 1;
	  }
	}
	ECBPtr = ECB->Link;
      }
    }
    s = s->next;
  }
  return (ESRFired);
}

int
check_ipx_ready(fd_set * set)
{
  ipx_socket_t *s;
  int ESRFired = 0;

  s = ipx_socket_list;
  while (s != NULL) {
    /* DANG_FIXTHIS - for now, just pick up one listen per poll, because we can only set up one ESR callout per relinquish */
    if (FD_ISSET(s->fd, set)) {
      break;
    }
    s = s->next;
  }
  if (s != NULL) {
    ESRFired = IPXReceivePacket(s);
  }
  return (ESRFired);
}

void
IPXRelinquishControl(void)
{
  /* DOS program has given us a time slice */
  /* let's use this as an opportunity to poll outstanding listens */
  fd_set fds;
  int selrtn;
  struct timeval timeout;
  int ESRFired = 0;

  if(IPXRunning) return;

  FD_ZERO(&fds);

  ipx_fdset(&fds);

  timeout.tv_sec = 0;
  timeout.tv_usec = 0;
  n_printf("IPX: starting repeated select\n");


  do {
      selrtn = select(255, &fds, NULL, NULL, &timeout);
  } while (selrtn == -1 && errno == EINTR);

  switch (selrtn) {
  case 0:			/* none ready */
    /*			n_printf("IPX: no receives ready\n"); */
    break;

  case -1:			/* error (not EINTR) */
    error("bad ipc_select: %s\n", strerror(errno));
    break;

  default:			/* has at least 1 descriptor ready */
    n_printf("IPX: receive ready, checking fd\n");
    ESRFired = check_ipx_ready(&fds);
    break;
  }

  if (!ESRFired) {
    /*		n_printf("IPX: checking AES events\n"); */
    ESRFired = IPXCheckForAESReady();
  }

}

int
IPXFarCallHandler(void)
{
  u_short port;			/* port here means DOS IPX socket */
  u_short newPort;
  ECB_t *ECB;
  unsigned short *timerticks;
  u_char *AddrPtr;
  far_t ECBPtr;
  unsigned long network, *networkPtr;
  int hops, ticks;
  unsigned char *ssp;
  unsigned long sp;

  ssp = (unsigned char *)(REG(ss)<<4);
  sp = (unsigned long) LWORD(esp);

  LWORD(eax) = popw(ssp, sp);
  LWORD(esp) += 2;
  n_printf("IPX: request number 0x%lX\n", _regs.ebx);
  switch (LWORD(ebx)) {
  case IPX_OPEN_SOCKET:
    port = LWORD(edx);
    LO(ax) = IPXOpenSocket(port, &newPort);
    LWORD(edx) = newPort;
    break;
  case IPX_CLOSE_SOCKET:
    port = LWORD(edx);
    LO(ax) = IPXCloseSocket(port);
    break;
  case IPX_GET_LOCAL_TARGET:
    /* do nothing here because routing is handled by IPX */
    /* normally this would return an ImmediateAddress, but */
    /* the ECB ImmediateAddress is never used, so just return */
    networkPtr = (unsigned long *)(((_regs.es & 0xffff)<<4) + LWORD(esi));
    network = *networkPtr;
    n_printf("IPX: GetLocalTarget for network %08lx\n", network );
    if( network==0 || network== *((unsigned long *)&MyAddress[0]) ) {
      n_printf("IPX: returning GLT success for local address\n");
      LO(ax) = RCODE_SUCCESS;
      LWORD(ecx) = 1;
    } else {
      if( IPXGetLocalTarget( network, &hops, &ticks )==0 ) {
        LO(ax) = RCODE_SUCCESS;
        LWORD(ecx) = ticks;
      } else {
        n_printf("IPX: GetLocalTarget failed.\n");
        LO(ax) = RCODE_CANNOT_FIND_ROUTE;
      }
    }
    break;
  case IPX_FAST_SEND:
    n_printf("IPX: fast send\n");
    /* just fall through to regular send */
  case IPX_SEND_PACKET:
    ECBPtr.segment = _regs.es & 0xffff;
    ECBPtr.offset = LWORD(esi);
    n_printf("IPX: send packet ECB at %x:%x\n",
	     ECBPtr.segment, ECBPtr.offset);
    IPXSendPacket(ECBPtr);
    break;
  case IPX_LISTEN_FOR_PACKET:
    ECBPtr.segment = _regs.es & 0xffff;
    ECBPtr.offset = LWORD(esi);
    n_printf("IPX: listen for packet ECB at %x:%x\n",
	     ECBPtr.segment, ECBPtr.offset);
    /* put this packet on the queue of listens for this socket */
    LO(ax) = IPXListenForPacket(ECBPtr);
    break;
  case IPX_SCHEDULE_IPX_EVENT:
    ECBPtr.segment = _regs.es & 0xffff;
    ECBPtr.offset = LWORD(esi);
    n_printf("IPX: schedule IPX event for ECB at %x:%x\n",
	     ECBPtr.segment, ECBPtr.offset);
    /* put this packet on the queue of AES events for this socket */
    LO(ax) = IPXScheduleEvent(ECBPtr, IU_ECB_IPX_WAITING,
			      LWORD(eax));
    break;
  case IPX_CANCEL_EVENT:
    ECBPtr.segment = _regs.es & 0xffff;
    ECBPtr.offset = LWORD(esi);
    n_printf("IPX: cancel event for ECB at %x:%x\n",
	     ECBPtr.segment, ECBPtr.offset);
    LO(ax) = IPXCancelEvent(ECBPtr);
    break;
  case IPX_SCHEDULE_AES_EVENT:
    ECB = (ECB_t *) (((_regs.es & 0xffff) << 4) + LWORD(esi));
    ECBPtr.segment = _regs.es & 0xffff;
    ECBPtr.offset = LWORD(esi);

    n_printf("IPX: schedule AES event ECB at %x\n", (unsigned int)ECB);
    n_printf("IPX: es:si = %x:%x\n", (unsigned int)(_regs.es & 0xffff),
      LWORD(esi));

    /* put this packet on the queue of AES events for this socket */
    LO(ax) = IPXScheduleEvent(ECBPtr, IU_ECB_AES_WAITING,
			      LWORD(eax));
    break;
  case IPX_GET_INTERVAL_MARKER:
    /* Note that timerticks is actually an unsigned long in BIOS */
    /* this works because of intel lo-hi architecture */
    timerticks = BIOS_TICK_ADDR;
    LWORD(eax) = *timerticks;
    n_printf("IPX: get interval marker %d\n", LWORD(eax));
    /*			n_printf("IPX: doing extra relinquish control\n"); */
    IPXRelinquishControl();
    break;
  case IPX_GET_INTERNETWORK_ADDRESS:
    n_printf("IPX: get internetwork address\n");
    AddrPtr = (u_char *) (((_regs.es & 0xffff) << 4) + LWORD(esi));
    memcpy(AddrPtr, MyAddress, 10);
    break;
  case IPX_RELINQUISH_CONTROL:
    n_printf("IPX: relinquish control\n");
    IPXRelinquishControl();
    break;
  case IPX_DISCONNECT:
    n_printf("IPX: disconnect\n");
    break;
  case IPX_SHELL_HOOK:
    n_printf("IPX: shell hook\n");
    break;
  case IPX_GET_MAX_PACKET_SIZE:
    n_printf("IPX: get max packet size\n");
    /* return max data size in AX, and suggested retries in CL */
    /* DANG_FIXTHIS - return a real max packet size here */
    LWORD(eax) = 1024;		/* must be a power of 2 */
    LO(cx) = 20;
    break;
  case IPX_GET_MEDIA_DATA_SIZE:
    n_printf("IPX: get max packet size\n");
    /* return max data size in AX, and suggested retries in CL */
    /* DANG_FIXTHIS - return a real max media size here */
    LWORD(eax) = 1480;
    LO(cx) = 20;
    break;
  case IPX_CLEAR_SOCKET:
    n_printf("IPX: clear socket\n");
    break;
  default:
    break;
  }
  return 1;
}

void IPXEndCall(void) {
	if(--IPXRunning < 0) IPXRunning = 0; /* Just incase */
    	n_printf("IPX: ESR Ended\n");
	if(!IPXRunning) IPXCallRel(); /* Ask for more */
	
}

void IPXCallRel(void) {
  unsigned char *ssp;
  unsigned long sp;
  ssp = (unsigned char *)(REG(ss)<<4);
  sp = (unsigned long) LWORD(esp);
  pushw(ssp, sp, LWORD(cs));
  pushw(ssp, sp, LWORD(eip));
  LWORD(esp) -= 4;
  _regs.cs = ESRFarCall.segment;
  _regs.eip = ESRFarCall.offset;
}

/* ipx_close is called on DOSEMU shutdown */
void
ipx_close(void)
{
  ipx_socket_t *s;

  /* run through the socket list and close each socket */
  s = ipx_socket_list;
  while (s != NULL) {
    IPXCloseSocket(SwapInt(s->socket));
    s = ipx_socket_list;
  }
}
#endif
