
 /*
 * Copyright (c) 1990-1994 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *
 * red.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1993,1991,1990  Arizona Board of Regents
 *
 *
 */


#include <sys/types.h>
#include "x_stdio.h"
#include "xkernel.h"
#include "eth.h"
#include "../protocols/eth/eth_i.h"
#include "ip.h"
#include "ip_i.h"
#include "sim_i.h"
#include "sim_router.h"
#include <stdio.h>
#include <sys/time.h>
#include <math.h>
#define PKTSIZE 1500
/*    
 * RED Routers
 */
/* early drop parameters */

struct _edp{
  /* user suppiled */
  double th_min;		/* min threshold */
  double th_max;		/* max threshold */
  double max_p_inv;		/* 1/max_p, max_p = max probability */
  double q_w;			/* queue weight */
  /* computed as a function of above */
  double ptc;			/* pkt time const, in pkts/sec */
};
/* early drop variables */
struct _edv{
  float v_ave;		/* avg queue size */
  float v_slope;	/* used in computing avg que size */
  float v_r;
  float v_prob;		/* prob of pkt drop */
  float v_prob1;	/* prob of pkt drop before count */
  float v_a;		/* v_prob = v_a * v_ave + v_b */
  float v_b;
  int count;		/* number of pkts since last drop */
  int old;		/* 0 when avg queue first exceeds thresh */
};


typedef struct redStateStruct {
  short currentQueue;
  /* Venkat's new Stuff */
  int idle;
  double idletime;
  struct _edv edv;
  struct _edp edp;
} RedState;

/* prototypes */
extern void red_init(int minth, int maxth, float linterm, void **sa, int rate);
extern void run_estimator(int nqueued, int m, RedState *s);
extern int drop_early(RedState *s);
extern void sim_RED_init (ROUTER_STATE *rs, int argc, char **argv);
static int red_inpkt(RedState *s);
static int red_inpkt(RedState *s);static double uniform();

extern u_long GlobalPacketsDropped ;
extern u_long GlobalBytesDropped;

/* returns a uniform random number */
static double
uniform() {
  return ((double)random() / 0x7fffffff);
}


/* this function called at init time: resets values to user specified ones */
void
red_init(int minth, int maxth, float linterm, void **sa, int rate)
{ 
  RedState *s;
  XTime curTime;
  
  s = (RedState *) xMalloc(sizeof(RedState));
  *sa = (void *)s;
  
  s->edp.th_min = minth;
  s->edp.th_max = maxth;
  s->edp.max_p_inv = linterm;
  s->edv.v_ave = 0.0;
  s->edv.v_slope = 0.0;
  s->edv.count = 0;
  s->edv.old =0;
  s->edv.v_a = 1/(s->edp.th_max - s->edp.th_min);
  s->edv.v_b = -s->edp.th_min / (s->edp.th_max - s->edp.th_min);
  s->idle = 1;
  xGetTime(&curTime);
  s->idletime = curTime.sec + (curTime.usec)/1000000.0;
  printf("INIT: Idle Time is %f\n",s->idletime);
  s->edp.ptc = rate/PKTSIZE;	/* VENKAT: Need to get gloabl pkt size here*/
}

/*
 * Compute the average queue size.
 * The code contains two alternate methods for this, the plain EWMA
 * and the Holt-Winters method.
 * nqueued can be packets
 */
void
run_estimator(int nqueued, int m, RedState *s)
{
	float f, f_sl, f_old;

	f = s->edv.v_ave;
	f_sl = s->edv.v_slope;
#define RED_EWMA
#ifdef RED_EWMA
        while (--m >= 1) {
                f_old = f;
                f *= 1.0 - s->edp.q_w;
        }
        f_old = f;
        f *= 1.0 - s->edp.q_w;
        f += s->edp.q_w * nqueued;
#endif
#ifdef RED_HOLT_WINTERS
	while (--m >= 1) {
		f_old = f;
		f += f_sl;
		f *= 1.0 - s->edp.q_w;
		f_sl *= 1.0 - 0.5 * s->edp.q_w;
		f_sl += 0.5 * s->edp.q_w * (f - f_old);
	}
	f_old = f;
	f += f_sl;
	f *= 1.0 - s->edp.q_w;
	f += s->edp.q_w * s->nqueued;
	f_sl *= 1.0 - 0.5 * s->edp.q_w;
	f_sl += 0.5 * s->edp.q_w * (f - f_old);
#endif
	s->edv.v_ave = f;
	s->edv.v_slope = f_sl;
}

int
drop_early(RedState *s) {
  double p;
  double count1;
  double u;
  if (s->edv.v_ave >= s->edp.th_max)
		s->edv.v_prob = 1.0;
	else {
		p = s->edv.v_a * s->edv.v_ave + s->edv.v_b;
		p /= s->edp.max_p_inv;
		s->edv.v_prob1 = p;
		if (s->edv.v_prob1 > 1.0) s->edv.v_prob1 = 1.0;
		count1 = s->edv.count;
		if (count1 * p < 1.0)
		  p /= (1.0 - count1 * p);
		else
		  p = 1.0;
		if (p>1.0) p=1.0;
		s->edv.v_prob = p;
	}
	u = uniform();
	if (u <= s->edv.v_prob) {
		s->edv.count = 0;
		return (1);
	}
	return (0);
}


static int
red_inpkt(RedState *s) {
  XTime curTime;
  int m;
  int rval;
  double now;
  xGetTime(&curTime);
   now=curTime.sec + (curTime.usec)/1000000.0;
   printf("In red_inpkt now is %f\n",now);
   printf("In red_inpkt idletime is %f\n",s->idletime);
  rval =0;	/* 0: delivered -1: dropped */
  if (s->idle) {
    s->idle = 0;
    m = s->edp.ptc * (now - s->idletime);
    printf(" m is %d\n",m);
  } else
    m = 0;
  /** XXXX: Need num in Queue */
  run_estimator(s->currentQueue, m+1,s);
  ++s->edv.count;
  if (s->edv.v_ave >= s->edp.th_min && s->currentQueue > 1) {
    if (s->edv.old == 0) {
      s->edv.count = 1;
      s->edv.old = 1;
    }
    else
      /*
      * Drop each packet with probability edv.v_prob.
      */
      if (drop_early(s)) {
	rval = -1;	/* indicates drop */
      }
  }
  else {
    s->edv.v_prob = 0.0;
    s->edv.old = 0;
  }
  return rval;
}

static void
red_opkt(RedState *s)
{
  XTime curTime;
  
  xGetTime(&curTime);
  if (--s->currentQueue == 0) {
    /* queue just emptied completely */
    s->idle = 1;
    s->idletime =curTime.sec + (curTime.usec)/1000000.0 ;
/*    printf("IN Outpkt idleTime is %f\n", s->idletime);*/
  }
  else
    s->idle = 0;
} 
    
void
sim_RED_init (ROUTER_STATE *rs, int argc, char **argv)
{
  int k;
  int minth=0, maxth=0;
  float linterm=0.0;
  int s;
  struct timeval tv;
  
  rs->inFun = sim_RED_input;
  rs->cbFun = sim_RED_callback;
  for (k=0; k<argc; k++) {
    if (!strcmp(argv[k], "minthresh="))
      sscanf(argv[++k], "%d", &minth);
    else if (!strncmp(argv[k], "maxthresh=", 5))
      sscanf(argv[++k], "%d", &maxth);
    else if (!strcmp(argv[k], "linterm="))
      sscanf(argv[++k], "%f", &linterm);
    }
    if (maxth <= 0)
       maxth = rs->defBufferCnt;
    if (minth <= 0)
      minth = maxth >> 5;
    if (linterm==0.0)
      linterm=30;
    gettimeofday(&tv,0);
    s = (tv.tv_sec ^ tv.tv_usec) & 0x7fffffff;
    srandom(s);
    /* venkat: changed this to a for loop */
    for (k=0;k<rs->connectCnt;k++)
      red_init(minth, maxth, linterm,(void **)&rs->outState[k],rs->outPortNets[k]->rate);
}

static void
sim_RED_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_RED_callback (Event ev, void *arg)
{ 
  CallBack      *cbp=(CallBack *)arg;
  int           outPort=(int)cbp->arg1;
  long 		t; /* venkat: changed from int */
  int           rvar; 
  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("[RED cb] host:%d, op:%d\n", cbp->host, cbp->op));
  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;
      red_opkt((RedState *)rs->outState[outPort]);
    
      t = traceGetTime();
      if ((itp = rs->traceOut[outPort] )!= NULL)
        TRACE_INOUT(itp, pkt->len, 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);
      } 
/*       else { */
/*      DO_TRACE_WT(rs->ts, TRACE_EVENT_QUEUE, rs->outQueueCnt[outPort],  */
/*                  outPort); */
/*       } */
      xsimDbg(SL2_FLAG, printf("[RED 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_RED_send(NULL, (void *)cbp);
    }   
    else {
      /* --- send failed, try again */
      /* --- Should I do exp backoff? */
      xsimDbg(SPC_FLAG, printf("[RED 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 RED 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]--;
        red_opkt((RedState *)rs->outState[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_RED_send(NULL, (void *)cbp);
      } 
      else {
        rs->outRetryDelay[outPort] *= 2;
        rvar = lrand48() % rs->outRetryDelay[outPort] + 1;
        xsimDbg(SPC_FLAG, printf("[RED cb] %s delaying for:%d slots\n",
                                 rs->name, rvar));
        evDetach(evSchedule(sim_RED_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("[RED cb] %s op:NOTBUSY_CB, q:%d\n",
                             rs->name,
                             rs->outQueueCnt[outPort]));
    if ((pkt = rs->outQueueBeg[outPort]) != NULL)
      sim_RED_send(NULL, (void *)cbp);
    else
      freeCallBack(cbp);
    break;
        
  default:                       
    printf("ERROR: Unknown callback (op: %d) in sim_RED_callback\n", cbp->op);
    freeCallBack(cbp);
  }       
}                    
          
void                 
sim_RED_input (ROUTER_STATE *rs, SimPacket *pkt, int inPort)
{     
  int           outPort, sendit=1;
  long 		t;	/* venkat: changed from int */
  void		*buf;
  IPheader      ihdr;
  Simhost       nextHop;
  Net           *np; 
  ITrace        *itp; 
  MaxMinTrace   *mtp;
                  
  xsimDbg(SL2_FLAG,
          printf("> RED_input %s, for packet %d 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->len, t);
  }       

  if (rs->inPortNets[inPort]->type == TYPE_ETH || pkt->type == TYPE_ETH) {
     /* venkat: changed to Pop from Peek TRUE */
    buf = msgPop(&pkt->msg, sizeof(ETHhdr));
    xAssert(buf);
    ethdMsgLoad(&pkt->ehdr, buf, sizeof(ETHhdr), 0);

    /* venkat: changed from msgPeek FALSE to msgPeek */
    buf = msgPeek(&pkt->msg, sizeof(IPheader));
    xAssert(buf);
    ipStdHdrLoad(&ihdr, buf, sizeof(IPheader), 0);

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

/*  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 RED_input\n");
    msgDestroy(&pkt->msg);
    freeSimPacket(pkt);
  }
  
  if ((itp = rs->traceInOut[outPort] )!= NULL) 
    TRACE_INOUT(itp, pkt->len, t);
  if (outPort==0) {
    printf("avg queu size for port 0 is %d\n",
	((RedState *)rs->outState[outPort])->edv.v_ave);
  }
  if (red_inpkt((RedState *)rs->outState[outPort]) == -1) {
    /* --- dropping packet */
     GlobalPacketsDropped++;
    GlobalBytesDropped += pkt->len;
    printf("RED ALGO FORCES PACKET DROP\n");
    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(">   RED  algo for %d  dropping packet\n", outPort));
    msgDestroy(&pkt->msg);
    freeSimPacket(pkt);
    return;
  }
  else  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\n", outPort));
    msgDestroy(&pkt->msg);
    freeSimPacket(pkt);
    return;
  }
  ((RedState *)rs->outState[outPort])->currentQueue++;
  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");
  }  
/*   rs->delay = 0; */
  if (rs->traceType == TRACE_TYPE_DETAILED) {
    DO_TRACE_L(rs->ts, TRACE_EVENT_QUEUE, outPort, rs->outQueueCnt[outPort],
               0);
  }
/*   else { */
/*     DO_TRACE_WT(rs->ts, TRACE_EVENT_QUEUE, rs->outQueueCnt[outPort], outPort
/*   } */
  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);
    }
  }   
}       

