/*
 * tcp.c --- internetwork streams for GNUEmacs.
 *
 * Author: Tomi Ollila <too@cs.hut.fi>
 *
 * Copyright (c) 1993, 1994  AmiTCP/IP Group, <amitcp-group@hut.fi>
 *                           Helsinki University of Technology, Finland.
 *                           All rights reserved.
 *
 * Created: Sun Apr  4 15:38:07 1993 too
 * Last modified: Mon May  9 10:59:41 1994 too
 *
 * $Id: tcp.c,v 3.5 1994/05/09 08:00:38 too Exp $
 *
 * HISTORY
 * $Log: tcp.c,v $
 * Revision 3.5  1994/05/09  08:00:38  too
 * New production version. Now smallel, faster and more robust
 *
 *
 * Revision 1.1  1993/06/15  18:22:40  too
 * Tcp package for GNUEmacs
 */

#if (__GNUC__ < 2) || !defined (AMIGA)
#error this program compiles only with amiga gcc version 2.x
#endif

#define NULL 0
#define FALSE 0
#define TRUE 1

#define SOCLIBNAME "bsdsocket.library"
#define SOCLIBVERS 3

#define FD_SETSIZE 1 /* this tcp module needs only one socket. */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <dos/dos.h>
#include <dos/dosextens.h>

#define BSDSOCKET_H /* defining BSDSOCKET_H inhibitis automatic inline incl. */
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netdb.h>
#include "amitcp/socketbasetags.h"

#define va_list void *
#define CLIB_SOCKET_INLINES_H /* well, currently inclusion of
				 clib/socket_inlines.h cannot be handled if
				 using local SocketBase in calls */

#include "tcp_inlines.h"  /* this has all inlines needed in this module */
			/* regenerate if necessary (changes in orig inlines) */
#undef va_list

asm(".text; jmp pc@(_start-.+2);");
/* asm(".ascii; \"$VER: tcp_AmiTCP 3.1 (14.1.93+06.15)\\0\""); */

static const char version[] = "\0$VER: tcp_AmiTCP 3.5 " __AMIGADATE__ "\n";

#ifdef DEBUG
#include <stdarg.h>
static void debug(const char *, ...);
#else
#define debug (void)
#endif

#define RAF2(funname, type1, arg1, reg1, type2, arg2, reg2) \
  funname(VOID)			    \
{				    \
  register type1 reg1 __asm(#reg1); \
  type1 arg1 = reg1; 		    \
  register type2 reg2 __asm(#reg2); \
  type2 arg2 = reg2;                     	
#if 0
}
#endif

/*
 * this definition of 'isdigit' works at least with ascii character sets 
 */
#define isdigit(c) ((c >= '0' && c <= '9')? TRUE: FALSE)

static int atoi(char * a)
{
  int i = 0;

  while (isdigit(*a)) {
    i *= 10;
    i += *a - '0';
    a++;
  }
  return i;
}

/*
 * These are slow, but these aren't needed in time critical sections.
 */
static inline void bcopy(char * a, char * b, int n)
{
  while (n-- > 0)
    *(b++) = *(a++);
}
#if 0
static inline void bzero(char * a, int n)
{
  while (n-- > 0)
    *(a++) = '\0';
}
#endif

struct ExecBase *	SysBase = NULL;
struct DosLibrary *	DOSBase = NULL;

static const int one = 1;

static int tcp(char * hostname, char * service,
	       struct FileHandle * inputfh, BPTR output);

#define WriteCS(o, p) Write(o, p, sizeof p - 1)

static char * dummyParseArgs(char **, int *, char);

int RAF2(start,
	 char *,cmdBuf,	a0,
	 int,	cmdLen,	d0)
#if 0  
{
#endif
  /*
   * return value to dos
   */
  int retval = 20;

  SysBase = *(struct ExecBase **)4;

  {
    struct Process * me;
    me = (struct Process *)SysBase->ThisTask;
  
    if (me->pr_CLI == 0) { /* is this process started from workbench? */
      struct Message * WBenchMsg;

      WaitPort(&me->pr_MsgPort);
      WBenchMsg = GetMsg(&me->pr_MsgPort);
      /*
       * This program won't run if started from workbench (and there is no
       * need for it, since this is indended to be started from Emacs
       */
      Forbid();
      ReplyMsg(WBenchMsg);
      return 20;
    }
  }
  if ((DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36)) != NULL){
    struct FileHandle * inputfh;
    BPTR output;

    output = Output();
    if ((inputfh = (struct FileHandle *)(BADDR(Input())))->fh_Type != NULL) {
      char * hostname, * service;
      
      if ((hostname = dummyParseArgs(&cmdBuf, &cmdLen, 'h')) &&
	  (service = dummyParseArgs(&cmdBuf, &cmdLen, 's')))
	retval = tcp(hostname, service, inputfh, output);
      else
	WriteCS(output, "Usage: tcp -h host -s service\n");
    }
    else
      WriteCS(output, "Can not take input from NIL:\n");

    CloseLibrary((struct Library *)DOSBase);
  }
  return retval;
}

static char * dummyParseArgs(char ** buf, int * len, char option)
{
  char * ptr;

  while (*len && **buf != '-')
    { (*len)--; (*buf)++; }
  (*buf)++;
  if (!*len || **buf != option)
    return NULL;
  *len-=2; (*buf)++; 
  while (*len && (**buf == ' ' || **buf == '"'))
    { (*len)--; (*buf)++; }
  ptr = *buf;
  while (*len && **buf != ' ' && **buf != '"' && **buf != '\n')
    { (*len)--; (*buf)++; }
  debug("%s\n", ptr);
  if (!*len)
    return NULL;
  **buf = '\0';

  return ptr;
}

#define NNTP "119"

static ULONG resttaglist[] = {
  SBTM_SETVAL(SBTC_DTABLESIZE),	FD_SETSIZE,
  TAG_DONE };

static void AbortPacket(struct FileHandle * fh, struct Message * dpmsg);

static int tcp(char * hostname, char * service,
	       struct FileHandle * fh_in, BPTR fd_out)
{
  struct Library * SocketBase = NULL;

  long sd;
  int n;
  struct sockaddr_in name = { 0 };
  struct hostent * host;
  
  ULONG /*emask,*/ sigflag;
  struct PublicMemory {
    struct Message pm_dpMsg;	/* StandardPacket Message structure */
    struct DosPacket pm_dp;	/* StandardPacket DosPacket structure */
    struct MsgPort pm_mp;
    char pm_ebuf[1024];
  } * pm;
#define PM(field) pm->pm_##field

  char sbuf[1024];

  if ((pm = (struct PublicMemory *)AllocMem(sizeof (struct PublicMemory),
					    MEMF_PUBLIC|MEMF_CLEAR))
      == NULL)
    goto Return;

  debug("check alignment -- msg: %lx dp: %lx mp: %lx\n",
	&PM(dpMsg), &PM(dp), &PM(mp));
  
  if ((SocketBase = OpenLibrary(SOCLIBNAME, SOCLIBVERS)) == NULL) 
    goto Return;

  /*
   * Initialize MsgPort
   */
  if ((n = AllocSignal(-1)) == -1)
    goto Return;
  PM(mp).mp_SigBit = n;
  sigflag = 1 << n;
  PM(mp).mp_Flags = PA_SIGNAL;
  PM(mp).mp_SigTask = SysBase->ThisTask;
  NewList(&PM(mp).mp_MsgList); /* NewList() should be in exec inline file */

  /*
   * Some socket settings. Handler events are notified when recv() fails.
   */
  (void)SocketBaseTags(SBTM_SETVAL(SBTC_BREAKMASK), sigflag | SIGBREAKF_CTRL_C,
		       TAG_MORE,		    (LONG)resttaglist);
  /*
   * Initialize DosPacket (to obtain automatically filled character buffer
   * 			   after data has arrived from emacs)
   */
  PM(dp).dp_Link = &PM(dpMsg);
  PM(dp).dp_Type = ACTION_READ;
  PM(dp).dp_Arg1 = fh_in->fh_Arg1;
  PM(dp).dp_Arg2 = (LONG)PM(ebuf);
  PM(dp).dp_Arg3 = sizeof PM(ebuf);
  /*
   * Initialize message structure of DosPacket (the only item needed).
   */
  PM(dpMsg).mn_Node.ln_Name = (char *)&PM(dp);

  if ((host = gethostbyname(hostname)) == NULL)
    goto Return;

  name.sin_family = host->h_addrtype;
  bcopy(host->h_addr, (char *)&name.sin_addr.s_addr,
	sizeof name.sin_addr.s_addr);

  if (service == 0)
    service = NNTP;
  if (isdigit(service[0]))
    name.sin_port = htons(atoi(service));
  else {
    struct servent * serv = getservbyname(service, "tcp");
    if (serv == NULL)
      goto Return;

    name.sin_port = serv->s_port;
  }

  if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    goto Return;

  (void)setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof one);

  if (connect(sd, (struct sockaddr *)&name, sizeof name) < 0)
    goto Return;

/* Connection established ... */

  /*
   * Send initial asynchronous read packet
   */
  SendPkt(&PM(dp), fh_in->fh_Type, &PM(mp));

  for(;;) {

    switch (n = recv(sd, sbuf, sizeof sbuf, 0)) {
    case -1:
      /*
       * recv failed. There is possible data from handler.
       */
      if (SetSignal(0, 0xFFFFFFFF) & SIGBREAKF_CTRL_C)
	goto abortpacket;

      if (GetMsg(&PM(mp))) {
	debug("Got %ld chars from console\n", PM(dp).dp_Res1);
	if (PM(dp).dp_Res1 <= 0)
	  goto Return;
	if (send(sd, PM(ebuf), PM(dp).dp_Res1, 0) < 0)
	  goto Return;
      
	SendPkt(&PM(dp), fh_in->fh_Type, &PM(mp));
      }
      break;
    default:
      debug("Got %ld chars from socket\n", n);
      if (Write(fd_out, sbuf, n) >= 0)
	break;
      /* FALL THROUGH if write fails */
    case 0:
      /* connection closed by foreign host */

      goto abortpacket;
    }
  }
 abortpacket:
  AbortPacket(fh_in, &PM(dpMsg));
  /*
   * Wait until DosPacket has returned.
   */
  while(!GetMsg(&PM(mp)))
    Wait(sigflag);
  
Return:
  /*
   * Now, if there is open socket descriptor, it should be closed, but -- for
   * demonstration purposes CloseLibrary(SocketBase) is let to do it.
   */
  if (SocketBase)
    CloseLibrary(SocketBase);
  /*
   * Release memory needed to be accessed from other processes
   */
  if (pm)
    FreeMem(pm, sizeof (struct PublicMemory));
  return 0;
}

#include <rexx/rexxio.h>

static void AbortPacket(struct FileHandle * fh, struct Message * dpmsg)
{
  int count = 0;
  static const char abrt = '\n';
  LONG pkt, arg2, arg3;

  do {
    switch (count++) {
    case 0:
      pkt = 5000;	/* FIFO: custom ACTION_REQUEST packet */
      arg2 = (LONG)dpmsg;
      arg3 = 3;		/* abort! */
      break;
    case 1:
      pkt = ACTION_STACK;
      arg2 = (LONG)&abrt;
      arg3 = 1;
      break;
    default:
      return;
    }
  } while (DoPkt3(fh->fh_Type, pkt, (LONG)fh->fh_Arg1, arg2, arg3)== DOSFALSE);
}
  
	
#ifdef DEBUG

static __inline LONG 
VPrintf (const char * format,LONG *argarray)
{
  register LONG  _res  __asm("d0");
  register struct DosLibrary *a6 __asm("a6") = DOSBase;
  register const char * d1 __asm("d1") = format;
  register LONG *d2 __asm("d2") = argarray;
  __asm __volatile ("jsr a6@(-0x3ba)"
  : "=r" (_res)
  : "r" (a6), "r" (d1), "r" (d2)
  : "a0","a1","d0","d1","d2", "memory");
  return _res;
}

static void debug(const char *fmt, ...)
{
  va_list ap;

  va_start(ap, fmt);
  VPrintf(fmt, (LONG *)ap);
  va_end(ap);
}

#endif
