
/*
 *  CONTROL.C
 *
 *  DNET (c)Copyright 1988, Matthew Dillon, All Rights Reserved.
 *
 *  Main packet control.  Medium level.  Interprets formatted received
 *  packets and sends formatted tx packets.  Uses low level packet
 *  construction and reception routines in PACKET.C
 */

#include "dnet.h"
#include <stdio.h>

void do_rupdate();
void do_reccmd();
void replywindow();
void StartWriteTimeout();

/*
 *  RTO:    read timeout.   Timeout occured while waiting for the rest of
 *	    a packet.  Reset state and restart read.
 *
 *  called from iosink loop, ok if signal mask cleared
 */

void
do_rto(ior)
IOT *ior;
{
    NetStartRead(RecvPacket(NULL, 0));
}

/*
 *  WTO:    Write timeout (unresolved output windows exist).  Resend a CHECK
 *	    command for all unresolved (READY) output windows
 */

void
do_wto(ior)
IOT *ior;
{
    short i;
    PKT *pkt;
    short to = 0;

    if (Restart) {
	WriteRestart();
	return;
    }
    for (i = 0; i < WPUsed; ++i) {
	pkt = WPak[i];
	if (pkt->state == READY) {
	    WriteChk((WPStart + i) & 7);
	    ++to;
	}
    }
    if (to)
	StartWriteTimeout(8);
}

void
do_rnet(ptr, len)
ubyte *ptr;
long len;
{
    long expect;
    long n;

    if (len < 0)
	ptr = NULL;
    expect = RecvPacket(ptr, len);
    n = NetReady();
    if (n > expect)
	expect = n;
    NetStartRead(expect);
}

/*
 *  StartWriteTimeout()
 *
 *  Begins a timeout for written packets.  This timeout normally never
 *  occurs even with data errors (the receiver detects missing windows
 *  and sends the appropriate NAK).
 */

void
StartWriteTimeout(secs)
{
    if (Wto_act) {
	AbortIO((IOR *)&Wto);
	WaitIO((IOR *)&Wto);
	Wto_act = 0;
    }
    if (secs) {
	Wto.tr_time.tv_secs = secs;
	Wto.tr_time.tv_micro= 0;
	SendIO((IOR *)&Wto);
	Wto_act = 1;
    }
}

/*
 *  DO_WUPDATE()
 *
 *  (1) Remove EMPTY windows at head of transmit queue.
 *  (2) Fill up transmit queue with pending requests, if any.
 *  (3) Start timeout for write if did send packet.
 *
 *  CMD 0x20-0x3F   0-31 bytes data
 *	0x40-0x7F   0-N  bytes data (w/ extend byte 0x20-0x7F)
 *	0x80-0xFF   Command.
 *
 *  Note that normal data encoding overhead uses 0x20-0x7F,
 *  thereby allowing 7 bit data to be transmitted with normal
 *  packets instead of expanded (8->6 bits) packets.
 */

void
do_wupdate()
{
    static short XPri;
    static ubyte DBuf[MAXPACKET];
    int maxpktsize;
    IOSTD *ior;
    PKT *pkt;
    long len;
    char wrotePkt = 0;

    while (WPUsed && WPak[0]->state == EMPTY) {
	pkt = WPak[0];
	WPak[0] = WPak[1];
	WPak[1] = WPak[2];
	WPak[2] = WPak[3];
	WPak[3] = pkt;
	--WPUsed;
	++WPStart;
    }
    if (Restart)
	return;

    /*
     *	Fill new packets and send.
     */

    while (WPUsed != 4 && (ior = (IOSTD *)RemHead(&TxList))) {
	long offset = 0;
	if (DDebug)
	    printf("Extract Request: %08lx %ld bytes\n", ior, ior->io_Length);
	{
	    short npri;

	    if (ior->io_Command == SCMD_DATA) {
		ubyte chan = (ulong)ior->io_Unit;
		if (Chan[chan].state == CHAN_CLOSE) {   /* channel closed */
		    ior->io_Error = 1;
		    ReplyMsg((MSG *)ior);
		    continue;
		}
		npri = ior->io_Message.mn_Node.ln_Pri << 2;
	    } else {
		npri = XPri;
	    }
	    if (npri >= XPri)
		XPri = npri;
	    else {
		if (XPri - npri > 100)
		    XPri -= 10;
		else if (XPri - npri > 50)
		    XPri -= 5;
		else
		    --XPri;
	    }
	    maxpktsize = MAXPKT - (XPri - npri);
	    if (maxpktsize < MINPKT)
		maxpktsize = MINPKT;
	}
	pkt = WPak[WPUsed];
	pkt->state = READY;

	for (;;) {
	    if (offset > (maxpktsize-8))        /*  not enough room */
		break;
	    if (ior->io_Command == SCMD_DATA && (ulong)ior->io_Unit != WChan) {
		/*  CSWITCH */
		WChan = (ulong)ior->io_Unit;
		DBuf[offset+0] = 0x80|SCMD_SWITCH|(2<<3);
		DBuf[offset+1] = WChan >> 8;
		DBuf[offset+2] = WChan;
		offset += 3;
	    }
	    len = ior->io_Length - ior->io_Actual;
	    if (ior->io_Command == SCMD_DATA) {     /*  DATA OUT   */
		if (offset + len > (maxpktsize-4))
		    len = (maxpktsize-4) - offset;
		if (len < 0x20) {
		    DBuf[offset] = len + 0x20;
		    ++offset;
		} else {
		    DBuf[offset+0] = 0x40 + len / 96;
		    DBuf[offset+1] = 0x20 + len % 96;
		    offset += 2;
		}
		BytesOut += len;
	    } else {				    /*	COMMAND OUT */
		DBuf[offset] = 0x80|ior->io_Command|(len<<3);
		++offset;
	    }
	    BMov((char *)ior->io_Data + ior->io_Actual, DBuf + offset, len);
	    offset += len;
	    ior->io_Actual += len;
	    if (ior->io_Actual == ior->io_Length) {
		ReplyMsg((MSG *)ior);
		ior = (IOSTD *)RemHead(&TxList);        /*  Next packet     */
		if (ior == NULL)
		    break;
	    }
	}

	BuildDataPacket(pkt, (WPStart + WPUsed) & 7, DBuf, offset);
	WritePacket(pkt);
	wrotePkt = 1;

	if (ior) {
	    ++ior->io_Message.mn_Node.ln_Pri;
	    Enqueue(&TxList, (NODE *)ior);
	    --ior->io_Message.mn_Node.ln_Pri;
	}
	++WPUsed;
	++PacketsOut;
	ResetIdle();
	break;	    /*	One at a time, else would take too long */
    }
    if (wrotePkt)
	StartWriteTimeout(8);
}


void
dumpcheck(ptr)
ubyte *ptr;
{
    short i;
    for (i = 0; i < 8; ++i) {
	if (ptr[i])
	    replywindow(i);
	ptr[i] = 0;
    }
}

void
do_cmd(ctl, buf, bytes)
uword ctl;	/*  usually just 8 bits though	*/
ubyte *buf;
{
    ubyte window = ctl & PKF_SEQUENCE;
    ubyte rwindow;
    static ubyte Chk, Chkwin[8];

    if (ctl == 0xFFFF) {
	if (Chk) {
	    dumpcheck(Chkwin);
	    Chk = 0;
	}
	return;
    }

    if (DDebug)
	printf("RECV-PACKET %02x %4ld bytes\n", ctl, bytes);

    if ((ctl & PKF_MASK) == PKCMD_CHECK) {
	Chkwin[window] = 1;
	Chk = 1;
	return;
    }
    if (Chk) {
	dumpcheck(Chkwin);
	Chk = 0;
    }
    switch(ctl & PKF_MASK) {
    case PKCMD_WRITE:
    case PKCMD_WRITE6:
    case PKCMD_WRITE7:
	rwindow = (window - RPStart) & 7;
	if (rwindow < 4) {
	    BMov(buf, RPak[rwindow]->data, bytes);
	    RPak[rwindow]->buflen = bytes;  /*	dummy	*/
	    RPak[rwindow]->state = READY;
	    do_rupdate();

	    /*
	     *	Check for missing receive packet.  rwindow 1..3 and
	     *	(rwindow - 1) != READY
	     */

	    if (rwindow && RPak[rwindow-1]->state != READY)
		WriteNak((window - 1) & 7);
	}
	replywindow(window);
	break;
    case PKCMD_ACK:
	rwindow = (window - WPStart) & 7;
	if (rwindow < WPUsed)       /*  mark as sent    */
	    WPak[rwindow]->state = EMPTY;
	break;
    case PKCMD_NAK:		    /*	resend		*/
	rwindow = (window - WPStart) & 7;
	if (rwindow < WPUsed) {     /*  resend          */
	    ++PacketsResent;
	    WritePacket(WPak[rwindow]);
	    StartWriteTimeout(8);
	} else {
	    printf("Soft Error: Illegal NAK: %ld %ld %ld %ld\n",
		window, WPStart, rwindow, WPUsed
	    );
	}
	break;
    case PKCMD_RESTART:
    case PKCMD_ACKRSTART:
	if ((ctl & PKF_MASK) == PKCMD_ACKRSTART)
	    Restart = 0;
	do_netreset();
	/* RxPtr? */

	if ((ctl & PKF_MASK) == PKCMD_RESTART) {
	    static ubyte buf[32];

	    strcpy(buf, " ");

	    /*
	     *	note, restart packet may contain only ascii 0x20-0x7F
	     *	00 is definitely out.
	     */

	    WritePacket(BuildRestartAckPacket(buf, strlen(buf)));
	    StartWriteTimeout(5);
	}
	break;
    }
    do_rupdate();
}

/*
 *  Multiplex data onto the low level stream
 *
 *  0x20-0x3F	0-31 bytes of data for current channel
 *  0x80-0xBF	control-command w/0-7 bytes of data
 *  0x40-0x7F	this + extend byte (0x20-0x7F) for long data pkts
 */

void
do_rupdate()
{
    while (RPak[0]->state == READY) {
	PKT *pkt = RPak[0];
	ubyte *ptr = pkt->data;
	uword len;
	uword iolen = pkt->buflen;
	ubyte cmd;

	while (iolen) {
	    cmd = SCMD_DATA;
	    len = ptr[0];
	    ++ptr;
	    --iolen;
	    if (len >= 128) {
		cmd = len & 7;
		len = (len >> 3) & 7;
	    } else {
		if (len < 0x40) {
		    len -= 0x20;
		    if (len < 0) {
			printf("HLP len error1 %d\n", len);
			len = 0;
		    }
		} else {
		    if (*ptr < 0x20)
			printf("HLP len error2 %02x %02x\n", len, *ptr);
		    len = (len - 0x40) * 96 + (*ptr - 0x20);
		    ++ptr;
		    --iolen;
		}
	    }
	    iolen -= len;
	    if (DDebug)
		printf("RECEIVE CMD %2ld ", cmd);
	    do_reccmd(cmd, ptr, len);
	    ptr += len;
	}
	RPak[0] = RPak[1];
	RPak[1] = RPak[2];
	RPak[2] = RPak[3];
	RPak[3] = pkt;
	pkt->state = EMPTY;
	++RPStart;
    }
}

void
do_reccmd(cmd, ptr, len)
int cmd;
ubyte *ptr;
int len;
{
    switch(cmd) {
    case SCMD_DATA:	/*  data for channel	*/
	if (RChan < MAXCHAN && (Chan[RChan].flags & CHANF_ROK)) {
	    IOSTD *ior = AllocMem(sizeof(IOSTD), MEMF_PUBLIC);
	    ior->io_Unit = (struct Unit *)RChan;
	    ior->io_Data = AllocMem(len, MEMF_PUBLIC);
	    ior->io_Length = len;
	    ior->io_Actual = 0;
	    BMov(ptr, ior->io_Data, len);
	    ior->io_Message.mn_Node.ln_Name = (char *)PKT_REQ;
	    ior->io_Command = DNCMD_WRITE;
	    ior->io_Message.mn_ReplyPort = IOSink;
	    PutMsg(Chan[RChan].port, (MSG *)ior);
	    BytesIn += len;
	    ResetIdle();    /*  not idle, have received data    */
	}
	break;
    case SCMD_SWITCH:
	RChan = (ptr[0]<<8)|ptr[1];
	break;
    case SCMD_OPEN:
	{
	    COPEN *cop = (COPEN *)ptr;
	    PORT *port;
	    CACKCMD ack;
	    char buf[32];
	    uword chan = (cop->chanh << 8) | cop->chanl;
	    uword portnum = (cop->porth << 8) | cop->portl;

	    ack.chanh = cop->chanh;
	    ack.chanl = cop->chanl;
	    ack.error = 0;

	    if (chan >= MAXCHAN || Chan[chan].state) {
		ack.error = 33;     /* magic */
		WriteStream(SCMD_ACKCMD, &ack, sizeof(CACKCMD), (uword)-1);
		break;
	    }
	    sprintf(buf, "DNET.PORT.%ld", portnum);
	    if ((port = (PORT *)FindPort(buf)) == NULL) {
		RunServer(portnum);
		if ((port = (PORT *)FindPort(buf)) == NULL) {
		    ack.error = 2;
		    WriteStream(SCMD_ACKCMD, &ack, sizeof(CACKCMD), (uword)-1);
		    break;
		}
	    }
	    /*	ln_Name type of 0 causes reply to go to DNetPort    */
	    WritePort(port, DNCMD_SOPEN, NULL, 0, 0, chan);
	    Chan[chan].state = CHAN_ROPEN;
	    Chan[chan].pri = cop->pri;
	}
	break;
    case SCMD_CLOSE:	/*  receive close   */
	{
	    CCLOSE *clo = (CCLOSE *)ptr;
	    uword chan = (clo->chanh<<8)|clo->chanl;

	    if (DDebug)
		printf("Remote close, chan %d state %d\n", chan, Chan[chan].state);

	    if (chan >= MAXCHAN || Chan[chan].state == CHAN_FREE) {
		break;
	    }
	    Chan[chan].state = CHAN_CLOSE;
	    Chan[chan].flags |= CHANF_RCLOSE;
	    Chan[chan].flags &= ~(CHANF_ROK|CHANF_WOK);
	    if (Chan[chan].flags & CHANF_LCLOSE) {
		if (DDebug)
		    printf("Local already closed, reply %08lx\n", Chan[chan].ior);
		Chan[chan].state = CHAN_FREE;
		ReplyMsg((MSG *)Chan[chan].ior);
		Chan[chan].ior = NULL;
	    } else {	/*  send EOF  */
		if (DDebug)
		    printf("Local not already closed\n");
		WritePort(Chan[chan].port, DNCMD_CLOSE, NULL, 0, PKT_REQ, chan);
	    }
	}
	break;
    case SCMD_ACKCMD:	/*  acknowledge of my open	*/
	{
	    CACKCMD *cack = (CACKCMD *)ptr;
	    uword chan = (cack->chanh<<8)|cack->chanl;
	    if (chan >= MAXCHAN || Chan[chan].state != CHAN_LOPEN) {
		break;
	    }
	    /*
	     *	Channel in use (collision), try again
	     */
	    if (cack->error == 33) {
		uword newchan = alloc_channel();
		COPEN co;
		if (newchan < MAXCHAN) {
		    Chan[newchan] = Chan[chan];
		    Chan[chan].state = CHAN_FREE;
		    Chan[chan].ior = NULL;
		    co.chanh = newchan >> 8;
		    co.chanl = newchan;
		    co.porth = (ulong)Chan[newchan].ior->io_Unit >> 8;
		    co.portl = (ulong)Chan[newchan].ior->io_Unit;
		    co.error = 0;
		    co.pri = Chan[chan].pri;
		    WriteStream(SCMD_OPEN, &co, sizeof(COPEN), chan);
		    break;
		}
	    }
	    if (cack->error) {
		Chan[chan].state = CHAN_FREE;
		Chan[chan].ior->io_Error = cack->error;
		ReplyMsg((MSG *)Chan[chan].ior);
		Chan[chan].ior = NULL;
	    } else {
		Chan[chan].state = CHAN_OPEN;
		Chan[chan].ior->io_Error = 0;
		Chan[chan].ior->io_Unit = (struct Unit *)chan;
		Chan[chan].flags = CHANF_ROK|CHANF_WOK;
		ReplyMsg((MSG *)Chan[chan].ior);
		Chan[chan].ior = NULL;
	    }
	}
	break;
    case SCMD_EOFCMD:	/*  EOF on channel		*/
	{
	    CEOFCMD *eof = (CEOFCMD *)ptr;
	    uword chan = (eof->chanh<<8)|eof->chanl;

	    if (chan < MAXCHAN && Chan[chan].state == CHAN_OPEN) {
		Chan[chan].flags &= ~eof->flags;
		if (eof->flags & CHANF_ROK)
		    WritePort(Chan[chan].port, DNCMD_EOF, NULL, 0, PKT_REQ, chan);
	    } else {
		printf("SCMD_EOFCMD: Error chan %ld  state %ld\n",
		    chan, Chan[chan].state
		);
	    }
	}
	break;
    case SCMD_IOCTL:
	{
	    CIOCTL *cio = (CIOCTL *)ptr;
	    uword chan = (cio->chanh<<8)|cio->chanl;

	    if (Chan[chan].state == CHAN_OPEN)
		WritePort(Chan[chan].port, DNCMD_IOCTL, cio, sizeof(*cio), PKT_REQ, chan);
	}
	break;
    default:
	if (DDebug)
	    printf("BAD SCMD, %ld\n", cmd);
	break;
    }
}

void
replywindow(window)
int window;
{
    ubyte rwindow = (window - RPStart) & 7;

    if (rwindow >= 4 || RPak[rwindow]->state == READY) {  /* data ready */
	WriteAck(window);
	++PacketsIn;
    } else {
	WriteNak(window);
	++PacketsNakd;
    }
}

