/*
 * Copyright (c) 1990-1997 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
 * Implie 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.
 */

/*  This is a FRED implementation heavly based on the RED code
 *  (red.cc) found in ns-2 version 2.0. The differences from red.cc
 *  are highlighted by comments containing "FRED" word. The changes
 *  were based on the FRED pseudocode published in SIGCOMM'97 
 * 
 * Contact: Ion Stoica (istoica@cs.cmu.edu) 
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Header: /afs/cs/project/cmcl-ns2/NetBSD1.2.1/ns-allinone/ns-2/RCS/fred.cc,v 1.2 1998/09/12 16:43:27 istoica Exp istoica $ (LBL)";
#endif

#include "fred.h"

//#define FRED_LOG

#ifndef max
#define max(x, y) (x > y ? x : y)
#endif

void  panic(char *s);

void droplog(int flowId, int type);

static class FREDClass : public TclClass {
public:
	FREDClass() : TclClass("Queue/FRED") {}
	TclObject* create(int, const char*const*) {
		return (new FREDQueue);
	}
} class_fred;

FREDQueue::FREDQueue() : link_(NULL), bcount_(0), de_drop_(NULL), idle_(1)
{
  for (int i = 0; i < MAXFLOWS; ++i) { // FRED 
    fs_[i].qlen    = 0;                // FRED 
    fs_[i].strike  = 0;                // FRED 
    fs_[i].present = 0;                // FRED 
#ifdef SRCID
    hashid_[i]     = 0;                // FRED 
#endif
  }                                    // FRED
  maxq  = MINQ;                        // FRED
  nactive = 0;                         // FRED 
  many_flows_ = 0;                     // FRED  
 
  memset(&edp_, '\0', sizeof(edp_));
  memset(&edv_, '\0', sizeof(edv_));
  
  bind("off_ip_", &off_ip_);                // FRED 
  bind_bool("bytes_", &edp_.bytes);	    // boolean: use bytes?
  bind_bool("queue-in-bytes_", &qib_);	    // boolean: q in bytes?
  bind("thresh_", &edp_.th_min);		    // minthresh
  bind("maxthresh_", &edp_.th_max);	    // maxthresh
  bind("mean_pktsize_", &edp_.mean_pktsize);  // avg pkt size
  bind("q_weight_", &edp_.q_w);		    // for EWMA
  bind_bool("wait_", &edp_.wait);
  bind("linterm_", &edp_.max_p_inv);
  bind_bool("setbit_", &edp_.setbit);	    // mark instead of drop
  bind_bool("drop-tail_", &drop_tail_);	    // drop last pkt or random
  bind_bool("many-flows_", &many_flows_);   // specifies the mode in which 
                                            // FRED works
  q_ = new PacketQueue();		    // underlying queue
  reset();
  
#ifdef notdef
  print_edp();
  print_edv();
#endif

}

void FREDQueue::reset()
{
  /*
   * if queue is measured in bytes, scale min/max thresh
   * by the size of an average packet
   */
  
  if (qib_) {
    edp_.th_min *= edp_.mean_pktsize;
    edp_.th_max *= edp_.mean_pktsize;
  }
  /*
   * compute the "packet time constant" if we know the
   * link bandwidth.  The ptc is the max number of (avg sized)
   * pkts per second which can be placed on the link
   */
  
  if (link_)
    edp_.ptc = link_->bandwidth() /
      (8. * edp_.mean_pktsize);
  edv_.v_ave = 0.0;
  edv_.v_slope = 0.0;
  edv_.count = 0;
  edv_.count_bytes = 0;
  edv_.old = 0;
  edv_.v_a = 1 / (edp_.th_max - edp_.th_min);
  edv_.v_b = - edp_.th_min / (edp_.th_max - edp_.th_min);
  
  idle_ = 1;
  if (&Scheduler::instance() != NULL)
    idletime_ = Scheduler::instance().clock();
  else
    idletime_ = 0.0; /* sched not instantiated yet */
  Queue::reset();
  
  bcount_ = 0;
}

/*
 * Compute the average queue size.
 * The code contains two alternate methods for this, the plain EWMA
 * and the Holt-Winters method.
 * nqueued can be bytes or packets
 */
void FREDQueue::run_estimator
(int nqueued, int m)
{
  float f, f_sl, f_old;
  
  f = edv_.v_ave;
  f_sl = edv_.v_slope;
#define FRED_EWMA
#ifdef FRED_EWMA
  while (--m >= 1) {
    f_old = f;
    f *= 1.0 - edp_.q_w;
  }
  f_old = f;
  f *= 1.0 - edp_.q_w;
  f += edp_.q_w * nqueued;
#endif
#ifdef FRED_HOLT_WINTERS
  while (--m >= 1) {
    f_old = f;
    f += f_sl;
    f *= 1.0 - edp_.q_w;
    f_sl *= 1.0 - 0.5 * edp_.q_w;
    f_sl += 0.5 * edp_.q_w * (f - f_old);
  }
  f_old = f;
  f += f_sl;
  f *= 1.0 - edp_.q_w;
  f += edp_.q_w * nqueued;
  f_sl *= 1.0 - 0.5 * edp_.q_w;
  f_sl += 0.5 * edp_.q_w * (f - f_old);
#endif
  edv_.v_ave = f;
  edv_.v_slope = f_sl;
}

/*
 * Output the average queue size and the dropping probability. 
 */
void FREDQueue::plot()
{
#ifdef notyet
  double t = Scheduler::instance().clock();
  sprintf(trace_->buffer(), "a %g %g", t, edv_.v_ave);
  trace_->dump();
  sprintf(trace_->buffer(), "p %g %g", t, edv_.v_prob);
  trace_->dump();
#endif
}

/*
 * print the queue seen by arriving packets only
 */
void FREDQueue::plot1(int)
{
#ifdef notyet
  double t =  Scheduler::instance().clock();
  sprintf(trace_->buffer(), "Q %g %d", t, length);
  trace_->dump();
#endif
}

/*
 * Return the next packet in the queue for transmission.
 */
Packet* FREDQueue::deque()
{
  Packet  *p;
  
  check();  /* FRED: check consistency of flows' states */
  
  p = q_->deque();
  
  if (p) {                                         // FRED
    
    hdr_ip*  hip    = (hdr_ip*)p->access(off_ip_); // FRED 
#ifdef SRCID
    int flowId = getId(hip->src());                // FRED
#else
    int flowId = hip->flowid();                    // FRED 
#endif                  
    --fs_[flowId].qlen;                            // FRED
    reset_flow_state(flowId);                      // FRED
  }                                                // FRED
  run_estimator(q_->length(), 1);                  // FRED 
  
  if (p != 0) {
    idle_ = 0;
    bcount_ -= ((hdr_cmn*)p->access(off_cmn_))->size();
  } else {
    idle_ = 1;
    // deque() may invoked by Queue::reset at init
    // time (before the scheduler is instantiated).
    // deal with this case
    if (&Scheduler::instance() != NULL)
      idletime_ = Scheduler::instance().clock();
    else
      idletime_ = 0.0;
  }
  return (p);
}

int FREDQueue::drop_early(Packet* pkt)
{
   hdr_cmn* ch = (hdr_cmn*)pkt->access(off_cmn_);
   double p = edv_.v_a * edv_.v_ave + edv_.v_b;
   hdr_ip*  hip = (hdr_ip*)pkt->access(off_ip_); // FRED 
#ifdef SRCID
   int      flowId = getId(hip->src());          // FRED 
#else
   int      flowId = hip->flowid();              // FRED 
#endif

   /* random drop from robust flows only */
   if (fs_[flowId].qlen >= max(MINQ, int(edv_.v_ave/nactive))) { // FRED

     p /= edp_.max_p_inv;
     edv_.v_prob1 = p;
     if (edv_.v_prob1 > 1.0)
       edv_.v_prob1 = 1.0;
     double count1 = edv_.count;
     if (edp_.bytes)
       count1 = (double) (edv_.count_bytes/edp_.mean_pktsize);
     if (edp_.wait) {
       if (count1 * p < 1.0)
	 p = 0.0;
       else if (count1 * p < 2.0)
	 p /= (2 - count1 * p);
       else
	 p = 1.0;
     } else {
       if (count1 * p < 1.0)
	 p /= (1.0 - count1 * p);
       else
	 p = 1.0;
     }
     if (edp_.bytes && p < 1.0) {
       p = p * ch->size() / edp_.mean_pktsize;
     }
     if (p > 1.0)
       p = 1.0;
     edv_.v_prob = p;
     
     // drop probability is computed, pick random number and act
     double u = Random::uniform();
     if (u <= edv_.v_prob) {
       // DROP or MARK
       edv_.count = 0;
       edv_.count_bytes = 0;
       if (edp_.setbit) {
	 hdr_flags* hf = (hdr_flags*)pkt->access(off_flags_);
	 hf->ecn_to_echo_ = 1; 
       } else {
	 return (1);
       }
     }
   }  // FRED 
   return (0);  // no DROP/mark
}

/*
 * Pick packet to drop. Having a separate function do this is convenient for
 * supporting derived classes that use the standard FRED algorithm to compute
 * average queue size but use a (slightly) different algorithm for choosing
 * the victim.
 */
Packet*
FREDQueue::pickPacketToDrop() {
  drop_tail_ = 1; /* FRED */
  int victim = drop_tail_ ? q_->length() - 1 : Random::integer(q_->length());
  return(q_->lookup(victim));
}

/*
 * Receive a new packet arriving at the queue.
 * The average queue size is computed.  If the average size
 * exceeds the threshold, then the dropping probability is computed,
 * and the newly-arriving packet is dropped with that probability.
 * The packet is also dropped if the maximum queue size is exceeded.
 */

#define	DTYPE_FORCED	1	/* a "forced" drop */
#define	DTYPE_UNFORCED	2	/* an "unforced" (random) drop */

void FREDQueue::enque(Packet* pkt)
{
  double now = Scheduler::instance().clock();
  hdr_cmn* ch = (hdr_cmn*)pkt->access(off_cmn_);
  hdr_ip*  hip = (hdr_ip*)pkt->access(off_ip_); // FRED
#ifdef SRCID
  int      flowId = getId(hip->src());          // FRED 
#else
  int      flowId = hip->flowid();              // FRED 
#endif
  
  int droptype = -1;
  int m = 0;
  check();  // FRED: check consistency of flows' states

  if (flowId >= MAXFLOWS) {                                 // FRED 
    printf("Error: flow number is %d and should be < %d\n", // FRED 
	   flowId, MAXFLOWS);                               // FRED 
    exit (-1);                                              // FRED 
  }

  /*
   * Install flow state; this is the first 
   * packet of connection flowId.
   * Note that there is no need to reset 
   * strike and qlen fields as they are already 0.
   * (Also, note that we assume that a flow exists 
   * (i.e., it is present) only as long as it has packets.) 
   */
  if (!fs_[flowId].present) {  // FRED 
    fs_[flowId].present = 1;   // FRED 
    nactive++;                 // FRED 
  }                            // FRED 
  
  /*
   * if we were idle, we pretend that m packets arrived during
   * the idle period.  m is set to be the ptc times the amount
   * of time we've been idle for
   */
  
  if (idle_) {
    /* To account for the period when the queue was empty.  */
    idle_ = 0;
    m = int(edp_.ptc * (now - idletime_));
  }
  
  /*
   * If queues was empty FRED computes the average queue length
   * here. 
   * To account for the current packet arrival the estimator is again
   * invoked at the end of this method; this is done only if the
   * packet is _not_ dropped (see pseudocode in the FRED
   * paper at SIGCOMM'97)
   *
   * Here the estimator is run with the scaled version (m) above 
   * [scaled due to idle time] (bcount_ maintains the byte 
   * count in the underlying queue)
   *
   * Note that the last argument is "m" instead of "m+1" as in the red.cc
   * code. This is because, in this case run_estimator is called
   * again to account for the new arrival at the end of this method.
   */
  if (m)                                              // FRED 
    run_estimator(qib_ ? bcount_ : q_->length(), m);  // FRED  
  

  if (!many_flows_) {                 // FRED
    /* this is block A in FRED pseudocode (see SIGCOMM'97 paper) */
    maxq =  int(edp_.th_min);        // FRED
    if (edv_.v_ave >=  edp_.th_max)  // FRED 
      maxq = MINQ;                   // FRED 
  }                                  // FRED

  /*
   * count and count_bytes keeps a tally of arriving traffic
   * that has not been dropped
   */
  ++edv_.count;
  edv_.count_bytes += ch->size();
  
  // see if we drop early
  
  register double qavg = edv_.v_ave;
  int qlen = qib_ ? bcount_ : q_->length();
  int qlim = qib_ ?  (qlim_ * edp_.mean_pktsize) : qlim_;
  double avgcq;

  /*
   * Compute per flow average queue length
   */  
  if (nactive)               // FRED
    avgcq = qavg/nactive;    // FRED
  else                       // FRED
    avgcq = qavg;            // FRED
  avgcq = max(avgcq, 1);     // FRED


  /* identify and manage non-adaptive flows */
  if (fs_[flowId].qlen >= maxq || (!many_flows_ &&                       // FRED
      /* the next two lines represent line B in the FRED pseudocode
       * (See SIGCOMM'97 paper) 
       */
      ((qavg >= edp_.th_max && fs_[flowId].qlen > 2*avgcq) ||            // FRED 
      ((double)(fs_[flowId].qlen) >= avgcq && fs_[flowId].strike > 1)))){// FRED 
    ++fs_[flowId].strike;                                                // FRED
    droptype = DTYPE_FORCED;                                             // FRED 
    droplog(flowId, 1);                                                  // FRED

  } else {
    /* operate in random drop mode */
    if (qavg >= edp_.th_min && qlen > 1) {
      if (qavg >= edp_.th_max) { 
        // drop-tail mode; the following is block C in the FRED pseudocde
	if (many_flows_) {
	  if (fs_[flowId].qlen >= MINQ) {
	    droptype = DTYPE_FORCED;
	    droplog(flowId, 2);
	  }
	} else { 
	  droptype = DTYPE_FORCED;
	  droplog(flowId, 2);
	}
      } else if (edv_.old == 0) {
	edv_.count = 1;
	edv_.count_bytes = ch->size();
	edv_.old = 1;
      } else {
	/*
         * (edp_.th_min <= qavg < edp_.th_max) && (qlen > 1) && (edv_.old == 1)
         *  Note: the last two clauses are inherited from RED and they shouldn't
         *  affect FRED. (qlen represents the current queue size, while 
         *  edv_.old avoids dropping when the average queue length exceeds
         *  first threshold first time  
         */ 
        if (drop_early(pkt)) { 
          /* drop from robust flows only (see drop_early()) */
	  droplog(flowId, 3);
	  droptype = DTYPE_UNFORCED;
        }
      }
    } else {
      edv_.v_prob = 0.0;
      edv_.old = 0;
    }
  }
	  
  if (qlen >= qlim) {
    droplog(flowId, 4);
    droptype = DTYPE_FORCED;
  }
  
  if (droptype != DTYPE_UNFORCED) {
    // enqueue packet
    q_->enque(pkt);
    bcount_ += ch->size();
    qlen = qib_ ? bcount_ : q_->length();
    ++fs_[flowId].qlen;          // FRED 
  }
  if (droptype == DTYPE_FORCED) {
    /* drop random victim or last one */
    pkt = pickPacketToDrop();
    q_->remove(pkt);
    bcount_ -= ((hdr_cmn*)pkt->access(off_cmn_))->size();
    /* update flow's queue length */
    {
      hdr_ip*  hip = (hdr_ip*)pkt->access(off_ip_); // FRED 
#ifdef SRCID
      int      flowId = getId(hip->src());          // FRED 
#else
      int      flowId = hip->flowid();              // FRED 
#endif
      --fs_[flowId].qlen;                           // FRED 
      reset_flow_state(flowId);
    }                   
  } else if (droptype == DTYPE_UNFORCED && de_drop_ != NULL) {
    de_drop_->recv(pkt);
    reset_flow_state(flowId);   // FRED
    return;
  }

  if (droptype == DTYPE_FORCED || droptype == DTYPE_UNFORCED) {
    drop(pkt);
  }
  reset_flow_state(flowId); // Just to be sure; probably this is not needed here

  /* 
   * If the packet was not dropped compute the average queue length.
   * Note that in RED this is done at the beginning of the method 
   * by calling run_estimator with the last parameter "m+1" instead of "m+1"
   * (Thus unlike RED, FRED to not update the average when the packet is 
   *  dropped.)
   */ 
  if (droptype != DTYPE_FORCED && droptype != DTYPE_UNFORCED) // FRED
    run_estimator(q_->length(), 1);                           // FRED
  
  return;
}

int FREDQueue::command(int argc, const char*const* argv)
{
  Tcl& tcl = Tcl::instance();
  if (argc == 2) {
    if (strcmp(argv[1], "reset") == 0) {
      reset();
      return (TCL_OK);
    }
    if (strcmp(argv[1], "early-drop-target") == 0) {
      if (de_drop_ != NULL)
	tcl.resultf("%s", de_drop_->name());
      return (TCL_OK);
    }
  } else if (argc == 3) {
    if (strcmp(argv[1], "link") == 0) {
      LinkDelay* del = (LinkDelay*)TclObject::lookup(argv[2]);
      if (del == 0) {
	tcl.resultf("FRED: no LinkDelay object %s",
		    argv[2]);
	return(TCL_ERROR);
      }
      // set ptc now
      link_ = del;
      edp_.ptc = link_->bandwidth() /
	(8. * edp_.mean_pktsize);
      
      return (TCL_OK);
    }
    if (strcmp(argv[1], "early-drop-target") == 0) {
      NsObject* p = (NsObject*)TclObject::lookup(argv[2]);
      if (p == 0) {
	tcl.resultf("no object %s", argv[2]);
	return (TCL_ERROR);
      }
      de_drop_ = p;
      return (TCL_OK);
    }
    if (!strcmp(argv[1], "packetqueue-attach")) {
      delete q_;
      if (!(q_ = (PacketQueue*) TclObject::lookup(argv[2])))
	return (TCL_ERROR);
      else
	return (TCL_OK);
    }
  }
  return (Queue::command(argc, argv));
}


void FREDQueue::reset_flow_state(int flowId) 
{
  if (fs_[flowId].qlen == 0 && fs_[flowId].present) { 
    fs_[flowId].present = 0;                          
    fs_[flowId].strike  = 0;                          
    --nactive;                                        
  }                                                   
}

/* for debugging help */
void FREDQueue::print_edp()
{
  printf("mean_pktsz: %d\n", edp_.mean_pktsize); 
  printf("bytes: %d, wait: %d, setbit: %d\n",
	 edp_.bytes, edp_.wait, edp_.setbit);
  printf("minth: %f, maxth: %f\n", edp_.th_min, edp_.th_max);
  printf("max_p_inv: %f, qw: %f, ptc: %f\n",
	 edp_.max_p_inv, edp_.q_w, edp_.ptc);
  printf("qlim: %d, idletime: %f\n", qlim_, idletime_);
  printf("=========\n");
}

void FREDQueue::print_edv()
{
  printf("v_a: %f, v_b: %f\n", edv_.v_a, edv_.v_b);
}


/* check state consistency for all flows */
void FREDQueue::check()
{
  int qlen = 0;
  static flowState* adr = NULL;

  for (int i = 0; i < MAXFLOWS; i++) {
  
    if (fs_[i].present) {
      if (!adr) adr = (flowState *)&fs_;
      qlen += fs_[i].qlen;
    } else {
      if (fs_[i].qlen) {
        printf("Error: invalid qlen: flow %d, qlen %d \n", i, fs_[i].qlen);
        exit(-1);
      }     
      if (fs_[i].strike) {
        printf("Error: invalid strike: flow %d, qlen %d \n", i, fs_[i].strike);
        exit(-1);
      }     
    }
  }
  
  if (qlen != q_->length()) {        
    printf("Error: invalid length: length %d, qlen %d \n", q_->length(), 
	   qlen);
    exit(-1);
  }     
}

#ifdef SRCID
/* get identifier from source address */
int FREDQueue::getId(int src) 
{
  int i = src % MAXFLOWS;
 
  if (!hashid_[i]) { 
    /* first packet of this flow */
    hashid_[i] = src; 
  } else 
    if (hashid_[i] != src)
      panic("Source addresses colision!\n");

  return i;
}
#endif


/* FRED function */
void droplog(int flowId, int type)
{
#ifdef FRED_LOG
  printf("d%d %f 1 t%d\n", flowId, Scheduler::instance().clock(), 
     type);
#else
  ;
#endif
}


