/*
 *  SUBS.C
 *
 *  DNET (c)Copyright 1988, Matthew Dillon, All Rights Reserved.
 *
 */

#include "dnet.h"
#include <stdio.h>
#ifdef LATTICE
#include <dos.h>
#endif

#ifndef LATTICE
char *FakeArgs;
long  FakeLen;
long  FakeGo;
long  FakeCLI;

#ifndef __GNUC__
extern char *malloc();
#else
extern void * malloc();
#endif /* __GNUC__ */
#endif /* !LATTICE */

void dneterror();

typedef struct CommandLineInterface CLI;

/*
 *  Add the high-level command to the queue of commands to be sent to
 *  the remote DNET.
 */

void
WriteStream(sdcmd, buf, len, chan)
int sdcmd;
void *buf;
int len;
uword chan;
{
    IOSTD *ior = AllocMem(sizeof(IOSTD), MEMF_PUBLIC);

    if (DDebug)
	printf("WriteStream: cmd %ld  buflen %ld  chan %ld\n", sdcmd, len, chan);
    ior->io_Message.mn_Node.ln_Name = (char *)PKT_REQ;
    ior->io_Message.mn_ReplyPort = IOSink;
    ior->io_Data = (APTR)AllocMem(len, MEMF_PUBLIC);
    ior->io_Length = len;
    ior->io_Actual = 0;
    ior->io_Command = sdcmd;
    ior->io_Error = 0;
    ior->io_Message.mn_Node.ln_Pri = (chan > MAXCHAN) ? 126 : Chan[chan].pri;
    BMov(buf, ior->io_Data, len);
    Enqueue(&TxList, (NODE *)ior);
    do_wupdate();
}

/*
 *  Send a packet to a port.  Used to send data/eof/close to channel
 *  ports, and to relay open requests to server ports.
 */

void
WritePort(port, scmd, buf, len, rettype, unit)
PORT *port;
int scmd;
void *buf;
int len;
int rettype;
int unit;
{
    IOSTD *ior = AllocMem(sizeof(IOSTD), MEMF_PUBLIC);

    if (DDebug)
	printf("WritePort: cmd %ld  buflen %ld  rt %ld unit %ld\n", scmd, len, rettype, unit);
    ior->io_Message.mn_Node.ln_Name = (char *)rettype;
    ior->io_Message.mn_ReplyPort = (rettype) ? IOSink : DNetPort;
    ior->io_Unit = (struct Unit *)unit;
    ior->io_Command = scmd;
    ior->io_Error = 0;
    ior->io_Data  = NULL;
    ior->io_Length = 0;
    ior->io_Actual = 0;
    if (buf) {
	ior->io_Data = (APTR)AllocMem(len, MEMF_PUBLIC);
	ior->io_Length = len;
	BMov(buf, ior->io_Data, len);
    }
    PutMsg(port, (MSG *)ior);
    if (rettype == PKT_REQ)
	++NumCon;
}

/*
 *  ALLOC_CHANNEL()
 *
 *	Allocate a channel.  Starting at a random point, find the first
 *	free channel.  Return -1 if no free channels found.
 */

alloc_channel()
{
    static ulong ran = 13;
    long stamp[3];
    uword i;

    DateStamp(stamp);
    ran = ((ran * 13) + 1) ^ (ran >> 9) + stamp[0] + stamp[1] + stamp[2];
    for (i = ran % MAXCHAN; i < MAXCHAN; ++i) {
	if (Chan[i].state == 0) {
	    return((int)i);
	}
    }
    for (i = ran % MAXCHAN; i != 0xFFFF; --i) {  /*  Fixed 28 September 1988 */
	if (Chan[i].state == 0) {
	    return((int)i);
	}
    }
    return(-1);
}


/*
 *  Get next node in a linked list.  If a pointer to a list base
 *  is passed, gets the first node in the linked list.	Returns
 *  NULL if no further nodes in the list.
 */

GetNext(node)
NODE *node;
{
    NODE *next;

    if(node == NULL) return NULL;
    
    next = node->ln_Succ;
    if (*(long *)next)
	return((long)next);
    return(NULL);
}


/*
 *  CHKBUF
 *
 *  Generate a two-byte checksum for data.  Uses a very simple, but
 *  effective algorithm.
 */

chkbuf(buf, bytes)
ubyte *buf;
uword bytes;
{
    uword i;
    ubyte c1,c2;

    for (i = c1 = c2 = 0; i < bytes; ++i) {
	c1 += buf[i];
	c2 += c1;
    }
    c1 = -(c1 + c2);
    return((c1<<8)|c2);
}

void
TimerOpen(req, sink)
IOT *req;
PORT *sink;
{
    if (OpenDevice("timer.device", UNIT_VBLANK, (IOR *)req, 0))
	dneterror("timer.device");
    req->tr_node.io_Message.mn_ReplyPort = sink;
    req->tr_node.io_Command = TR_ADDREQUEST;
}

void
TimerClose(req)
IOT *req;
{
    CloseDevice((IOR *)req);
    req->tr_node.io_Device = NULL;
}



/*
 *  RUNSERVER
 *
 *	Search the file [S:]DNET.SERVERS for the specified server and
 *	attempt to LoadSeg()/CreateProc() it.  The port number must be
 *	non-zero (allows commening out lines in DNET.SERVERS), and
 *	regardless of whether the server is able to DListen(), it must
 *	perform a small handshake sequence with DNET.
 */


static MLIST SvList;

void
InitServers()
{
    NewList(&SvList);
}

void
RunServer(portnum)
uword portnum;
{
    FILE *fi;
    char buf[128];
    char s1[64], s2[64];
    long v1;
    long seg;

    if (!portnum) return;

    /* find the program for the portnum to be run */
    if ((fi = fopen("dnet.servers", "r")) == NULL)
	fi = fopen("s:dnet.servers", "r");
    if (fi) {
	while (fgets(buf, 128, fi)) {
	    if (buf[0] == ';') continue; /* save a sscanf call */
	    if (sscanf(buf, "%ld %s %s", &v1, s1, s2) == 3 && v1 == portnum)
		goto success;
	}
	fclose(fi);
    }
    return;
success:
    /* at this point, s1= pointer to progran name, s2 is its homedir */

    if (fi) 
    	fclose(fi);

    if (seg = (long)LoadSeg(s1)) {  /* prepare program to be started */
	PNODE	*pnode;
	PROC	*myproc;
	PROC	*proc;
	PORT    *procport;
	char	*name;
	BSTR	oldclicmd;
	CLI	*cli;
	char	*fakestr;

	MSG	msg;

	printf("Addr: %08lx\n", seg); /* debugging info */

	myproc = (PROC *)FindTask(NULL);

	pnode = AllocMem(sizeof(PNODE), MEMF_PUBLIC);

	if(pnode == NULL) {
		printf("Out of memory in RunServer!");
		return;
	}
	sprintf(pnode->name, "DNET.SERVER.%ld", portnum);
	pnode->seg = seg;


	/* cli = myproc->pr_CLI; */

	/* create the process */
	printf("Starting Server: %s %s\n", s1, pnode->name);
	if ( procport = CreateProc(pnode->name, 0, seg, 4096)) {
	    /* CreateProc returns the process's message Port! */
	    /* convert prot to actually point to process structure */
	    proc = (PROC *)((char *)procport - OFFSET(proc, pr_MsgPort));

	    /* Hmm. interesting note in RKM:Libraries p.434:
	    ** "...Process.pr_MsgPort. This message port is only for use by 
	    **	Workbench and the DOS library itself. _No_application_should 
	    **	use_this_port_for_its_own_use!_"
	    **    Maybe there's a legitimage excuse for this here.
	    **  Like this process is intercepting startup messages just like
	    **  workbench does.
	    **  Otherwise, Matt, you're a bozo.
	    */

	    /* Pass message to the new process */
	    msg.mn_ReplyPort = &myproc->pr_MsgPort;
	    msg.mn_Node.ln_Name = s2;
	    PutMsg( procport, &msg);        /*  startup handshake */

	    printf("Waiting for startup message from %s (%s)\n", s1, pnode->name);
	    WaitPort(&myproc->pr_MsgPort);
	    (void)GetMsg(&myproc->pr_MsgPort);


	    printf("Got startup message..\n");
	    AddTail(&SvList, pnode);   /* add to list of processes, so it can be
	    			       ** killed later */
	} else {
	    printf("Unable to start server: %s %s\n", s1, pnode->name);
	    FreeMem(pnode, sizeof(PNODE));
	}

	/* cli->cli_CommandName = oldclicmd; */
	/* deal with keeping CLI numbers for processes straight later */

	free(fakestr);
    } else {
	printf("Server not found: %s\n", s1);
    }
}

void
DeleteServers()
{
    PNODE *pnode;
    PROC *proc;
    while (pnode = (PNODE *)RemHead(&SvList)) {
	Forbid();
	while (proc = (PROC *)FindTask(pnode->name)) {
	    Signal(proc, SIGBREAKF_CTRL_C);
	    Permit();
	    printf("Waiting for server %s to exit\n", pnode->name);
	    Forbid();
	}
	Permit();
	UnLoadSeg(pnode->seg);
	FreeMem(pnode, sizeof(PNODE));
    }
}



/*
 *  This cleans up as best as possible, but if there are still ACTIVE
 *  servers or any CLIENTS, this call will not completely get rid of
 *  them...
 *
 *  Note that this call will KILL any servers automatically started up
 *  by DNET and then UnLoadSeg() them.  If the server has already exited,
 *  it simply unloads the server's segment.
 */

void
dneterror(str)
char *str;
{
    short i;
    if (str)
	puts(str);
    if (DNetPort) {
	IOSTD *ior;
	Forbid();
	while ((ior = (IOSTD *)RemHead(&TxList)) || (ior = (IOSTD *)GetMsg(DNetPort))) {
	    ior->io_Error = -1;
	    ReplyMsg((MSG *)ior);
	}
	DeletePort(DNetPort);
	Permit();
	DNetPort = NULL;
    }
    if (Wto.tr_node.io_Device) {
	if (Wto_act) {
	    AbortIO((IOR *)&Wto);
	    WaitIO((IOR *)&Wto);
	    Wto_act = 0;
	}
	TimerClose(&Wto);
    }
    if (Rto.tr_node.io_Device) {
	if (Rto_act) {
	    AbortIO((IOR *)&Rto);
	    WaitIO((IOR *)&Rto);
	    Rto_act = 0;
	}
	TimerClose(&Rto);
    }
    if (Cto.tr_node.io_Device) {
	if (Cto_act) {
	    AbortIO((IOR *)&Cto);
	    WaitIO((IOR *)&Cto);
	    Cto_act = 0;
	}
	TimerClose(&Cto);
    }

    NetClose();
    if (IOSink)
	DeletePort(IOSink);
    IOSink = NULL;
    for (i = 0; i < MAXCHAN; ++i) {
	if (Chan[i].state != CHAN_FREE && Chan[i].ior) {
	    Chan[i].ior->io_Error = -1;
	    Chan[i].state = CHAN_FREE;
	    ReplyMsg((MSG *)Chan[i].ior);
	}
    }
    DeleteServers();

    /*
    if (IPCPort)
	CloseIPC(IPCPort);
    if (DResBase)
	CloseLibrary(DResBase);
    */
    exit(1);
}

void
fixsignal(port)
PORT *port;
{
    if ((long)port->mp_MsgList.lh_Head != (long)&port->mp_MsgList.lh_Tail)
	Signal(port->mp_SigTask, 1 << port->mp_SigBit);
}

void
SetTimeouts(baud)
{
    WTimeoutVal = ((MAXPKT * 2) * 1000 / (baud / 10 + 1));
    RTimeoutVal = ((MAXPKT + 50) * 1000 / (baud / 10 + 1));
    WTimeoutVal *= 1000;
    RTimeoutVal *= 1000;
}

