/* 1515, Sat 7 Jul 01 (NZT)

   FLOWHASH.C:  RTFM Meter, using hash tables for counts

   Copyright (C) 1992-2002 by Nevil Brownlee,
   CAIDA | University of Auckland */

/*
 * $Log: flowhash.c,v $
 * Revision 1.1.1.2.2.14  2002/02/23 01:57:26  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.12  2001/05/24 02:19:49  nevil
 * LfapMet implemented by Remco Poortinga.
 * MinPDUs implemented by Nevil.
 *
 * Revision 1.1.1.2.2.10  2000/12/28 00:48:13  nevil
 * Count unrequested DNS responses as LostPDUs
 *
 * Revision 1.1.1.2.2.8  2000/10/26 23:23:58  nevil
 * Write log_msg messages to meter.log file
 *
 * Revision 1.1.1.2.2.7  2000/08/08 19:44:49  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.5  2000/06/06 03:38:17  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.2  2000/01/12 02:57:08  nevil
 * Implement 'packet pair matched' turnaroundtime distribution attributes.
 * Fix ASN-related bugs in NeTraMet, distribution-related bugs in fd_filter.
 *
 * Revision 1.1.1.2.2.1  1999/11/29 00:17:24  nevil
 * Make changes to support NetBSD on an Alpha (see version.history for details)
 *
 * Revision 1.1.1.2  1999/10/03 21:06:22  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.17  1999/09/29 22:29:14  nevil
 * Changes (mainly changing // to /* comments) for Irix
 *
 * Revision 1.1.1.1.2.16  1999/09/29 02:42:47  nevil
 * Fix problems discovered on PC meters
 * - Use NETRAMET compile-time variable to leave out C++ declarations
 * - Clean up mistakes in porting OCX_BGP code from meter_pc to meter_oc
 *
 * Revision 1.1.1.1.2.15  1999/09/24 02:58:37  nevil
 * Polish up code to get rid of warning messages from Borland (DOS) compiler.
 * Make manager PeerAddress buffers NSAP_ADDR_LEN bytes long.
 * Add asn_lookup variable - only call FindSubnet if ruleset uses ASNs.
 *
 * Revision 1.1.1.1.2.14  1999/09/22 05:38:40  nevil
 * Improve code to work properly on 64-bit machines
 * - Add OS=ALPHA handling to configure.in
 * - Clean up the Alpha compiler warnings
 * - Change all the snmp-related code to use Bit32 instead of unsigned long
 *
 * Revision 1.1.1.1.2.13  1999/09/14 00:45:26  nevil * 4.3 Release ..
 *  - Implement -D option to run NeTraMet as a daemon
 *  - Tidy up the on-line help displays
 *
 * Revision 1.1.1.1.2.12  1999/05/26 02:41:37  nevil
 * Integrate V6 and ASN code into PC versions of the meter.
 * This required a rework of the makefiles, using @cflags.opt files
 * to provide a much longer command line to the Borland C compiler.
 *
 * Revision 1.1.1.1.2.11  1999/05/18 03:36:26  nevil
 * Implement IPv6 in NeTraMet, and its manager/collectors.
 * - This is controlled by the V6 #define
 * - NeTraMet recognises v6 packets and fishes through their extension
 *     headers until it finds the actual payload.
 * - NeMaC et al display v6 addresses in the fom specified by RFC 2373
 * - fd_util and fd_extract allow colons in addresses (for defining tags)
 *
 * Revision 1.1.1.1.2.10  1999/05/17 00:11:53  nevil
 * Fixed 'reuse of ruleset row' problem:
 *   When a ruleset row is destroyed, set_RowStatus calls free_rulespace()
 *   and recover_flows().  free_rulespace() zeroed its varibales in the
 *   ri[] row, but recover_flows() didn't (it should have zeroed
 *   ri_flow_chain).  This left ri_flow_chain pointing to a free flow,
 *   which cause problems if a manager tried to reuse this row for a
 *   new ruleset.  Fixed by zeroing all of the ri[] row. (Also commented
 *   out the zeroing code from free_rulesace).
 *
 */

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

#define DEBUG  /* Required if any of the following are set */

#define PME_TRACE      0  /* Trace rules during pattern match */
#define PM_DEBUG       0

#define CR_TRACE       0
#define PP_DEBUG       0
#define PP_DEBUG_XX         0

#define DNS_ROOT_DEBUG      0
#define DNS_ROOT_DEBUG_2    0

#define STREAM_CHAIN_DEBUG  0
#define SCC_CHECK      0

#define DIST_DBG       0
#define RS_CH_DEBUG    0
#define V6_TESTV4      0  /* Turns IP4 addresses into ::IP4 addresses */
#define noSTACK_CHECK     /* Keep an eye on pattern & return stack extents */
#define noGC_TEST         /* Monitor garbage collector */

#define noMATCH_TRACE  /* Testing, testing .. */
#define noCOMPARE_TRACE
#define noCOUNT_TRACE
#define noP_TRACE
#define noGC_DEBUG
#define noTESTING
#define noGC_TEST1
#define noH_TEST
#define TCP_TESTING  0
#define TCP_TRACE    0

#define noDECNET

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#if NEW_ATR
#include <math.h>
#endif

#if !defined(DOS)
# if HAVE_MALLOC_H
# include <malloc.h>
# endif
#include <sys/time.h>
#include <netinet/in.h>
#include "../bgp/types/types.h"  /* Needed here for subnet.h */

#else

#include <alloc.h>
#include <mem.h>
#include <conio.h>
#include "..\intel\byteswap.h"
# ifdef DEBUG
# include <dos.h>
# endif
#endif  /* DOS */

#include "ausnmp.h"

#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"

#include <sys/types.h>
#include <string.h>

#include "pktsnap.h"

#define EXTFLOW
#include "flowhash.h"
#include "decnet.h"
#include "met_vars.h"

#if NEW_ATR
#define MX_PQ_LEN_TCP       100  /* Enough for a big window */
#define MX_PQ_LEN_OTHER      50  /* No windows in UDP or ICMP */
#define STR_TO_MULTIPLIER  10.0  /* For 'LostPackets' */
#endif

#if TCP_PKT_TRACE

# define TRACE_SEQ_DECR  0
# define TRACE_NEG_BLP   0
# define BLP_FRACT  0.50

FILE *pt_file;
# define ptf_title "ntm.trace"
void print_pkt_trace(FILE *f,  char *id,  /* Identifies invocation */
   struct stream *sfp, char *fmt, ...);  /* Supporting detail */
#endif

#ifdef DEBUG
void quack(int x) { }  /* Use for a debugger break point */

void paddr(Bit32 a);
void daddr(Bit32 a);
void dport(Bit16 a);
void fdaddr(FILE *f, Bit32 a);
void fdport(FILE *f, Bit16 a);
void pkey(char *msg, struct flow *n);
#endif

#if PM_DEBUG
int pm_dbg = 0;
char dbg_msg[80];
#endif


#if NF_OCX_BGP
# if !defined(DOS)  /* Unix meter */
#  include "../bgp/integrat/readbgp.h"
#  include "../bgp/integrat/subnet.h"
# else  /* OCX or DOS */
#  include "..\integrat\readbgp.h"
#  include "..\integrat\subnet.h"
# endif
#endif

void dump_flow(struct flow *fp)
{
   Bit32 pa;
   log_msg(LOG_WARNING, -1,
      "   flow %u (%x), Ruleset=%d, PeerAddrType=%d, TransAddrType=%d,"
      " FlowKind=%d, FlowClass=%d",
      flow_nbr(fp),fp, fp->FlowRuleSet, fp->fk.PeerAddrType, 
      fp->fk.TransAddrType, fp->fk.FlowKind, fp->fk.FlowClass);
#if QBYTE_PEER
   pa = ntohl(fp->fk.Low.PeerAddress.qbyte);
#else
   pa = ntohl(fp->fk.Low.PeerAddress.peer32[0]);
#endif
   log_msg(LOG_WARNING, -1,
      "   Source Interface=%d, PeerAddress=%d.%d.%d.%d, TransAddress=%u",
      fp->fk.Low.Interface,
      pa >> 24, (pa >> 16) & 0xFF, (pa >> 8) & 0xFF, pa & 0xFF,
      ntohs(fp->fk.Low.Interface), ntohs(fp->fk.Low.TransAddress));
#if QBYTE_PEER
   pa = ntohl(fp->fk.High.PeerAddress.qbyte);
#else
   pa = ntohl(fp->fk.High.PeerAddress.peer32[0]);
#endif
   log_msg(LOG_WARNING, -1,
      "   Dest Interface=%d, PeerAddress=%d.%d.%d.%d, TransAddress=%u",
      pa >> 24, (pa >> 16) & 0xFF, (pa >> 8) & 0xFF, pa & 0xFF,
      ntohs(fp->fk.High.Interface), ntohs(fp->fk.High.TransAddress));
   }

void dump_bp(struct pkt *bp)
{
   Bit32 pa;
   log_msg(LOG_WARNING, -1,
      "pkt %x, PeerAddrType=%d, TransAddrType=%d",
      bp, bp->PeerAddrType, bp->TransAddrType);
#if QBYTE_PEER
   pa = ntohl(bp->Low.PeerAddress.qbyte);
#else
   pa = ntohl(bp->Low.PeerAddress.peer32[0]);
#endif
   log_msg(LOG_WARNING, -1,
      "   Source PeerAddress=%d.%d.%d.%d, TransAddress=%u",
      pa >> 24, (pa >> 16) & 0xFF, (pa >> 8) & 0xFF, pa & 0xFF,
      ntohs(bp->Low.TransAddress));
#if QBYTE_PEER
   pa = ntohl(bp->High.PeerAddress.qbyte);
#else
   pa = ntohl(bp->High.PeerAddress.peer32[0]);
#endif
   log_msg(LOG_WARNING, -1,
      "   Dest PeerAddress=%d.%d.%d.%d, TransAddress=%u",
      pa >> 24, (pa >> 16) & 0xFF, (pa >> 8) & 0xFF, pa & 0xFF,
      ntohs(bp->High.TransAddress));
   }

void dump_sp_key(struct stream *sp)
{
   Bit32 pa;
   struct stream_name *sfn = &sp->sfn;
   log_msg(LOG_WARNING, -1, "stream %x: Ruleset=%d, TransAddrType=%d",
      sp, sfn->RuleSet, sfn->TransType);
#if QBYTE_PEER
   pa = ntohl(sfn->SrcPeerAddr);
#else
   pa = ntohl(sfn->SrcPeerAddr[0]);
#endif
   log_msg(LOG_WARNING, -1,
      "   Source PeerAddress=%d.%d.%d.%d, TransAddress=%u",
      pa >> 24, (pa >> 16) & 0xFF, (pa >> 8) & 0xFF, pa & 0xFF,
      ntohs(sfn->SrcTransAddr));
#if QBYTE_PEER
   pa = ntohl(sfn->DestPeerAddr);
#else
   pa = ntohl(sfn->DestPeerAddr[0]);
#endif
   log_msg(LOG_WARNING, -1,
      "   Dest PeerAddress=%d.%d.%d.%d, TransAddress=%u",
      pa >> 24, (pa >> 16) & 0xFF, (pa >> 8) & 0xFF, pa & 0xFF,
      ntohs(sfn->DestTransAddr));
   }

#if STREAM_CHAIN_DEBUG
int mx_chain_len = 0;

int check_flow_hash_chains(char *msg)
{
   int x, n, rv = 0;
   struct flow **sf;
   struct flow *f, *lf;

   for (x = 0; x != fthashmod; ++x) {
      sf = &flow_tbl_htp->hash_ent[x];
      if ((f = sf[0]) == NULL) {
         lf = NULL;  /* Empty hash chain */
         }
      else {
         n = 0; do {
            lf = f;
            if (++n > mx_chain_len) {
               printf("check_f_s_h(%s): hash %d has > %d flows <<<<\n",
                  msg, x, ++mx_chain_len);
               if (n > 15) rv = 1;
               }
            } while ((f = lf->ht_next) != (struct flow *)sf);
         f = NULL;  /* Loop stops with lf -> last flow in chain */
         }
      }
   return rv;
   }

void dump_stream_chain(struct stream_data *std, char *msg)
{
   struct stream *sp, *lsp;
   int n = 0;

   printf("dump_s_c(%s): std=%x  sq=%x", msg, std, sp = std->sq);
   if (sp == NULL)
      printf("->0");
   else do {
      sp = sp->next_sp;
      printf("->%u", sp);
      } while (sp != NULL && ++n <= 10);
   printf("  sq_tail=%x", sp = std->sq_tail);
   if (sp == NULL)
      printf("=>0");
   else do {
      sp = sp->prev_sp;
      printf("=>%x", sp);
      } while (sp != NULL && ++n <= 20);
   printf("\n");  fflush(stdout);
   }

void dump_flow_key(struct flow_key *fkp, int hash, char *msg)
{
   int j;
   printf("flow_key(%s) fkp=%x, hash=%d: PeerAddrType=%d, TransAddrType=%d\n"
      "      FlowClass=%d, FlowKind=%d, MeterId=%d, DSCP=%d\n",
      msg, fkp, hash, fkp->PeerAddrType, fkp->TransAddrType,
      fkp->FlowClass, fkp->FlowKind,fkp-> MeterId, fkp->DSCodePoint);
   printf("   Low: ");
   for (j = 0; j != sizeof(struct key); ++j)
      printf(" %02x", ((Bit8 *)(&fkp->Low))[j]);
   printf("\n  High: ");
   for (j = 0; j != sizeof(struct key); ++j)
      printf(" %02x", ((Bit8 *)(&fkp->High))[j]);
   printf("\n");
   }

int check_stream_chains(struct flow *fp, char *msg)
{  /* Consistency check for a flow's streams */
   struct stream_data *sdp;
   struct stream *fsp, *rsp;
   char buf[100];
   int k, nas, rv;


   if ((sdp = fp->stdata) == NULL)
      return 1;

   nas = sdp->active_streams;
   fsp = sdp->sq;  rsp = sdp->sq_tail;

   for (rv = 1, k = 0; ; ++k) {
      if (fsp == NULL || rsp == NULL) {
         if (fsp != rsp) {
            printf("s_c_c(%s): fsp=%x != rsp=%x\n", msg, fsp, rsp);
            quack(1001);
            rv = 0;
            }
         break;
         }
      fsp = fsp->next_sp;  rsp = rsp->prev_sp;
      }
   if (k != nas) {
      printf("s_c_c(%s): k=%u != nas=%u\n", msg, k, nas);
      for (k=0, fsp = sdp->sq; fsp != NULL; fsp = fsp->next_sp) {
         printf(" %d:%x", k++, fsp);
         }
      printf("\n");
      rv = 0;
      quack(1002);
      }
   return rv;
   }

void check_all_stream_chains(int rs, char *msg)
{
   Bit32 x;
   struct flow *fp;
   char buf[100];
   int k;

   sprintf(buf, "c_a_s_c(%s)", msg);
   x = ri[rs-1].ri_flow_chain;
   for (fp = find_flow(x); fp != NULL; ) {
      if (fp->stdata) {
         if (!check_stream_chains(fp, buf)) exit(123);
         }
      x = fp->rs_next;
      fp = x != 0 ? find_flow(x) : NULL;
      }
   }

int check_stream_hash_chains(char *msg)
{
   int x;
   struct stream **sha;
   struct stream *sp, *lsp, *isp;

   for (x = 0; x != sfhashmod; ++x) {
      sha = &sfht[x];
         /* Address of stream hash table entry */
      if ((sp = *sha) == NULL)
         lsp = NULL;  /* Empty hash chain */
      else {
         isp = sp;
         for (;;) {
	    if (sp->next_hc == NULL) {
               printf("check_stream_h_c(%s): x=%d, isp=%x, lsp=%x, sp=%x\n",
                  msg, x, isp, lsp, sp);
               return 0;
	       }
            lsp = sp;
            if ((sp = lsp->next_hc) == (struct stream *)sha) {
               sp = NULL;  break;
               /* Loop stops with lsp -> last stream in chain */
               }
            }
         }
      }
   return 1;
   }
#endif /* STREAM_CHAIN_DEBUG */

void free_flow(struct flow *q)  /* Free memory allocated for a flow */
       /* Caution: must call mark_idle() before calling free_flow() <<< */
{
#if NEW_ATR
   struct distribution *d, *nd;
   struct pktdata *p, *np;
   struct stream_data *sdp;

   if (q->stdata != NULL) {
      sdp = q->stdata;
      q->stdata = NULL;  /* Don't let it point into stream_data free list! */
      terminate_all_streams(q, sdp);  /* May update distribs! */
      free_st_data(sdp);
      }

   for (d = q->distrib_list; d != NULL; d = nd) {  
      nd = d->next; 
      free_dist(d);
      }
#endif  /* NEW_ATR */

/*   q->FlowRuleSet = 0;  /* Now it doesn't look like part of an rs chain! */
   /* Don't need this, it's done in mark_idle().  Nevil, 15 May 99 */
   }

#if NEW_ATR
static int complained_about_distribs = 0;
struct distribution *get_dist(void)  /* Get an unused distribution */
{
   struct distribution *d;
   if (free_distribs != NULL) {
      d = free_distribs;  free_distribs = free_distribs->next;
      --n_free_distribs;
      return d;
      }
   else {
      if (!complained_about_distribs) {
         log_msg(LOG_WARNING, 0, "Not enough distributions, increase -v <<<\n");
         complained_about_distribs = 1;
         }
      return NULL;
      }
   }

void free_dist(struct distribution *d)  /* Return distribution to free list */
{
   switch (d->selector) {
   case FTTOBITRATE:
   case FTFROMBITRATE:
   case FTTOPDURATE:
   case FTFROMPDURATE:
      unlink_distrib_from_event(d);
      break;
      }
   d->next = free_distribs;  free_distribs = d;
   ++n_free_distribs;
   complained_about_distribs = 0;
   }

static int complained_about_events = 0;
struct event *get_event(void)  /* Get an unused event */
{
   struct event *e;
   if (free_events != NULL) {
      e = free_events;  free_events = free_events->next;
      --n_free_events;
      return e;
      }
   else {
      if (!complained_about_events) {
         log_msg(LOG_WARNING, 0, "Not enough dist events, increase -e <<<\n");
         complained_about_events = 1;
         }
      return NULL;
      }
   }

void free_event(struct event *e)  /* Return event to free list */
{
   e->next = free_events;  free_events = e;
   ++n_free_events;
   complained_about_events = 0;
   }

void schedule_event(struct event *ev)
{
   struct event *eqp, *lqp;
   lqp = &eventq;  eqp = eventq.next;
   for ( ; eqp != NULL; lqp = eqp, eqp = eqp->next) {
      if (ev->t <= eqp->t) break;
      }
   lqp->next = ev;  ev->next = eqp;
   return;
   }

static int complained_about_pktdata = 0;
struct pktdata *get_pktdata(void)  /* Get an unused pktdata */
{
   struct pktdata *pd;
   if (free_pkt_dat != NULL) {
      pd = free_pkt_dat;  free_pkt_dat = free_pkt_dat->next;
      --n_free_pktdata;  ++PktsCreated;
      return pd;
      }
   else {
      if (!complained_about_pktdata) {
         log_msg(LOG_WARNING, 0, "Not enough packet data blocks, increase -a <<<\n");
         complained_about_pktdata = 1;
         }
      return NULL;
      }
   }

void free_pktdata(struct pktdata *pd)  /* Return pktdata to free list */
{
   pd->next = free_pkt_dat;  free_pkt_dat = pd;
   ++n_free_pktdata;  ++PktsRecovered;
   complained_about_pktdata = 0;
   }

int make_distrib_list(struct flow *f);
void bump_dist(  /* Add point y into distrib d */
   struct distribution *d, double y);
double inv_scale(struct distribution *d, Bit16 j);  /* limit -> value */
Bit32 scale(struct distribution *d, double y);

void compute_rate(struct distribution *d)
{
   struct flow *f;
   counter64 r;
   double delta;
   f = d->fp;
   switch (d->selector) {
   case FTTOBITRATE:
      subtr64(r, f->UpOctets,f->LastUpOctets);
      assign64(f->LastUpOctets, f->UpOctets);
      delta = (doublefrom64(r))*8.0;  /* Bits */
      break;
   case FTFROMBITRATE:
      subtr64(r, f->DownOctets,f->LastDownOctets);
      assign64(f->LastDownOctets, f->DownOctets);
      delta = (doublefrom64(r))*8.0;  /* Bits */
      break;
   case FTTOPDURATE:
      subtr64(r, f->UpPDUs,f->LastUpPDUs);
      assign64(f->LastUpPDUs, f->UpPDUs);
      delta = doublefrom64(r);  /* Packets */
      break;
   case FTFROMPDURATE:
      subtr64(r, f->DownPDUs,f->LastDownPDUs);
      assign64(f->LastDownPDUs, f->DownPDUs);
      delta = doublefrom64(r);  /* Packets */
      break;
      }
   bump_dist(d, delta/((double)d->RateInterval));
   }

void check_rates(Bit32 now)
{
   struct event *eqp;
   struct distribution *d;
   for (;;) {
      eqp = eventq.next;
      if (eqp == NULL || eqp->t > now) return;
      eventq.next = eqp->next;  /* Remove from event queue */
      if ((d = eqp->dp) == NULL)  /* No distributions queued */
         free_event(eqp);
      else {
         do {
            compute_rate(d);
            d = d->nrate;
            } while (d != NULL);
         eqp->t += eqp->interval;
         schedule_event(eqp);      
         }
      }
   }

void link_distrib_to_event(struct distribution *d)
{
   struct event *eqp;
   struct distribution *dq;
   for (eqp = eventq.next; eqp != NULL; eqp = eqp->next) { 
      if (eqp->interval == d->RateInterval) break;
      }
   if (eqp == NULL) {  /* Make new event for this interval */
      if ((eqp = get_event()) != NULL) {
         eqp->dp = NULL;
         eqp->interval = d->RateInterval;
         eqp->t = uptime_s() + eqp->interval;
         schedule_event(eqp);
         }
      }
   if (eqp != NULL) {  /* Add distrib to head of event chain */
      /* Was eqp == NULL, fixed Sat 3 Jun 00 */
      d->nrate = eqp->dp;
      eqp->dp = d;
      }
   }

#if DIST_DBG
void print_dist_chain(struct event *ep)
{
   struct distribution *dq;
   for (dq = ep->dp; dq != NULL; dq = dq->nrate)
      printf("%d@%lu ", dq->fp->fk.PeerAddrType, dq);
   printf("\n");
   }
#endif

void unlink_distrib_from_event(struct distribution *d)
{
   struct event *eqp;
   struct distribution *dq, *lq;
   for (eqp = eventq.next; eqp != NULL; eqp = eqp->next) { 
      if (eqp->interval == d->RateInterval) break;
      }
#if DIST_DBG
   printf("unlink_distrib(%lu): ", d);  /* $$$ */
   print_dist_chain(eqp);
#endif
   if (eqp == NULL) {
      scpos(0,scr_lrow);
      log_msg(LOG_ERR, 0, 
         "No event for %ds rate distribution <<<", d->RateInterval);
      return;
      }
   for (lq = NULL, dq = eqp->dp; dq != d; lq = dq, dq = dq->nrate) {
      if (dq == NULL) {
         scpos(0,scr_lrow);
         log_msg(LOG_ERR, 0, "Couldn't find rate distribution <<<");
         return;
         }
      }
   if (lq == NULL) eqp->dp = d->nrate;
   else lq->nrate = d->nrate;
#if DIST_DBG
   printf("   ");  /* $$$ */
   print_dist_chain(eqp);
#endif
   }

static int complained_about_str_blocks = 0;
struct stream_data *get_st_data(void)  /* Get unused st_data */
{
   struct stream_data *sh;
   if (free_str_blocks != NULL) {
      sh = free_str_blocks;  free_str_blocks = free_str_blocks->next;
      --n_free_str_blocks;
      memset(sh, 0, sizeof(struct stream_data));
      return sh;
      }
   else {
      if (!complained_about_str_blocks) {
         log_msg(LOG_WARNING, 0, "Not enough stream data blocks, increase -b <<<\n");
         complained_about_str_blocks = 1;
         }
      return NULL;
      }
   }

void free_st_data(struct stream_data *sh)  /* Return st_data to free list */
{
   sh->next = free_str_blocks;  free_str_blocks = sh;
   ++n_free_str_blocks;
   complained_about_str_blocks = 0;
   }

static int complained_about_streams = 0;
struct stream *get_stream(void)
{
   struct stream *sf;
   if (free_streams != NULL) {
      sf = free_streams;  free_streams = free_streams->next_sp;
      --n_free_streams;  ++StreamsCreated;
      return sf;
      }
   else {
      if (!complained_about_streams) {
         log_msg(LOG_WARNING, 0, "Not enough stream space, increase -t <<<\n");
         complained_about_streams = 1;
         }
      return NULL;
      }
   }

void free_stream(struct stream *sp)
{
   sp->next_sp = free_streams;  free_streams = sp;
   ++n_free_streams;  ++StreamsRecovered;
   complained_about_streams = 0;
   }
#endif  /* NEW_ATR */

#ifdef DEBUG
FILE *logfl;
int logf_nbr = 1;

void open_log()
{
   char fn[30];
   sprintf(fn,"flows.%03d",logf_nbr++);
   logfl = fopen(fn, "w");
   }

void paddr(Bit32 a)
{
   fprintf(logfl,"%u.%u.%u.%u",
      a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF);
   }

void daddr(Bit32 a)
{
   printf("%u.%u.%u.%u",
      a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF);
   }

void dport(Bit16 a)
{
   printf("%u", a);
   }

void fdaddr(FILE *f, Bit32 a)
{
   fprintf(f, "%u.%u.%u.%u",
      a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF);
   }

void fdport(FILE *f, Bit16 a)
{
   fprintf(f, "%u",a);
   }

void pkey(char *msg, struct flow *f)
{
   if (!logfl) open_log();
   fprintf(logfl,"%s: %lu  %u\n   {", msg,f, f->fk.PeerAddrType);
#if QBYTE_PEER
   paddr(f->fk.Low.PeerAddress.qbyte); fprintf(logfl," & ");
#else
   paddr(f->fk.Low.PeerAddress.peer32[0]); fprintf(logfl," & ");
#endif
   paddr(*(Bit32 *)Masks[f->fk.Low.PeerMaskVal].Val); 
   fprintf(logfl,"} -> {");
#if QBYTE_PEER
   paddr(f->fk.High.PeerAddress.qbyte); fprintf(logfl," & ");
#else
   paddr(f->fk.High.PeerAddress.peer32[0]); fprintf(logfl," & ");
#endif
   paddr(*(Bit32 *)Masks[f->fk.High.PeerMaskVal].Val);
   fprintf(logfl,"}\n");
   }
#endif

#define DRT_SIZE  3  /* Default rule set: 3 rules */
#ifdef OLD_CT
#define DCT_SIZE  1  /*   1 count */
#endif
struct rule default_rule_table[DRT_SIZE] = {
   /* Value,    Selector, Mask, JumpIndex, Action */
   {
# if CLNS
      {AT_DUMMY,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
# elif V6 || FULL_IPX
      {AT_DUMMY,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
# else  /* v4 */
      {AT_DUMMY,0,0,0,0,0},
# endif
      FTLOWPEERTYPE, 1, 0, RA_IGNORE},
      /* SourcePeerType & 255 = dummy: Ignore, 0; # 1 */
   {
# if CLNS
      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
# elif V6 || FULL_IPX
      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
# else  /* v4 */
      {0,0,0,0,0,0},
# endif
      RS_NULL, 0, 3, RA_GOTOACT},
      /* Null & 0 = 0: GotoAct, Next; # 2 */
   {
# if CLNS
      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
# elif V6 || FULL_IPX
      {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
# else  /* v4 */
      {0,0,0,0,0,0},
# endif
      FTLOWPEERTYPE, 1, 1, RA_COUNTPKT}
      /* SourcePeerType & 255 = 0: CountPkt, 1; # 3 */
   };

Bit32 p2primes[] = {  /* Primes just below powers of two */
   7, 13, 31, 61, 127, 251, 509, 1021,      /*  0 .. 7  1024 */
   2039, 4093, 8191, 16381, 32749, 65521,   /*  8 .. 13  65536 */
   131071, 262139, 524287, 1048573};        /* 14 .. 17 1048576 */

#if 0
 int primes[22] = {  /* Two for Trans bytes + enough for NSAP bytes */
   211, 251,
   113, 139, 173, 227, 179, 193, 181, 131,  67,  73, 
    83,  97, 103, 127, 149,  23,  53,  89,  59,  47};
#endif

Bit32 primes[22] = {  /* Two for Trans bytes + enough for NSAP bytes */
   97241, 98467, 97171, 95881, 96953,
   95027, 93559, 95089, 96289, 97039,
   97301, 99761, 95803, 94873, 95131,
   96457, 91867, 94007, 97501, 99961,
   96337, 95383};

#define  prime_0  95027
#define  prime_1  93559
#define  prime_2  95089
#define  prime_3  96289
#define  prime_4  97039
#define  prime_5  97301
#define  prime_6  99761
#define  prime_7  95803
#define  prime_8  94873
#define  prime_9  95131
#define prime_10  96457
#define prime_11  91867
#define prime_12  94007
#define prime_13  97501
#define prime_14  99961
#define prime_15  96337
#define prime_16  95383

Bit32 compute_rule_hash(int attrib, union address *rvp)
{  /* Must compute these **exactly** as for hashed compare in match */
#if !QBYTE_PEER
   Bit32 hash;
   int j;
#endif

   switch (attrib) {
   case FTLOWPEERADDRESS:
   case FTHIPEERADDRESS:
#if QBYTE_PEER
      return rvp->qbyte*prime_0;
#else
      for (j = hash = 0;  j !=(PEER_ADDR_LEN+3)/4;  ++j) hash += 
         rvp->peer32[j]*primes[j];
      return hash;
#endif

   case FTLOWPEERTYPE:
   case FTHIPEERTYPE:
      return rvp->byte*prime_1;
   case FTLOWTRANSADDRESS:
   case FTHITRANSADDRESS:
      return rvp->trans*prime_2;
   case FTLOWTRANSTYPE:
   case FTHITRANSTYPE:
      return rvp->byte*prime_3;
   case FTLOWADJACENTADDRESS:
   case FTHIADJACENTADDRESS:
      return rvp->ma.ms4*prime_4 + rvp->ma.ls2*prime_5;
   case FTSOURCECLASS:
   case FTDESTCLASS:
   case FTFLOWCLASS:
      return rvp->byte*prime_9;
   case FTSOURCEKIND:
   case FTDESTKIND:
   case FTFLOWKIND:
      return rvp->byte*prime_10;
   case FTLOWINTERFACE:
   case FTHIINTERFACE:
      return rvp->byte*prime_7;
   case FTLOWADJACENTTYPE:
   case FTHIADJACENTTYPE:
      return rvp->byte*prime_6;
   case FTDSCODEPOINT:
      return rvp->byte*prime_8;

#if NF_ASN_ATT
   case FTLOWROUTEASN:
   case FTHIROUTEASN:
      return rvp->byte*prime_11;
   case FTLOWROUTEPREFIX:
   case FTHIROUTEPREFIX:
      return rvp->byte*prime_12;
#endif
#if NF_OTHER_ATT
   case FTMETERID:
      return rvp->byte*prime_13;
#endif
      }
   }

int optimise_rule_table(int rs)
{
   struct rtinf_rec *rip = &ri[rs-1];
   Bit16 crs, j, k, g, h, x, l,
      nht;  /* Nbr of rule_hash_tbls */
   Bit32 ths,  /* Sum of rule_hash_tbl sizes */
      rhss;  /* Total rule_hash sequence size */
   struct rule *crt, *rp, *hp;
   Bit16 *rs_rule_hash;
   struct rule_hash_tbl *c_rh;
   Bit32 hash;
   char msg[60];

   crt = rip->ri_rule_table;
   crs = rip->ri_Size;
#ifdef H_TEST
   scpos(0,scr_lrow);  printf("\nRT: crs=%u, RULE_ADDR_LEN=%d\n",
      crs,RULE_ADDR_LEN);
#endif
   hp = &crt[0];  hp->hash_tbl_index = 1;
   for (j = 1;  j != crs;  ++j) {  /* Mark match break points */
      rp = &crt[j];

      rp->push_rule = (  /* For build_search_key */
         (rp->RuleAction != RA_PUSHPKTTO) &&
         (rp->RuleAction != RA_PUSHPKTTOACT) &&
         (rp->RuleAction != RA_COUNTPKT) );

      rp->hash_tbl_index = rp->RuleSelector != hp->RuleSelector ||
         rp->RuleMaskVal != hp->RuleMaskVal;
      hp = rp;
      }
   for (j = 0;  j != crs;  ++j) {  /* Mark goto break points */
      rp = &crt[j];  rp->hash_link = 0;
      k = rp->RuleAction;
      if (k == RA_PUSHTO  /* OK to gotoact in middle of compare group */
            || k == RA_PUSHPKTTO || k == RA_GOTO
            || k == RA_GOSUB     || k == RA_ASSIGN
            || k == RA_POPTO
            ) {
         hp = &crt[rp->RuleJumpIndex-1];
         hp->hash_tbl_index = 2;
         }
      }
#ifdef H_TEST
   scpos(0,scr_lrow);  printf("rule  index  link  MARKED\n");
   for (j = 0;  j != crs;  ++j) {
      rp = &crt[j];
      printf("%3d %5d %5d  %d  ",
         j,rp->hash_tbl_index,rp->hash_link, rp->RuleSelector);
      daddr((Bit8 *)Masks[rp->RuleMaskVal].Val);
      printf("\n");
      }
#endif
   nht = ths = 0;
   hp = &crt[0];  k = 1;
   for (j = 1;  j != crs;  ++j) {  /* Find compare groups */
      rp = &crt[j];
      if ((g = rp->hash_tbl_index) == 0) ++k;  /* Same group */
      if (g != 0 || j == crs-1) {  /* End of a group */
         if (k >= MNCGRPSZ && hp->RuleSelector != RS_NULL) {
            for (h = 0; h != sizeof(p2primes)/sizeof(Bit32)-1 &&
               p2primes[h] <= (Bit32)k; ++h) ;  /* h = next prime above k */
            if (p2primes[h] > MXRHTSZ) h = MXRHTSZ;
            else h = p2primes[h];
            hp->hash_tbl_index = k;  /* Remember group and */
            hp->hash_link = h;       /*   hash table sizes */
            ++nht;  ths += h;
            }
         else hp->hash_tbl_index = 0;
         hp = rp;  k = 1;
         }
      rp->hash_tbl_index = 0;  /* Last row can't start a group */
      }
   rhss = 1 + nht*2 + ths;  /* rule_hash set size (Bit16s) */
   if (rhss > 0xFFFF) {
      log_msg(LOG_ERR, 0, msg,"Ruleset '%s': rhss = %ul (> 65535)", 
         rip->ri_Name, rhss);
      return 0;  /* Fail */
      }
#ifdef H_TEST
   printf("rule  index  link  GROUPED\n");
   for (j = 0;  j != crs;  ++j) {
      rp = &crt[j];
      printf("%3d %5d %5d\n", j, rp->hash_tbl_index,rp->hash_link);
      }
#endif

   if (!alloc_rulespace(rs, 0, rhss))  /* Alloc rule hash space */
      return 0;  /* No room for rule hash table! */

   rs_rule_hash = rip->ri_rule_hash;
   rs_rule_hash[0] = 0;  k = 1;  rp = &crt[j = 0];
   for ( ; nht != 0; --nht, k += 2+h) {
      c_rh = (struct rule_hash_tbl *)&rs_rule_hash[k];
      while (rp->hash_tbl_index == 0) {
         ++rp; ++j;
         }
      g = rp->hash_tbl_index;  /* Group size */
      h = rp->hash_link;  /* Hash table size */
      rp->hash_tbl_index = k;  /* Index of hash_tbl in rule_hash */
      rp->hash_link = 0;
      c_rh->group_last = j+g;
      c_rh->hashmod = h;
      for (x = 0; x != h; ++x) c_rh->hash_ent[x] = 0;
      for ( ; g != 0; --g) {  /* Build rule hash tables */
         hash = compute_rule_hash(rp->RuleSelector, 
            &rp->RuleMatchedValue) % h;

         if ((l = c_rh->hash_ent[hash]) == 0)
            c_rh->hash_ent[hash] = j+1;  /* First rule with this hash */
         else {
            do {
               hp = &crt[l-1];  l = hp->hash_link;
               } while (l != 0);
            hp->hash_link = j+1;  /* Add to end of hash chain */
            }
         ++rp; ++j;
         }
      }
#ifdef H_TEST
   printf("Rule Hash Tables ..\n\nrule index  n_rules n_hash n_zero\n\n");
   for (j = 0;  j != crs;  ++j) {
      rp = &crt[j];
      if ((k = rp->hash_tbl_index) == 0) continue;
      c_rh = (struct rule_hash_tbl *)&rs_rule_hash[k];
      h = c_rh->hashmod+1;
      for (g = 0, hash = 0; hash != h; ++hash)
         if (c_rh->hash_ent[hash] == 0) ++g;
      printf("%5d%5d  %5d%5d%5d\n     ",
         j+1, k, c_rh->group_last-j+1, h, g);
      printf("\n");
      }
#endif
   return 1;
   }

void empty_hash_chain(int rs)  /* Unlink flows for ruleset rs */
{
   Bit32 k;
   struct flow **sf;
   struct flow *fp, *lfp;
   struct flow *chp, *ctp;

   /* Could walk the rs chain,rather than linear searching the whole
      whole table!  But we don't shut down rule sets very often! */

   for (k = 0; k != fthashmod; ++k) {
      sf = &flow_ht[k];  /* Address of hash table entry */
      if ((fp = *sf) != NULL) {
         chp = ctp = NULL;  /* New chain for remaining active flows */
         do {
            lfp = fp;  fp = lfp->ht_next;
            if (lfp->FlowRuleSet == rs)
               mark_orphan(lfp);
            else {
               if (chp == NULL) chp = ctp = lfp;
               else {
                  ctp->ht_next = lfp;
                  ctp = lfp;
                  }
               }
            } while (fp != (struct flow *)sf);
         if (chp != NULL) {
            ctp->ht_next = fp;
            *sf = chp;
            }
         else {  /* No remaining active flows */
            *sf = NULL;
            --n_hash_ents;
            }
         }
      }
   }

#if PME_TRACE
#define MXTRACE      250
#define MXTRACE_MSGS   5  /* Per rule set */

static Bit16 ruletrace[MXTRACE], rtx;
static int tr_msgs_left[MXRTBLS];
#endif

void open_rule_set(int n, int open_rs)
{
   int lrx,rrx, k, p;
   Bit16 rsx;
   struct rule *rp;
   if (n == 0) return;  /* 0 = no rule set to run */
   if (ri[n-1].ri_Status != RS_ACTIVE) return;

   if (open_rs) {
      if (++ri[n-1].ri_running != 1)  /* Already running */
         return;
      else {
         if (running_rts == 0)  /* Starting first ruleset */
            running_rts = n;
         else {
            for (rrx = running_rts; ; ) {
               if (ri[rrx-1].next_running_rt == 0) break;
               rrx = ri[rrx-1].next_running_rt;
               }
            ri[rrx-1].next_running_rt = n;
            }
         ri[n-1].next_running_rt = 0;
#if PME_TRACE
         tr_msgs_left[n-1] = MXTRACE_MSGS;
#endif
         }
      }
   else {  /* Close ruleset */
      if (--ri[n-1].ri_running != 0)  /* Still running */
         return;

      for (lrx = 0, rrx = running_rts; rrx != n; ) {
         lrx = rrx;  rrx = ri[rrx-1].next_running_rt;
         }
      if (lrx == 0) running_rts = ri[rrx-1].next_running_rt;
      else ri[lrx-1].next_running_rt = ri[rrx-1].next_running_rt;
      ri[n-1].next_running_rt = 0;

      empty_hash_chain(n);
      }

   c_rt = ri[n-1].ri_rule_table;  c_rsz = ri[n-1].ri_Size;
   for (rsx = 0; rsx != c_rsz; ++rsx) {
      rp = &c_rt[rsx];
      if (rp->RuleSelector == FTLOWPEERTYPE ||
            rp->RuleSelector == FTHIPEERTYPE) {
         p = rp->RuleMatchedValue.rule[0];
         if (rp->RuleAction == RA_PUSHPKTTO ||  /* All peer types */
               rp->RuleAction == RA_PUSHPKTTOACT ||
               rp->RuleAction == RA_COUNTPKT)
            for (k = 0; k != sizeof(proto_reqd)/sizeof(int); ++k) {
               if (open_rs) ++proto_reqd[k];  
               else --proto_reqd[k];
               }
         else if (p <= MX_PROTOCOLS && *proto_names[p] != '\0') {
 	    /* Was & above!  Sebastian Zander, 6 Jan 2000 */
            if (open_rs) ++proto_reqd[p];  /* Specific peer type */
            else --proto_reqd[p];
            }
         }
      if ((rp->RuleSelector == FTLOWADJACENTADDRESS ||
            rp->RuleSelector == FTHIADJACENTADDRESS) ||
            ((rp->RuleAction == RA_ASSIGN ||
            rp->RuleAction == RA_ASSIGNACT) &&
            (rp->RuleMatchedValue.rule[0] == FTLOWADJACENTADDRESS ||
            rp->RuleMatchedValue.rule[0] == FTHIADJACENTADDRESS)) )
         if (open_rs) ++adj_reqd;
         else --adj_reqd;
#if NF_OCX_BGP
      if (rp->RuleSelector == FTLOWROUTEASN ||
            rp->RuleSelector == FTHIROUTEASN)
         if (open_rs) ++asn_lookup;
         else --asn_lookup;
#endif
      }
   if (adj_reqd) {
      for (k = 0; k != sizeof(proto_reqd)/sizeof(int); ++k)
         if (proto_reqd[k]) return;  /* At least one protocol specified */
      for (k = 0; k != sizeof(proto_reqd)/sizeof(int); ++k) {
         if (open_rs) ++proto_reqd[k];  /* All peer types */
         else --proto_reqd[k];
         }
      }
   }

#ifdef GC_DEBUG
int dup_flow(struct flow *f, char *msg)
{
   Bit32 x;
   for (x = 253; x <= mxflows; ++x)
      if (flow_ix[x-1] != NULL) {
         scpos(0,scr_lrow);
         printf("   flow_ix[%d]=%lu !!\n", x,flow_ix[x-1]);
         }
   for (x = 1; x <= mxflows; ++x)
      if (flow_ix[x-1] == f) {
         scpos(0,scr_lrow);
         printf("Flow %lu is flow_ix[%d], msg=%s\n", f,x, msg);
         exit(0);
         }
   return 0;
   }
#endif

Bit32 rsc_n_searches,
   rsc_mx_range, rsc_av_range,  /* Flow index range searched */
   rsc_mx_tests, rsc_av_tests;  /* Nbr of TimeFilter tests */

# if RS_CH_DEBUG
void check_rs_chain(int rs, char *msg)
{
   Bit32 fx;
   int count;
   for (count = 0, fx = ri[rs-1].ri_flow_chain;
         fx != 0;  fx = fa[fx-1].rs_next) {
      if (++count > ri[rs-1].ri_FlowRecords) break;
      }
   if (count != ri[rs-1].ri_FlowRecords)
      printf("check_rs_chain(%d, %s): count=%d, FlowRecords=%d\n",
         rs,msg, count, ri[rs-1].ri_FlowRecords);
   }
# endif

void mark_idle(Bit32 fx)
{
   struct flow *fp = &fa[fx-1];
   fp->FlowRuleSet = 0;   /* Mark idle */
   if (free_flow_tail == 0) free_flow_head = fx;
   else fa[free_flow_tail-1].rs_next = fx;  /* Add to tail of free chain */
   free_flow_tail = fx;
   fp->rs_next = 0;  /* End marker for rs chain */
   }

Bit32 alloc_flow_nbr(void)  /* Allocate nbr for a new a flow */
{
   Bit32 fx;

   if (already_complained)  /* We know there are no free flows */
      return 0;  /* already_complained reset by set_LastTime() */

   if (free_flow_head == 0) {  /* No idle flows: force garbage collection */
      if (garbage_collect(0))
         already_complained = 0;
      }
   if (free_flow_head != 0) {
      fx = free_flow_head;
      free_flow_head = fa[fx-1].rs_next;  /* Take from head of free chain */
      if (free_flow_head == 0) free_flow_tail = 0;
      return fx;
      }
   if (already_complained) return 0;
   more_garbage();  /* Increase garbage collecting rate */
# if defined(DOS)
   scpos(0,scr_lrow);  printf("Recovering flows too slow !!!");
# else
   log_msg(LOG_WARNING, 0, "Recovering flows too slow !!!\n");
# endif
   already_complained = 1;
   return 0;
   }

Bit32 number_flow()  /* Allocate a flow_nbr */
{
   Bit32 start_ix = empty_ix;

   if (already_complained)  /* We know there are no free flows */
      return 0;  /* already_complained reset by set_LastTime() */

   do {
      if (++empty_ix > mxflows)  /* empty_ix goes from 1 to MXFLOWS */
         empty_ix = 1;  /* Flows go from 1 to mxflows too */
      if (flow_idle(empty_ix)) {
#  ifdef GC_DEBUG
         printf("nbr_flow(%lu) -> %u\n", fp,empty_ix);
#  endif
         return empty_ix;  /* 1-org */
         }
      } while (empty_ix != start_ix);

   if (garbage_collect(0)) {  /* No unused flows: force garbage collection */
      already_complained = 0;
      return empty_ix = gcf_ix;  /* 1-org */
      }
   if (already_complained) return 0;
   more_garbage();  /* Increase garbage collecting rate */
#if defined(DOS)
   scpos(0,scr_lrow);  printf("Recovering flows too slow !!!");
#else
   printf("Recovering flows too slow !!!\n");
#endif
   already_complained = 1;
   return 0;
   }

struct flow *find_flow(Bit32 x)  /* Find flow with flow_nbr x */
{
   if (x > mxflows || x < 1) {
      log_msg(LOG_ERR, 0, "Invalid flow number (%d)", x);
      return NULL;
      }
#if FLOW_INDEX
   return flow_ix[x-1];
#else
   return &fa[x-1];
#endif
   }

void more_garbage()
{
   int max_gc_f;
   gc_interval >>= 1;  /* Decrease garbage collection interval */
   if (gc_interval < 2) {
      gc_interval = 2;
      max_gc_f = (mxflows)/7;
      gc_f <<= 1;  /* Increase nbr of flows checked each collection */
      if (gc_f > max_gc_f) gc_f = max_gc_f;
      }
   }

int garbage_collect(int incremental)
{
   int f,e;
   Bit32 s_gcf_ix;
   Bit32 GCtime;
   struct rule *rp;
   struct flow *fp, *np, *tp, *ltp;
#ifdef GC_DEBUG
   int k;
#endif
   Bit32 rs_first_flow;
   struct flow *pf;
   int rs;

   e = gc_f;  f = gc_f/4;
#ifdef GC_TEST1
   scpos(0,scr_lrow);
   printf("GC: gcf_ix=%u, e=%d, f=%d\n", gcf_ix,e,f);
#endif
   s_gcf_ix = gcf_ix;
   do {
      if (++gcf_ix > mxflows)  /* gcf_ix goes from 1 to MXFLOWS */
         gcf_ix = 1;
      if (flow_idle(gcf_ix)) {  /* Unused flow index */
         if (incremental && --e == 0) return 1;
         continue;
         }
#if FLOW_INDEX
      fp = flow_ix[gcf_ix-1];
#else
      fp = &fa[gcf_ix-1];
#endif
      GCtime = ri[fp->FlowRuleSet-1].ri_gc_time;
#ifdef GC_TEST1
      printf("   GCtime=%lu, FlowRuleSet=%u, gcf_ix=%lu\n",
         GCtime,fp->FlowRuleSet,gcf_ix);
#endif
      if (fp->ntm_LastTime <= GCtime) {  /* Inactive, recover it */
#ifdef GC_TEST1
         printf("   f=%d, gcf_ix=%d, ntm_LastTime=%lu\n",
            f,gcf_ix,fp->ntm_LastTime);
#endif
         if (!flow_orphan(fp)) {  /* Remove from hash chain */
#ifdef GC_DEBUG
            scpos(0,scr_lrow);
            printf("GC: set=%u, ix=%u, fp=%lu, np=%lu\n",
               fp->FlowRuleSet,gcf_ix, fp,fp->ht_next);
            k = 0;
#endif
            np = fp->ht_next;
            tp = fp;  do {
               ltp = tp;
#ifdef GC_DEBUG
printf("   k=%d, ltp=%lu, tp=%lu\n", ++k, ltp,ltp->ht_next);
if (tp->ht_next == NULL || k > 10) exit(0);  /* Infinite loop - stop */
#endif
               } while ((tp = tp->ht_next) != fp);
            if (ltp == np) {  /* Single flow in hash chain */    
               mark_orphan(ltp);
               --n_hash_ents;
	       }
            else ltp->ht_next = np;
            mark_orphan(fp);  /* Added 15 May 99, Nevil */
            }

         --ri[fp->FlowRuleSet-1].ri_FlowRecords;
         rs = fp->FlowRuleSet;
         rs_first_flow = ri[rs-1].ri_flow_chain;
         if (gcf_ix == rs_first_flow) {  /* First flow of chain */
            ri[rs-1].ri_flow_chain = fp->rs_next;
            }
         else {  /* Find previous link in ruleset chain */
            pf = fp;  do {
               --pf;
               } while (pf->FlowRuleSet != rs);
            pf->rs_next = fp->rs_next;
            }
# if RS_CH_DEBUG
         check_rs_chain(rs, "GC");
# endif
         mark_idle(gcf_ix);

#ifdef GC_DEBUG
         printf("\ngarbage_collect ..\n");
#endif
         free_flow(fp);
         --nflows;  ++FlowsRecovered;
         if (!incremental) return 1;  /* Forced collection succeeded,
                                         flow index gcf_ix is now free */
#ifdef GC_TEST
         scpos(0,scr_lrow);
         printf("Flow %d recovered\n", gcf_ix);
#endif
         }
      else {  /* Not inactive */
#if NEW_ATR
         if (fp->stdata != NULL &&           /* Flow has >= 1 stream attribute */
             fp->stdata->pp_match_reqd &&    /* Packet-pair matching required */
	    (fp->fk.PeerAddrType == AT_IP4
# if V6
               || fp->fk.PeerAddrType == AT_IP6
# endif
               ) && fp->fk.TransAddrType == PT_TCP)  /* It's a TCP flow */
	    check_tcp_idle_streams(fp, 1);
#endif  /* NEW_ATR */
         }
      if (incremental && --f == 0) return 1;
      } while (gcf_ix != s_gcf_ix);
   return 0;  /* Failed to recover any flows */
   }

#define MIGNORE 2  /* match() result */
#define MSUC    1
#define MFAIL   0
#define MFWD    1  /* count() parameter */
#define MREV    0

struct search_result {
   struct flow **sf;  /* Address of hash table entry */
   struct flow *lf;  /* Last flow in hash chain */
   };
int match(int forward, struct pkt *bp,
   struct pkt_key *from, struct pkt_key *to);
void build_search_key(Bit32 *search_hash, struct flow_key *key,
   struct pkt *bp, struct pkt_key *Low, struct pkt_key *High);
struct flow *current(struct search_result*s_r,
   Bit32 hash, struct flow_key *search_key);
struct flow *create(struct search_result *s_r,
   struct flow_key *search_key, struct pkt *bp);
void count(struct flow *fp,
   int flow_order, struct pkt *bp);

Bit16 p_stack[RSTKSIZ];  /* Pattern stack */
   /* Was int p_stack.  22 Apr 98 */
int a_stack[RSTKSIZ];  /* Attribute values for pattern stack */
int p_s_depth;

Bit8 user_vbls[N_VBLS];  /* User variables */
Bit16 env_stack[ESTKSIZ];  /* Environment stack */
int e_s_tos;

#ifdef GC_DEBUG
void print_chain(struct hash_tbl *t, Bit16 hash, char *msg)
{
   struct flow **sf;
   struct flow *f, *lf;
   sf = &t->hash_ent[hash];  /* Address of hash table entry */
   scpos(0,scr_lrow);  printf("Chain: t=%lu, hash=%u, sf=%lu, msg=%s\n",
      t,hash,sf, msg);
   lf = f = sf[0];
   do {
      printf("   f=%lu\n", f);
      if ((lf = f) == NULL) return;
      } while ((f = lf->ht_next) != (struct flow *)sf);
   }
#endif

void build_search_key(Bit32 *search_hash, struct flow_key *key,
   struct pkt *bp, struct pkt_key *Low, struct pkt_key *High)
   /* Builds a key which current() will use to search the flow table */
{
   Bit32 hash;
   struct rule *rp;
   int pal, j, k;
   union address *Maskp;

   hash = c_rtx*prime_14;
      /* Include ruleset ix in search key.  Nevil, 11 Sep 98 */
   memset(key, 0, sizeof(struct flow_key));  /* Start with a null key */
   pal = (PEER_ADDR_LEN+3)/4;  /* Could be any type, start by using max */
#ifdef TESTING
   if (!logfl) open_log();
   fprintf(logfl,"p_s_depth=%d\n", p_s_depth);
#endif
   for (j = 0; j != p_s_depth; ++j) {
      rp = &c_rt[p_stack[j]-1];
      Maskp = Masks[rp->RuleMaskVal].Val;
#ifdef TESTING
      fprintf(logfl,"   j=%d, attrib=%d\n", j,a_stack[j]);
#endif
      switch (a_stack[j]) {
      case FTLOWPEERADDRESS:
         key->Low.PeerMaskVal = rp->RuleMaskVal;
#if QBYTE_PEER
         if (rp->push_rule)
            hash += (key->Low.PeerAddress.qbyte =
               rp->RuleMatchedValue.qbyte)*prime_0;
         else
            hash += (key->Low.PeerAddress.qbyte =
               Low->PeerAddress.qbyte & Maskp->qbyte)*prime_0;
#else
         if (rp->push_rule) for (k = 0; k != pal; ++k)
            hash += (key->Low.PeerAddress.peer32[k] =
               rp->RuleMatchedValue.peer32[k])*primes[k];
         else for (k = 0; k != pal; ++k)
            hash += (key->Low.PeerAddress.peer32[k] =
               Low->PeerAddress.peer32[k] & Maskp->peer32[k])*primes[k];
#endif
         break;
      case FTHIPEERADDRESS:
         key->High.PeerMaskVal = rp->RuleMaskVal;
#if QBYTE_PEER
         if (rp->push_rule)
            hash += (key->High.PeerAddress.qbyte =
               rp->RuleMatchedValue.qbyte)*prime_0;
         else
            hash += (key->High.PeerAddress.qbyte =
               High->PeerAddress.qbyte & Maskp->qbyte)*prime_0;
#else
         if (rp->push_rule) for (k = 0; k != pal; ++k)
            hash += (key->High.PeerAddress.peer32[k] =
               rp->RuleMatchedValue.peer32[k])*primes[k];
         else for (k = 0; k != pal; ++k)
            hash += (key->High.PeerAddress.peer32[k] =
               High->PeerAddress.peer32[k] & Maskp->peer32[k])*primes[k];
#endif
         break;
      case FTLOWPEERTYPE:
      case FTHIPEERTYPE:
         if (rp->push_rule) hash +=
            (key->PeerAddrType = rp->RuleMatchedValue.byte)*prime_1;
         else hash +=
            (key->PeerAddrType = bp->PeerAddrType)*prime_1;
         pal = addr_len32[key->PeerAddrType];  /* Now we know peer type */
         break;
      case FTLOWTRANSADDRESS:
         key->Low.TransMaskVal = rp->RuleMaskVal;
         if (rp->push_rule)
            hash += (key->Low.TransAddress =
               rp->RuleMatchedValue.trans)*prime_2;
         else
            hash += (key->Low.TransAddress =
               Low->TransAddress & Maskp->trans)*prime_2;
         break;
      case FTHITRANSADDRESS:
         key->High.TransMaskVal = rp->RuleMaskVal;
         if (rp->push_rule)
            hash += (key->High.TransAddress =
               rp->RuleMatchedValue.trans)*prime_2;
         else
            hash += (key->High.TransAddress =
               High->TransAddress & Maskp->trans)*prime_2;
         break;
      case FTLOWTRANSTYPE:
      case FTHITRANSTYPE:
         if (rp->push_rule) hash +=
            (key->TransAddrType = rp->RuleMatchedValue.byte)*prime_3;
         else hash +=
            (key->TransAddrType = bp->TransAddrType)*prime_3;
         break;
      case FTLOWADJACENTADDRESS:
         key->Low.AdjMaskVal = rp->RuleMaskVal;
         if (rp->push_rule) {
            hash += (key->Low.AdjAddr_ms4 = 
               rp->RuleMatchedValue.ma.ms4)*prime_4;
            hash += (key->Low.AdjAddr_ls2 =
               rp->RuleMatchedValue.ma.ls2)*prime_5;
            }
         else {
            hash += (key->Low.AdjAddr_ms4 =
               Low->AdjAddr_ms4 & Maskp->ma.ms4)*prime_4;
            hash += (key->Low.AdjAddr_ls2 =
               Low->AdjAddr_ls2 & Maskp->ma.ls2)*prime_5;
            }
         break;
      case FTHIADJACENTADDRESS:
         key->High.AdjMaskVal = rp->RuleMaskVal;
         if (rp->push_rule) {
            hash += (key->High.AdjAddr_ms4 = 
               rp->RuleMatchedValue.ma.ms4)*prime_4;
            hash += (key->High.AdjAddr_ls2 =
               rp->RuleMatchedValue.ma.ls2)*prime_5;
            }
         else {
            hash += (key->High.AdjAddr_ms4 =
               High->AdjAddr_ms4 & Maskp->ma.ms4)*prime_4;
            hash += (key->High.AdjAddr_ls2 =
               High->AdjAddr_ls2 & Maskp->ma.ls2)*prime_5;
            }
         break;
      case FTSOURCECLASS:
         hash += (key->Low.Class = rp->RuleMatchedValue.byte)*prime_9;
         break;
      case FTDESTCLASS:
         hash += (key->High.Class = rp->RuleMatchedValue.byte)*prime_9;
         break;
      case FTFLOWCLASS:
         hash += (key->FlowClass = rp->RuleMatchedValue.byte)*prime_9;
         break;
      case FTSOURCEKIND:
         hash += (key->Low.Kind = rp->RuleMatchedValue.byte)*prime_10;
         break;
      case FTDESTKIND:
         hash += (key->High.Kind = rp->RuleMatchedValue.byte)*prime_10;
         break;
      case FTFLOWKIND:
         hash += (key->FlowKind = rp->RuleMatchedValue.byte)*prime_10;
         break;
      case FTLOWINTERFACE:
         if (rp->push_rule) hash +=
            (key->Low.Interface = rp->RuleMatchedValue.byte)*prime_7;
         else hash +=
            (key->Low.Interface = Low->Interface);
         break;
      case FTHIINTERFACE:
         if (rp->push_rule) hash +=
            (key->High.Interface = rp->RuleMatchedValue.byte)*prime_7;
         else hash +=
            (key->High.Interface = High->Interface);
         break;
      case FTLOWADJACENTTYPE:
         if (rp->push_rule) hash +=
            (key->Low.AdjAddrType = rp->RuleMatchedValue.byte)*prime_6;
         else hash +=
            (key->Low.AdjAddrType = Low->AdjType)*prime_6;
         break;
      case FTHIADJACENTTYPE:
         if (rp->push_rule) hash +=
            (key->High.AdjAddrType = rp->RuleMatchedValue.byte)*prime_6;
         else hash +=
            (key->High.AdjAddrType = High->AdjType)*prime_6;
         break;
      case FTDSCODEPOINT:
         if (rp->push_rule) hash +=
            (key->DSCodePoint = rp->RuleMatchedValue.byte)*prime_8;
         else hash +=
            (key->DSCodePoint = bp->DSCodePoint)*prime_8;
         break;
#if NF_ASN_ATT
      case FTLOWROUTEASN:
         key->Low.ASNMaskVal = rp->RuleMaskVal;
         if (rp->push_rule)
            hash += (key->Low.RouteASN = rp->RuleMatchedValue.trans)*prime_11;
         else
            hash += (key->Low.RouteASN = Low->nf_ASN & Maskp->trans)*prime_11;
         break;
      case FTHIROUTEASN:
         key->High.ASNMaskVal = rp->RuleMaskVal;
         if (rp->push_rule)
            hash += (key->High.RouteASN = rp->RuleMatchedValue.trans)*prime_11;
         else
            hash += (key->High.RouteASN = High->nf_ASN & Maskp->trans)*prime_11;
         break;
      case FTLOWROUTEPREFIX:
         if (rp->push_rule) hash +=
            (key->Low.RoutePrefix = rp->RuleMatchedValue.byte)*prime_12;
         else hash +=
            (key->Low.RoutePrefix = Low->nf_mask)*prime_12;
         break;
      case FTHIROUTEPREFIX:
         if (rp->push_rule) hash +=
            (key->High.RoutePrefix = rp->RuleMatchedValue.byte)*prime_12;
         else hash +=
            (key->High.RoutePrefix = High->nf_mask)*prime_12;
         break;
#endif
#if NF_OTHER_ATT
      case FTMETERID:
         if (rp->push_rule) hash +=
            (key->MeterId = rp->RuleMatchedValue.byte)*prime_13;
         else hash +=
            (key->MeterId = bp->ntm_interface)*prime_13;
         break;
#endif
         }
      }
#ifdef MATCH_TRACE
   printf("\nType,Class,Kind: %d  %d %d %d  %d %d %d  hash=%d\n",
      key->PeerAddrType,
      key->Low.Class,key->High.Class,key->FlowClass,
      key->Low.Kind,key->High.Kind,key->FlowKind,
      hash);
#endif

   *search_hash = hash % fthashmod;
   }

#ifdef STACK_CHECK
void dump_pat_stack(char *reason)
{
   int j;
   scpos(0,24);
   printf("match %s: stk=", reason);
   for (j = 0; j != RSTKSIZ; ++j)
      printf("%u+%u,", a_stack[j],p_stack[j]);
   printf("\n   LowPeer=");  daddr(search_key.Low.PeerAddress);
   printf(", HighPeer=");  daddr(search_key.High.PeerAddress);
   printf("\n");
   }
#endif

#if PME_TRACE
void log_PME_TRACE(void)
{
   int ptx;
   for (ptx = 0; ptx <= rtx-1; ptx+=10)
      log_msg(LOG_INFO, 0, "   %d, %d, %d, %d, %d, %d, %d, %d, %d, %d,",
         ruletrace[ptx], ruletrace[ptx+1], ruletrace[ptx+2],
         ruletrace[ptx+3], ruletrace[ptx+4], ruletrace[ptx+5],
         ruletrace[ptx+6], ruletrace[ptx+7], ruletrace[ptx+8],
         ruletrace[ptx+9]);
   }
#endif

Bit8  /* Computed attribute values for this packet */
   psc,pdc,pfc, psk,pdk,pfk;

int match(int forward, struct pkt *bp,
   struct pkt_key *from, struct pkt_key *to)
{
   int rx, attrib, j, match, test_reqd,
      pal;  /* Peer address length (qbytes) */
   struct rule *rp;

#if PME_TRACE
   rtx = 0;  /* Initialise match trace */
#endif

   e_s_tos = p_s_depth = 0;  test_reqd = 1;
   psc = pdc = pfc = psk = pdk = pfk = 0;
   pal = (PEER_ADDR_LEN+3)/4;
   for (rx = 1; rx <= c_rsz; ) {  /* Search the rule table */
#if PME_TRACE
      if (rtx != MXTRACE) ruletrace[rtx++] = rx;
      else {
         if (tr_msgs_left[c_rtx-1] != 0) {
            log_msg(LOG_ERR, 0, "PME match loop, ri[%d]: rtx=%d", c_rtx, rtx); 
            log_PME_TRACE();
            --tr_msgs_left[c_rtx-1];
	    }
         quack(123);
         return MFAIL;
         }
#endif
      rp = &c_rt[rx-1];
      attrib = rp->RuleSelector;
      if (test_reqd && attrib != RS_NULL) {
         union address *Maskp = Masks[rp->RuleMaskVal].Val;

         if (USER_ATTRIB(attrib))  /* User variable */
            attrib = user_vbls[rp->RuleSelector-FTV1];  /* Dereference it */

         if (bp->PeerAddrType != AT_DUMMY) ++n_matches;
         if (rp->hash_tbl_index == 0) {  /* Simple compare */
            switch (attrib) {
            case FTLOWPEERADDRESS:
#if QBYTE_PEER
               match = (from->PeerAddress.qbyte & Maskp->qbyte) ==
                     rp->RuleMatchedValue.qbyte;
#else
               for (j = 0;  j != pal;  ++j) {
                  if ((from->PeerAddress.peer32[j] & Maskp->peer32[j]) !=
                        rp->RuleMatchedValue.peer32[j]) {
                     match = 0; break;
                     }
                  }
               match = 1;
#endif
               break;
            case FTHIPEERADDRESS:
#if QBYTE_PEER
               match = (to->PeerAddress.qbyte & Maskp->qbyte) ==
                     rp->RuleMatchedValue.qbyte;
#else
               for (j = 0;  j != pal;  ++j) {
                  if ((to->PeerAddress.peer32[j] & Maskp->peer32[j]) !=
                        rp->RuleMatchedValue.peer32[j]) {
                     match = 0; break;
                     }
                  }
               match = 1;
#endif
               break;
            case FTLOWPEERTYPE:
            case FTHIPEERTYPE:
               match = bp->PeerAddrType == rp->RuleMatchedValue.byte;
               if (match) pal = addr_len32[bp->PeerAddrType];
               break;
            case FTLOWTRANSADDRESS:
               match = (from->TransAddress & Maskp->trans) ==
                  rp->RuleMatchedValue.trans;
               break;
            case FTHITRANSADDRESS:
               match = (to->TransAddress & Maskp->trans) ==
                  rp->RuleMatchedValue.trans;
               break;
            case FTLOWTRANSTYPE:
            case FTHITRANSTYPE:
               match = bp->TransAddrType == rp->RuleMatchedValue.byte;
               break;
            case FTLOWADJACENTADDRESS:
               match = ((from->AdjAddr_ms4 & Maskp->ma.ms4) ==
                     rp->RuleMatchedValue.ma.ms4) &&
                  ((from->AdjAddr_ls2 & Maskp->ma.ls2) ==
                     rp->RuleMatchedValue.ma.ls2);
               break;
            case FTHIADJACENTADDRESS:
               match = ((to->AdjAddr_ms4 & Maskp->ma.ms4) ==
                     rp->RuleMatchedValue.ma.ms4) &&
                  ((to->AdjAddr_ls2 & Maskp->ma.ls2) ==
                     rp->RuleMatchedValue.ma.ls2);
               break;
            case FTFORWARD:  /* Addresses in packet order */
               match = forward == rp->RuleMatchedValue.byte;
               break;
            case FTSOURCECLASS:
               match = (psc & Maskp->byte) == rp->RuleMatchedValue.byte;
               break;
            case FTDESTCLASS:
               match = (pdc & Maskp->byte) == rp->RuleMatchedValue.byte;
               break;
            case FTFLOWCLASS:
               match = (pfc & Maskp->byte) == rp->RuleMatchedValue.byte;
               break;
            case FTSOURCEKIND:
               match = (psk & Maskp->byte) == rp->RuleMatchedValue.byte;
               break;
            case FTDESTKIND:
               match = (pdk & Maskp->byte) == rp->RuleMatchedValue.byte;
               break;
            case FTFLOWKIND:
               match = (pfk & Maskp->byte) == rp->RuleMatchedValue.byte;
               break;
            case FTLOWINTERFACE:
               match = from->Interface == rp->RuleMatchedValue.byte;
                  /* Kevin, 23 Nov 98 */
               break;
            case FTHIINTERFACE:
               match = to->Interface == rp->RuleMatchedValue.byte;
                  /* Kevin, 23 Nov 98 */
               break;
            case FTLOWADJACENTTYPE:
               match = from->AdjType == rp->RuleMatchedValue.byte;
               break;
            case FTHIADJACENTTYPE:
               match = to->AdjType == rp->RuleMatchedValue.byte;
               break;
            case FTDSCODEPOINT:
               match = bp->DSCodePoint == rp->RuleMatchedValue.byte;
               break;
#if NF_ASN_ATT
            case FTLOWROUTEASN:
               match = (from->nf_ASN & Maskp->trans) !=
                  rp->RuleMatchedValue.trans;
               break;
            case FTHIROUTEASN:
               match = (to->nf_ASN & Maskp->trans) !=
                  rp->RuleMatchedValue.trans;
               break;
            case FTLOWROUTEPREFIX:
               match = from->nf_mask == rp->RuleMatchedValue.byte;
               break;
            case FTHIROUTEPREFIX:
               match = to->nf_mask == rp->RuleMatchedValue.byte;
               break;
#endif
#if NF_OTHER_ATT
            case FTMETERID:
               match = bp->ntm_interface == rp->RuleMatchedValue.byte;
               break;
#endif
            case RS_NULL:
            default:
               match = 1;  /* Don't test anything */
            }
         }
      else {  /* Hashed compare */
         union address masked_value;
         Bit32 hash;
         int ral;  /* Rule address length (bytes) */
         struct rule_hash_tbl *c_rh;

         match = 0;
         switch (attrib) {
            case FTLOWPEERADDRESS:
#if QBYTE_PEER
               hash = (masked_value.qbyte = from->PeerAddress.qbyte
                  & Maskp->qbyte)*prime_0;
               ral = 4;
#else
               for (j = hash = 0;  j != pal;  ++j) hash += 
                  (masked_value.peer32[j] = from->PeerAddress.peer32[j]
                     & Maskp->peer32[j])*primes[j];
               ral = pal*4;
#endif
               break;
            case FTHIPEERADDRESS:
#if QBYTE_PEER
               hash = (masked_value.qbyte = to->PeerAddress.qbyte
                  & Maskp->qbyte)*prime_0;
               ral = 4;
#else
               for (j = hash = 0;  j != pal;  ++j) hash += 
                  (masked_value.peer32[j] = to->PeerAddress.peer32[j]
                     & Maskp->peer32[j])*primes[j];
               ral = pal*4;
#endif
               break;
            case FTLOWPEERTYPE:
            case FTHIPEERTYPE:
               hash = (masked_value.byte = 
                  bp->PeerAddrType & Maskp->byte)*prime_1;
               ral = 1;
               break;
            case FTLOWTRANSADDRESS:
               hash = (masked_value.trans = 
                  from->TransAddress & Maskp->trans)*prime_2;
               ral=TRANS_ADDR_LEN;
               break;
            case FTHITRANSADDRESS:
               hash = (masked_value.trans = 
                  to->TransAddress & Maskp->trans)*prime_2;
               ral=TRANS_ADDR_LEN;
               break;
            case FTLOWTRANSTYPE:
            case FTHITRANSTYPE:
               hash = (masked_value.byte = 
                  bp->TransAddrType & Maskp->byte)*prime_3;
               ral = 1;
               break;
            case FTLOWADJACENTADDRESS:
               hash = (masked_value.ma.ms4 =
                     from->AdjAddr_ms4 & Maskp->ma.ms4)*prime_4 +
                  (masked_value.ma.ls2 =
                     from->AdjAddr_ls2 & Maskp->ma.ls2)*prime_5;
               ral = MAC_ADDR_LEN;
               break;
            case FTHIADJACENTADDRESS:
               hash = (masked_value.ma.ms4 =
                     to->AdjAddr_ms4 & Maskp->ma.ms4)*prime_4 +
                  (masked_value.ma.ls2 =
                     to->AdjAddr_ls2 & Maskp->ma.ls2)*prime_5;
               ral = MAC_ADDR_LEN;
               break;
            case FTSOURCECLASS:
               hash = (masked_value.byte = psc & Maskp->byte)*prime_9;
               ral = 1;
               break;
            case FTDESTCLASS:
               hash = (masked_value.byte = pdc & Maskp->byte)*prime_9;
               ral = 1;
               break;
            case FTFLOWCLASS:
               hash = (masked_value.byte = pfc & Maskp->byte)*prime_9;
               ral = 1;
               break;
            case FTSOURCEKIND:
               hash = (masked_value.byte = psk & Maskp->byte)*prime_10;
               ral = 1;
               break;
            case FTDESTKIND:
               hash = (masked_value.byte = pdk & Maskp->byte)*prime_10;
               ral = 1;
               break;
            case FTFLOWKIND:
               hash = (masked_value.byte = pfk & Maskp->byte)*prime_10;
               ral = 1;
               break;
            case FTLOWINTERFACE:
               hash = (masked_value.byte = 
                  from->Interface & Maskp->byte)*prime_7;
               ral = 1;
               break;
            case FTHIINTERFACE:
               hash = (masked_value.byte = 
                  to->Interface & Maskp->byte)*prime_7;
               ral = 1;
               break;
            case FTLOWADJACENTTYPE:
               hash = (masked_value.byte = 
                  from->AdjType & Maskp->byte)*prime_6;
               ral = 1;
               break;
            case FTHIADJACENTTYPE:
               hash = (masked_value.byte = 
                  to->AdjType & Maskp->byte)*prime_6;
               ral = 1;
               break;
            case FTDSCODEPOINT:
               hash = (masked_value.byte = 
                  bp->DSCodePoint & Maskp->byte)*prime_8;
               ral = 1;
               break;

#if NF_ASN_ATT
            case FTLOWROUTEASN:
               hash = (masked_value.trans = 
                  from->nf_ASN & Maskp->trans)*prime_11;
               ral=TRANS_ADDR_LEN;
               break;
            case FTHIROUTEASN:
               hash = (masked_value.trans = 
                  to->nf_ASN & Maskp->trans)*prime_11;
               ral=TRANS_ADDR_LEN;
               break;
            case FTLOWROUTEPREFIX:
               hash = (masked_value.byte = 
                  from->nf_mask & Maskp->byte)*prime_12;
               ral = 1;
               break;
            case FTHIROUTEPREFIX:
               hash = (masked_value.byte = 
                  to->nf_mask & Maskp->byte)*prime_12;
               ral = 1;
               break;
#endif
#if NF_OTHER_ATT
            case FTMETERID:
               hash = (masked_value.byte = 
                  bp->ntm_interface & Maskp->byte)*prime_13;
               ral = 1;
               break;
#endif
            case RS_NULL:
            default:
               match = 1;  /* Don't test anything */
               }
            if (!match) {
               c_rh = (struct rule_hash_tbl *)&c_rht[rp->hash_tbl_index];
               hash %= c_rh->hashmod;
#ifdef P_TRACE
               ptx = rx;
#endif
               if ((rx = c_rh->hash_ent[hash]) != 0) {
                  do {
                     rp = &c_rt[rx-1];
                     if (memcmp((Bit8 *)masked_value.rule,
                           rp->RuleMatchedValue.rule, ral) == 0) {
                        match = 1;  break;  /* Matched */
                        }
                     } while ((rx = rp->hash_link) != 0);
                  }
               }
            if (match == 0) rx = c_rh->group_last;  /* Not in group */
#ifdef P_TRACE
            printf("Hash cmp: rx=%d, att=%d, mtch=%d, m_rx=%d, act=%d, ix=%d\n",
               ptx,attrib, match,rx, rp->RuleAction,rp->RuleJumpIndex);
#endif
            }
         }
      else {  /* No test required */
#ifdef P_TRACE
         printf("No test: rx=%d, attrib=%d, action=%d, index=%d\n",
            rx,attrib, rp->RuleAction,rp->RuleJumpIndex);
#endif
         test_reqd = 1;  /* Didn't test for this rule */
         match = 1;
         }

      if (match) {
         switch (rp->RuleAction) {
         case RA_IGNORE:
#if PM_DEBUG
            if (c_rtx == pm_dbg) {
               sprintf(dbg_msg, " f=%d, rx=%d, IGNORE", forward,rx);
               display_msg(0,dbg_msg);
               }
#endif
            return MIGNORE;
         case RA_RETRY:
#if PM_DEBUG
            if (c_rtx == pm_dbg) {
               sprintf(dbg_msg, " f=%d, rx=%d, RETRY", forward,rx);
               display_msg(0,dbg_msg);
               }
#endif
            return MFAIL;
         case RA_COUNT:
         case RA_COUNTPKT:
            if (attrib != 0 || rp->RuleMaskVal != 0) {  /* Non-zero mask */
                  /* Count rule specified an attribute */
               if (USER_ATTRIB(attrib))  /* User variable */
                  attrib = user_vbls[rp->RuleSelector-FTV1];  /* Deref it */
               a_stack[p_s_depth] = attrib;  /* Remember last matched rule */
               p_stack[p_s_depth++] = rx;
               }
#ifdef COUNT_TRACE
/*            if ((from->PeerAddress[2] == 4 && from->PeerAddress[3] == 33) ||
                     (to->PeerAddress[2] == 4 && to->PeerAddress[3] == 33)) */
                  {
               printf("Count: %u+%u",a_stack[0],p_stack[0]);
               for (j = 1; j != p_s_depth; ++j) printf(",%u+%u, ",
                  a_stack[j],p_stack[j]);
               daddr(from->PeerAddress);  printf(":");
               dport(from->TransAddress);
               printf(" %s ", forward ? "->" : "<-");
               daddr(to->PeerAddress);  printf(":");
               dport(to->TransAddress);  printf("\n");
               }
#endif
#if PM_DEBUG
            if (c_rtx == pm_dbg) {
               sprintf(dbg_msg, " f=%d, rx=%d, COUNT", forward,rx);
               display_msg(0,dbg_msg);
               }
#endif
            return MSUC;
         case RA_RETURN:
            --e_s_tos;
#ifdef STACK_CHECK
            if (e_s_tos < 0) {
               scpos(0,scr_lrow);
               printf("gosub oflo: depth=%d, stk=", e_s_tos);
               for (j = 0; j != ESTKSIZ; ++j) printf("%u,", env_stack[j]);
               printf("\n");
               return MFAIL;
               }
#endif
#ifdef P_TRACE
            scpos(0,scr_lrow);
            printf("Return: rx=%d, attrib=%d, gosub@%d, index=%d\n",
               rx,rp->RuleSelector, env_stack[e_s_tos],rp->RuleJumpIndex);
#endif
            rx = env_stack[e_s_tos] + rp->RuleJumpIndex;
            test_reqd = 0;  /* Always return to Action */
            continue;
         case RA_ASSIGN:
         case RA_ASSIGNACT:
            user_vbls[rp->RuleSelector-FTV1]
               = rp->RuleMatchedValue.byte;  /* Used to be RuleJumpIndex */
            /* ++rx;  Bug!  Nevil, 6 Mar 97 */
            rx = rp->RuleJumpIndex;
            if (rp->RuleAction == RA_ASSIGNACT) test_reqd = 0;
            continue;
         case RA_GOSUB:
         case RA_GOSUBACT:
            env_stack[e_s_tos++] = rx;  /* Save index of gosub rule */
#ifdef STACK_CHECK
            if (e_s_tos >= ESTKSIZ) {
               scpos(0,scr_lrow);
               printf("gosub oflo: depth=%d, stk=", e_s_tos);
               for (j = 0; j != e_s_tos; ++j) printf("%u,", env_stack[j]);
               printf("\n");
               return MFAIL;
               }
#endif
            rx = rp->RuleJumpIndex;
            if (rp->RuleAction == RA_GOSUBACT) test_reqd = 0;
            continue;
         case RA_GOTO:
         case RA_GOTOACT:
            rx = rp->RuleJumpIndex;
#ifdef TESTING
            scpos(0,scr_lrow);  printf("   GOTO %d\n", rx);
#endif
            if (rp->RuleAction == RA_GOTOACT) test_reqd = 0;
            continue;
         case RA_PUSHTO:
         case RA_PUSHTOACT:
            if (USER_ATTRIB(attrib))  /* User variable */
               attrib = user_vbls[rp->RuleSelector-FTV1];  /* Deref it */
            if (attrib >= FTSOURCECLASS && attrib <= FTFLOWKIND)
                  switch (attrib) {  /* Save 'packet' computed attrib value */
            case FTSOURCECLASS:
               psc = rp->RuleMatchedValue.byte;  break;
            case FTDESTCLASS:
               pdc = rp->RuleMatchedValue.byte;  break;
            case FTFLOWCLASS:
               pfc = rp->RuleMatchedValue.byte;  break;
            case FTSOURCEKIND:
               psk = rp->RuleMatchedValue.byte;  break;
            case FTDESTKIND:
               pdk = rp->RuleMatchedValue.byte;  break;
            case FTFLOWKIND:
               pfk = rp->RuleMatchedValue.byte;  break;
               }
            a_stack[p_s_depth] = attrib;  /* Remember which rules matched */
            p_stack[p_s_depth++] = rx;
            rx = rp->RuleJumpIndex;
            if (rp->RuleAction == RA_PUSHTOACT) test_reqd = 0;
#ifdef STACK_CHECK
            if (p_s_depth >= RSTKSIZ) {
               dump_pat_stack("oflo");
               return MFAIL;
               }
#endif
            continue;
         case RA_PUSHPKTTO:
         case RA_PUSHPKTTOACT:
            if (USER_ATTRIB(attrib))  /* User variable */
               attrib = user_vbls[rp->RuleSelector-FTV1];  /* Deref it */
            a_stack[p_s_depth] = attrib;  /* Remember which rules matched */
            p_stack[p_s_depth++] = rx;
            rx = rp->RuleJumpIndex;
            if (rp->RuleAction == RA_PUSHPKTTOACT) test_reqd = 0;
#ifdef STACK_CHECK
            if (p_s_depth >= RSTKSIZ) {
               dump_pat_stack("oflo");
               return MFAIL;
               }
#endif
            continue;
	 case RA_POPTO:
	 case RA_POPTOACT:
	    --p_s_depth;  /* Forget last matched rule */
	    rx = rp->RuleJumpIndex;
	    if (rp->RuleAction == RA_POPTOACT)
               test_reqd = 0;
#ifdef STACK_CHECK
	    if (p_s_depth < 0) {
               dump_pat_stack("uflo");
	       return MFAIL;
	       }
#endif
	    continue;

         default:  /* Not a valid action! */
            log_msg(LOG_ERR, 0, 
               "Bad action, ruleset %d, rule %d", c_rtx,rx);
#if PME_TRACE
            log_PME_TRACE();
#endif
            return MFAIL;
            }
         }
      else ++rx;
      }
   log_msg(LOG_ERR, 0, "Bad index, ruleset %d, rule %d", c_rtx,rx);
#if PME_TRACE
   log_PME_TRACE();
   quack(456);
#endif
   return MFAIL;  /* Not matched */
   }

void show_flow_hash(void)
{
   int j, inuse_slots,  entries, cl, hc_len[51], mx_cl;
   struct flow **sf, *f;

   inuse_slots = entries = 0;
   memset(hc_len, 0, sizeof(hc_len));
   for (j = 0; j != fthashmod; ++j) {
      if (flow_ht[j] != NULL) {
         ++inuse_slots;  ++entries;
         sf = &flow_ht[j];  f = *sf;
         for (cl = 1; ; ++cl, ++entries) {
            if ((f = f->ht_next) == (struct flow *)sf) break;
	    }
         ++hc_len[cl > sizeof(hc_len)/sizeof(int) ? 
            sizeof(hc_len)/sizeof(int)-1 : cl-1];
         }
      }

   printf("current() hash performance:\n");
   printf("   %d hash slots, %d in use (%.2f\%)\n",
      fthashmod, inuse_slots, inuse_slots*100.0/fthashmod);

   for (mx_cl = sizeof(hc_len)/sizeof(int)-1; mx_cl != 0; --mx_cl)
      if (hc_len[mx_cl] != 0) break;
   if (mx_cl >= 0) {
      ++mx_cl;
      printf("   %d entries in table, average %.2f entries/inuse_slot\n",
         entries, (float)entries/(inuse_slots == 0 ? 1 : inuse_slots));
      printf("   chain lengths:");
      for (j = 0; j != mx_cl; ++j) printf(" %d", hc_len[j]);
      printf(" (max %d)\n", mx_cl);
      }
   }

struct flow *current(
   struct search_result *s_r,
   Bit32 hash, struct flow_key *search_key)
{
   struct flow **sf;
   struct flow *f, *lf;
   Bit32 *qp, *hqp;
   int j;

   sf = &flow_ht[hash];  /* Address of hash table entry */
#ifdef MATCH_TRACE
   printf("  current(): cht=%Fp, sf=%Fp\n", cht,sf);
#endif
   ++n_hash_searches;
   if ((f = sf[0]) == NULL) {
      lf = NULL;  /* Empty hash chain */
      ++n_hash_compares;  /* Make sure compares >= searches! */
      }
   else {
      do {
         ++n_hash_compares;
         for (;;) {  /* Compare whole fields - data has been masked */
            for (hqp = (Bit32 *)&f->fk, qp = (Bit32 *)search_key, j = 0;
                  j != sizeof(struct flow_key)/sizeof(Bit32);  ++j) {
               if (*hqp++ != *qp++) goto s_no_match;
               }
            return f;  /* Matched */
            }

      s_no_match:
         lf = f;
         } while ((f = lf->ht_next) != (struct flow *)sf);
      f = NULL;  /* Loop stops with lf -> last flow in chain */
      }
   s_r->sf = sf;  s_r->lf = lf;
      /* Save sf and lf; we'll need them to insert a new flow */
#ifdef MATCH_TRACE
   printf("  current() -> sf=%Fp, lf=%Fp\n", s_r->sf,s_r->lf);
#endif
   return f;
   }

#if NEW_ATR
int make_distrib_list(  /* From pattern stack */
   struct flow *f)
{
   struct rule *rp;
   int j, a;
   struct distribution *r, *d, *ld;
   double N,D, UL;

   if (f->distrib_list != NULL) {  /* $$$ */
      scpos(0,scr_lrow);
      log_msg(LOG_ERR, 0, "Non-empty distribution list <<<");
      return 0;
      }

   for (j = 0; j != p_s_depth; ++j) {
      if (stream_atr[a = a_stack[j]]  /* Stream attribute */
            && f->stdata == NULL) {
         if ((f->stdata = get_st_data()) != NULL)
            f->stdata->U = OTHER_STREAM_TIMEOUT;  /* Default (cs) */
         }
      if (dist_atr[a]) {  /* Distribution-valued attribute */
#if PP_DEBUG_XX
   printf("make_distrib_list(1); distrib attribute a=%d\n", a);
#endif
         rp = &c_rt[p_stack[j]-1];
         if ((d = get_dist()) == NULL) 
            return 0;
         memset(d, 0, sizeof(struct distribution));
         d->selector = a;
         memcpy(d->mask_params, Masks[rp->RuleMaskVal].Val, RULE_ADDR_LEN);
         memcpy(d->value_params, rp->RuleMatchedValue.rule, RULE_ADDR_LEN);

         d->Transform = d->mask_params[0];
         d->ScaleFactor = d->mask_params[1];
         d->LowerLimit = (d->mask_params[2] << 8) | d->mask_params[3];
         d->UpperLimit = (d->mask_params[4] << 8) | d->mask_params[5];
         d->Buckets = d->value_params[0];
         if (d->Buckets > MXBUCKETS) {
            scpos(0,scr_lrow);
            log_msg(LOG_ERR, 0,
               "Distribution with > %d buckets **", MXBUCKETS);
            d->Buckets = d->value_params[0] = MXBUCKETS;
	    }
         d->Parameter1 = d->value_params[1];
         d->Parameter2 = (d->value_params[2] << 8) | d->value_params[3];
         d->Parameter3 = (d->value_params[4] << 8) | d->value_params[5];

         N = d->Buckets - 1;
         switch (d->Transform) {
         case DS_LIN:
            D = d->UpperLimit - d->LowerLimit;
            d->M = N/D;
            break;
         case DS_LOG:
            D = log10((double)d->UpperLimit/(double)d->LowerLimit);
            d->M = N/D;
            break;
         case DS_DYN_REQ:
            d->max_val = d->lowerlim = d->LowerLimit;
            d->min_val = d->upperlim = d->UpperLimit;
            d->n_values = 0;
            break;
            }

         if (f->distrib_list == NULL)
            f->distrib_list = d;
         else ld->next = d;
         ld = d;

         f->distrib_bits |= 1 << d->selector-FTTOPACKETSIZE;
         d->fp = f;  d->nrate = NULL;

         switch (a) {  /* Special handling for particular d-v attributes */
         case FTTOBITRATE:
         case FTFROMBITRATE:
         case FTTOPDURATE:
         case FTFROMPDURATE:
	    if (d->RateInterval != 0) link_distrib_to_event(d);
            else log_msg(LOG_WARNING, 0, "Rate distribution with zero interval !!!");
            break;
         case FTTOTCPSIZE:             /* TCP attributes: */
         case FTFROMTCPSIZE:           /*    (param1 = stream-size scale) */ 
         case FTTOTCPTIME:             /*    Must build streams, */
         case FTFROMTCPTIME:           /*    don't care about pp_type */
         case FTTOTCPRATE1:
         case FTFROMTCPRATE1:
         case FTTOTCPRATE2:
         case FTFROMTCPRATE2:
	   if (f->stdata != NULL)
               f->stdata->pp_match_reqd = 1;
            break;

#if 0
         case FTTOTRAINLENGTH:         /* In train if <= UpperSize */
         case FTFROMTRAINLENGTH:       /*   (and > LowerSize) */
#endif
         case FTTOTURNAROUNDTIME4:
         case FTFROMTURNAROUNDTIME4:
            break;

         case FTTOINTERARRIVALTIME:    /* Inter-packet time attributes: */
         case FTFROMINTERARRIVALTIME:  /*    Don't have to build streams */
                                     
         case FTTOTURNAROUNDTIME1:     /* Turnaround time attributes: */
         case FTFROMTURNAROUNDTIME1:   /*    Must build streams, */
         case FTTOTURNAROUNDTIME2:     /*    (param1 = pp_type selector) */  
         case FTFROMTURNAROUNDTIME2:
         case FTTOTURNAROUNDTIME3:
         case FTFROMTURNAROUNDTIME3:

         case FTFLOWTIME:
            if (f->stdata == NULL || d->PP_Type == PP_NO_TEST)
	       break;  /* No matching, just inter-packet times */
            else {
               f->stdata->pp_match_reqd = 1;
               UL =  /* LostPacket time (cs) */
                  (inv_scale(d, d->UpperLimit)/10000.0)*STR_TO_MULTIPLIER;
               if (UL < OTHER_STREAM_TIMEOUT) {  /* Lower than default */
                  if (f->stdata->U == OTHER_STREAM_TIMEOUT ||
                        /* First distrib to set U */
                     UL > f->stdata->U)
                        /* Max of distrib Upper values */
                  f->stdata->U = UL;
                  }
               }

            if (f->fk.PeerAddrType != AT_IP4
#if V6
                  && f->fk.PeerAddrType != AT_IP6
#endif
                  ) {
               log_msg(LOG_WARNING, 0, "PP test on non-IP flow !!!");
               break;
               }
            if ((d->PP_Type & PP_TCP) == PP_TCP) {
               if (f->fk.TransAddrType != PT_TCP) {
                  log_msg(LOG_WARNING, 0, "PP TCP test on non-TCP flow !!!");
                  break;
	          }
	       }
            else switch (d->PP_Type) {
            case PP_UDP_DNS:
               if (f->fk.Low.TransAddress != htons(WNP_DOMAIN) &&
                     f->fk.High.TransAddress != htons(WNP_DOMAIN)) {
                  log_msg(LOG_WARNING, 0, 
                     "PP DNS test on non-DNS port (%04x->%04x) !!!",
                     f->fk.Low.TransAddress, f->fk.High.TransAddress);
                  break;
	          }
               if (f->fk.TransAddrType != PT_UDP) {
                  log_msg(LOG_WARNING, 0, "PP DNS test on non-UDP flow !!!");
                  break;
	          }
               break;
            case PP_ICMP_ECHO:
               if (f->fk.TransAddrType != PT_ICMP) {
                  log_msg(LOG_WARNING, 0, "PP ping test on non-ICMP flow !!!");
                  break;
	          }
               break;

            case PP_NO_TEST:
            case PP_OTHER:
               break;
            case PP_ICMP_TIMSTMP:
            case PP_UDP_ECHO:
            case PP_UDP_SNMP:
               break;
            default:
               log_msg(LOG_WARNING, 0, "Unknown PP value for flow !!!");
               break;
	       }
            break;
            }
	 }
      }
   return 1;
   }

#if TCP_TESTING || TCP_PKT_TRACE || PKT_QUEUE_TEST || TCP_TAT_TRACE
void print_stream_name(FILE *f, char *msg, 
   struct stream *sp)
{
   fprintf(f, "str(%s) ", msg);
   fdaddr(f, sp->sfn.SrcPeerAddr);  fprintf(f, ", ");
   fdport(f, sp->sfn.SrcTransAddr);  fprintf(f, " -> ");
   fdaddr(f, sp->sfn.DestPeerAddr);  fprintf(f, ", ");
   fdport(f, sp->sfn.DestTransAddr);
   fprintf(f, ", TransType=%d, rs=%d: ", sp->sfn.TransAddr, sp->sfn.RuleSet);
   }
#endif

#if SCC_CHECK
int check_stream_hash(struct stream *isp, struct stream *l_n_h, char *msg)
{
   int n = 0;
   struct stream *sp, *lsp;
   Bit8 *np;  int j;  Bit32 hash;
   struct stream **sha;
   int sha_seen;

   for (np = (Bit8 *)&isp->sfn, hash = 0, j = 0;
          j != sizeof(isp->sfn); ++np, ++j)
      /* hash += *np * primes[j]; */
      hash = (hash << 4) ^ (hash >> 28) ^ *np;
   sha = &sfht[hash % sfhashmod];

   if (isp->next_hc ==(struct stream *)sha && l_n_h == isp)
     return;  /* Just removed lone entry from hash chain */
   /*   if ((sp = isp) == NULL) return; */

   for (sha_seen = 0, sp = isp; ; ) {
      if (sp->next_hc == NULL
	  /*	  && !(l_n_h == isp && sp == (struct stream *)sha) */
   ) {
         printf("check_stream_h(%s): isp=%x, lsp=%x, sp=%x\n", msg, isp, lsp, sp);
         return 0;
         }
      lsp = sp;
      if ((sp = lsp->next_hc) == isp)
         /* Loop stops with lsp -> last stream in chain */
         break;
      if (sp == (struct stream *)sha) {
         if (sha_seen) break;  /* Entry removed leaving loop from sha */
         sha_seen = 1;
         }
      if (++n == 20) {
         printf("check_stream_h(%s): runaway next_hc chain, isp=%x\n", msg, isp);
         }
      }
   return 1;
   }
#endif  /* SCC_CHECK */

void terminate_stream(struct stream_data *sdp, struct stream *sp)
   /* Removes stream from stream hash table, frees its pktdata blocks */
{
   struct pktdata *pk, *npk;
   struct stream *np, *tp, *ltp;
#if SCC_CHECK
   struct stream *l_n_h;
#endif

   for (pk = sp->pq; pk != NULL; pk = npk) {
      npk = pk->next;  /* Free stream's pktdata blocks */
      if (sdp->pp_match_reqd) {
         /* pktdatas still in queue are considered lost! */
         if (pk->direction == MFWD) ++sdp->ToLostPDUs;
         else ++sdp->FromLostPDUs;
         }
      free_pktdata(pk);
      }
#if SCC_CHECK
   check_stream_hash(sp, NULL, "term-before");  /* ??? */
#endif
   np = sp->next_hc;  /* Remove stream from hash chain */
   tp = sp;  do {
      ltp = tp;
      } while ((tp = tp->next_hc) != sp);
#if SCC_CHECK
   l_n_h = ltp->next_hc;
#endif
   if (ltp == np)  /* Single flow in hash chain */
#if SCC_CHECK
      {
      check_stream_hash(sp, l_n_h, "term-middle");  /* ??? */
      ltp->next_hc = NULL;
      }
#else
      ltp->next_hc = NULL;
#endif
   else ltp->next_hc = np;
#if SCC_CHECK
   check_stream_hash(sp, l_n_h, "term-after");  /* ??? */
#endif
   }

void terminate_all_streams(struct flow *fp, struct stream_data *sdp)
{
   struct stream *sp, *lsp;

#if SCC_CHECK
   check_stream_chains(fp, "term_all_streams: before");
#endif
   if ((sp = sdp->sq) != NULL) {
      do {
         lsp = sp;  sp = sp->next_sp;

         flow_stream_stats(fp, sdp, lsp);
         terminate_stream(sdp, lsp);

         if (sp == NULL) {  /* Only one stream in queue */
            sdp->sq = sdp->sq_tail = NULL;
            }
         else {
            sdp->sq = sp;  sp->prev_sp = NULL;
            }
         free_stream(lsp);
         } while (sp != NULL);
      }
#if SCC_CHECK
   check_stream_chains(fp, "term_all_streams: after");
#endif
   }

void flow_stream_stats(struct flow *fp,
   struct stream_data *std, struct stream *sp)
   /* Computes flow-based statistics for terminating streams */
{
   struct distribution *d;
   double delta;

   for (d = fp->distrib_list; d != NULL; d = d->next) {
      switch (d->selector) {
      case FTTOFLOWOCTETS:
         bump_dist(d, sp->ToOctets);
         break;
      case FTFROMFLOWOCTETS:
         bump_dist(d, sp->FromOctets);
         break;
      case FTTOFLOWPDUS:
         bump_dist(d, sp->ToPDUs);
         break;
      case FTFROMFLOWPDUS:
         bump_dist(d, sp->FromPDUs);
         break;
      case FTFLOWTIME:
             /* Use highest estimate for total stream time */
         if (sp->ToSeen && sp->FromSeen) {
            delta = sp->LastFromTime > sp->LastToTime ?
               sp->LastFromTime : sp->LastToTime;
            delta -= sp->StrFirstTime;
            }
         else if (sp->ToSeen)  /* Only 'to' packets seen */
            delta = sp->LastToTime - sp->StrFirstTime;
         else if (sp->FromSeen)  /* Only 'from' packets seen */
            delta = sp->LastFromTime - sp->StrFirstTime;
         else continue;  /* Didn't see any packets! */
         bump_dist(d, delta);  /* us */
         break;
         }
      }
   }

void tcp_stream_stats(struct flow *fp,
   struct stream_data *std, struct stream *sp)
   /* Computes TCP-based statistics for terminating streams */
{
   struct distribution *d;
   Bit32 bytes, b2;
   double delta;
   int n;

#if TRACE_NEG_BLP
   double ToBLF, FromBLF;
   int bad_blp, hdr_printed;
#endif
#if TRACE_NEG_BLP
   FromBLF = ToBLF = 1.0;
   if (str->ToTCPSentOctets.low != 0 &&
         str->ToTCPSeqOctets.low != 0)
      ToBLF = (double)str->ToTCPSentOctets.low /
         (double)str->ToTCPSeqOctets.low;
   if (str->FromTCPSentOctets.low != 0 &&
         str->FromTCPSeqOctets.low != 0)
      FromBLF = (double)str->FromTCPSentOctets.low/
         (double)str->FromTCPSeqOctets.low;

   bad_blp = ToBLF < BLP_FRACT || FromBLF < BLP_FRACT;
/*   if ((str->flags_seen & 0x0C) == 0x0C) /* Seen both SYNs */
   hdr_printed = 0;
#endif
#if TRACE_NEG_BLP
	 if (bad_blp) {
	    if (!hdr_printed) {
               fprintf(pt_file, "BAD BLP: " 
                  "ToBLF=%.2f [%lu %lu], FromBLF=%.2f [%lu %lu], str=%lu <<<<<\n",
                  ToBLF, str->ToTCPSentOctets.low, str->ToTCPSeqOctets.low,
                  FromBLF, str->FromTCPSentOctets.low,str->FromTCPSeqOctets.low,
                  str);
               hdr_printed = 1;
	       }
            print_pkt_trace(pt_file, "+++", sp,
               "LT=%lu, sp=%lu", sp->LastTime, sp); 
            }
#endif

   for (d = fp->distrib_list; d != NULL; d = d->next) {
      switch (d->selector) {
      case FTTOTCPSIZE:
      case FTTOTCPTIME:
      case FTTOTCPRATE1:
      case FTTOTCPRATE2:
         bytes = sp->ToSeqOctets > sp->FromAckOctets ? 
            sp->ToSeqOctets : sp->FromAckOctets;
         break;
      case FTFROMTCPSIZE:
      case FTFROMTCPTIME:
      case FTFROMTCPRATE1:
      case FTFROMTCPRATE2:
         bytes = sp->FromSeqOctets > sp->ToAckOctets ? 
            sp->FromSeqOctets : sp->ToAckOctets;
         break;
         }
      if (bytes == 0) continue;  /* No data for stream! */

      if (d->LowerSize != 0 || d->UpperSize != 0) {
         n = scale(d, bytes);  /* Divide by 10**SizeScale */
#define TCPTIME_TEST 0
#if TCPTIME_TEST
         if (d->selector == FTFROMTCPTIME)
            printf(" TCPFromTime: FromB=%lu|ToB=%lu, n=%u, L=%u|U=%u\n",
               bytes,b2, n, d->LowerSize,d->UpperSize);
#endif
         if (d->LowerSize != 0 && n <= (Bit32)d->LowerSize) continue;
         if (d->UpperSize != 0 && n > (Bit32)d->UpperSize) continue;
         }

          /* Use highest estimate for total stream time */
      if (sp->ToSeen && sp->FromSeen) {
         delta = sp->LastFromTime > sp->LastToTime ?
            sp->LastFromTime : sp->LastToTime;
         delta -= sp->StrFirstTime;
         }
      else if (sp->ToSeen)  /* Only 'to' packets seen */
         delta = sp->LastToTime - sp->StrFirstTime;
      else if (sp->FromSeen)  /* Only 'from' packets seen */
         delta = sp->LastFromTime - sp->StrFirstTime;
      else continue;  /* Didn't see any packets! */

      switch (d->selector) {
      case FTTOTCPSIZE:
      case FTFROMTCPSIZE:
         bump_dist(d, bytes);  /* Bytes */
         break;
      case FTTOTCPTIME:
      case FTFROMTCPTIME:
         bump_dist(d, delta);  /* us */
         break;
      case FTFROMTCPRATE1:
      case FTFROMTCPRATE2:
         if (delta == 0) delta = 1;  /* Don't divide by 0 */
         bump_dist(d, bytes*1000000.0/delta);  /* B/s */
         break;
      case FTTOTCPRATE1:
      case FTTOTCPRATE2:
         if (delta == 0) delta = 1;  /* Don't divide by 0 */
         bump_dist(d, bytes*1000000.0/delta);  /* B/s */
         break;
         }
      }

#if TCP_TESTING
         print_stream_name(stdout, "fin", sp);
         printf(": active=%d, To %lu/%lu, From %lu/%lu",
            str->active_streams,
            str->ToTCPSentOctets.low,str->ToTCPSeqOctets.low,
            str->FromTCPSentOctets.low,str->FromTCPSeqOctets.low);
            printf("\n");)
#endif
   }

static last_ntm_LastTime = 0;

void check_other_idle_streams(struct flow *fp)
     /* Checks timeouts for streams other than tcp pp_match.  Streams
           time out after being idle for OTHER_STREAM_TIMEOUT cs */
{
   struct stream *sp, *nsp, *iq_tail;
   Bit32 uc, IDLE_time, pkts;
   struct stream_data *sdp;
   double av_int;
   Bit32 sto, inact;
   int keep, n_streams, n_kept, t_bkwd;

   if (fp->ntm_LastTime == last_ntm_LastTime)  /* centiseconds */
     return;  /* Nothing can have aged out yet */
   last_ntm_LastTime = fp->ntm_LastTime;

   sdp = fp->stdata;
   uc = sdp->U;  /* Centiseconds */
   if (fp->ntm_LastTime <= uc)
      return;  /* Haven't run long enough for streams to time out yet */

   IDLE_time = fp->ntm_LastTime - uc;  /* centiseconds */
   n_streams = n_kept = t_bkwd = 0;
   sp = sdp->sq;  iq_tail = sdp->sq_tail;
#if SCC_CHECK
   check_stream_chains(fp, "check_o_i_s: before");
#endif
   while (sp != NULL && sp != iq_tail) {
      ++n_streams;
      nsp = sp->next_sp;  /* Next stream to check */

      if (sp->LastTime <= IDLE_time) {  /* Inactive, can we recover it? */
         if (sp->FirstTime < sp->LastTime) {
            /* Was FirstTime > LastTime !  Nevil, 25 Feb 01 */
            if ((pkts = sp->ToPDUs + sp->FromPDUs) == 0) pkts = 1;
            av_int = (double)(sp->LastTime - sp->FirstTime)/(double)pkts;
            sto = av_int*STO_MULTIPLIER;
            inact = fp->ntm_LastTime - sp->LastTime;
            if (keep = (inact < sto)) ++n_kept;
            }
	 else {  /* Trace files can have odd time jumps in them */
	   keep = 0;  /* No data to improve sto estimate */
            ++t_bkwd;
            }

         sdp->sq = sp->next_sp;  /* Remove stream from queue */
         if (sp->next_sp != NULL)
            sp->next_sp->prev_sp = sp->prev_sp;
         else sdp->sq_tail = sp->prev_sp;

         if (keep) {
            if (sdp->sq == NULL) {  /* Add it to tail of queue */
               sdp->sq = sp;  sp->prev_sp = NULL;
               }
            else {
               sdp->sq_tail->next_sp = sp;
               sp->prev_sp = sdp->sq_tail;
               }
            sp->next_sp = NULL;  sdp->sq_tail = sp;
            }
         else {  /* Timed-out stream */
            flow_stream_stats(fp, sdp, sp);
            terminate_stream(sdp, sp);
            --sdp->active_streams;
            free_stream(sp);
#if SCC_CHECK
            check_stream_chains(fp, "check_o_i_s: after 1");
#endif
            }
         }
      else if (sp->LastTime > IDLE_time)
         break;  /* Later streams can't have timed out yet */

      sp = nsp;            
      }

/*   printf("other_idle(): flow %x, IDLE %u, %u streams, %u kept, %u with first > last\n",
      fp, IDLE_time, n_streams, n_kept, t_bkwd);
*/
#if SCC_CHECK
   check_stream_chains(fp, "check_o_i_s: after 2");
#endif
   }

void check_tcp_idle_streams(struct flow *fp, int gc_call)
     /* Checks timeouts for flow's IDLE and terminating streams.
        Called by garbage collector for *active* flows (gc_call)
        to check for all terminating streams */
{
   struct stream *sp, *nsp, *iq_tail;
   Bit32 uc, FIN_WAIT_time, IDLE_time, pkts;
   struct stream_data *sdp;
   double av_int;
   Bit32 sto, inact;
/*   struct distribution *d;
   Bit32 bytes, b2;
   double delta;
   Bit32 n; */
   int term, keep;
#if CR_TRACE
   int s_rcv = 0;
#endif

   if (fp->ntm_LastTime == last_ntm_LastTime)  /* centiseconds */
     return;  /* Nothing can have aged out yet */
   last_ntm_LastTime = fp->ntm_LastTime;

   sdp = fp->stdata;
   uc = sdp->U;  /* Centiseconds */
   if (fp->ntm_LastTime <= uc)
      return;  /* Haven't run long enough for streams to time out yet */
   IDLE_time = fp->ntm_LastTime - uc;
   FIN_WAIT_time = fp->ntm_LastTime - TCP_STREAM_TIMEOUT;  /* centiseconds */

   sp = sdp->sq;  iq_tail = sdp->sq_tail;
   while (sp != NULL && sp != iq_tail) {
      nsp = sp->next_sp;  /* Next stream to check */

      if ((term = sp->terminating && sp->LastTime <= FIN_WAIT_time) ||
            sp->LastTime <= IDLE_time) {

         if (term) keep = 0;  /* Terminated */
         else {  /* Inactive, can we recover it? */
            if (sp->FirstTime < sp->LastTime) {
               if ((pkts = sp->ToPDUs + sp->FromPDUs) == 0) pkts = 1;
               av_int = (double)(sp->LastTime - sp->FirstTime)/(double)pkts;
               sto = av_int*STO_MULTIPLIER;
               inact = fp->ntm_LastTime - sp->LastTime;
               keep = inact < sto;
               }
	    else {  /* Trace files can have odd time jumps in them */
               keep = 0;  /* No data to improve sto estimate */
               }
            }

         sdp->sq = sp->next_sp;  /* Remove stream from queue */
         if (sp->next_sp != NULL)
            sp->next_sp->prev_sp = sp->prev_sp;
         else sdp->sq_tail = sp->prev_sp;

         if (keep) {
            if (sdp->sq == NULL) {  /* Add it to tail of queue */
               sdp->sq = sp;  sp->prev_sp = NULL;
               }
            else {
               sdp->sq_tail->next_sp = sp;
               sp->prev_sp = sdp->sq_tail;
               }
            sp->next_sp = NULL;  sdp->sq_tail = sp;
            }
         else {  /* Timed-out stream */
            flow_stream_stats(fp, sdp, sp);
            terminate_stream(sdp, sp);
            --sdp->active_streams;
            if (sp->terminating) {
               if (fp->distrib_list)
                  tcp_stream_stats(fp, sdp, sp);
               --sdp->terminating_streams;
               }
            free_stream(sp);
#if CR_TRACE
            ++s_rcv;
#endif
            }
         }
      else {
         if ((!gc_call && sp->LastTime > IDLE_time) ||
               (gc_call && sp->terminating && sp->LastTime > FIN_WAIT_time)) {
            break;  /* Later streams can't have timed out yet */
            }
         }
      sp = nsp;            
      }

#if CR_TRACE
   if (s_rcv != 0) {
      if (s_rcv < 10) s_rcv = '.';
      else if (s_rcv < 100) s_rcv = 'o';
      else s_rcv = '*';
      printf("%c", s_rcv);  fflush(stdout);
      }
#endif
   }

int tcp_stream_update(struct flow *fp,
   struct stream *sp, const struct pktdata *pd)
   /* Updates TCP state for stream sp of flow fp.
      pd points to packet in stream queue (if we saved it),
      or to packet in free queue (if we didn't) */
{
   struct stream_data *sdp = fp->stdata;
   int flow_order = pd->direction;
   Bit32 tlen = pd->pi.tcp.d_len;
   Bit32 Bytes;

# if TCP_TRACE
   static Bit32 sf_count = 0;
# endif
# if TCP_PKT_TRACE
   struct trace_rec *sftp;
# endif

#define MX_SEQ_DELTA   65535  /* Max TCP window size */
/* #define MX_SEQ_DELTA   29200  /* 20 Ethernet TCP packets */

   if (pd->pi.tcp.flags & tcp_FlagRST)
      sp->flags_seen |= RSTseen;
   else if (flow_order == MREV) {
      if (pd->pi.tcp.flags & tcp_FlagSYN) sp->flags_seen |= FromSYNseen;
      if (pd->pi.tcp.flags & tcp_FlagFIN) sp->flags_seen |= FromFINseen;
     }
   else {
      if (pd->pi.tcp.flags & tcp_FlagSYN) sp->flags_seen |= ToSYNseen;
      if (pd->pi.tcp.flags & tcp_FlagFIN) sp->flags_seen |= ToFINseen;
     }

   if (!sp->terminating && (sp->flags_seen & RSTseen ||
         (sp->flags_seen & BothFINseen) == BothFINseen)) {
      sp->terminating = 1;
      ++fp->stdata->terminating_streams;
      }

   if (!sp->terminating) {
      if (flow_order == MREV) {

         if (sp->FromSeq_seen) {
            if (scmp32_ge(pd->pi.tcp.exp_seq, sp->FromLastSeq)) {  /* 'Normal' */
               Bytes = pd->pi.tcp.exp_seq - sp->FromLastSeq;
               if (Bytes <= MX_SEQ_DELTA) {
                  add_Bit32_to_counter64(sdp->FromTCPSeqOctets, Bytes);
                  sp->FromSeqOctets += Bytes;
                  }
               else {
# if TRACE_SEQ_DECR
                  print_pkt_trace(pt_file, 
                     "FROM-SEQ", sp, ": SeqBytes=%lu <<<", Bytes);
# endif
                  }
               sp->FromLastSeq = pd->pi.tcp.exp_seq;
               }
            else ++sdp->FromTCPDecrSeq;  /* Backward jump in seq */
            }
         else if (pd->pi.tcp.exp_seq != 0) {
            add_Bit32_to_counter64(sdp->FromTCPSeqOctets, tlen);
               /* tlen data bytes in this packet */
            sp->FromSeqOctets = tlen;
            sp->FromLastSeq = pd->pi.tcp.exp_seq;
            sp->FromSeq_seen = 1;
            }

         if (sp->ToAck_seen) {
            if (scmp32_ge(pd->pi.tcp.ack, sp->ToLastAck)) {
               Bytes = pd->pi.tcp.ack - sp->ToLastAck;
               add_Bit32_to_counter64(sdp->ToTCPAckOctets, Bytes);
               sp->ToAckOctets += Bytes;
               }
            sp->ToLastAck = pd->pi.tcp.ack;
            }
         else if (pd->pi.tcp.ack != 0) {
            add_Bit32_to_counter64(sdp->ToTCPAckOctets, tlen);
              /* Assume these will be acked (!) */
            sp->ToAckOctets = tlen;
            sp->ToLastAck = pd->pi.tcp.ack;
            sp->ToAck_seen = 1;
            }

         if (tlen > 0) {
            add_Bit32_to_counter64(sdp->FromTCPLenOctets, tlen);
            }
#define  SU_DEBUG  0
#if SU_DEBUG
printf("  MREV(%u): seq=%u, len=%u, Ack=%u -> FSS=%d, FLen=%u, Fseq=%u, TAS=%d, Tack=%u\n",
    sdp, pd->pi.tcp.exp_seq, tlen, pd->pi.tcp.ack,
    sp->FromSeq_seen, sdp->FromTCPLenOctets.low, sdp->FromTCPSeqOctets.low, 
    sp->ToAck_seen, sdp->ToTCPAckOctets.low);
#endif
         }
      else {  /* MFWD */

         if (sp->ToSeq_seen) {
            if (scmp32_ge(pd->pi.tcp.exp_seq, sp->ToLastSeq)) {  /* 'Normal' */
               Bytes = pd->pi.tcp.exp_seq - sp->ToLastSeq;
               if (Bytes <= MX_SEQ_DELTA) {
                  add_Bit32_to_counter64(sdp->ToTCPSeqOctets, Bytes);
                  sp->ToSeqOctets += Bytes;
                  }
               else {
# if TRACE_SEQ_DECR
                  print_pkt_trace(pt_file, 
                     "TO-SEQ", sp, ": SeqBytes=%lu <<<", Bytes);
# endif
                  }
               sp->ToLastSeq = pd->pi.tcp.exp_seq;
               }
            else ++sdp->ToTCPDecrSeq;  /* Backward jump in seq */
            }
         else if (pd->pi.tcp.exp_seq != 0) {
            add_Bit32_to_counter64(sdp->ToTCPSeqOctets, tlen);
               /* tlen data bytes in this packet */
            sp->ToSeqOctets = tlen;
            sp->ToLastSeq = pd->pi.tcp.exp_seq;
            sp->ToSeq_seen = 1;
            }

         if (sp->FromAck_seen) {
            if (scmp32_ge(pd->pi.tcp.ack, sp->FromLastAck)) {
               Bytes = pd->pi.tcp.ack - sp->FromLastAck;
               add_Bit32_to_counter64(sdp->FromTCPAckOctets, Bytes);
               sp->FromAckOctets += Bytes;

               } 
            sp->FromLastAck = pd->pi.tcp.ack;
            }
         else if (pd->pi.tcp.ack != 0) {
            add_Bit32_to_counter64(sdp->FromTCPAckOctets, tlen);
            sp->FromAckOctets = tlen;
               /* Assume these will be acked (!) */
            sp->FromLastAck = pd->pi.tcp.ack;
            sp->FromAck_seen = 1;
            }

         if (tlen > 0) {
            add_Bit32_to_counter64(sdp->ToTCPLenOctets, tlen);
            }
#if SU_DEBUG
printf("MFWD(%u): seq=%u, len=%u, Ack=%u -> TSS=%d, TLen=%u, Tseq=%u, FAS=%d, Fack=%u\n",
    sdp, pd->pi.tcp.exp_seq, tlen, pd->pi.tcp.ack,
    sp->ToSeq_seen, sdp->ToTCPLenOctets.low, sdp->ToTCPSeqOctets.low, 
    sp->FromAck_seen, sdp->FromTCPAckOctets.low);
#endif
         }
      }

   check_tcp_idle_streams(fp, 0);

#  if 0
      if (flow_order == MFWD) {
         sp->FromWindow = pd->pi.tcp.send_win = ntohs(bp->pi.tcp.th.window);
         pd->pi.tcp.recv_win = sp->ToWindow;
         }
      else {
         sp->ToWindow = pd->pi.tcp.send_win = ntohs(bp->pi.tcp.th.window);
         pd->pi.tcp.recv_win = sp->FromWindow;
         }
#  endif

# if TCP_PKT_TRACE
      sftp = &sp->sft.tr[sp->sft.sf_trx];
      sftp->pkt_nbr = ++sp->sft.pkt_nbr;
      sftp->time_int = (pd->ts - sp->sft.last_pkt_time) / 1000.0;
      sp->sft.last_pkt_time = pd->ts;
      sftp->TCPlen = tlen;
      sftp->flags = pd->tcp.flags;
      sftp->s_flags = sp->flags_seen;
      if (flow_order == MREV) {
         sftp->maxSeq = sp->FromMaxSeq;
         }
      else {
         sftp->s_flags |= sess_Forward;
         sftp->maxSeq = sp->ToMaxSeq;
         }
      sftp->Seq = ThisSeq;
      if (++sp->sft.sf_trx == TRACE_SZ) sp->sft.sf_trx = 0;
# endif

# if TCP_TRACE
      fprintf(stderr,
      "%5d %9.5f %6.3f %s %s%s len=%4d, ack=%lu, seq=%lu, flg=%04x\n", 
         sp->sf_nbr,
         microseconds(bp->arrival_time)/1000000.0, 
         (microseconds(bp->arrival_time)-
            (flow_order == MREV ? sp->at.LastToTime : sp->at.LastFromTime)
            )/1000000.0,
         flow_order == MREV ? "<-" : "->",
	 flow_order != sp->at.LastDirection ? " " : "=",
         sp->at.TimeOKrev ? "R" : " ",
         tlen, ThisAck, ThisSeq, ThisFlags
         );
# endif
   }

FILE *pt_file;
#define ptf_title "ntm.trace"

# if TCP_PKT_TRACE
void print_pkt_trace(FILE *f,  char *id,  /* Identifies invocation */
   struct stream *sfp, char *fmt, ...)  /* Supporting detail */
{
   va_list ap;
   int j,k, seq, mx_seq;
   char *dirn;
   struct sf_trace *sftp = &sfp->sft;
   struct trace_rec *trp;

   print_stream_name(f, id, sfp);
   va_start(ap, fmt);  vfprintf(f, fmt, ap);  va_end(ap);
   fprintf(f, "\n");

   for (j = sftp->sf_trx, k = 0; k != TRACE_SZ; ++k) {
      trp = &sftp->tr[j];
      if (trp->pkt_nbr != 0 && !(trp->s_flags & sess_PktPrinted)) {
         seq = trp->Seq;  mx_seq = trp->maxSeq;
         if (trp->s_flags & sess_Forward) {
            dirn = "->";
            if (trp->s_flags & ToSYNseen) {
               seq -= sfp->ToFirstSeq;  mx_seq -= sfp->ToFirstSeq;
               }
	    }
         else {  /* Back */
            dirn = "<-";
            if (trp->s_flags & FromSYNseen) {
               seq -= sfp->FromFirstSeq;  mx_seq -= sfp->FromFirstSeq;
               }
	    }
         fprintf(f,
/*          "   %5d %7.3f %s %5d %c%c%c%c %lu %lu\n", */
            "  %02x %02x  %5d %7.3f %s %5d %c%c%c%c %lu %lu\n",
       trp->flags, trp->s_flags,
            trp->pkt_nbr, trp->time_int/1000.0, dirn, trp->TCPlen,
	    trp->flags & tcp_FlagACK ? 'A' : ' ',
	    trp->flags & tcp_FlagRST ? 'R' : ' ',
	    trp->flags & tcp_FlagSYN ? 'S' : ' ',
	    trp->flags & tcp_FlagFIN ? 'F' : ' ',
            seq, mx_seq);
         trp->s_flags |= sess_PktPrinted;  /* Only print each packet once */
         }
      if (++j == TRACE_SZ) j = 0;
      }
   fflush(f);
   }
# endif

#define TCP_TAT_TRACE 0

#if TCP_TAT_TRACE
void print_tcp_pktdata(struct pktdata *pd)
{
   printf("%.3f %s %c%c%c%c%c %lu:%lu(%u) ack %lu, pkt %u\n",
      pd->ts/1000.0,
      pd->direction == MFWD ? "->" : "<-",
      pd->pi.tcp.flags & tcp_FlagACK  ? 'A' : ' ',
      pd->pi.tcp.flags & tcp_FlagPUSH ? 'P' : ' ',
      pd->pi.tcp.flags & tcp_FlagRST  ? 'R' : ' ',
      pd->pi.tcp.flags & tcp_FlagSYN  ? 'S' : ' ',
      pd->pi.tcp.flags & tcp_FlagFIN  ? 'F' : ' ',
      pd->pi.tcp.exp_seq - pd->pi.tcp.d_len,
      pd->pi.tcp.exp_seq,  pd->pi.tcp.d_len,
     pd->pi.tcp.ack, pd->pi.tcp.pkt_nbr);
   /*      pd->pi.tcp.send_win, pd->pi.tcp.recv_win); */
   }

void print_tcp_list(struct pktdata *pd, char *msg)
{
   int n = 0;
   printf("   %s:\n",msg);
   for (; pd != NULL; pd = pd->next) {
      printf("      ");  print_tcp_pktdata(pd);
      }
   }
#endif  /* TCP_TAT_TRACE */

#define PKT_QUEUE_TEST 0

#if PKT_QUEUE_TEST
int q_len(struct pktdata *pq)
{
   struct pktdata *fp;  int n;
   for (n = 0, fp = pq; fp != NULL; fp = fp->next) ++n;
   return n;
   }
#endif

int tcp_turnaround(int *pp_type, double *delta_us,
   struct flow *fp, struct stream *sp, struct pktdata *tp)
   /* Compares incoming TCP packet with those in stream's packet queue */
{
   struct pktdata *pd, *lpd, *npd;
   int acked, resent, delete, prev_delete, overlap;
   Bit32 a,b,c,d;
   int flow_order = tp->direction;
   int delta_OK = 0;
   Bit8 TaT_OK = 0;
/*
   tp = new packet
   pd = queue of old packets, kept in ascending exp_seq order

   For opposite-direction pkts:
      if ack matches exp_seq, set TaT_OK flags
      Delete pkts with seq <= ack
   For same-direction packets:
      Test for overlaps in seq:
         Overlap -> bump lostPDUs
   Save new pkt if it has data, SYN or FIN,
      i.e. if it affects exp_ack
   */

#if PKT_QUEUE_TEST
   int ql = q_len(sp->pq);
   if (ql != sp->pq_len) {
      print_stream_name(stdout, "tcp_ta(1)", sp);
      printf(" ql=%d, pq_len=%d\n", ql,sp->pq_len);
      }
#endif
   b = tp->pi.tcp.exp_seq;  a = b - tp->pi.tcp.d_len;  /* For overlap test */
   acked = resent = prev_delete = 0;
   *pp_type = PP_NO_TEST;
   for (pd = sp->pq; pd != NULL; ) {
      delete = 0;
      if (pd->direction != flow_order) {  /* Opposite-direction packet */
         if (tp->pi.tcp.ack == pd->pi.tcp.exp_seq &&  /* Ack matched */
               !acked) {  /* Only ack the oldest copy! */
	    if (pd->pi.tcp.flags & tcp_FlagSYN) {
               if ((tp->pi.tcp.flags & (tcp_FlagACK | tcp_FlagSYN))
                     == (tcp_FlagACK | tcp_FlagSYN) ) {
	          TaT_OK |= PP_OK_SYNACK;
	          }
               else if ((tp->pi.tcp.flags & (tcp_FlagACK | tcp_FlagRST))
                     == (tcp_FlagACK | tcp_FlagRST) ) {
	          TaT_OK |= PP_OK_SYNRST;
	          }
	       }
            if (pd->pi.tcp.d_len != 0) {  /* Acking sent data */
               if (prev_delete)  /* Acking 2nd (or later) packet of queue */
                  TaT_OK |= PP_OK_MULTI;
               else if (pd->next == NULL) /* Only one pkt in queue */
                  TaT_OK |= PP_OK_SINGLE;
	       else TaT_OK |= PP_OK_INGROUP;  /* Other pkts in queue */
	       }
            if (TaT_OK)  /* Compute turnaround time */
               *delta_us = tp->ts - pd->ts;  delta_OK = 1;
#  if TCP_TAT_TRACE == 1
            if (TaT_OK & PP_OK_SYNRST) {
               print_stream_name(stdout, "SYN/RST", sp);
               printf("\n   bump %s, Tat=%.3f ms, recv_win=%d, OK=%02x\n      ", 
                  TaT, TaT/1000.0, pd->pi.tcp.recv_win, TaT_OK);
               print_tcp_pktdata(tp);
               print_tcp_list(sp->pq, "TCP(match)");  printf("\n");
               }
#  endif
            acked = delete = 1;  /* Delete acked packet */
	    }
         else if (tp->pi.tcp.ack > pd->pi.tcp.exp_seq)
	    delete = 1;  /* Delete pkts with exp_seq < ack */
         }
      else {  /* Same-direction packet */
         /*               a---b    New packet */
         /*     c---------d        Old packet */
         d = pd->pi.tcp.exp_seq;  c = d - pd->pi.tcp.d_len;
         overlap = pd->pi.tcp.flags & tp->pi.tcp.flags &
               (tcp_FlagSYN | tcp_FlagFIN) ?  /* Two SYN/FIN packets */
            a <= d && b >= c :  /* SYN/FIN packets may not overlap at all */
            a < d && b > c;  /* Data packets may overlap by one byte */
         if (overlap) {
#  if TCP_TAT_TRACE == 2
            printf("--- c=%lu,d=%lu, a=%lu,b=%lu, overlap=%d, resent=%d\n", 
               c,d, a,b, overlap, a ==c && b == d);
            print_stream_name(stdout, "TCP overlap", sp);  printf("\n      ");
            print_tcp_pktdata(tp);
            print_tcp_list(sp->pq.packet, "Overlap queue");  printf("\n");
#  endif
            if (b >= d) {   /* Don't count 'lower-part-only' overlap */
               if (!resent) {  /* Only count resent packet once! */
                  if (pd->direction == MFWD) ++fp->stdata->ToLostPDUs;
                  else ++fp->stdata->FromLostPDUs;
                  resent = (a == c && b == d);
                  }
	       }
            }
         }
      if (delete) {
         prev_delete = 1;  /* At least one packet deleted from queue */
         npd = pd->next;
         if (pd == sp->pq) sp->pq = npd;
         else lpd->next = npd;
         --sp->pq_len;
         free_pktdata(pd);
         pd = npd;
#if PKT_QUEUE_TEST
   ql = q_len(sp->pq);
   if (ql != sp->pq_len) {
      print_stream_name(stdout, "tcp_ta(2)", sp);
      printf(" ql=%d, pq_len=%d\n", ql,sp->pq_len);
      }
#endif
         }
      else {
         lpd = pd;  pd = pd->next;
         }
      }
#if PKT_QUEUE_TEST
   ql = q_len(sp->pq);
   if (ql != sp->pq_len) {
      print_stream_name(stdout, "tcp_ta(3)", sp);
      printf(" ql=%d, pq_len=%d\n", ql,sp->pq_len);
      }
#endif

   if (tp->pi.tcp.d_len != 0 ||  /* Enqueue packet if it affects seq */
         tp->pi.tcp.flags & (tcp_FlagSYN | tcp_FlagFIN)) {
      if ((pd = sp->pq) == NULL) {  /* Empty queue */
         sp->pq = tp;  tp->next = NULL;
         }
      else if (pd->direction == flow_order &&
            tp->pi.tcp.exp_seq < pd->pi.tcp.exp_seq) {
         sp->pq = tp;  tp->next = pd;  /* Enqueue at head */
         }
      else for (; ; pd = pd->next) {
         if ((npd = pd->next) == NULL) {  /* Enqueue at tail */
            pd->next = tp;  tp->next = NULL;
            break;
            }
	 if (pd->direction == flow_order &&
               tp->pi.tcp.exp_seq < npd->pi.tcp.exp_seq) {
            pd->next = tp;  tp->next = npd;
            break;
            }
         }
      if (++sp->pq_len > sp->mx_pq_len) {
#if CR_TRACE
         printf("!");  fflush(stdout);
#endif
         for (pd = sp->pq; pd != NULL; ) {  /* Discard pkts from head of queue */
            if (pd->direction == MFWD) {
               ++fp->stdata->ToLostPDUs;  ++fp->stdata->ToPQOverflows;
               }
            else {
               ++fp->stdata->FromLostPDUs;  ++fp->stdata->FromPQOverflows;
               }
            npd = pd->next;  free_pktdata(pd);
            if (--sp->pq_len <= sp->mx_pq_len) break;
            pd = npd;
            }
         sp->pq = npd;
         }

#  if TCP_TAT_TRACE == 3
      if (sp->pq_len >= MX_PQ_LEN_TCP) {
         print_stream_name(stdout, "TCP queue too big", sp); 
         printf("\n      ");
         print_tcp_pktdata(tp);
         print_tcp_list(sp->pq.packet, "Queue");  printf("\n");
         }
#  endif
#if PKT_QUEUE_TEST
      ql = q_len(sp->pq);
      if (ql != sp->pq_len) {
         print_stream_name(stdout, "tcp_ta(4)", sp);
         printf(" ql=%d, pq_len=%d\n      ", ql,sp->pq_len);
#  if TCP_TAT_TRACE == 3
         print_tcp_pktdata(tp);
         print_tcp_list(sp->pq, "Queue");  printf("\n");
#  endif
         }
#endif
      }
   else free_pktdata(tp);  /* Don't keep packets which don't affect seq */

   *pp_type = PP_TCP | TaT_OK;
   return delta_OK;
   }

void show_stream_hash(int display)
{
   int j, inuse_slots,  entries, cl, hc_len[51], mx_cl;
   struct stream **sha, *sp;
#define STREAM_KINDS  1
#if STREAM_KINDS
   double av_int;
   Bit32 sto, inact, pkts, keep;

   int str_count[50];  /* One for each FlowKind value */
   int str_1m[50], str_2m[50], str_5m[50], expired[50];
   memset(str_count, 0, sizeof(str_count));
   memset(str_1m, 0, sizeof(str_1m));
   memset(str_2m, 0, sizeof(str_2m));
   memset(str_5m, 0, sizeof(str_5m));
   memset(expired, 0, sizeof(expired));
#endif

   inuse_slots = entries = 0;
   memset(hc_len, 0, sizeof(hc_len));
   for (j = 0; j != sfhashmod; ++j) {
      if (sfht[j] != NULL) {
         ++inuse_slots;  ++entries;
         sha = &sfht[j];  sp = *sha;
         for (cl = 1; ; ++cl, ++entries) {
#if STREAM_KINDS
            ++str_count[sp->flowp->fk.FlowKind];
            if (sp->flowp->ntm_LastTime - sp->FirstTime > 6000) {
               if (sp->FirstTime < sp->LastTime) {
                  if ((pkts = sp->ToPDUs + sp->FromPDUs) == 0) pkts = 1;
                  av_int = (double)(sp->LastTime - sp->FirstTime)/(double)pkts;
                  sto = av_int*STO_MULTIPLIER;
                  inact = sp->flowp->ntm_LastTime - sp->LastTime;
                  keep = inact < sto;
                  }
	       else {  /* Trace files can have odd time jumps in them */
                  keep = 0;  /* No data to improve sto estimate */
                  }
               if (!keep) ++expired[sp->flowp->fk.FlowKind];
               }
            if (sp->flowp->ntm_LastTime - sp->FirstTime > 30000)
               ++str_5m[sp->flowp->fk.FlowKind];   /* 300 s */
            else if (sp->flowp->ntm_LastTime - sp->FirstTime > 12000)
               ++str_2m[sp->flowp->fk.FlowKind];   /* 120 s */
            else if (sp->flowp->ntm_LastTime - sp->FirstTime >  6000)
               ++str_1m[sp->flowp->fk.FlowKind];   /*  60 s */
#endif
            if ((sp = sp->next_hc) == (struct stream *)sha) break;
               /* Loop stops with lsp -> last stream in chain */
	    }
         ++hc_len[cl > sizeof(hc_len)/sizeof(int) ? 
            sizeof(hc_len)/sizeof(int)-1 : cl-1];
         }
      }

   if (display) {
      printf("find_stream_helper() hash performance:\n");
      printf("   %d hash slots, %d in use (%.2f\%)\n",
         sfhashmod, inuse_slots, inuse_slots*100.0/sfhashmod);

      for (mx_cl = sizeof(hc_len)/sizeof(int)-1; mx_cl >=0; --mx_cl)
         if (hc_len[mx_cl] != 0) break;
      if (mx_cl >= 0) {
         ++mx_cl;
         printf("   %d entries in table, average %.2f entries/inuse_slot\n",
            entries, (float)entries/(inuse_slots == 0 ? 1 : inuse_slots));
         printf("   chain lengths:");
         for (j = 0; j != mx_cl; ++j) printf(" %d", hc_len[j]);
         printf(" (max %d)\n", mx_cl);
         }
#if STREAM_KINDS
      for (mx_cl = sizeof(str_count)/sizeof(int)-1; mx_cl >=0; --mx_cl)
         if (str_count[mx_cl] != 0) break;
      if (mx_cl >= 0) {
         ++mx_cl;
         printf("   Streams per FlowKind value: count 1m 2m 5m expired\n");
         for (j = 0; j != mx_cl; ++j) 
            printf("      %2d  %7d %7d %6d %6d  %5d\n", j, str_count[j],
               str_1m[j], str_2m[j], str_5m[j], expired[j]);
         }
#endif
      }
   else log_msg(LOG_INFO, 0,
      "stream_hash: %d slots, %d in use, %d entries",
      sfhashmod, inuse_slots, entries);
   }

struct stream *find_stream_helper(int *make_new,
   struct flow *fp, int flow_order, struct pkt *bp)
{
   struct stream_name sfn;
   Bit32 *qp, *hqp;
   int j;  Bit32 hash;
   struct stream **sha;
   struct stream *sp, *lsp, *nsp;
   struct stream_data *sdp;
#if SCC_CHECK
   struct stream *l_n_h;
#endif

   sfn.TransType = bp->TransAddrType;
   sfn.RuleSet = fp->FlowRuleSet;
   sfn.rsv1 = 0;  /* It's included in the hash */
   if (flow_order == MREV) {
#if QBYTE_PEER
      sfn.SrcPeerAddr = bp->High.PeerAddress.qbyte;
      sfn.DestPeerAddr = bp->Low.PeerAddress.qbyte;
#else
      memcpy(sfn.SrcPeerAddr,
         &bp->High.PeerAddress.byte, sizeof(sfn.SrcPeerAddr));
      memcpy(sfn.DestPeerAddr,
         &bp->Low.PeerAddress.byte, sizeof(sfn.DestPeerAddr));
#endif
      if (bp->TransAddrType == PT_TCP || bp->TransAddrType == PT_UDP) {
         sfn.SrcTransAddr = bp->High.TransAddress;
         sfn.DestTransAddr = bp->Low.TransAddress;
         }
      else  /* No port numbers */
         sfn.SrcTransAddr = sfn.DestTransAddr = 0;
      }
   else {
#if QBYTE_PEER
      sfn.SrcPeerAddr = bp->Low.PeerAddress.qbyte;
      sfn.DestPeerAddr = bp->High.PeerAddress.qbyte;
#else
      memcpy(sfn.SrcPeerAddr,
         &bp->Low.PeerAddress.byte, sizeof(sfn.SrcPeerAddr));
      memcpy(sfn.DestPeerAddr,
         &bp->High.PeerAddress.byte, sizeof(sfn.DestPeerAddr));
#endif
      if (bp->TransAddrType == PT_TCP || bp->TransAddrType == PT_UDP) {
         sfn.SrcTransAddr = bp->Low.TransAddress;
         sfn.DestTransAddr = bp->High.TransAddress;
         }
      else  /* No port numbers */
         sfn.SrcTransAddr = sfn.DestTransAddr = 0;
      }

   for (qp = (Bit32 *)&sfn, hash = 0, j = 0;
         j != sizeof(sfn)/sizeof(Bit32); ++qp, ++j) {
      /* hash += *qp * primes[j];  */
      hash = (hash << 4) ^ (hash >> 28) ^ *qp;
      /* Rotating hash performs slightly better */
      }

   sha = &sfht[hash % sfhashmod];
      /* Address of stream hash table entry */
   if ((sp = *sha) == NULL)
      lsp = NULL;  /* Empty hash chain */
   else {
      for (;;) {
         for (hqp = (Bit32 *)&sp->sfn, qp = (Bit32 *)&sfn, j = 0;
               j != sizeof(sfn)/sizeof(Bit32);  ++j) {
            if (*hqp++ != *qp++) goto s_no_match;
            }
         break;  /* Matched */

      s_no_match:
         lsp = sp;
         if ((sp = lsp->next_hc) == (struct stream *)sha) {
            sp = NULL;  break;
            /* Loop stops with lsp -> last stream in chain */
	    }
         }
      }

   if (sp != NULL)  /* Existing stream */
      *make_new = 0;
   else if (*make_new) {  /* New stream */
      nsp = get_stream();
      if (nsp != NULL) {
         memset(nsp, 0, sizeof(struct stream)-sizeof(sfn));
         memcpy(&nsp->sfn, &sfn, sizeof(sfn));

         nsp->flowp = fp;  /* Remember which flow the stream belongs to! */
#if SCC_CHECK
         check_stream_chains(fp, "f_s_helper: before");
         if (lsp != NULL)
            check_stream_hash(lsp, l_n_h = lsp->next_hc, "fre-help-before");
#endif

         if (lsp == NULL) *sha = nsp;  /* Add stream to hash chain */
         else lsp->next_hc = nsp;
         nsp->next_hc = (struct stream *)sha;
#if SCC_CHECK
         if (lsp != NULL)
            check_stream_hash(nsp, l_n_h, "term-after");
#endif

         sdp = fp->stdata;
	 if (sdp->sq == NULL)  /* Add stream to tail of flow's stream queue */
            sdp->sq = nsp;  /* First stream for this flow */
         else {
            sdp->sq_tail->next_sp = nsp;
            nsp->prev_sp = sdp->sq_tail;
            }
         sdp->sq_tail = nsp;
         ++sdp->n_streams;  ++sdp->active_streams;
#if SCC_CHECK
         check_stream_chains(fp, "f_s_helper: after");
#endif
         }
      return nsp;
      }
   return sp;
   }

#define OTHER_COUNT_VAL  30
int other_count = OTHER_COUNT_VAL;

struct stream *find_stream(struct flow *fp,
   int *flow_order, struct pkt *bp, double at_us)
     /* Given incoming packet bp, finds stream from flow's stream data */
{
   int rfo, make_new;
   struct stream *sp;
   struct stream_data *sdp;
#if PP_DEBUG
   int j, new = 0;
#endif
#if DNS_ROOT_DEBUG || DNS_ROOT_DEBUG_2
   int ifo = *flow_order;  /* Save PME's order */
#endif


   if ((bp->invalid_addrs & (INV_PEER | INV_TRANS)) != 0)
      return NULL;  /* Didn't get IP adresses & ports */

   sdp = fp->stdata;  /* This flow's stream_data */

#if 0
   rfo = *flow_order == MREV ? MFWD : MREV;  /* Search for reverse flow_order */
   make_new = 0;
   sp = find_stream_helper(&make_new, fp, rfo, bp);  /* Reverse FO */
   if (sp != NULL) {
      *flow_order = rfo;
      }
   else {
      make_new = 1;  /* OK to make new stream */
      sp = find_stream_helper(&make_new, fp, *flow_order, bp);  /* FO */
      }
#endif

   /* *Provided* that rulesets test carefully to establish proper
      direction for flows, we can safely assume that match() will
      tell us the correct direction for a stream match.  Which
      means we don't have to test both directions!  Oct 01 */

   make_new = 1;  /* OK to make new stream */
   sp = find_stream_helper(&make_new, fp, *flow_order, bp);  /* FO */

   if (!make_new) {  /* Packet for existing stream */
#if DNS_ROOT_DEBUG_2
      log_msg(LOG_WARNING, -1, /* Keep log file open */
         "Existing stream %x:  ifo=%d, fo=%d, This flow ...",
         sp, ifo, *flow_order);
      dump_flow(fp);  dump_bp(bp);  dump_sp_key(sp);
      log_msg(LOG_WARNING, 0, "");  /* Close log file */
#endif
#if SCC_CHECK
      check_stream_chains(fp, "f_stream: before");
#endif
      if (sp->prev_sp != NULL)  /* Remove stream from queue */
         sp->prev_sp->next_sp = sp->next_sp;
      else sdp->sq = sp->next_sp;
      if (sp->next_sp != NULL)
         sp->next_sp->prev_sp = sp->prev_sp;
      else sdp->sq_tail = sp->prev_sp;
      --sdp->active_streams;

      if (sp->flowp != fp) {
         log_msg(LOG_WARNING, -1, /* Keep log file open */
            "Stream %x could belong to more than one flow !!", sp);
         log_msg(LOG_WARNING, -1, "This flow ...");  
         dump_flow(fp);  dump_bp(bp);
         log_msg(LOG_WARNING, -1, "Other flow ...");
         dump_flow(sp->flowp);  dump_sp_key(sp);
         log_msg(LOG_WARNING, 0,  /* Close log file */
            "Meter abandoning this stream <<<<<");
         terminate_stream(sdp, sp);  /* Kill it (don't update distribs) */
         --sdp->active_streams;
         free_stream(sp);  /* Give back it's memory */
#if 0
            "Meter won't let this flow build streams <<<<<"); */
         quack(911);  /* Debugger breakpoint */
         free_st_data(sdp);  /* Don't let this flow build streams */
         fp->stdata = NULL;
#endif
         return NULL;
         }
#if SCC_CHECK
      check_stream_chains(fp, "f_stream: middle");
#endif

      if (sdp->sq == NULL) {  /* Add it to tail of queue */
         sdp->sq = sp;  sp->prev_sp = NULL;
         }
      else {
         sdp->sq_tail->next_sp = sp;
         sp->prev_sp = sdp->sq_tail;
         }
      sp->next_sp = NULL;  sdp->sq_tail = sp;
      ++sdp->active_streams;
#if SCC_CHECK
      check_stream_chains(fp, "f_stream: after");
#endif
      }

   else  /* First packet for this stream */
      if (sp != NULL) {  /* We did get a stream block */
#if DNS_ROOT_DEBUG_2
         log_msg(LOG_WARNING, -1, /* Keep log file open */
            "New stream %x:  ifo=%d, fo=%d, This flow ...",
               sp, ifo, *flow_order);
         dump_flow(fp);  dump_bp(bp);  dump_sp_key(sp);
         log_msg(LOG_WARNING, 0, "");  /* Close log file */
#endif
         if (sp->flowp != fp) {
            log_msg(LOG_WARNING, -1, /* Keep log file open */
               "New stream %x could belong to more than one flow !!", sp);
            log_msg(LOG_WARNING, -1, "This flow ...");  
            dump_flow(fp);  dump_bp(bp);
            log_msg(LOG_WARNING, -1, "Other flow ...");
            dump_flow(sp->flowp);  dump_sp_key(sp);
            log_msg(LOG_WARNING, 0,  /* Close log file */
               "Meter abandoning this stream <<<<<");
            quack(912);  /* Debugger breakpoint */
      	    terminate_stream(sdp, sp);  /* Kill it (don't update distribs) */
            --sdp->active_streams;
            free_stream(sp);  /* Give back it's memory */
#if 0
               "Meter won't let this flow build streams <<<<<");
            free_st_data(sdp);  /* Don't let this flow build streams */
            fp->stdata = NULL;
#endif
            return NULL;
            }

      if (bp->PeerAddrType == AT_IP4
# if V6
            || bp->PeerAddrType == AT_IP6
# endif
            ) {
         sp->mx_pq_len = bp->TransAddrType == PT_TCP ?
            MX_PQ_LEN_TCP : MX_PQ_LEN_OTHER;
         }
      sp->FirstTime = fp->ntm_LastTime;
      sp->StrFirstTime = at_us;
#if PP_DEBUG
      new = 1;
#endif
      if (sdp->active_streams > sdp->mx_active_streams)
         sdp->mx_active_streams = sdp->active_streams;

# if TCP_TRACE
      sp->sf_nbr = ++sf_count;
# endif
# if TCP_PKT_TRACE
      sp->sft.last_pkt_time = at_us;
      sp->sft.pkt_nbr = sp->sft.sf_trx = 0;
      memset(sp->sft.tr, 0, sizeof(sp->sft.tr));
# endif
# if TCP_TESTING
      print_stream_name(stdout, "start", sp);
      printf("\n");
# endif
      }
   if (!sdp->pp_match_reqd ||
      (bp->PeerAddrType != AT_IP4
# if V6
         && bp->PeerAddrType != AT_IP6
# endif
         ) || bp->TransAddrType != PT_TCP) {
      /* Not a tcp-matched flow, try to free some idle streams */
      /* Used to do this before initialising mx_pq_len !!! */
      if (sdp->n_streams % 10 == 0)   /* Don't do this too often */
         check_other_idle_streams(fp);  
      }

# if DNS_ROOT_DEBUG
   log_msg(LOG_WARNING, -1, /* Keep log file open */
      "Flow %d: ifo=%d, fo=%d, new=%d, sp=%x, pq_len=%d",
      flow_nbr(fp), ifo, *flow_order, make_new, sp, sp->pq_len);
   dump_sp_key(sp);
   log_msg(LOG_WARNING, 0, ""); /* Close log file */
# endif

   return sp;
   }
#endif  /* NEW_ATR */

struct flow *create(struct search_result *s_r,
   struct flow_key *search_key, struct pkt *bp)
{
   struct flow *f, *ff, *lf;
   Bit32 fx;
   Bit32 rs_first_flow;
   struct flow *pf;
#if NEW_ATR || defined(MATCH_TRACE)
   int j;
#endif
   if (FloodMode == TV_TRUE)  /* Running in flood mode */
      return NULL;  /* Don't garbage collect for every new flow! */

   if ((fx = alloc_flow_nbr()) == 0) {  /* Find a flow data row */
      bump_noflowpkts(bp->ntm_interface);
      return NULL;  /* No unused rows in flow table */
      }
   f = &fa[fx-1];
#if FLOW_INDEX
   flow_ix[fx-1] = f;  /* 1-org */
#endif
   ++nflows;  /* Nbr of active accounting flows */
   ++FlowsCreated;
   ++ri[c_rtx-1].ri_FlowRecords;  /* 1-org */

   if (f == s_r->lf) {  /* Garbage collector recovered the
               last flow of the hash chain!  (Yes, it can happen) */
      if ((ff = s_r->sf[0]) == NULL) {
         lf = NULL;  /* Empty hash chain */
         }
      else {
         do {
            lf = ff;
            } while ((ff = lf->ht_next) != (struct flow *)s_r->sf);
         }  /* Loop stops with lf -> last flow in chain */
      s_r->lf = lf;
      }

   f->ht_next = (struct flow *)(s_r->sf);  /* Link f into hash chain */
   if (s_r->lf == NULL) {  /* Empty hash chain */
      s_r->sf[0] = f;  ++n_hash_ents;
      }
   else (s_r->lf)->ht_next = f;

   rs_first_flow = ri[c_rtx-1].ri_flow_chain;  /* Link f into rs chain */
   if (rs_first_flow == 0) {  /* First flow for this ruleset */
      f->rs_next = 0;
      ri[c_rtx-1].ri_flow_chain = fx;
      }
   else if (fx < rs_first_flow) {  /* Add to front of chain */
      f->rs_next = rs_first_flow;
      ri[c_rtx-1].ri_flow_chain = fx;
      }
   else {  /* Link into ruleset chain */
      pf = f;  do {
         --pf;
         } while (pf->FlowRuleSet != c_rtx);
      f->rs_next = pf->rs_next;
      pf->rs_next = fx;
      }
# if RS_CH_DEBUG
   check_rs_chain(c_rtx, "create");
# endif

   memcpy(&f->fk,  /* Copy values from search_key */
      search_key, sizeof(struct flow_key));
#ifdef MATCH_TRACE
   printf("new flow, hash=%u :  f=%Fp\n", search_hash,f);
   printf("Count: %u+%u",a_stack[0],p_stack[0]);
   for (j = 1; j != p_s_depth; ++j) printf(",%u+%u",a_stack[j],p_stack[j]);
   printf(": ");
   daddr(search_key->Low.PeerAddress);  printf("->");
   daddr(search_key->High.PeerAddress);  printf("\n");
#endif
#if NEW_ATR
   f->distrib_list = NULL;
   f->distrib_bits = 0;
   f->stdata = NULL;
   make_distrib_list(f);
   setzero64(f->LastUpOctets);
   setzero64(f->LastUpPDUs);
   setzero64(f->LastDownOctets);
   setzero64(f->LastDownPDUs);
#endif  /* NEW_ATR */
   setzero64(f->UpOctets);
   setzero64(f->UpPDUs);
   setzero64(f->DownOctets);
   setzero64(f->DownPDUs);
   f->FlowRuleSet = c_rtx;
#if NF_CISCO_DATA
   f->LastTime = f->FirstTime = bp->dFirst;
#elif LFAP_METER
   f->LastTime = f->FirstTime = bp->FirstTime;
#else
   f->FirstTime = centiseconds(bp->arrival_time);
#endif
   return f;
   }

#if NEW_ATR
static double pwr_of_10[10] = {
   1.0,
   0.1,
   0.01,
   0.001,
   0.0001,
   0.00001,
   0.000001,
   0.0000001,
   0.00000001,
   0.000000001 };

Bit32 scale(struct distribution *d, double y)
{
   double x;
   x = y*pwr_of_10[d->SizeScale > 9 ? 9 : d->SizeScale];
   return (Bit32)floor(x);
   }

double inv_scale(struct distribution *d, Bit16 j)
{
   return ((double)j)/pwr_of_10[d->ScaleFactor > 9 ? 9 : d->ScaleFactor];
   }

void bump_dist(struct distribution *d, double y)
{
   double x, T;
   int n;
   Bit32 ix;

   if (d->Transform == DS_DYN_REQ && d->n_values <= d->Buckets) {
      if (d->n_values == d->Buckets) {
            /* Too many actual values, start using bins */
         Bit32 values[MXBUCKETS+1];
         double R, N,D;
         int m, j;

         R = d->max_val - d->min_val;  /* Leave room at both ends */
         m = d->min_val - 0.125*R;  /* May be negative */
         d->LowerLimit = m < d->lowerlim ? d->lowerlim : m;
         d->UpperLimit = d->max_val + 0.25*R;
         N = d->Buckets - 1;
         D = d->UpperLimit - d->LowerLimit;
         d->M = N/D;
#if 0  /* +-+ */
{ char *strmov(char *d, char *s);  /* +-+ */
char nbr[20], buf[2000], *bp;
   log_msg(LOG_WARNING, -1,
      "DD: lower=%d, upper=%d, min_val=%d, max_val=%d, Lower=%d, Upper=%d",
      d->lowerlim,d->upperlim, d->min_val,d->max_val,
      d->LowerLimit, d->UpperLimit);
   for (bp = buf, j = 0; j != d->Buckets; ++j) {
      sprintf(nbr," %d", d->counts[j]);
      bp = strmov(bp, nbr);
      }
   *bp = '\0';
   log_msg(LOG_WARNING, -1,  /* %% */
      "  counts=%s", buf);
   }
#endif

         memcpy(values, d->counts, sizeof(d->counts));
         memset(d->counts, 0, sizeof(d->counts));
         for (j = 0; j != d->Buckets; ++j) {
            ix = values[j];
            if (ix <= d->LowerLimit) n = 0;
            else if (ix > d->UpperLimit) n = d->Buckets;
            else {
               T = ((double)(ix - d->LowerLimit)) * d->M;
               n = 1 + (int)floor(T);
               }      
            ++d->counts[n];
            }

         d->n_values = d->Buckets+1;
         }
      else {  /* d->n_values < d->Buckets */
         ix = y*pwr_of_10[d->ScaleFactor > 9 ? 9 : d->ScaleFactor];
         if (ix < d->min_val && ix >= d->lowerlim) d->min_val = ix;
         if (ix > d->max_val && ix <= d->upperlim) d->max_val = ix;

         d->counts[d->n_values++] = ix;  d->non_empty = 1;
         return;
         }
      }

   x = y*pwr_of_10[d->ScaleFactor > 9 ? 9 : d->ScaleFactor];
   if (x <= (double)d->LowerLimit) n = 0;
   else if (x > (double)d->UpperLimit) {
      n = d->Buckets;
      }
   else switch (d->Transform) {
   case DS_LIN:
   case DS_DYN_REQ:
      T = (x - (double)d->LowerLimit) * d->M;
      n = 1 + (int)floor(T);
      break;
   case DS_LOG:
      T = log10(x/(double)d->LowerLimit) * d->M;
      n = 1 + (int)floor(T);
      break;
      }
   ++d->counts[n];
   d->non_empty = 1;
   }

void other_stream_update(struct flow *fp,
   struct stream *sp, struct pktdata *tp)
   /* Checks for timed-out pktdatas, adds tp to tail of sp's pq */
{
   double reply_timeout;
   struct pktdata *pd, *lpd, *npd;

   reply_timeout = tp->ts - fp->stdata->U*10000.0;  /* U centiseconds */
# if DNS_ROOT_DEBUG
   log_msg(LOG_WARNING, -1, /* Keep log file open */
      "other_st_ud(): flow=%d, sp=%x, pq_len=%d, mx_len=%d, tp->ts=%f, U=%f",
      flow_nbr(fp), sp, sp->pq_len,sp->mx_pq_len, tp->ts, fp->stdata->U);
   log_msg(LOG_WARNING, 0, ""); /* Close log file */
#endif
   lpd = NULL;
   for (pd = sp->pq; pd != NULL; ) {  /* Look for pkts to discard */
      if (sp->pq_len >= sp->mx_pq_len ||  /* Too many packets in queue */
            pd->ts < reply_timeout) {     /* Older than upper limit */
         if (pd->direction == MFWD) ++fp->stdata->ToLostPDUs;
         else ++fp->stdata->FromLostPDUs;
# if DNS_ROOT_DEBUG
   log_msg(LOG_WARNING, 0,
      "Lost packet: flow=%d, sp=%x, pq_len=%d, mx_len=%d, r_to=%f, pd->ts=%f",
      flow_nbr(fp), sp, sp->pq_len,sp->mx_pq_len, reply_timeout, pd->ts);
#endif
         if (sp->pq_len >= sp->mx_pq_len) {
            if (pd->direction == MFWD) ++fp->stdata->ToPQOverflows;
            else ++fp->stdata->FromPQOverflows;
            }
         --sp->pq_len;
         npd = pd->next;
         if (lpd == NULL) sp->pq = npd;
         else lpd->next = npd;
         free_pktdata(pd);  pd = npd;
         }
      else {
         lpd = pd;  pd = pd->next;
         }
      }

   if (lpd == NULL) sp->pq = tp;  /* Add new pkt to tail of queue */
   else lpd->next = tp;
   ++sp->pq_len;  /* Was missing, causing pktdatas to be
      wrongly discarded as lost packets.  Nevil, 27 Oct 00 */
   tp->next = NULL;
   }
#endif  /* NEW_ATR */

#define CHECK_UPTIME_DECR   1

#if CHECK_UPTIME_DECR
Bit32 lll_Time = 0, lll_good = 0, lll_bad = 0;
#endif

#define LOG_DNS_PKTS  0
#if LOG_DNS_PKTS

FILE *dns_log = NULL;

void log_dns(struct flow *fp, struct pkt *bp, double at_us)
{
   if (dns_log == NULL) {
      if ((dns_log = fopen("dns.log", "w")) == NULL)
         log_msg(LOG_ERR, 1, "Couldn't open dns.log <<<<<");
      }
   fprintf(dns_log, "%013.6f %d %d  ",
      at_us/1000000.0,  fp->fk.FlowKind, fp->fk.FlowClass);
   if (bp->pi.udp.dns.p0 & dns_resp)  /* DNS response */
#if QBYTE_PEER
      fdaddr(dns_log, ntohl(bp->High.PeerAddress.qbyte));
#else
      fdaddr(dns_log, ntohl(*(Bit32 *)&bp->High.PeerAddress));
#endif
   else  /* DNS request */
#if QBYTE_PEER
      fdaddr(dns_log, ntohl(bp->Low.PeerAddress.qbyte));
#else
      fdaddr(dns_log, ntohl(*(Bit32 *)&bp->Low.PeerAddress));
#endif
   fprintf(dns_log, "  %04x %02x%02x\n",
      bp->pi.udp.dns.ident,  bp->pi.udp.dns.p0,bp->pi.udp.dns.p1);
   }
#endif


void count(struct flow *fp,
   int flow_order, struct pkt *bp)
{
   struct search_result *s_r;
   Bit32 ThisUptime;
#if NEW_ATR
   struct stream *sp = NULL;
   struct pktdata *pd, *lpd;
   struct distribution *d;
   int tlen;

   Bit16 ThisFlags;
   Bit32 ThisAck, ThisSeq;

   double at_us, delta_us, iat_delta_us;
   int delta_OK = 0,  /* delta_us is a valid turanaround time */
      pp_type = PP_NO_TEST;  /* Packet-Pair type which produced delta_us */

   int in_train;
#endif
# if DNS_ROOT_DEBUG
   int matched = 0;
# endif

#if NF_CISCO_DATA
   if (flow_order == MREV) {
      add_Bit32_to_counter64(fp->DownOctets,bp->dOctets);
      add_Bit32_to_counter64(fp->DownPDUs,bp->dPkts);
      }
   else {
      add_Bit32_to_counter64(fp->UpOctets,bp->dOctets);
      add_Bit32_to_counter64(fp->UpPDUs,bp->dPkts);
      }
   if (bp->dFirst < fp->FirstTime)
      fp->FirstTime = bp->dFirst;
   if (bp->dLast > fp->LastTime)
      fp->LastTime = bp->dLast;
   fp->ntm_LastTime = uptime_cs();  /* NetFlowMet meter time! */
#elif LFAP_METER
   if (flow_order == MREV)
   {
      add_Bit32_to_counter64(fp->DownOctets,bp->dOctets_sent);
      add_Bit32_to_counter64(fp->DownPDUs,bp->dPackets_sent);
      add_Bit32_to_counter64(fp->UpOctets,bp->dOctets_recvd);
      add_Bit32_to_counter64(fp->UpPDUs,bp->dPackets_recvd);
   }
   else
   {
      add_Bit32_to_counter64(fp->UpOctets,bp->dOctets_sent);
      add_Bit32_to_counter64(fp->UpPDUs,bp->dPackets_sent);
      add_Bit32_to_counter64(fp->DownOctets,bp->dOctets_recvd);
      add_Bit32_to_counter64(fp->DownPDUs,bp->dPackets_recvd);
   }
/*	done in create(...);
   fp->FirstTime = bp->FirstTime; */
   fp->LastTime  = bp->LastTime;
#else
   if (flow_order == MREV) {
      add_Bit32_to_counter64(fp->DownOctets,bp->p_len);
      incr_counter64(fp->DownPDUs);
      }
   else {
      add_Bit32_to_counter64(fp->UpOctets,bp->p_len);
      incr_counter64(fp->UpPDUs); 
     }

   fp->ntm_LastTime = centiseconds(bp->arrival_time);
      /* Also sets LastTime (see flowhash.h) */
#endif  /* NF_CISCO_DATA */

#if CHECK_UPTIME_DECR
   if (fp->ntm_LastTime < lll_Time) {
      log_msg(LOG_WARNING, 0,
         "LastTime jump backward: if=%d, lll=%u, Last=%u, dif=%d, good=%u, bad=%u", 
         bp->Low.Interface,
         lll_Time, fp->ntm_LastTime, lll_Time-fp->ntm_LastTime,
         lll_good, ++lll_bad);
      }
   else  ++lll_good;
   lll_Time = fp->ntm_LastTime;
#endif

#if NEW_ATR && !NF_CISCO_DATA && !LFAP_METER /* Can see individual packets */
   at_us = microseconds(bp->arrival_time);

   if (fp->stdata != NULL &&  /* Flow has stream_data (>= 1 stream attrib) */
         (sp = find_stream(fp, &flow_order, bp, at_us)) != NULL) {

      sp->LastTime = fp->ntm_LastTime;  /* Time last stream pkt was seen */

      if (fp->stdata->pp_match_reqd) {
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
      "count(1) stream to match: sp=%x, fp=%x, order=%d", sp, fp, flow_order);
#endif

         if (bp->TransAddrType == PT_TCP) {
            if ((pd = get_pktdata()) != NULL) {
               ThisFlags = ntohs(bp->pi.tcp.th.flags);
               tlen = bp->pi.tcp.tcp_len - tcp_dataoffset(ThisFlags);
               ThisSeq = ntohl(bp->pi.tcp.th.seqnum);
               ThisAck = ntohl(bp->pi.tcp.th.acknum);
               pd->ts = at_us;  pd->pkt_len = bp->p_len;
               pd->pp_type = PP_TCP;
               pd->direction = flow_order;
               pd->pi.tcp.ack = ThisAck;
               pd->pi.tcp.exp_seq = ThisSeq + tlen;
               if (ThisFlags & (tcp_FlagSYN | tcp_FlagFIN))
                  ++pd->pi.tcp.exp_seq;  /* SYN and FIN consume one seq nbr */
               pd->pi.tcp.d_len = tlen;
               pd->pi.tcp.pkt_nbr = ++sp->pkt_nbr;
               pd->pi.tcp.flags = ThisFlags & tcp_FlagALL;

               delta_OK = tcp_turnaround(&pp_type, &delta_us, fp, sp, pd);
 
               tcp_stream_update(fp, sp, pd);

#if CR_TRACE
               if (delta_OK) {
                  printf(" %c%02X ", flow_order == MFWD ? '+' : '-', pp_type);
                  fflush(stdout);
                  }
#endif
               }
            }
         else if (bp->TransAddrType == PT_UDP) {
            if (bp->High.TransAddress == htons(WNP_DOMAIN) ||
                  bp->Low.TransAddress == htons(WNP_DOMAIN)) {
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
	   "  count(2) DNS pkt: sp=%x, info_sz=%d, ident=%x, info=%02x%02x",
      sp, bp->pktinfo_sz, bp->pi.udp.dns.ident,
      bp->pi.udp.dns.p0,bp->pi.udp.dns.p1);
#endif

#if LOG_DNS_PKTS
   log_dns(fp,bp,at_us);
#endif

               pp_type = PP_UDP_DNS;
               if (bp->pi.udp.dns.p0 & dns_resp) {  /* DNS response */
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
      "  count(3) DNS response: sp=%x", sp);
#endif
                  for (pd = sp->pq; pd != NULL; pd = pd->next) {
                     if (pd->pi.udp.dns.ident == bp->pi.udp.dns.ident &&
                           pd->direction != flow_order) {
                        delta_us = at_us - pd->ts;
                        if (pd == sp->pq) sp->pq = pd->next;
                        else lpd->next = pd->next;
                        --sp->pq_len;
# if DNS_ROOT_DEBUG
   log_msg(LOG_WARNING, 0,
      "   --- DNS response matched, stream %x, pq_len=%d", sp, sp->pq_len);
   matched = 1;
#endif
                        free_pktdata(pd);
                        delta_OK = 1;
                        break;
         	        }
                     lpd = pd;
#if PP_DEBUG_XX
   if (delta_OK)
      log_msg(LOG_WARNING, 0,"    count(4) DNS match: sp=%x", sp);
#endif
                     }
                  if (pd == NULL) {  /* Unrequested response; count as lost */
                     if (flow_order == MFWD) ++fp->stdata->ToLostPDUs;
                     else ++fp->stdata->FromLostPDUs;
                     }
# if DNS_ROOT_DEBUG
   if (!matched) log_msg(LOG_WARNING, 0,
      "   ??? DNS response NOT matched, stream %x, pq_len=%d", sp, sp->pq_len);
#endif
                  }
               else {  /* DNS query */
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0, "  count(3) DNS query: sp=%x", sp);
#endif
                  if ((pd = get_pktdata()) != NULL) {
                     pd->ts = at_us;  pd->pkt_len = bp->p_len;
                     pd->direction = flow_order;
                     pd->pi.udp.dns.ident = bp->pi.udp.dns.ident;
# if DNS_ROOT_DEBUG
   log_msg(LOG_WARNING, 0,
      "   +++ DNS request queued, stream %x, pq_len=%d,", sp, sp->pq_len);
#endif

                     other_stream_update(fp, sp, pd);
                     }
		  }
               }
	    /* else if (fp->fk.High.TransAddress[1] != .. Other UDP pairs */
            }
         else if (bp->TransAddrType == PT_ICMP) {
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
      "count(5) ICMP pkt: sp=%x, info_sz=%d, ident=%04x, sequence=%04x",
      sp, bp->pktinfo_sz, bp->pi.icmp.ident, bp->pi.icmp.sequence);
#endif
            if (bp->Low.TransAddress == 0) {  /* Echo response */
#if PP_DEBUG_XX
if (bp->pi.icmp.sequence != 0)
   log_msg(LOG_WARNING, 0,
      "  count(6) ICMP echo response: sp=%x, info_sz=%d, ident=%04x, sequence=%04x, dirn=%d",
      sp, bp->pktinfo_sz, bp->pi.icmp.ident, bp->pi.icmp.sequence, flow_order);
 quack(76);
#endif
               pp_type = PP_ICMP_ECHO;
               for (pd = sp->pq; pd != NULL; pd = pd->next) {
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
      "  count(7) ICMP queue: ident=%04x, sequence=%04x, dirn=%d",
	   pd->pi.icmp.ident, pd->pi.icmp.sequence, pd->direction);
#endif
                  if (pd->pi.icmp.ident == bp->pi.icmp.ident &&
                        pd->pi.icmp.sequence == bp->pi.icmp.sequence &&
                        pd->direction != flow_order) {
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
      "  count(8) ICMP echo response MATCHED <<<<<<<: sp=%x", sp);
#endif
                     delta_us = at_us - pd->ts;
                     if (pd == sp->pq) sp->pq = pd->next;
                     else lpd->next = pd->next;
                     --sp->pq_len;
                     free_pktdata(pd);
                     delta_OK = 1;
                     break;
                     }
                  lpd = pd;
                  }
               }
            else if (bp->Low.TransAddress == 8) {  /* Echo request */
#if PP_DEBUG_XX
if (bp->pi.icmp.sequence != 0)
   log_msg(LOG_WARNING, 0,
      "  count(9) ICMP echo request: sp=%x, info_sz=%d, ident=%04x, sequence=%04x, dirn=%d",
      sp, bp->pktinfo_sz, bp->pi.icmp.ident, bp->pi.icmp.sequence, flow_order);
#endif
               pp_type = PP_ICMP_ECHO;
               if ((pd = get_pktdata()) != NULL) {
                  pd->ts = at_us;  pd->pkt_len = bp->p_len;
                  pd->direction = flow_order;
                  pd->pi.icmp.ident = bp->pi.icmp.ident;
                  pd->pi.icmp.sequence = bp->pi.icmp.sequence;

                  other_stream_update(fp, sp, pd);
		  }
               }
	    /* else if (fp->fk.High.TransAddress[1] ==  .. Other ICMP pairs */
            }
         /* else if (bp->TransAddrType == .. other protocols */
         }
      else {  /* Not pp matching */
         }
      }

#if PP_DEBUG_XX
if (fp->stdata)
   log_msg(LOG_WARNING, 0,
      "  count(10): order=%d, match_rq=%d, pp_type=%d, delta_OK=%d",
      flow_order, fp->stdata->pp_match_reqd, pp_type, delta_OK);
#endif
   for (d = fp->distrib_list; d != NULL; d = d->next) {  /* Update distribs */
      if (flow_order == MREV) {
         switch (d->selector) {
         case FTFROMPACKETSIZE:
            bump_dist(d, bp->p_len);
            break;
         case FTFROMINTERARRIVALTIME:
            if (sp != NULL && sp->FromSeen) {
               iat_delta_us = at_us - sp->LastFromTime;
               bump_dist(d, iat_delta_us);
               }
            else if (fp->stdata && fp->stdata->FromSeen) {
               iat_delta_us = at_us - fp->stdata->LastFromTime;
               bump_dist(d, iat_delta_us);
               }
            break;
         case FTTOTURNAROUNDTIME1:
         case FTTOTURNAROUNDTIME2:
         case FTTOTURNAROUNDTIME3:
/*         case FTTOTURNAROUNDTIME4: */
#if PP_DEBUG_XX
if (delta_OK)
   log_msg(LOG_WARNING, 0,
      "  count(11) MREV: delta_OK=%d, pp_type=%02X, d->PP_type=%02X, count=%02X",
      delta_OK, pp_type, d->PP_Type, d->PP_Type & pp_type );
#endif
            if (delta_OK) {
               if ((bp->TransAddrType == PT_TCP &&
                     d->PP_Type & pp_type & ~PP_TCP) ||  /* Bits in common */
                     (bp->TransAddrType != PT_TCP &&
                     d->PP_Type == pp_type)) {  /* Exact match */
                  bump_dist(d, delta_us);
                  }
#if PP_DEBUG_XX
   log_msg(LOG_WARNING, 0,
      "  count(12): counted, attrib=%d\n", d->selector);
#endif
	       }
            break;
/*          case FTFROMTRAINLENGTH: */
         case FTFROMTURNAROUNDTIME4:
            if (d->UpperSize != 0 && bp->p_len > d->UpperSize)
               in_train = 0;
            else if (d->LowerSize != 0 && bp->p_len <= d->LowerSize)
               in_train = 0;
            else in_train = 1;

            if (d->last_was_in_train) {
               if (in_train) ++d->trainlength;
               else {  /* End of train */
                  bump_dist(d, d->trainlength);
                  d->trainlength = 0;
                  }
               }
            d->last_was_in_train = in_train;
            break;
         case FTTCPDATA:
            pp_type = PP_TCP;
               /* ??????? Do we need this for REV as well ????? */
            break;
            }
         }
      else {  /* MFWD */
         switch (d->selector) {
         case FTTOPACKETSIZE:
            bump_dist(d, bp->p_len);
            break;
         case FTTOINTERARRIVALTIME:
            if (sp != NULL && sp->ToSeen) {
               iat_delta_us = at_us - sp->LastToTime;
               bump_dist(d, iat_delta_us);
               }
            else if (fp->stdata && fp->stdata->ToSeen) {
               iat_delta_us = at_us - fp->stdata->LastToTime;
               bump_dist(d, iat_delta_us);
               }
            break;
         case FTFROMTURNAROUNDTIME1:
         case FTFROMTURNAROUNDTIME2:
         case FTFROMTURNAROUNDTIME3:
/*         case FTFROMTURNAROUNDTIME4: */
#if PP_DEBUG_XX
if (delta_OK)
   log_msg(LOG_WARNING, 0,
      "  count(13) MFWD: delta_OK=%d, pp_type=%02X, d->PP_type=%02X, count=%02X",
      delta_OK, pp_type, d->PP_Type, d->PP_Type & pp_type );
#endif
            if (delta_OK) {
               if ((bp->TransAddrType == PT_TCP &&
                     d->PP_Type & pp_type & ~PP_TCP) ||  /* Bits in common */
                     (bp->TransAddrType != PT_TCP &&
                     d->PP_Type == pp_type)) {  /* Exact match */
                  bump_dist(d, delta_us);
                  }
	       }
            break;
/*         case FTTOTRAINLENGTH: */
         case FTTOTURNAROUNDTIME4:
            if (d->UpperSize != 0 && bp->p_len > d->UpperSize)
               in_train = 0;
            else if (d->LowerSize != 0 && bp->p_len <= d->LowerSize)
               in_train = 0;
            else in_train = 1;

            if (d->last_was_in_train) {
               if (in_train) ++d->trainlength;
               else {  /* End of train */
                  bump_dist(d, d->trainlength);
                  d->trainlength = 0;
                  }
               }
            d->last_was_in_train = in_train;
            break;
            }
         }
      }

   if (flow_order == MREV) {  /* Set new LastTimes after updating distributions */
      if (sp != NULL) {
         sp->LastFromTime = at_us;  sp->FromSeen = 1;
         ++sp->FromPDUs;
         sp->FromOctets += bp->p_len;
         }
      else if (fp->stdata) {
         fp->stdata->LastFromTime = at_us;  fp->stdata->FromSeen = 1;
         }
      }
   else {  /* MFWD */
      if (sp != NULL) {
         sp->LastToTime = at_us;  sp->ToSeen = 1;
         ++sp->ToPDUs;
         sp->ToOctets += bp->p_len;
         }
      else if (fp->stdata) {
         fp->stdata->LastToTime = at_us;  fp->stdata->ToSeen = 1;
         }
      }
                     
#endif  /* NEW_ATR && !NF_CISCO_DATA   Individual packets */
   }

#if 0
@@@@@@@@@ Found old stream on very first test! @@@@@@@@@@
This flow ...
flow 80c6270, PeerAddrType=1, TransAddrType=17
   Source PeerAddress=0.180.0.2, TransAddress=0
   Dest PeerAddress=0.1.0.1, TransAddress=53
f_s_r(): stream 818cbb8 found, rev chain flow 80a5f30
flow 80a5f30, PeerAddrType=1, TransAddrType=17
   Source PeerAddress=0.1.0.1, TransAddress=0
   Dest PeerAddress=0.180.0.2, TransAddress=53
#endif

struct pkt_key
   k1 = { 0,180,0,2 },
   k2 = { 0,1,0,1 };

int bad_flow(struct pkt *bp)
{
   if (bp->TransAddrType == PT_ICMP) return 0;
   if (memcmp(&bp->Low.PeerAddress.byte, &k1.PeerAddress,
         PEER_ADDR_LEN) == 0 &&
      memcmp(&bp->High.PeerAddress.byte, &k2.PeerAddress,
         PEER_ADDR_LEN) == 0)
      return 1;
   if (memcmp(&bp->High.PeerAddress.byte, &k1.PeerAddress,
         PEER_ADDR_LEN) == 0 &&
      memcmp(&bp->Low.PeerAddress.byte, &k2.PeerAddress,
         PEER_ADDR_LEN) == 0)
      return 1;
   return 0;
   }

void pkt_monitor(struct pkt *bp)
{
   int x, r;
   struct rtinf_rec *rip;
   struct flow_key sd_key, ds_key;
   Bit32 sd_hash, ds_hash;
   struct search_result sd_pointers, ds_pointers;
   struct flow *f;
   int bad;  /* () () () () */

#if defined(DOS)
   if (bp->PeerAddrType == AT_DUMMY) {
      if (++dummypackets == 1000) {
         ++kilodummypackets;  dummypackets = 0;
         }
      ++dummypacketrate;
      }
#endif

   for (x = running_rts; x != 0; x = rip->next_running_rt) {
      rip = &ri[x-1];
      c_rtx = x;  /* Rule table index of current rule table */
      c_rt = rip->ri_rule_table;  /* Current Rule Table */
      c_rht = rip->ri_rule_hash;  /* Current Rule Hash Table */
      c_rsz = rip->ri_Size;  /* Nbr of rules in current rule table */

#define SK_TRACE  0
#if !SK_TRACE  /* () () () () */
      r = match(1, bp, &bp->Low,&bp->High);
      if (r == MIGNORE)
         continue;  /* Try next rule set */
         /* match() builds search_key from bp */
      else if (r == MFAIL) {  /* S->D Fail */
         r = match(0, bp, &bp->High,&bp->Low);
         if (r != MSUC)
            continue;  /* Ignore or fail */
         else { 
            build_search_key(&ds_hash,&ds_key,
               bp, &bp->High,&bp->Low);
            if ((f = current(&ds_pointers,
                  ds_hash,&ds_key)) != NULL)
               count(f,MREV,bp);
            else {
               f = create(&ds_pointers, &ds_key, bp);
               if (f == NULL) continue;  /* Coudn't create flow */
               count(f,MREV,bp);
               }
            }
         }
      else {  /* S->D Suc */
         build_search_key(&sd_hash,&sd_key,
            bp, &bp->Low,&bp->High);
         if ((f = current(&sd_pointers,
               sd_hash,&sd_key)) != NULL)
            count(f,MFWD,bp);
         else {
            build_search_key(&ds_hash,&ds_key,
               bp, &bp->High,&bp->Low);
            if ((f = current(&ds_pointers,
                  ds_hash,&ds_key)) != NULL)
               count(f,MREV,bp);
            else {
               f = create(&sd_pointers, &sd_key, bp);
               if (f == NULL) continue;  /* Coudn't create flow */
               count(f,MFWD,bp);
               }
            }
         }
#else  /* () () () () */
      bad = bad_flow(bp);
      if (bad) {
	printf(">>> packet ");  dump_bp(bp);  quack(21); }
      r = match(1, bp, &bp->Low,&bp->High);
      if (r == MIGNORE)
         continue;  /* Try next rule set */
      else if (r == MFAIL) {  /* S->D Fail */
         r = match(0, bp, &bp->High,&bp->Low);
         if (r != MSUC)
            continue;  /* Ignore or fail */
         else { 
            build_search_key(&ds_hash, &ds_key,
               bp, &bp->High, &bp->Low);
            if (bad) { dump_flow_key(&ds_key, ds_hash,"@@1 D->S succeed"); quack(22); }
            if ((f = current(&ds_pointers,
                  ds_hash, &ds_key)) != NULL)
  	           {
               count(f,MREV,bp);
	       if (bad) {
	       printf("   count(f,MREV,bp), f=%x\n", f);  quack(1); }
		   }
            else {
               f = create(&ds_pointers, &ds_key, bp);
               if (bad) printf("   created f=%x\n", f);
               if (bad) dump_flow_key(&ds_key, ds_hash,"@@2 create D->S");
               if (f == NULL) continue;  /* Coudn't create flow */
	      {
               count(f,MREV,bp);
	       if (bad) {
	       printf("   count(f,MREV,bp), f=%x\n", f);  quack(2); }
	      }
               }
            }
         }
      else {  /* S->D Suc */

         build_search_key(&sd_hash, &sd_key,
            bp, &bp->Low, &bp->High);
         if (bad) dump_flow_key(&sd_key, sd_hash,"@@3 S->D succeed");
         if ((f = current(&sd_pointers,
               sd_hash, &sd_key)) != NULL)
	   {
            count(f,MFWD,bp);
	    if (bad) {
	       printf("   count(f,MFWD,bp), f=%x\n", f);  quack(3); }
	   }
         else {  /* match() built S->D key; save it then build D->S key */
            build_search_key(&ds_hash, &ds_key,
               bp, &bp->High, &bp->Low);
            if (bad) dump_flow_key(&ds_key, ds_hash,"@@4 not current S->D");
            if ((f = current(&ds_pointers,  /* Swapped-over key is in search_key */
                  ds_hash, &ds_key)) != NULL)
               {
               count(f,MREV,bp);
	       if (bad) {
	       printf("   count(f,MREV,bp), f=%x\n", f);  quack(4); }
	       }
            else {  /* Not current(D->S); restore S->D key for create() */
               if (bad) dump_flow_key(&sd_key, sd_hash,"@@5 not current D->S");
               f = create(&sd_pointers, &sd_key, bp);
               if (bad) printf("   created f=%x\n", f);
               if (f == NULL) continue;  /* Coudn't create flow */
               {
               count(f,MFWD,bp);
	       if (bad) {
               printf("   count(f,MFWD,bp), f=%x\n", f);  quack(5); }
	       }
               }
            }
         }
#endif  /* () () () () */
      }
   }

#define MAXPKTLEN  1526

#if !NF_CISCO_DATA && !LFAP_METER
   /* Front-end calls the following routines to get attrib values */

#ifdef DECNET
int dn_node(Bit8 *dn_addr)
{
   return dn_addr[1]<<8 | dn_addr[0];
   }

#define dn_area(node) (node >> 10)
#define dn_host(node) (node & 0x3FF)
#endif

void unpack_dn_node(Bit8 *ap, const Bit8 *dn_addr)
{
   Bit8 dl = dn_addr[1];
   ap[0] = dl >> 2;    /* DECnet area */
   ap[1] = dl & 0x03;  ap[2] = dn_addr[0];  /* DECnet host */
   ap[3] = 0;
   }

void unpack_at_node(Bit8 *ap, const Bit8 *at_net, const Bit8 at_node)
{
   ap[0] = at_net[0];  ap[1] = at_net[1];
   ap[2] = at_node;
   ap[3] = 0;
   }

#define BAD_ADJ    1
#define BAD_PEER   2
#define BAD_TRANS  3

void zero_pkt_fields(int which, struct pkt *bp)
{
   switch (which) {
   case BAD_ADJ:
      bp->Low.AdjAddr_ms4 = bp->High.AdjAddr_ms4 = 0;
      bp->Low.AdjAddr_ls2 = bp->High.AdjAddr_ls2 = 0;
      bp->invalid_addrs |= INV_ADJ;
      break;
   case BAD_PEER:
#if QBYTE_PEER
      bp->Low.PeerAddress.qbyte = bp->High.PeerAddress.qbyte = 0;
#else
      memset(&bp->Low.PeerAddress.byte,  0, PEER_ADDR_LEN+TRANS_ADDR_LEN);
      memset(&bp->High.PeerAddress.byte, 0, PEER_ADDR_LEN+TRANS_ADDR_LEN);
#endif
      bp->invalid_addrs |= INV_PEER;
      break;
   case BAD_TRANS:
      bp->Low.TransAddress = bp->High.TransAddress = 0;
      bp->invalid_addrs |= INV_TRANS;
      break;
      }
   }

#if V6
int find_v6_type(int *prot, int *hx, const Bit8 *p, const int hl)
{
   int nh, nx;

   nh = p[6];  nx = 40;
   for (;;) {
      switch (nh) {
      case PT_6FRAG:  /* Fragment */
      case PT_6ESP:   /* Encapsulating Security Payload (ESP) RFC 2406 */
      case PT_6NONH:  /* No next header */
         *prot = nh;
         return 0;  /* Can't see into this packet */

      /* case PT_6ICMP:  /* ICMPv6 */
      default:
         *prot = nh;  *hx = nx;
         return 1;  /* Can see into this one */

      case PT_6HOP:   /* Hop-by-hop options */
      case PT_6ROUT:  /* Routing (Type 0) */
      case PT_6DEST:  /* Destination options */
      case PT_6AH:    /* Authentication (AH) RFC 2402 */
         break;  /* Keep looking */
         }
      if (hl < nx) {
         *prot = -1;  /* Not enough header bytes to get Next Header */
         return 0;
         }
      nh = p[nx];
      nx += (p[nx]+1) * 8;
      }
   }
#endif

void pkt_extract(struct pkt *bp,
   const int type, const Bit8 *hdrp, const int hl)
{
   int prot, dtype, dx, j;
   union decnet *dp;
#ifdef DECNET
   int dlen, snode, dnode, area, host;
#endif
#if NF_OCX_BGP
   Subnet *my_subnet;
   Bit32 my_IP4addr;
   Bit16 my_ASN;
#endif

   bp->PeerAddrType = type;
   bp->invalid_addrs = 0;
   if (hl < 0) {  /* Didn't get the header! */
      zero_pkt_fields(BAD_ADJ, bp);
      return;
      }

   switch (type) {
#if V6
   case AT_IP6:
      bp->DSCodePoint =  /* Traffic Class  field, RFC 2460 */
         (hdrp[1] >> 6) | ((hdrp[0] & 0x0F) << 2);
      if (hl < 8+IP6_ADDR_LEN*2) {  /* Didn't get the peer addresses! */
         zero_pkt_fields(BAD_PEER, bp);
         return;
         }
      memcpy(&bp->Low.PeerAddress,&hdrp[8],IP6_ADDR_LEN);  /* IPv6 source */
      memcpy(&bp->High.PeerAddress,&hdrp[24],IP6_ADDR_LEN);  /* IPv6 dest */
      if (!find_v6_type(&prot,&dx, hdrp,hl)) {
         if (prot < 0)  /* Not enough bytes to determine protocol */
            bp->TransAddrType = 0;
	else  /* Can't see into packet body */
            bp->TransAddrType = prot;
         zero_pkt_fields(BAD_TRANS, bp);
         return;
         }

      switch (bp->TransAddrType = prot) {
      case PT_UDP:
      case PT_TCP:
         if (hl < dx+4) {  /* Didn't get the IP ports! */
            zero_pkt_fields(BAD_TRANS, bp);
            return;
            }
         bp->Low.TransAddress = *(Bit16 *)&hdrp[dx];  /* Source port */
         bp->High.TransAddress = *(Bit16 *)&hdrp[dx+2];  /* Dest port */
         break;
      case PT_6ICMP:  /* RFC 2463 */
      case PT_ICMP:
         if (hl < dx+2) {  /* Didn't get the type,code fields */
            zero_pkt_fields(BAD_TRANS, bp);
            return;
            }
         bp->Low.TransAddress = htons(hdrp[dx]);  /* Type field */
         bp->High.TransAddress = htons(hdrp[dx+1]);  /* Code field */
         break;
         }
#if NEW_ATR
      if (prot == PT_TCP) {
         if (hl < dx+4+sizeof(bp->pi.tcp.th)) {  /* Didn't get the tcp header */
            bp->pi.tcp.tcp_len = 0;
            memset(&bp->pi.tcp.th, 0, sizeof(bp->pi.tcp.th));
            bp->pktinfo_sz = 0;
            }
         else {
            memcpy(&bp->pi.tcp.th, &hdrp[dx+4],
               bp->pktinfo_sz = sizeof(bp->pi.tcp.th));
            bp->pi.tcp.tcp_len = ntohs(*(Bit16 *)&hdrp[2]) - dx;
            }
         }
#endif  /* NEW_ATR */
      return;
#endif
   case AT_IP4:
      bp->DSCodePoint = hdrp[1] >> 2;  /* Old TOS field, RFC 2474 */
      if (hl < 12+IP4_ADDR_LEN*2) {  /* Didn't get the peer addresses! */
         zero_pkt_fields(BAD_PEER, bp);
         return;
         }
#if QBYTE_PEER
      bp->Low.PeerAddress.qbyte = *(Bit32 *)&hdrp[12];  /* IPv4 source */
      bp->High.PeerAddress.qbyte = *(Bit32 *)&hdrp[16];  /* IPv4 dest */
#else
      memcpy(&bp->Low.PeerAddress,&hdrp[12],IP4_ADDR_LEN);  /* IPv4 source */
      memcpy(&bp->High.PeerAddress,&hdrp[16],IP4_ADDR_LEN);  /* IPv4 dest */
#endif
      prot = bp->TransAddrType = hdrp[9];  /* IPv4 Protocol Type */
      if (hdrp[6] & 0x1F != 0 || hdrp[7] != 0) {  /* Fragment offset != 0 */
         zero_pkt_fields(BAD_TRANS, bp);  /* Can't see into fragment */
         return;
         }
      dx = (hdrp[0] & 0x0F) << 2;  /* HLEN */
      /* Data offset.  HLEN = IPv4 header length in 32-bit units */
      if (prot == PT_ICMP) {
         if (hl < dx+2) {  /* Didn't get the type,code fields */
            zero_pkt_fields(BAD_TRANS, bp);
            return;
            }
         bp->Low.TransAddress = htons(hdrp[dx]);  /* Type field */
         bp->High.TransAddress = htons(hdrp[dx+1]);  /* Code field */
         }
      else {
         if (hl < dx+4) {  /* Didn't get the IP ports! */
            zero_pkt_fields(BAD_TRANS, bp);
            return;
            }
         bp->Low.TransAddress = *(Bit16 *)&hdrp[dx];  /* Source port */
         bp->High.TransAddress = *(Bit16 *)&hdrp[dx+2];  /* Dest port */
            /* In **network** order within Bit16, to match rule values */
         }
#if NEW_ATR
      if (prot == PT_ICMP) {
         if (hl < dx+4+sizeof(bp->pi.icmp)) {  /* Didn't get enough ICMP data */
            memset(&bp->pi.icmp, 0, sizeof(bp->pi.icmp));
            bp->pktinfo_sz = 0;
            }
         else {
            memcpy(&bp->pi.icmp, &hdrp[dx+4],
               bp->pktinfo_sz = sizeof(bp->pi.icmp));
            }
         }
      else if (prot == PT_UDP) {
         if (hl < dx+8+sizeof(bp->pi.udp)) {  /* Didn't get enough UDP data */
            memset(&bp->pi.udp, 0, sizeof(bp->pi.udp));
            bp->pktinfo_sz = 0;
            }
         else {
            memcpy(&bp->pi.udp, &hdrp[dx+8],
               bp->pktinfo_sz = sizeof(bp->pi.udp));
            }
#if PP_DEBUG_XXX
   printf("\npkt_extract(2) UDP pkt: info_sz=%d, ",  bp->pktinfo_sz);
   for (j = 0; j != bp->pktinfo_sz; ++j) printf(" %02x", ((char *)&bp->pi.udp)[j]);
   printf("\n");
#endif
         }
      else if (prot == PT_TCP) {
         if (hl < dx+4+sizeof(bp->pi.tcp.th)) {  /* Didn't get the tcp header */
            bp->pi.tcp.tcp_len = 0;
            memset(&bp->pi.tcp.th, 0, sizeof(bp->pi.tcp.th));
            bp->pktinfo_sz = 0;
            }
         else {
            memcpy(&bp->pi.tcp.th, &hdrp[dx+4],
               bp->pktinfo_sz = sizeof(bp->pi.tcp.th));
            bp->pi.tcp.tcp_len = (hdrp[2] << 8 | hdrp[3]) - dx;
	    }
         }
#endif  /* NEW_ATR */
#if NF_OCX_BGP
#define get32(a)  ((a[0] << 8 | a[1]) << 8 | a[2]) << 8 | a[3]
      if (asn_lookup) {
         my_IP4addr = get32(bp->Low.PeerAddress);
         if ((my_subnet = FindSubnet(ntohl(my_IP4addr))) != NULL) {
            my_ASN = use_owner_asns ? my_subnet->src_as : my_subnet->exit_as;
            bp->Low.nf_ASN[0] = my_ASN >> 8;
            bp->Low.nf_ASN[1] = my_ASN & 0xff;
            bp->Low.nf_mask = bits_in_mask(my_subnet->mask);
            }
         else bp->Low.nf_ASN[0] = bp->Low.nf_ASN[1] = 0;
         my_IP4addr = get32(bp->High.PeerAddress);
         if ((my_subnet = FindSubnet(ntohl(my_IP4addr))) != NULL) {
            my_ASN = use_owner_asns ? my_subnet->src_as : my_subnet->exit_as;
            bp->High.nf_ASN[0] = my_ASN >> 8;
            bp->High.nf_ASN[1] = my_ASN & 0xff;
            bp->High.nf_mask = bits_in_mask(my_subnet->mask);
            }
         else bp->High.nf_ASN[0] = bp->High.nf_ASN[1] = 0;
         }
#endif
      return;
   case AT_NOVELL:
      bp->TransAddrType = hdrp[5];  /* XNS packet type */
      if (hl < 18+IPX_ADDR_LEN) {  /* Didn't get the peer addresses! */
         zero_pkt_fields(BAD_PEER, bp);
         return;
         }
      memcpy(&bp->High.PeerAddress,&hdrp[6],IPX_ADDR_LEN);  /* IPX dest net */
      memcpy(&bp->Low.PeerAddress,&hdrp[18],IPX_ADDR_LEN);  /* IPX soure net */
      if (hl < 30) {  /* Didn't get the IPX sockets! */
         zero_pkt_fields(BAD_TRANS, bp);
         return;
         }
      bp->High.TransAddress = *(Bit16 *)&hdrp[16];
         /* IPX dest socket */
      bp->Low.TransAddress = *(Bit16 *)&hdrp[28];
         /* IPX source socket */
      return;
   case AT_ETHERTALK:
      if (hl < 10) {  /* Didn't get the peer addresses! */
         zero_pkt_fields(BAD_PEER, bp);
         return;
         }
      unpack_at_node(&bp->Low.PeerAddress.byte, &hdrp[6],hdrp[9]);  /* AT source */
      unpack_at_node(&bp->High.PeerAddress.byte, &hdrp[4],hdrp[8]);  /* AT source */
      if (hl < 13) {  /* Didn't get the AT sockets! */
         zero_pkt_fields(BAD_TRANS, bp);
         return;
         }
      bp->TransAddrType =hdrp[12];  /* AT DDP protocol type */
      bp->Low.TransAddress = (Bit16)hdrp[11];  /* AT source socket */
      bp->High.TransAddress = (Bit16)hdrp[10];  /* AT dest socket */
      return;
   case AT_CLNS:
      bp->TransAddrType = hdrp[5] & 0x1F;  /* CLNS packet type */
      bp->Low.TransAddress = bp->High.TransAddress = 0;
      memcpy(&bp->High.PeerAddress,&hdrp[11],j = hdrp[10]);  /* Dest */
      if (j < NSAP_ADDR_LEN)
         memset(&bp->High.PeerAddress + j, 0, NSAP_ADDR_LEN-j);
      memcpy(&bp->Low.PeerAddress,&hdrp[12+j],dx = hdrp[11+j]);  /* Source */
      if (dx < NSAP_ADDR_LEN)
         memset(&bp->Low.PeerAddress + dx, 0, NSAP_ADDR_LEN-dx);
      return;
   case AT_DECNET:
      bp->Low.TransAddress = bp->High.TransAddress = 0;
      dtype = hdrp[2];  /* DECnet packet type */
      if (dtype != 0x81) dp = (union decnet *)&hdrp[3];
      else {
         dtype = hdrp[3];
         dp = (union decnet *)&hdrp[4];
         }
      switch (bp->TransAddrType = dtype & 0x0F) {
      case 0x06:  /* Data */
      case 0x0E:  /* Data + discard flag */
         unpack_dn_node(&bp->Low.PeerAddress.byte, dp->d.src_dn_addr);
         unpack_dn_node(&bp->High.PeerAddress.byte, dp->d.dest_dn_addr);
#ifdef DECNET
         dnode = dn_node(dp->d.dest_dn_addr);
         snode = dn_node(dp->d.src_dn_addr);
         fprintf(logfl,"Data to %d.%d from %d.%d\n",
            dn_area(dnode),dn_host(dnode),
            dn_area(snode),dn_host(snode) );
#endif
         break;
      case 0x07:  /* Level 1 routing */
         unpack_dn_node(&bp->Low.PeerAddress.byte, dp->l1r.src_dn_addr);
#ifdef DECNET
         snode = dn_node(dp->l1r.src_dn_addr);
         fprintf(logfl,"Level 1 routing from %d.%d\n",
            dn_area(snode),dn_host(snode));
#endif
         break;
      case 0x09:  /* Level 2 routing */
         unpack_dn_node(&bp->Low.PeerAddress.byte, dp->l2r.src_dn_addr);
#ifdef DECNET
         snode = dn_node(dp->l2r.src_dn_addr);
         fprintf(logfl,"Level 2 routing from %d.%d\n",
            dn_area(snode),dn_host(snode));
#endif
         break;
      case 0x0B:  /* Router hello */
         unpack_dn_node(&bp->Low.PeerAddress.byte, dp->rh.src_dn_addr);
#ifdef DECNET
         snode = dn_node(dp->rh.src_dn_addr);
         dnode = dn_node(dp->rh.rtr_dn_addr);
         fprintf(logfl,"Router hello from %d.%d, other router %d.%d\n",
            dn_area(snode),dn_host(snode),
            dn_area(dnode),dn_host(dnode) );
#endif
         break;
      case 0x0D:  /* Endnode hello */
         unpack_dn_node(&bp->Low.PeerAddress.byte, dp->eh.src_dn_addr);
#ifdef DECNET
         snode = dn_node(dp->eh.src_dn_addr);
         dnode = dn_node(dp->eh.rtr_dn_addr);
         fprintf(logfl,"Endnode hello from %d.%d, designated router %d.%d\n",
            dn_area(snode),dn_host(snode),
            dn_area(dnode),dn_host(dnode) );
#endif
         break;
      default:  /* Unknown DECnet type */
         scpos(0,scr_lrow);
         printf("\nDN pkt type %02x: ", dtype);
         for (j=0; j != 16; ++j) printf(" %02x",hdrp[j]);
         printf("\n");
         for (j=16; j != 34; ++j) printf(" %02x",hdrp[j]);
         printf("\n");
#ifdef TESTING
         if (!logfl) open_log();
         fprintf(logfl, "\nDN pkt type %02x: ", dtype);
         for (j=17; j != 34; ++j) fprintf(logfl," %02x",hdrp[j]);
         fprintf(logfl, "\n");
#endif
         }
      return;
      }
   }

#define putshort(a,n)  \
   a[0] = (n >> 8) & 0xFF;  a[1] = n & 0xFF;

void other_extract(struct pkt *bp,
   const Bit16 ethertype, const Bit16 lsap)
{
   bp->PeerAddrType = AT_OTHER;
   memset(&bp->Low.PeerAddress.byte, 0, sizeof(bp->Low.PeerAddress));
   memset(&bp->High.PeerAddress.byte, 0, sizeof(bp->High.PeerAddress));
   bp->Low.PeerAddress.trans = htons((Bit16)ethertype);  /* Blue Book type */
   bp->High.PeerAddress.trans = htons((Bit16)lsap);  /* 802.2 LSAP */
   bp->TransAddrType = 0;
   bp->Low.TransAddress = bp->High.TransAddress = 0;
   }

int handle_pkt(struct pkt *pp,
   const Bit16 ether_type, const Bit16 lsap,
   const Bit8 *ethp, 
   const Bit8 *p, const int pl)
{
   pp->PeerAddrType = AT_DUMMY;
#if V6 && V6_TESTV4
   memset(pp->Low.PeerAddress,0,PEER_ADDR_LEN);
   memset(pp->High.PeerAddress,0,PEER_ADDR_LEN);
#endif
   switch (ether_type) {
   case 0x0800:
#if V6 && V6_TESTV4
      if (proto_reqd[AT_IP6]) {
         pkt_extract(pp, AT_IP4, p,pl);
         if (use_ip_length) pp->p_len = p[2] << 8 | p[3];
         memcpy(&pp->Low.PeerAddress[12],&pp->Low.PeerAddress[0],4);
         memcpy(&pp->High.PeerAddress[12],&pp->High.PeerAddress[0],4);
         memset(pp->Low.PeerAddress,0,4);
         memset(pp->High.PeerAddress,0,4);
         pp->PeerAddrType = AT_IP6;
         }
      else if (proto_reqd[AT_IP4]) {
         pkt_extract(pp, AT_IP4, p,pl);
         if (use_ip_length) pp->p_len = p[2] << 8 | p[3];
         }
#elif V6
      if ((p[0] & 0xF0) == 0x40 && proto_reqd[AT_IP4]) {
         pkt_extract(pp, AT_IP4, p,pl);
         if (use_ip_length) pp->p_len = p[2] << 8 | p[3];
         }
      else if ((p[0] & 0xF0) == 0x60 && proto_reqd[AT_IP6]) {
         pkt_extract(pp, AT_IP6, p,pl);
         if (use_ip_length) pp->p_len = p[4] << 8 | p[5];
         }
#else
      if (proto_reqd[AT_IP4]) {
         pkt_extract(pp, AT_IP4, p,pl);
         if (use_ip_length) pp->p_len = p[2] << 8 | p[3];
         }
#endif
      break;
   case 0x809B:
      if (proto_reqd[AT_ETHERTALK]) pkt_extract(pp, AT_ETHERTALK, p,pl);
      break;
   case 0x8137:
      if (proto_reqd[AT_NOVELL]) pkt_extract(pp, AT_NOVELL, p,pl);
      break;
   case 0x6003:
      if (proto_reqd[AT_DECNET]) pkt_extract(pp, AT_DECNET, p,pl);
      break;
   case 0x0000:  /* 802.3 packet other than SNAP */
      switch (lsap) {
      case 0xFFFF:
         if (proto_reqd[AT_NOVELL]) pkt_extract(pp, AT_NOVELL, p,pl);
         break;  /* Novell "Raw 802.2" doesn't use LLC */
#ifdef CLNS
      case 0xFEFE:  /* CLNS */
         if (proto_reqd[AT_CLNS]) pkt_extract(pp, AT_CLNS, p+2,pl-2);
         break;  /* ISO 8473-1 uses 1-org, byte 0 is LLC control byte */
#endif
      case 0xE0E0:  /* Novell 802.2 */
         if (proto_reqd[AT_NOVELL]) pkt_extract(pp, AT_NOVELL, p+3,pl-3);
         break;
/*    case 0xF0F0:  NetBIOS */
         }
      }
   if (proto_reqd[AT_OTHER] && pp->PeerAddrType == AT_DUMMY)
      other_extract(pp, ether_type,lsap);


   if (pp->PeerAddrType != AT_DUMMY && adj_reqd) {
      pp->Low.AdjAddr_ms4 = *(Bit32 *)&ethp[6];  /* Source */
      pp->Low.AdjAddr_ls2 = *(Bit16 *)&ethp[10];
      pp->High.AdjAddr_ms4 = *(Bit32 *)&ethp[0];  /* Dest */
      pp->High.AdjAddr_ls2 = *(Bit16 *)&ethp[4];
      }

   return pp->PeerAddrType != AT_DUMMY;
   }

#endif  /* NF_CISCO_DATA */

#ifdef DOS
void save_time(void)
{
   s_tod_h = tod_h;  s_tod_m = tod_m;  s_tod_s = tod_s;
   elapsed_sec = 0;
   }
#endif

char *u_fmt(char *buf, Bit32 n)
{
   if (n < 1024)
      sprintf(buf, "[%u B]", n);
   else if (n < 1024*1024)
      sprintf(buf, "[%u KB]", (n+512)/1024);
   else
      sprintf(buf, "[%u MB]", (n+512*1024)/(1024*1024));
   return buf;
   }

void show_memory(void)
{
   char msg[90], nb[30];
   int t, r,rh, f,fh, s,sd, d,de, p;

   r = mxrules*sizeof(struct rule);
   rh = mxrulehash*sizeof(Bit16);
   f = mxflows*sizeof(struct flow);
   fh = fthashmod*sizeof(struct flow *);
#if NEW_ATR
   s = mxstreams*sizeof(struct stream) + sfhashmod*sizeof(struct stream *);
   sd = mxstr_blocks*sizeof(struct stream_data);
   d = mxdistribs*sizeof(struct distribution);
   de = mxdistevents*sizeof(struct event);
   p = mxpktdatas*sizeof(struct pktdata);
#else
   s = sd = 0;
   d = de = p = 0;
#endif
   t = r+rh + f+fh + s+sd + d+de + p;

   display_msg(1,"Memory Usage Info ..");
   sprintf(msg, "Rules: %lu max, %lu free %s", 
      mxrules, mxrules-inuse_rules, u_fmt(nb,r));
   display_msg(0,msg);
   sprintf(msg, "RuleHash: %lu max, %lu free %s", 
      mxrulehash, mxrulehash-inuse_rule_hash, u_fmt(nb,rh));
   display_msg(0,msg);
   sprintf(msg, "Flows: %lu max (%d B/flow) %s", 
      mxflows, sizeof(struct flow), u_fmt(nb,f));
   display_msg(0,msg);
   sprintf(msg, "FlowHash: %lu slots %s",
      fthashmod, u_fmt(nb,fh));
   display_msg(0,msg);
#if NEW_ATR
   sprintf(msg, "TCP streams: %lu max, %lu free %s",
      mxstreams, n_free_streams, u_fmt(nb,s));
   display_msg(0,msg);
   sprintf(msg, "Flows with streams: %lu alloc, %lu free (%d B/str) %s",
      mxstr_blocks, n_free_str_blocks, sizeof(struct stream_data), u_fmt(nb,sd));
   display_msg(0,msg);
   sprintf(msg, "Distribs: %lu max, %lu free (%d B/dist) %s",
      mxdistribs, n_free_distribs, sizeof(struct distribution), u_fmt(nb,d));
   display_msg(0,msg);
   sprintf(msg, "Distrib Events: %lu alloc, %lu free %s",
      mxdistevents, n_free_events, u_fmt(nb,de));
   display_msg(0,msg);
   sprintf(msg, "Packet data: %lu alloc, %lu free (%d B/pkt) %s",
      mxpktdatas, n_free_pktdata, sizeof(struct pktdata), u_fmt(nb,p));
   display_msg(0,msg);
#endif

#if defined(DOS)
   sprintf(msg, "malloc(): %lu bytes free", coreleft());
   display_msg(0,msg);
#else
   sprintf(msg, "Total allocated data: %s", u_fmt(nb,t));
   display_msg(0,msg);
#endif
   }

#if NEW_ATR
void show_new_attrib(void)
{
   char msg[60];
   display_msg(1,"New Attribute Info ..");
   sprintf(msg, "Streams (-t): %lu in use, %lu max",
      mxstreams-n_free_streams, mxstreams);
   display_msg(0,msg);
   sprintf(msg,"  %lu recovered", StreamsRecovered);
   display_msg(0,msg);
   if (stats_time != 0) {
      sprintf(msg,"  Creating   %.03f stream/s", (double)StreamsCreated/stats_time);
      display_msg(0,msg);
      sprintf(msg,"  Recovering %.03f stream/s", (double)StreamsRecovered/stats_time);
      display_msg(0,msg);
      }
   sprintf(msg, "Packet data (-a): %lu in use, %lu max",
      mxpktdatas-n_free_pktdata, mxpktdatas);
   display_msg(0,msg);
   sprintf(msg,"  %lu recovered", PktsRecovered);
   display_msg(0,msg);
   if (stats_time != 0) {
      sprintf(msg,"  Creating   %.03f pktdata/s", (double)PktsCreated/stats_time);
      display_msg(0,msg);
      sprintf(msg,"  Recovering %.03f pktdata/s", (double)PktsRecovered/stats_time);
      display_msg(0,msg);
      }
   }
#endif

void show_chains(int reset)
{
   char msg[60];

   display_msg(1,"Ruleset Chain Info ..");
   sprintf(msg, "%d tests", rsc_n_searches);
   display_msg(0,msg);
   if (rsc_n_searches == 0) return;
   sprintf(msg, "Range: %lu max, %lu av", 
      rsc_mx_range, rsc_av_range/rsc_n_searches);
   display_msg(0,msg);
   sprintf(msg, "Tests: %lu max, %lu av", 
      rsc_mx_tests, rsc_av_tests/rsc_n_searches);
   display_msg(0,msg);

   if (reset) {
      rsc_n_searches = 
         rsc_mx_range = rsc_av_range =
         rsc_mx_tests = rsc_av_tests = 0;
      }
   }

Bit32 active_flows(void)
{
   Bit32 af,j;
   struct flow *fp;
   Bit32 GCtime;
   for (af = 0, j = 1; j <= mxflows; ++j) {
      /* Flow indexes from 1 to MXFLOWS */
      if (flow_idle(j)) continue;
#if FLOW_INDEX
      fp = flow_ix[j-1];
#else
      fp = &fa[j-1];
#endif
      GCtime = ri[fp->FlowRuleSet-1].ri_gc_time;
      if (fp->ntm_LastTime > GCtime) ++af;  /* Can't be recovered */
      }
   return af;
   }

void show_flow_table(void)
{
   char msg[60];
   Bit32 f;
   display_msg(1,"Flow Table Info ..");
   sprintf(msg, "InactTime %d, Flood %d",
      InactivityTimeout,FloodMark);
   display_msg(0,msg);
   sprintf(msg, "Flows: %lu active, %u used",
      active_flows(), nflows);
   display_msg(0,msg);
   sprintf(msg,"  %lu recovered (GC: %u s, %u flow)",
      FlowsRecovered, gc_interval,gc_f);
   display_msg(0,msg);
   if (stats_time != 0) {
      f = FlowsCreated*1000L/stats_time;
      sprintf(msg,"  Creating   %lu.%03lu flow/s", f/1000,f%1000);
      display_msg(0,msg);
      f = FlowsRecovered*1000L/stats_time;
      sprintf(msg,"  Recovering %lu.%03lu flow/s", f/1000,f%1000);
      display_msg(0,msg);
      }
   }

void show_protocols(void)
{
   int j;
   char msg[60];
   display_msg(1,"Protocols being metered ..");
   sprintf(msg,"      %3.3s  %s",  adj_reqd ? "Yes" : " No", "Adjacent");
   display_msg(0,msg);
#if NF_OCX_BGP
   sprintf(msg,"      %3.3s  %s",  asn_lookup ? "Yes" : " No", "ASN lookup");
   display_msg(0,msg);
#endif
   for (j = 0; j != sizeof(proto_reqd)/sizeof(int); ++j) {
      if (proto_names[j][0] != '\0') {
         sprintf(msg,"  %2d  %3.3s  %s",
            j, proto_reqd[j] ? "Yes" : " No", proto_names[j]);
         display_msg(0,msg);
         }
      }
   }

void zero_stats(int show_msg)
{
   FlowsCreated = FlowsRecovered =
#if NEW_ATR
      PktsCreated = PktsRecovered =  StreamsCreated = StreamsRecovered =
#endif
      n_matches = n_hash_compares = n_hash_searches = 0L;
   clear_pkt_stats = 1;
   if (show_msg) {
#ifdef DOS
      scpos(0,scr_lrow);  printf("Statistics Zeroed");
#else
      display_msg(0,"Statistics Zeroed");
#endif
      }
   }

void show_stats(void)
{
   int i,j;
   double aps;
   Bit32 apb, kdp;
   char msg[60];
#ifdef DOS
   if (stats_time == 0 || npackets == 0 ||
     kilodummypackets == 0 || mindummyrate == 0)
#else
   if (stats_time == 0 || npackets == 0)
#endif
      return;  /* Avoid divides by zero */

   display_msg(1,"Meter Statistics ..");
   aps = (double)npackets/(double)stats_time;
#ifdef DOS
   apb = (t_backlog*10+5L)/(stats_time*10L);
   sprintf(msg,"Av pkt/s %u, av pkt backlog %u", aps,apb);
   display_msg(0,msg);
   sprintf(msg,"Max pkt/s %u, max pkt backlog %u",
      max_pkt_rate,max_pkt_backlog);
   display_msg(0,msg);
   if (kilodummypackets != 0) {
      if (dummypackets >= 500) ++kilodummypackets;
      i = kilodummypackets*1000L/(kilodummypackets+npackets/1000L);
      j = (mindummyrate*10000L+5L)/((mindummyrate+mdpacketrate)*10L);
      sprintf(msg,"Idle time av %u.%u, min %u.%u %", i/10,i%10, j/10,j%10);
      display_msg(0,msg);
      }
#else
   sprintf(msg,"Av pkt/s %lu, max pkt/s %u", aps,max_pkt_rate);
   display_msg(0,msg);
#endif
   sprintf(msg,"%lu flows active (max %lu)",active_flows(),mxflows);
   display_msg(0,msg);
   sprintf(msg,"%u masks in table (max %u)", n_masks,MXMASKS);
   display_msg(0,msg);
   i = (n_matches*100L+5L)/(npackets*10L);
   j = (n_hash_searches*100L+5L)/(npackets*10L);
   sprintf(msg,"%u.%u rules/pkt, %u.%u searches/pkt",
      i/10,i%10, j/10,j%10);
   display_msg(0,msg);
   if (n_hash_searches != 0) {
      i = (n_hash_compares*100L+5L)/(n_hash_searches*10L);
      sprintf(msg,"%u.%u compares/search", i/10,i%10);
      display_msg(0,msg);
      sprintf(msg,"%u hash slots, %u in use, ", fthashmod,n_hash_ents);
      }
   }

void show_rulesets(void)
{
   int c,j,t;
   struct rtinf_rec *rip;
   char buf[80];
   display_msg(1,"Ruleset Table ..");
   for (c = t = j = 0; j != sizeof(ri)/sizeof(struct rtinf_rec); ++j) {
      rip = &ri[j];
      if (rip->ri_Status != RS_ACTIVE &&
            rip->ri_Status != RS_NOTINSERVICE
         ) continue;
      ++c;
      if (t == 0) {
         display_msg(0,"   Rdy  Flows Rules  Name    Owner");
         t = 1;
         }
      sprintf(buf, "%2d  %c  %5lu  %5u  %s  %s",
         j+1,  /* 1-org */
         rip->ri_Status == RS_ACTIVE ? 'T' : 'F',
         rip->ri_FlowRecords,
         rip->ri_Size, rip->ri_Name,
         rip->ri_Owner);
      display_msg(0,buf);
      }
   if (c == 0) display_msg(0,"No ruleset rows active");
   }

void show_managers(void)
{
   int c,j,t;
   struct mgr_rec *mip;
   char buf[80];
   display_msg(1,"Manager Table ..");
   for (c = t = j = 0; j != sizeof(mi)/sizeof(struct mgr_rec); ++j) {
      mip = &mi[j];
      if (mip->mi_Status != RS_ACTIVE) continue;
      ++c;
      if (t == 0) {
         display_msg(0,"  Crnt Stby HWM RSby  Owner");
         t = 1;
         }
      sprintf(buf, "%2d  %2d  %2d  %3d   %c  %s",
         j+1,  /* 1-org */
         mip->mi_CurrentRuleSet, mip->mi_StandbyRuleSet,
         mip->mi_HighWaterMark,
         mip->mi_RunningStandby == TV_TRUE ? 'T' : 'F',
         mip->mi_Owner);
      display_msg(0,buf);
      }
   if (c == 0) display_msg(0,"No manager rows active");
   }

void show_readers(void)
{
   int c,j,t;
   struct rdr_rec *cip;
   Bit32 now;
   char buf[80];
   display_msg(1,"Meter Reader Table ..");
   now = uptime_cs();
   for (c = t = j = 0; j != sizeof(ci)/sizeof(struct rdr_rec); ++j) {
      cip = &ci[j];
      if (cip->ci_Status != RS_ACTIVE) continue;
      ++c;
      if (t == 0) {
         display_msg(0,"    Rsx  Last  Prev  Owner");
         t = 1;
         }
      sprintf(buf, "%2d  %2d  %5lu %5lu  %s",
         j+1,  /* 1-org */
         cip->ci_RuleSet,
         (now - cip->ci_LastTime)/100L, (now - cip->ci_PrevTime)/100L,
         cip->ci_Owner);
      display_msg(0,buf);
      }
   if (c == 0) display_msg(0,"No reader rows active");
   }

void init_monitor(int n_interfaces)
{
   int j;

#ifdef DOS
   set_tod();
   save_time();  /* For screen display */
#endif
   start_uptime_clock();

   if ((fa = (struct flow *)malloc(
        sizeof(struct flow)*mxflows)) == NULL)
      log_msg(LOG_ERR, 1, "Not enough memory for flow table!");
   for (j = 0; j != mxflows; ++j) fa[j].FlowRuleSet = 0;

   for (j = 0; j != mxflows-1; ++j) fa[j].rs_next = j+2;  /* 1-org */
   fa[mxflows-1].rs_next = 0;
   free_flow_head = 1;  free_flow_tail = mxflows;

   rsc_n_searches = 
      rsc_mx_range = rsc_av_range =
      rsc_mx_tests = rsc_av_tests = 0;

#if FLOW_INDEX
   if ((flow_ix = (struct flow **)malloc(
         sizeof(struct flow *)*mxflows)) == NULL)
      log_msg(LOG_ERR, 2, "Not enough memory for flow index!");
   for (j = 0; j != mxflows; ++j) flow_ix[j] = NULL;
#endif

   nflows = 0;  /* No accounting flows in use yet */
   gcf_ix = empty_ix = mxflows;  /* Flow 1 will be the first allocated */
   fthashmod = mxflows;  /* Doesn't need to be mutually prime */

#if 0
   for (j = sizeof(p2primes)/sizeof(Bit32)-1; 
      j != 8 && p2primes[j] > mxflows; --j) ;
   fthashmod = p2primes[j];  /* Min fthashmod = p2primes[8] (2039) */
#endif
   flow_ht = (struct flow **)malloc(fthashmod*sizeof(struct flow *));
   if (flow_ht == NULL)
      log_msg(LOG_ERR, 3, "Not enough memory for flow hash table!");
   memset(flow_ht, 0, fthashmod*sizeof(struct flow *));

   if ((rule_space = (struct rule *)malloc(
         sizeof(struct rule)*mxrules)) == NULL)
      log_msg(LOG_ERR, 4, "Not enough memory for rulesets!");
   inuse_rules = 0;

   mxrulehash = 1 + mxrules*3/2;
   if ((rule_hash_space = (Bit16 *)malloc(
         sizeof(Bit16)*mxrulehash)) == NULL)
      log_msg(LOG_ERR, 5, "Not enough memory for rule hashes!");
   inuse_rule_hash = 0;

#if NEW_ATR
   if ((sfa = (struct stream *)malloc(
        sizeof(struct stream)*mxstreams)) == NULL)
      log_msg(LOG_ERR, 6, "Not enough memory for streams!");
   free_streams = sfa;  n_free_streams = mxstreams;
   for (j = 0; j != mxstreams-1; ++j) sfa[j].next_sp = &sfa[j+1];
   sfa[mxstreams-1].next_sp = NULL;
   sfhashmod = mxstreams;  /* Doesn't need to be mutually prime */

#if 0
   for (j = sizeof(p2primes)/sizeof(Bit32)-1; 
      j != 8 && p2primes[j] > mxstreams; --j) ;
   sfhashmod = p2primes[j];
#endif
   sfht = (struct stream **)malloc(sfhashmod*sizeof(struct stream *));
   if (sfht == NULL)
      log_msg(LOG_ERR, 7, "Not enough memory for stream hash table!");
   memset(sfht, 0, sfhashmod*sizeof(struct stream *));

   if ((strb = (struct stream_data *)malloc(
        sizeof(struct stream_data)*mxstr_blocks)) == NULL)
      log_msg(LOG_ERR, 8, "Not enough memory for stream data blocks!");
   free_str_blocks = strb;  n_free_str_blocks = mxstr_blocks;
   for (j = 0; j != mxstr_blocks-1; ++j) strb[j].next = &strb[j+1];
   strb[mxstr_blocks-1].next = NULL;

# if TCP_PKT_TRACE
   pt_file = fopen(ptf_title, "a");
# endif

   if ((distribs = (struct distribution *)malloc(
        sizeof(struct distribution)*mxdistribs)) == NULL)
      log_msg(LOG_ERR, 9, "Not enough memory for distributions!");
   free_distribs = distribs;  n_free_distribs = mxdistribs;
   for (j = 0; j != mxdistribs-1; ++j) distribs[j].next = &distribs[j+1];
   distribs[mxdistribs-1].next = NULL;

   if ((events = (struct event *)malloc(
        sizeof(struct event)*mxdistevents)) == NULL)
      log_msg(LOG_ERR, 10, "Not enough memory for distrib events!");
   free_events = events;  n_free_events = mxdistevents;
   for (j = 0; j != mxdistevents-1; ++j) events[j].next = &events[j+1];
   events[mxdistevents-1].next = NULL;

   if ((packet_data = (struct pktdata *)malloc(
        sizeof(struct pktdata)*mxpktdatas)) == NULL)
      log_msg(LOG_ERR, 11, "Not enough memory for packet data blocks!");
   free_pkt_dat = packet_data;  n_free_pktdata = mxpktdatas;
   for (j = 0; j != mxpktdatas-1; ++j) packet_data[j].next = &packet_data[j+1];
   packet_data[mxpktdatas-1].next = NULL;
#endif  /* NEW_ATR */

   memset(ri, 0, sizeof(dist_atr));
   memset(ri, 0, sizeof(stream_atr));
   for (j = 0; ; ++j) {
      if (attribs[j].attr == 0) break;
      dist_atr[attribs[j].attr] = attribs[j].distrib;
      stream_atr[attribs[j].attr] = attribs[j].stream;
      }

   memset(ri, 0, sizeof(ri));
   memset(rs_rdr, 0, sizeof(rs_rdr));  /* For nifty */
   /* Create a ruleset info entry for the default rule table */
   ri[0].ri_rule_table = (struct rule *)default_rule_table;
   ri[0].ri_Size = DRT_SIZE;
   optimise_rule_table(1);  /* Alloc rule hash */
   memcpy(ri[0].ri_Owner, "NeTraMet", 8);
   memcpy(ri[0].ri_Name, "1", 1);  /* For compatibility with v3 */
   ri[0].ri_TimeStamp = 1;
   ri[0].ri_Status = RS_ACTIVE;

   tbl_size[2] = n_interfaces;  /* Interface table */

   memset(mi, 0, sizeof(mi));  /* Manager info */
   for (j = 1; j != sizeof(mi)/sizeof(struct mgr_rec); ++j)
      mi[j].mi_RunningStandby = TV_FALSE;
   memcpy(mi[0].mi_Owner, "NeTraMet", 8);
   mi[0].mi_CurrentRuleSet = 1;
   mi[0].mi_RunningStandby = TV_FALSE;
   mi[0].mi_TimeStamp = 1;
   mi[0].mi_Status = RS_ACTIVE;

   for (j = 0; j != sizeof(proto_reqd)/sizeof(int); ++j) proto_reqd[j] = 0;
   adj_reqd = 0;
   open_rule_set(1, 1); /* Open set 1 */

   memset(ci, 0, sizeof(ci));  /* Reader (collector) info */
   }

void show_help(void)
{
   display_msg(0,"Copyright (C) 1992-2000");
   display_msg(0,"  Nevil Brownlee");
   display_msg(0,"  ITSS Technology Development");
   display_msg(0,"  The University of Auckland");
   display_msg(0,"");
   display_msg(0,"Keyboard commands ..");
   display_msg(0,"");
   display_msg(0,"  e: show mEter reader table");
   display_msg(0,"  a: show mAnager table");
   display_msg(0,"  u: show rUleset table");
   display_msg(0,"  f: show Flow Table");
   display_msg(0,"  p: show Protocols");
   display_msg(0,"  s: show Statistics");
   display_msg(0,"  v: show meter Version");
#if defined(DOS)
   display_msg(0,"  z: Zero statistics");
# if !(OCX_NTM || OC3_NTM)
   display_msg(0,"  b: show Bad packet counts");
# endif
   display_msg(0,"  m: show Memory usage");
#endif
   if (kb_enabled) {
      display_msg(0,"");
      display_msg(0,"Esc: stop metering, exit NeTraMet");
      }
   }

void handle_kb(int ch)
{
   int c;
   switch (ch) {
   case 'a':
      show_managers();
      break;
#if NEW_ATR
   case 'd':
      show_new_attrib();
      break;
#endif
   case 'C':
   case 'c':
      show_chains(ch == 'C');
      break;
   case 'e':
      show_readers();
      break;
   case 'h':
      show_flow_hash();
      break;
   case 'H':
      show_stream_hash(1);
      break;
   case 'm':
      show_memory();
      break;
   case 'p':
      show_protocols();
      break;
   case 'u':
      show_rulesets();
      break;
   case 's':
      show_stats();
      break;
   case 't':
      show_meter_time();
      break;
#if PM_DEBUG
   case '\\':
      printf("pm_debug: ");
#ifdef DOS
      c = getch();
#else
      c = getchar();
#endif
      if (c == '\\') {
         pm_dbg = 0;
         printf("ri[] off");
         }
      else {
         c -= '0';
         pm_dbg = c == 0 ? 10 : c;
         printf("ri[%d]", pm_dbg);
         }
      break;
#endif
   case 'z':
      zero_stats(1);
      break;
   case '?':
      show_help();
      break;
   default:
      break;
      }
   }
