/*
 * tcp.c --- internetwork streams for GNUEmacs.
 *
 * Author: Tomi Ollila <too@cs.hut.fi>
 *
 * Copyright (c) 1993 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: Thu Aug 12 14:21:59 1993 jraja
 *
 * $Id: tcp.c,v 1.4 1993/08/12 11:22:14 jraja Exp $
 *
 * HISTORY
 * $Log: tcp.c,v $
 * Revision 1.4  1993/08/12  11:22:14  jraja
 * Changed email-address, updated version string...
 *
 * Revision 1.3  1993/08/12  11:19:38  jraja
 * Fixed version string.
 *
 * Revision 1.2  1993/08/11  08:26:08  too
 * Added version string
 *
 * Revision 1.1  1993/06/15  18:22:40  too
 * Tcp package for GNUEmacs
 *
 *
 * NOTE: this program has experimental stuff inside, like some self written
 * c -library functions and DOS- and SocketBase as first argument to
 * Dos and Socket library functions, respectively. This is no way recommended
 * programming style, but demonstrates one way of doing re-entrant code and
 * a way of writing c programs in amiga without using startup module
 * (No workbench support in startup code).
 *
 * compile this: gcc -O3 -Wall -Wno-uninitialized -nostdlib -o tcp_AmiTCP tcp.c
 * ( add -DBUGGY_COMPILER w/ GCCs up to 2.3.3 (at least) )
 * ( with -DMEMORY_LOW compilation uses much less memory )
 */

#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 2

#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 <workbench/startup.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 <netinet/in.h>
#include <netdb.h>

#define va_list void *
#ifdef MEMORY_LOW

#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) */
#else

#include <inline/exec.h>

#define BASE_EXT_DECL
#define BASE_PAR_DECL  struct DosLibrary * DOSBase,
#define BASE_PAR_DECL0 struct DosLibrary * DOSBase
#include <inline/dos.h>

#define BASE_EXT_DECL
#define BASE_PAR_DECL  struct Library * SocketBase,
#define BASE_PAR_DECL0 struct Library * SocketBase
#include <inline/socket.h>

#endif
#undef va_list

asm(".text; jmp pc@(_start-.+2);");

const static char version[] = "$VER: tcp_AmiTCP 2.0 (13.8.93)";

#ifdef BUGGY_COMPILER
#define WaitSelect WaitSelect2
static int WaitSelect(struct Library *, int, fd_set *,
		      struct timeval *, ULONG *);
#define SOME_NULLS
#else
#define SOME_NULLS NULL, NULL,
#endif

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

struct CmdLine {
  int	 cl_DosCmdLen;
  char * cl_DosCmdBuf;
  int	 cl_len;
  char * cl_buf;
};


#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)

int strncmp(char *, char *, int);
int atoi(char *);

inline void bcopy(char * a, char * b, int n)
{
  while (n-- > 0)
    *(b++) = *(a++);
}
inline void bzero(char * a, int n)
{
  while (n-- > 0)
    *(a++) = '\0';
}

static int getHostAndService(int dosCmdLen, char * dosCmdBuf,
			     char ** host, char ** service);
static int getNextCmdArg(register struct CmdLine * cmdline);

int tcp(struct DosLibrary *, char *, char *);

struct ExecBase * SysBase = NULL;

int RAF2(start,
	 int,	 dosCmdLen,	d0,
	 char *, dosCmdBuf,	a0)
#if 0  
{
#endif

  struct DosLibrary * DOSBase = NULL;
  struct Process * me; /* = (struct Process *)SysBase->ThisTask; */
  /*
   * host and service parsed from command line
   */
  char * host;
  char * service = NULL;
  /*
   * return value to dos
   */
  int retval = 20;

  if (SysBase == NULL)
    SysBase = *(struct ExecBase **)4;

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

    WaitPort(&me->pr_MsgPort);
    WBenchMsg = (struct WBStartup *)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
     * I figure Workbench does Permit() after this returns.
     */
    Forbid();
    ReplyMsg((struct Message *)WBenchMsg);
    goto exit;
  }
  if ((DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36)) == NULL)
    goto exit;

  if (getHostAndService(dosCmdLen, dosCmdBuf, &host, &service)
      == FALSE) {
#define USAGESTRING "Usage: tcp -h HOST [-s SERVICE]\n"
    Write(DOSBase, Output(DOSBase), USAGESTRING, sizeof USAGESTRING - 1);
    goto exit;
  }
  retval = tcp(DOSBase, host, service);

exit:
  if (DOSBase)
    CloseLibrary((struct Library *)DOSBase);
  return retval;
}

static int getHostAndService(int dosCmdLen, char * dosCmdBuf,
			     char ** host, char ** service)
  
{
  int hostlen;
  int servicelen = 0;
  struct CmdLine cmdline;
  
  cmdline.cl_DosCmdLen = dosCmdLen;
  cmdline.cl_DosCmdBuf = dosCmdBuf;
  
  if (getNextCmdArg(&cmdline) == FALSE ||
      strncmp(cmdline.cl_buf, "-h", cmdline.cl_len) != 0)
    return FALSE;
  if (getNextCmdArg(&cmdline) == FALSE)
    return FALSE;
  *host = cmdline.cl_buf;
  hostlen = cmdline.cl_len;
  (*host)[hostlen] = '\0';
  
  if (getNextCmdArg(&cmdline) == TRUE) {
    if (strncmp(cmdline.cl_buf, "-s", cmdline.cl_len) != 0)
      return FALSE;
    if (getNextCmdArg(&cmdline) == FALSE)
      return FALSE;
    *service = cmdline.cl_buf;
    servicelen = cmdline.cl_len;
    (*service)[servicelen] = '\0';
  }
  return TRUE;
}

/*
 * getNextCmdArg is a simplified form of standard argument parsing
 * but good enough here.
 */
static int getNextCmdArg(register struct CmdLine * cmdline)
{
  enum {CLS_SKIPSPACES, CLS_SEARCHEND} state = CLS_SKIPSPACES;
  int retval = FALSE;
  int startlen; 
  char * startbuf;
  
  while((! retval) && (cmdline->cl_DosCmdLen-- > 0)) {
    switch (state) {

    case CLS_SKIPSPACES:
      if (*cmdline->cl_DosCmdBuf == ' ' || *cmdline->cl_DosCmdBuf == '"')
	break;
      startlen = cmdline->cl_DosCmdLen;
      startbuf = cmdline->cl_DosCmdBuf;
      state = CLS_SEARCHEND;
      break;
      
    case CLS_SEARCHEND:
      if (*cmdline->cl_DosCmdBuf != ' ' && *cmdline->cl_DosCmdBuf != '"' &&
	  *cmdline->cl_DosCmdBuf != '\n')
	break;
      retval = TRUE;
      cmdline->cl_len = startlen - cmdline->cl_DosCmdLen;
      cmdline->cl_buf = startbuf;
      break;
    }

    cmdline->cl_DosCmdBuf++;
  }

  return retval;
}

#define NNTP "119"

int tcp(struct DosLibrary * DOSBase,
	char * hostname,
	char * service)
{
  struct Library * SocketBase = NULL;
  struct FileHandle *fh_in = BADDR(Input(DOSBase));
  BPTR fd_out = Output(DOSBase);

  fd_set readfds;
  long sd;
  int n;
  struct sockaddr_in name = { 0 };
  struct hostent * host;
  struct timeval timeout = { 20000, 0 };
  
  ULONG mask, emask;
  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 (fh_in == NULL || fd_out == 0)
    goto Return;
  if ((pm = (struct PublicMemory *)AllocMem(sizeof (struct PublicMemory),
					    MEMF_PUBLIC|MEMF_CLEAR))
      == NULL)
    goto Return;

  if ((SocketBase = OpenLibrary(SOCLIBNAME, SOCLIBVERS)) == NULL) 
    goto Return;

  (void)SetDTableSize(SocketBase, FD_SETSIZE);
  /*
   * Initialize MsgPort
   */
  if ((n = AllocSignal(-1)) == -1)
    goto Return;
  PM(mp).mp_SigBit = n;
  emask = 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 */

  /*
   * Initialize DosPacket (to obtain automatically filled character buffer
   * 			   after data has arrived from emacs)
   */
  PM(dp).dp_Link = &PM(dpMsg);
  PM(dp).dp_Port = &PM(mp);
  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.
   */
  PM(dpMsg).mn_Node.ln_Succ = PM(dpMsg).mn_Node.ln_Pred = 0;
  PM(dpMsg).mn_Node.ln_Name = (char *)&PM(dp);
  PM(dpMsg).mn_Node.ln_Type = NT_MESSAGE;
  PM(dpMsg).mn_Node.ln_Pri = 0;
  PM(dpMsg).mn_ReplyPort = NULL;
  PM(dpMsg).mn_Length = sizeof (struct Message) + sizeof (struct DosPacket);
  if ((host = gethostbyname(SocketBase, hostname)) == NULL)
    goto Return;

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

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

    name.sin_port = serv->s_port;
  }
  
  if ((sd = socket(SocketBase, AF_INET, SOCK_STREAM, 0)) < 0)
    goto Return;
    
  if (connect(SocketBase, sd, (struct sockaddr *)&name, sizeof name) < 0)
    goto Return;

/* Connection established ... */

  FD_ZERO(&readfds);
  SendPkt(DOSBase, &PM(dp), fh_in->fh_Type, &PM(mp));

  for(;;) {
    FD_SET(sd, &readfds);
    mask = emask;

    if (WaitSelect(SocketBase, sd + 1, &readfds, SOME_NULLS &timeout,
		   &mask) < 0)
      break;

    debug(DOSBase, "%lx %lx %lx\n", mask, emask, SIGBREAKF_CTRL_C);

    if (FD_ISSET(sd, &readfds)) {
      n = recv(SocketBase, sd, sbuf, sizeof sbuf, 0);
      if (n <= 0) {
	if (n == 0)
	  break;
	else	/* there could be different handling here */
	  break;
      }
      debug(DOSBase, "Got %ld chars from socket\n", n);
      if (Write(DOSBase, fd_out, sbuf, n) < 0)
	break; /* No further output possible if write fails */
    }
    
    if (mask & emask) {
      if (!GetMsg(&PM(mp)))
	continue;
      debug(DOSBase, "Got %ld chars from console\n", PM(dp).dp_Res1);
      if (PM(dp).dp_Res1 <= 0)
	goto Return;
      if (send(SocketBase, sd, PM(ebuf), PM(dp).dp_Res1, 0) < 0)
	goto Return;
      PM(dp).dp_Port = &PM(mp);
      PM(dpMsg).mn_Node.ln_Type = NT_MESSAGE;
      
      SendPkt(DOSBase, &PM(dp), fh_in->fh_Type, &PM(mp));
    }
  }

  /*
   * Wait until DosPacket has returned (AbortPacket & WaitPacket
   * can not be used here (or anywhere else) :( )
   */
  /* AbortPtk(DOSBase, &PM(mp), &PM(dp)); */
  /* WaitPkt(DOSBase); */
  while(!GetMsg(&PM(mp)))
    Wait(emask);
 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;
}

/*
 * WaitSelect as non-inline if compiler can't handle this many register args
 */
#ifdef BUGGY_COMPILER
static int WaitSelect(struct Library *	SocketBase,
		      int 		nfds,
		      fd_set *		readfds,
		      struct timeval *	timeout,
		      ULONG *		sigmp)
{
#undef WaitSelect
  return WaitSelect(SocketBase, nfds, readfds, NULL, NULL, timeout, sigmp);
}  
#endif BUGGY_COMPILER

/*
 * standard c library functions that are needed here. (some are written
 * at the beginning as inline functions.)
 */

int strncmp(char * a, char * b, int n)
{
  while (--n > 0) { /* don't check last one */
    if ((*a == '\0') || (*a != *b))
      break;
    a++;
    b++;
  }
  return (*b - *a);
}

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

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

#ifdef DEBUG

struct stuffchar {
  char * start;
  char * end;
};

static void RAF2(stuffchar,
		 char, 			ch, d0,
		 struct stuffchar *, 	sc, a3)
#if 0
{
#endif
  if (sc->start < sc->end && (*(sc->start) = ch))
    sc->start++;
}

void debug(struct DosLibrary * DOSBase, const char *fmt, ...)
{
  va_list ap;
  struct stuffchar sc;

  char buf[256];
  sc.start = buf;
  sc.end = buf + 256;

  va_start(ap, fmt);
  RawDoFmt((STRPTR)fmt, ap, stuffchar, &sc);
  va_end(ap);

  *(sc.start) = '\0';
  Write(DOSBase, Output(DOSBase), buf, (sc.start - buf));
}

#endif
