/* 
 * fcfs.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 */

#include <sys/types.h>
#include "x_stdio.h"
#include "xkernel.h"
#include "eth.h"
#include "eth_i.h"
#include "ip.h"
#include "ip_i.h"
#include "sim_i.h"
#include "sim_router.h"
#include <stdio.h>

extern u_long GlobalPacketsDropped ;
u_long GlobalBytesDropped ;

/* LSB: Note - when arrayNum is too big, could also make timeInc *= 2, and
 *             modify previous stored values!
 */

void
sim_FCFS_init(ROUTER_STATE *rs, int argc, char **argv)
{
  rs->inFun = sim_FCFS_input;
  rs->cbFun = sim_FCFS_callback;
}

static void
sim_FCFS_send(Event ev, void *arg)
{
  CallBack	*cbp=(CallBack *)arg;
  int		outPort=(int)cbp->arg1;
  ROUTER_STATE	*rs=(ROUTER_STATE *)cbp->state;
  SimPacket	*pkt;
  Net		*np;

  pkt = rs->outQueueBeg[outPort];
  np = pkt->net;

  if (np->control(np, OP_ISBUSY, NULL, NULL, NULL, NULL)) {
    if (rs->traceType == TRACE_TYPE_DETAILED) {
      DO_TRACE_L(rs->ts, TRACE_EVENT_SEND_BUSY, rs->outQueueCnt[outPort], 
		 pkt->id, outPort);
    }
    np->control(np, OP_NOTBUSY_CB, rs->cbFun, (void *)rs, 
		(void *)outPort, NULL);
  }
  else {
    if (rs->traceType == TRACE_TYPE_DETAILED) {    
      DO_TRACE_L(rs->ts, TRACE_EVENT_SEND_OKAY, pkt->len, pkt->id, outPort);
    }
    np->control(np, OP_SEND, rs->cbFun, (void *)rs, (void *)outPort, 
		(void *)pkt);
  }
  freeCallBack(cbp);
}


void
sim_FCFS_callback(Event ev, void *arg)
{
  CallBack	*cbp=(CallBack *)arg;
  int		outPort=(int)cbp->arg1;
  int		rvar;
  long	 	t; /* VENKAT: Changed this from int */
  ROUTER_STATE 	*rs=(ROUTER_STATE *)cbp->state;
  SimPacket	*pkt=(SimPacket *)cbp->arg2;
  ITrace	*itp;
  MaxMinTrace	*mtp;

  /* --- May want to free callback on top, so it can be reused (still in 
     cache) */

  xsimDbg(SL2_FLAG, printf("[FCFS cb] host:%d, op:%d pkt:%ld\n", cbp->host, cbp->op,pkt->id));
  /*printf("[FCFS cb] host:%d, op:%d pkt:%ld\n", cbp->host, cbp->op,pkt->id);*/
  if (rs->traceType == TRACE_TYPE_DETAILED) {
    DO_TRACE_WT(rs->ts, TRACE_EVENT_CB, cbp->op, cbp->rv);
  }
  if (cbp->op == OP_SENT_CB  ||  cbp->op == OP_RESEND_CB) {
    if (rs->traceType == TRACE_TYPE_DETAILED) {
      DO_TRACE_L(rs->ts, TRACE_EVENT_DATA, outPort, pkt->id, 0);
    }
  }
  else {
    if (rs->traceType == TRACE_TYPE_DETAILED) {
      DO_TRACE_L(rs->ts, TRACE_EVENT_DATA, outPort, 0, 0);
    }
  }
  switch (cbp->op) {

  case OP_SENT_CB: 
    if (cbp->rv == RV_SUCCEED) {
      /* --- sent successfully */
      rs->outQueueCnt[outPort]--;
      rs->outQueueLen[outPort] -= pkt->len;

      t = traceGetTime();
      if ((itp = rs->traceOut[outPort] )!= NULL) 
	TRACE_INOUT(itp, pkt->bwLen, t);
      if ((mtp=rs->traceQueue[outPort]) != NULL) 
	TRACE_QUEUE(mtp, rs->outQueueCnt[outPort], t);
      if ((mtp=rs->traceQueueLen[outPort]) != NULL) 
	TRACE_QUEUE(mtp, rs->outQueueLen[outPort]/1024, t);
	
      if (rs->traceType == TRACE_TYPE_DETAILED) {
	DO_TRACE_L(rs->ts, TRACE_EVENT_SENT_OKAY, pkt->len, 
		   rs->outQueueCnt[outPort], outPort);
      }
      xsimDbg(SL2_FLAG, printf("[FCFS cb] %s op:SENT_CB succeeded, q:%d\n", 
			       rs->name, 
			       rs->outQueueCnt[outPort]));
      rs->outQueueBeg[outPort] = rs->outQueueBeg[outPort]->next;
      rs->outRetryCnt[outPort] = 1;
      rs->outRetryDelay[outPort] = 1;
      if (rs->outQueueBeg[outPort] == NULL) {
	rs->outQueueEnd[outPort] = NULL;
	freeCallBack(cbp);
      }
      else 
	sim_FCFS_send(NULL, (void *)cbp);
    }
    else {
      /* --- send failed, try again */
      /* --- Should I do exp backoff? */
      xsimDbg(SPC_FLAG, printf("[FCFS cb] %s op:SENT_CB failed, q:%d\n", 
			       rs->name, 
			       rs->outQueueCnt[outPort]));
      if (++(rs->outRetryCnt[outPort]) > 15) {
	GlobalPacketsDropped++;
	GlobalBytesDropped += pkt->len;
	printf("WARNING in FCFS router, 15 retries already, dropping pkt\n");
	if (rs->traceType == TRACE_TYPE_DETAILED) {
	  DO_TRACE_L(rs->ts, TRACE_EVENT_DROP, pkt->len, 
		     rs->outQueueCnt[outPort], outPort);
	}
	else {
	  DO_TRACE_WT(rs->ts, TRACE_EVENT_DROP, pkt->len, outPort);
	}
	rs->outQueueCnt[outPort]--;
	/* --- Need to destroy packet */
	msgDestroy(&pkt->msg); 
	rs->outQueueBeg[outPort] = rs->outQueueBeg[outPort]->next;
	freeSimPacket(pkt); 
	rs->outRetryCnt[outPort] = 1;
	rs->outRetryDelay[outPort] = 1;
	if (rs->outQueueBeg[outPort] == NULL) {
	  rs->outQueueEnd[outPort] = NULL;
	  freeCallBack(cbp);
	}
	else 
	  sim_FCFS_send(NULL, (void *)cbp);
      }
      else {
	rs->outRetryDelay[outPort] *= 2;
	rvar = lrand48() % rs->outRetryDelay[outPort] + 1;
	xsimDbg(SPC_FLAG, printf("[FCFS cb] %s delaying for:%d slots\n",
				 rs->name, rvar));
	evDetach(evSchedule(sim_FCFS_send, (void *)cbp, rvar*51));
	if (rs->traceType == TRACE_TYPE_DETAILED) {
	  DO_TRACE_L(rs->ts, TRACE_EVENT_BACKOFF, rvar*51, 
		     rs->outQueueCnt[outPort], 0);
	  DO_TRACE_L(rs->ts, TRACE_EVENT_DATA, rs->outRetryCnt[outPort], 
		     rs->outRetryDelay[outPort], 0);
	}
      }
    }
    break;

  case OP_NOTBUSY_CB:
    xsimDbg(SL2_FLAG, printf("[FCFS cb] %s op:NOTBUSY_CB, q:%d\n", 
			     rs->name, 
			     rs->outQueueCnt[outPort]));
    if ((pkt = rs->outQueueBeg[outPort]) != NULL) 
      sim_FCFS_send(NULL, (void *)cbp);
    else
      freeCallBack(cbp);
    break;

  default:
    printf("ERROR: Unknown callback (op: %d) in sim_FCFS_callback\n", cbp->op);
    freeCallBack(cbp);
  }
}  

void
sim_FCFS_input(ROUTER_STATE *rs, SimPacket *pkt, int inPort)
{
  int         k, outPort, sendit=1;
  long 		t; /* VENKAT: Changed from int */
  IPheader    ihdr;
  tcpHDR      tcpHdr;
  Simhost     nextHop;
  Net         *np;
  ITrace      *itp;
  MaxMinTrace *mtp;
  char        *buf;

  xsimDbg(SL2_FLAG, 
	  printf("> FCFS_input %s, for packet %ld at port %d\n", 
		 rs->name, pkt->id, inPort));

  if (rs->traceType == TRACE_TYPE_DETAILED) {
    DO_TRACE_WT(rs->ts, TRACE_EVENT_IN, pkt->len, inPort);
    DO_TRACE_L(rs->ts, TRACE_EVENT_DATA, 0, pkt->id, 0);
  }
  t = traceGetTime();
  if ((itp = rs->traceIn[inPort] )!= NULL) 
    TRACE_INOUT(itp, pkt->bwLen, t);
  
  if (rs->inPortNets[inPort]->type == TYPE_ETH || pkt->type == TYPE_ETH) {
    buf = msgPop(&pkt->msg, sizeof(ETHhdr));
    xAssert(buf);
    ethdMsgLoad(&pkt->ehdr, buf, sizeof(ETHhdr), 0);

    buf = msgPeek(&pkt->msg, sizeof(IPheader));
    xAssert(buf);
    ipStdHdrLoad(&ihdr, (void *)buf, sizeof(IPheader), 0);

    pkt->type = TYPE_IP;
    pkt->dst.type = TYPE_IP;
    pkt->dst.addr.ip = ihdr.dest;
    pkt->len -= sizeof(ETHhdr);
    pkt->prot = ihdr.prot;

/*    buf = msgPush(&pkt->msg, sizeof(IPheader));
    xAssert(buf);
    ipHdrStore(&ihdr, buf, sizeof(IPheader), 0);
*/    
    xsimDbg(SL2_FLAG, 
	    printf(">    input packet is type ETH, final dest: [%s]\n", 
		   sim_simaddr2str(&pkt->dst)));
  }

  if (sim_getNextHop(rs, &pkt->dst.addr.ip, &outPort, &nextHop)) {
    if (rs->traceType == TRACE_TYPE_DETAILED) {
      DO_TRACE_L(rs->ts, TRACE_EVENT_ERROR, pkt->len, 0, inPort);
    }
    else {
      DO_TRACE_WT(rs->ts, TRACE_EVENT_ERROR, pkt->len, inPort);
    }
    printf("getNextHop error in FCFS_input\n");
    msgDestroy(&pkt->msg);
    freeSimPacket(pkt);
  }

  if ((itp = rs->traceInOut[outPort] )!= NULL) 
    TRACE_INOUT(itp, pkt->bwLen, t);
  
  /* LSB: option to trace the headers of packets comming in! (ip and tcp) */
  if (rs->traceType == TRACE_TYPE_PKT || rs->traceType == TRACE_TYPE_DETAILED) {
    XTime xtnow;
    long now;

    xGetTime(&xtnow);
    now = xtnow.sec*1000 + xtnow.usec/1000;
    if (rs->pktTracePortOut[outPort] && now >= rs->pktTraceBeginTime &&
      now <= rs->pktTraceEndTime) {
      if (rs->traceType == TRACE_TYPE_PKT)
        DO_TRACE_WT(rs->ts, TRACE_EVENT_IN, pkt->len, inPort | 0x80);
      k = 0;
      DO_TRACE_S(rs->ts, TRACE_EVENT_DATA, pkt->prot, pkt->origSrc.addr.eth.mid,
		 (pkt->dst.addr.ip.c)<<8 | pkt->dst.addr.ip.d, outPort);
      if (pkt->prot == 201) { /* tcp packet */
        buf = (char *)msgPeek(&pkt->msg, sizeof(IPheader)+sizeof(tcpHDR));
        xAssert(buf);
        buf += sizeof(IPheader);
        tcpStdHdrLoad(&tcpHdr, (void *)buf, sizeof(tcpHDR), 0);
        DO_TRACE_S(rs->ts, TRACE_EVENT_DATA, rs->outQueueCnt[outPort],
                   tcpHdr.th_sport, tcpHdr.th_dport, tcpHdr.th_flags);
      }
    }
    else if (rs->pktTracePortIn[inPort] && now >= rs->pktTraceBeginTime &&
	     now <= rs->pktTraceEndTime) {
      if (rs->traceType == TRACE_TYPE_PKT)
        DO_TRACE_WT(rs->ts, TRACE_EVENT_IN, pkt->len, inPort | 0x80);
      k = 0;
      DO_TRACE_S(rs->ts, TRACE_EVENT_DATA, pkt->prot, pkt->origSrc.addr.eth.mid,
                 (pkt->dst.addr.ip.c)<<8 | pkt->dst.addr.ip.d, outPort);
      if (pkt->prot == 201) { /* tcp packet */
        buf = (char *)msgPeek(&pkt->msg, sizeof(IPheader)+sizeof(tcpHDR));
        xAssert(buf);
        buf += sizeof(IPheader);
        tcpStdHdrLoad(&tcpHdr, (void *)buf, sizeof(tcpHDR), 0);
        DO_TRACE_S(rs->ts, TRACE_EVENT_DATA, rs->outQueueCnt[outPort],
                   tcpHdr.th_sport, tcpHdr.th_dport, tcpHdr.th_flags);
      }
    }
  }
  if (rs->outQueueCnt[outPort] >= rs->defBufferCnt) {
    /* --- dropping packet */
    GlobalPacketsDropped++;
    GlobalBytesDropped += pkt->len;
    if (rs->traceType != TRACE_TYPE_DETAILED) {
      DO_TRACE_WT(rs->ts, TRACE_EVENT_DROP, pkt->len, outPort);
    }
    else {
      DO_TRACE_L(rs->ts, TRACE_EVENT_DROP, pkt->len, rs->outQueueCnt[outPort],
		 outPort);
    }
    xsimDbg(SL2_FLAG,  
 	    printf(">   output port %d full!, dropping packet %ld\n", outPort,pkt->id)); 
    msgDestroy(&pkt->msg); 
    freeSimPacket(pkt); 
    return; 
  }
  rs->outQueueCnt[outPort]++; 
  rs->outQueueLen[outPort] += pkt->len;
  if ((mtp=rs->traceQueue[outPort]) != NULL) 
    TRACE_QUEUE(mtp, rs->outQueueCnt[outPort], t);
  if ((mtp=rs->traceQueueLen[outPort]) != NULL) 
    TRACE_QUEUE(mtp, rs->outQueueLen[outPort]/1024, t);

  if (rs->outQueueEnd[outPort] == NULL) {
    rs->outQueueBeg[outPort] = rs->outQueueEnd[outPort] = pkt;
    rs->outRetryCnt[outPort] = 1;
    rs->outRetryDelay[outPort] = 1;
  }
  else {
    sendit = 0;
    rs->outQueueEnd[outPort]->next = pkt;
    rs->outQueueEnd[outPort] = pkt;
  }
  pkt->next = NULL;
/*   rs->outQueuedCnt[outPort]++; */
  pkt->src = rs->myAddr[outPort];
  pkt->hop = nextHop;
  np = rs->outPortNets[outPort];

  if (nextHop.type == TYPE_ETH) {
    pkt->type = TYPE_ETH;
    pkt->len += sizeof(ETHhdr);
    rs->outQueueLen[outPort] += sizeof(ETHhdr);
    pkt->ehdr.src = rs->myAddr[outPort].addr.eth;
    pkt->ehdr.dst = nextHop.addr.eth;
    xsimDbg(SL2_FLAG, 
	    printf(">    sending on ETH to %s\n", 
		   sim_addr2str(&pkt->ehdr.dst, TYPE_ETH)));

    buf = msgPush(&pkt->msg, sizeof(ETHhdr));
    xAssert(buf);
    ethdMsgStore(&pkt->ehdr, buf, sizeof(ETHhdr), 0);

    pkt->net = np;
  }
  else if (nextHop.type = TYPE_IP) {
    pkt->type = TYPE_IP;
    pkt->net = rs->outPortNets[outPort];
  }
  else {
    Kabort("Wrong type in FSCS_input\n");
  }
  if (rs->traceType == TRACE_TYPE_DETAILED) {
    DO_TRACE_L(rs->ts, TRACE_EVENT_QUEUE, outPort, rs->outQueueCnt[outPort], 
	       0);
  }
  if (sendit) {
    if (np->control(np, OP_ISBUSY, NULL, NULL, NULL, NULL)) {
      if (rs->traceType == TRACE_TYPE_DETAILED) {
	DO_TRACE_L(rs->ts, TRACE_EVENT_SEND_BUSY, rs->outQueueCnt[outPort], 
		   pkt->id, outPort);
      }
      np->control(np, OP_NOTBUSY_CB, rs->cbFun, (void *)rs, 
		  (void *)outPort, NULL);
    }
    else {
      if (rs->traceType == TRACE_TYPE_DETAILED) {
	DO_TRACE_L(rs->ts, TRACE_EVENT_SEND_OKAY, pkt->len, pkt->id, outPort);
      }
      np->control(np, OP_SEND, rs->cbFun, (void *)rs, (void *)outPort, 
		  (void *)pkt);
    }
  }
}



