/* 1906, Sun 25 Nov 01 (PDT)

   NM_RC.C:  A simple remote display console for NeTraMet

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

#define  PERCENT_DEBUG  0

/*
 * $Log: nm_rc.c,v $
 * Revision 1.1.1.2.2.9  2002/02/23 01:57:18  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.5  2000/08/08 19:44:43  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.3  2000/06/06 03:38:09  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2  1999/10/03 21:06:15  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.6  1999/09/22 05:38:34  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.5  1999/05/25 22:30:49  nevil
 * Make sure IPv6 managers interwork properly with IPv4 meters
 * - Determine meter's RULE_ADDR_SIZE by reading mask from rule 1
 *   of default ruleset.
 * - Print warning if meter rulesize < manager rulesize.
 * - Use smaller of these when downloading rules.
 *
 * Revision 1.1.1.1.2.4  1999/02/15 21:24:06  nevil
 * Distribution file for 4.3b9
 *
 * Revision 1.1.1.1.2.3  1999/01/27 04:26:13  nevil
 * Minor corrections to fix compiler warnings
 *
 * Revision 1.1.1.1.2.2  1999/01/20 04:01:33  nevil
 * Implementation of TCP attributes, part 4
 *
 * Revision 1.1.1.1.2.1  1999/01/08 01:38:29  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1  1998/11/16 03:57:27  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:00  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1.2.1  1998/11/11 23:14:37  nevil
 * Only include malloc.h if we HAVE_MALLOC_H
 *
 * Revision 1.1.1.1  1998/10/28 20:31:24  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2.2.2  1998/10/26 22:17:39  nevil
 * Display help message when run with no args (Nicolai)
 *
 * Revision 1.1.3.2.2.1  1998/10/23 03:57:10  nevil
 * Include set_mibfile() prototype from parse.h
 *
 * Revision 1.1.3.2  1998/10/18 23:44:06  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:19  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:28  nguba
 * NetraMet 4.2 Original Distribution
 *
 * Revision 1.5  1998/06/03 04:52:11  rtfm
 * Don't set snmp_delay (only done in nmc_snmp.c)
 * Use LastTime instead of sysUptime
 *
 * Revision 1.4  1998/05/20 04:21:18  rtfm
 * Fix bugs in 4.2b2
 */

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

#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/time.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif

#define EXTSNMP  /* Declares au_snmp_port.  Nevil, 29 Apr 97 */
#include "ausnmp.h"

#include "asn1.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "snmpapi.h"
#include "snmpclnt.h"
#include "mib.h"
#include "parse.h"

#define EXTERN
#include "nmc.h"
#include "nmc_c64.h"

int request_stop = 0;

void sigint_handler(int x)
{
   request_stop = 1;
   }

unsigned int max_flows;
struct flow_data {  /* Current values */
   unsigned short ruleset;
   unsigned long starttime;
   counter64
      upbci,   /* To byte rate */
      dnbci,   /* From byte rate */
      uppci,   /* To packet rate */
      dnpci;   /* From packet rate */
#if NEW_ATR
   Bit32
      n_subflows;
#endif  /* NEW_ATR */
   };
struct flow_data *flow_table;

struct flow_data32 {  /* Sample-to-sample differences */
   Bit32
      upbci,   /* To byte rate */
      dnbci,   /* From byte rate */
      uppci,   /* To packet rate */
      dnpci;   /* From packet rate */
#if NEW_ATR
   Bit32
      n_subflow_i;
#endif  /* NEW_ATR */
   };
struct flow_data32 curr_rate, total_rate;

int nd_flows;  /* Nbr of flows to display */
struct display_data {
   unsigned int traffic;
   struct flow_info *fi;
   };
struct flow_info *dflowi;
#if NEW_ATR
struct subflow_data *dsfdi;
#endif  /* NEW_ATR */
struct display_data *dflows;

int criterion;  /* How we'll choose flows to be displayed */
#define C_TOTBYTES  1  /* Max bytes up + down (default) */
#define C_STREAMS   2  /* Max TCP streams */

char show_names;

char rfname[NAME_LN], meter[NAME_LN], owner[NAME_LN], community[NAME_LN];
unsigned int def_sync, def_sample_interval, def_lag,
   def_ci_Timeout, def_ci_MinPDUs,
   def_download, def_no_write_meter,
   def_GCIntervalReqd, def_InactivityTime,
   def_HighWaterMark, def_FloodMark,
   def_trace_interval;
int no_user_format, plain_format;

int sort_cmp(const void *a, const void *b)
{
   if (((struct display_data *)a)->traffic <  /* Reverse numeric order */ 
      ((struct display_data *)b)->traffic) return 1;
   else if (((struct display_data *)a)->traffic == 
      ((struct display_data *)b)->traffic) return 0;
   else return -1;
   }

void no_write_warning(struct meter_status *ms)
{
   if (ms->no_write_warned) return;
   fprintf(stdout,"Community %s doesn't have write access to meter %s!\n"
      "   Collections won't trigger recovery of idle flows <<<\n",
      ms->community,ms->name);
   ms->no_write_warned = 1;
   }

void add_event(struct calendar_entry *entry, struct meter_status *ms)
{
   struct calendar_entry *cp, *lcp;
   entry->next_event = ms->next_event;  entry->ms = ms;
   entry->next = NULL;
   if (calendar == NULL) {  /* First entry in queue */
      calendar = entry;
      return;
      }
   lcp = NULL;  cp = calendar;
   do {
      if (ms->next_event < cp->next_event) {
         entry->next = cp;
         if (lcp == NULL) calendar = entry;  /* Add to head */
         else lcp->next = entry;  /* Link into queue */
         return;
         }
      lcp = cp;  cp = cp->next;
      } while (cp != NULL);
   lcp->next = entry;  /* Add to tail */
   }

void ruleset_row(struct meter_status *ms, struct row_info *rip)
{
   struct meter_rule_info mri;
   printf(">> ruleset %d:  Status=%d, Owner=%s, Name=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner, rip->tr_Name);
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
      mri.ri_Index = rip->tr_Index;
      ruleset_util(ms, RU_DESTROY, &mri);
      }
   else if (strcmp(rip->tr_Owner, "NeTraMet") == 0) {
         /* We've found NeTraMet meter's default ruleset */
      ms->d_ruleset.ri_Index = rip->tr_Index; 
      }
   }

void manager_row(struct meter_status *ms, struct row_info *rip)
{
   printf(">> manager %d:  Status=%d, Owner=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner);
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0
      || strcmp(rip->tr_Owner, "NeTraMet") == 0) {
      ms->mi_u_Index = rip->tr_Index;
      task_util(ms, TU_DESTROY);
      }
   }

void reader_row(struct meter_status *ms, struct row_info *rip)
{
   printf(">> reader %d:  Status=%d, Owner=%s\n",
      rip->tr_Index, rip->tr_Status, rip->tr_Owner);
   if (strcmp(rip->tr_Owner, ms->owner_name) == 0) {
      ms->ci_u_Index = rip->tr_Index;
      reader_util(ms, CU_DESTROY, CU_UTIL);
      }
   }

int create_meter(struct meter_status *ms, time_t first_t, char *pn)
{
   struct calendar_entry *entry;
   int a,b,n, syntax;

   if (ms->name[0] == '\0' || ms->community[0] == '\0') {
      fprintf(stdout,"Meter name or community not specified !!!\n");
      return 0;
      }
   if (testing) printf("About to create_meter name=%s, community=%s\n", 
      ms->name, ms->community);

   if (!start_snmp_session(ms)) return 0;

   ms->status = MT_MANAGE;
   if (meter_info(ms)) {
      ms->status |= (MT_UP | MT_INFO);
      ms->next_event = ms->next_sample = 
         first_t;  /* Get first sample immediately */
      entry = (struct calendar_entry *)calloc(
         sizeof(struct calendar_entry), 1);
      add_event(entry,ms);
      if (testing) 
         printf("t=%u, next_event=%u, next_keepalive=%u, next_sample=%u\n",
            first_t, ms->next_event, ms->next_keepalive, ms->next_sample);
#ifndef _AIX
      if (!meter_is_current(ms)) {
         fprintf(stdout,"Warning: meter %s (version %s) not same as %s!\n",
            ms->name,ms->version, pn);
         }
#endif
#if V6
      if (ms->meter_ra_len < RULE_ADDR_LEN) fprintf(stdout,
         "Meter %s is not IPv6 capable!\n", ms->name);
#endif
      if (ms->MaxFlows > max_flows) max_flows = ms->MaxFlows;
      }
   else {
      fprintf(stdout,"Couldn't get meter info from %s!\n"
         "   Does community %s have read or write access to the meter?\n",
         ms->name,ms->community);
      return 0;
      }

   if (ms->trace_interval != 0) {  /* User expceted a Trace File */
      if (meter_maj(ms) < 4 || meter_min(ms) < 4) {
         fprintf(stderr,"Meter doesn't support trace files <<<\n");
         log_msg(LOG_INFO, FALSE,
            "Meter doesn't support trace files <<<");
         return 0;
         }
      else if (ms->crl_info == NULL) {
         fprintf(stderr,"Meter isn't reading a trace file !!!\n");
         log_msg(LOG_INFO, FALSE,
            "Meter isn't reading a trace file !!!");
         return 0;;
         }
      }

   set_meter_params(ms);

   if (ms->ci_Timeout == 0)  /* Reader row timeout */
      ms->ci_Timeout = 10*ms->sample_interval;
   search_table(ms, ST_READER,  reader_row);

   search_table(ms, ST_MANAGER, manager_row);
   search_table(ms, ST_RULESET, ruleset_row);

   syntax = ms->download_level != 0;  /* 0 => initial download of rules */
   if (ms->c_ruleset.filename[0] == '\0') {  /* No rule file specified */
      fprintf(stdout,"No rule file specified !!!\n");
      return 0;
      }
   else {  /* Download the rules we want to use */
      n = parse_rulefile(ms,listrules,syntax,0,0);  /* Initial load */
      if (!n || rferrors != 0) return 0;
      }

   /* search_table() finds NeTraMet's default ruleset */   
   reader_util(ms, CU_INIT, CU_CURRENT);
   reader_util(ms, CU_SET_MINPDUS, CU_CURRENT);

   /* Make sure we get the info we need to select 'top n' flows */
   ms->required[FTFLOWINDEX] = 
      ms->required[FTRULESET] = ms->required[FTFIRSTTIME] = 
      ms->required[FTUPOCTETS] = ms->required[FTDOWNOCTETS] =
      ms->required[FTUPPDUS] = ms->required[FTDOWNPDUS] =
      ms->required[FTLOWPEERTYPE] = 1;
#if NEW_ATR
   if (criterion == C_STREAMS)  /* Max TCP streams */
      ms->required[FTTCPDATA] = 1;
#endif

   if (no_user_format = ms->format[0] == 0) {  /* No format specified */
      ms->format[0] = FTFIRSTTIME;
      ms->format[1] = FTUPPDUS;
      ms->format[2] = FTDOWNPDUS;
      ms->format[3] = FTUPOCTETS;
      ms->format[4] = FTDOWNOCTETS;
      ms->format[5] = FTLOWPEERTYPE;
      ms->format[6] = FTLOWPEERADDRESS;  ms->required[FTLOWPEERADDRESS] = 1;
      ms->format[7] = FTHIPEERADDRESS;  ms->required[FTHIPEERADDRESS] = 1;
      ms->format[8] = FTLOWTRANSTYPE;  ms->required[FTLOWTRANSTYPE] = 1;
      ms->format[9] = FTLOWTRANSADDRESS; ms->required[FTLOWTRANSADDRESS] = 1;
      ms->format[10] = FTHITRANSADDRESS;  ms->required[FTHITRANSADDRESS] = 1;
#if NEW_ATR
      if (criterion == C_STREAMS) {  /* Max TCP streams */
         ms->format[11] = FTTCPDATA;  ms->required[FTTCPDATA] = 1;
         }
      ms->format[12] = 0;
#else
      ms->format[11] = 0;
#endif
      for (a = 0; a != 14; ++a) ms->separator[a] = " ";
      }

   ++nmeters;

   if (!ms->write_OK) no_write_warning(ms);

   ms->OurLastCollectTime = 0;  /* Was 1L */

   fprintf(stdout, "#Format: ");
   if ((n = ms->format[a = 0]) != 0) for (;;) {
      for (b = 1; b <= SZ_ATTRIBS && attribs[b].index != n; ++b) ;
      if (b <= SZ_ATTRIBS) fprintf(stdout,attribs[b].name);
      else fprintf(stdout,"attr*%u", n);  /* Unknown attribute */
      if ((n = ms->format[a+1]) == 0) {
         if (strcmp(ms->separator[a]," ") != 0)
            fprintf(stdout, ms->separator[a]);
         break;
         }
      fprintf(stdout,ms->separator[a++]);
      }

   fprintf(stdout,"\n");
   return 1;
   }

int main(int argc, char *argv[])
{
   int syntax;
   char *ap, arg[NAME_LN];
   int a, j, cs;
   time_t t1,t2;  int busy_seconds;
   struct meter_status *ms, *nms;
   struct calendar_entry *cp;
   struct meter_rule_info mri;
   struct stat stat_buf;
   char have_config_file;  FILE *cnf;
   char download_only;
#define DEBUG_TRACE_STATE  0
#if DEBUG_TRACE_STATE
   int last_cs = -1;
#endif

   if (argc < 2) {  /* Help Screen when called with no args */
      fprintf (stderr, 
         "\nUsage: %s [OPTION]... meter-hostname community\n"
         "Remote Console for the NeTraMet meter:\n\n"
         "  -a SEC \t Seconds collections are to lag after their\n"
         "         \t   synchronised time\n"
         "  -b MBF \t Read in alternate mib file, MBF\n"
         "  -c SEC \t Required collection interval (seconds)\n"
         "         \t If SEC is zero program will download rule files\n"
         "         \t   to the meter, then exit without collecting any\n"
         "         \t   flow data\n"
         "  -g GCI \t Specify garbage collection interval (seconds)\n"
         "  -h HWM \t Set high water mark as percentage of flow space\n"
         "  -i ITO \t Inactivity timeout interval (seconds)\n"
         "  -m PNO \t Specify UDP port to use for communication with meter\n"
         "  -n NOF \t Number of flows to display after each collection\n"
         "  -o FLM \t Set flook mark as percentage of flow space\n"
         "  -p     \t Plain output\n"
         "  -r RLE \t Give name of rule file RLE, to be read and\n"
         "         \t   downloaded to one or more meters\n"
         "  -s     \t Check syntax of rule file but don't download to meter\n"
         "  -u     \t Samples should be unsyncronized, ie every SEC\n"
         "         \t   seconds from nm_rc startup\n"
         "  -C     \t Meter is reading a trace file, use its -T interval\n"
         "  -E SEC \t Timeout in seconds for reader rows in meter\n"
         "  -L NAM \t Name of log file to write\n"
         "  -M MPK \t Only retrieve flows with MPK packets to or from\n"
     /*  "  -N     \t Display domain names instead of IP addresses\n" */
         "\n"
         "For more information see the NeTraMet Reference Manual and User Guide.\n"
         "\n"
         "Report bugs to netramet@auckland.ac.nz\n", argv[0]);
      exit(0);
      }

   signal(SIGINT, sigint_handler);
   signal(SIGTERM, sigint_handler);

   fprintf(stdout,"nm_rc: Remote Console for NeTraMet: " ver_str "\n");
      /* Check on meter version done in nmc_snmp.c */

   incl_depth = syntax = verbose = testing = listrules = standard = 0;
   def_sync = 1;  /* Samples synchronised with TOD clock */
   def_sample_interval = 120;  /* Default 2 minutes */
   def_lag = 0;  /* No time lag for collections */
   rfname[0] = meter[0] = owner[0] = community[0] = '\0';
   max_flows = 0;
   def_download = 0;  /* Download rule sets on startup */
   def_no_write_meter = 0;
   nd_flows = 10;  /* Display top 10 flows by default */
   def_HighWaterMark = 35;  /* 35% for nm_rc and nifty */
   def_ci_Timeout = 0;  /* nm_rc collector rows never time out */
   def_ci_MinPDUs = 0;  /* Reader MinPDU filter value */
   def_GCIntervalReqd =  /* Use meter defaults for MIB variables */ 
   def_InactivityTime = def_FloodMark = 0;
   def_trace_interval = 0;  /* Live meter, not tracefile */
   plain_format = 0;
   show_names = 0;
   criterion = C_TOTBYTES;  /* How we select flows to display */

   for (a = 1; a < argc; ++a) {
      if (argv[a][0] == '-') {
         ap = argv[a]+2;
	 switch (argv[a][1]) {
	 case 'a':
            if (*ap == '\0') ap = argv[++a];
	    def_lag = atoi(ap);
	    break;
	 case 'b':
	   if (*ap == '\0') ap = argv[++a];
	   set_mibfile(ap);
	   break;
	 case 'c':
            if (*ap == '\0') ap = argv[++a];
	    j = atoi(ap);
            if (j == 0) download_only = 1;
            else def_sample_interval = j;
	    break;
	 case 'd':
	    snmp_dump_packet++;
	    break;
	 case 'e':
            if (*ap == '\0') ap = argv[++a];
	    criterion = atoi(ap);
	    break;
         case 'g':
            if (*ap == '\0') ap = argv[++a];
	    def_GCIntervalReqd = atoi(ap);
            break;
         case 'h':
            if (*ap == '\0') ap = argv[++a];
	    def_HighWaterMark = atoi(ap);
            break;
         case 'i':
            if (*ap == '\0') ap = argv[++a];
	    def_InactivityTime = atoi(ap);
            break;
	 case 'l':
	    listrules++;
	    break;
	 case 'm':
 	    if (*ap == '\0') ap = argv[++a];
	    au_snmp_port = atoi(ap);
	    break;
	 case 'n':
            if (*ap == '\0') ap = argv[++a];
	    nd_flows = atoi(ap);  /* Nbr of flows to display */
	    break;
         case 'o':
            if (*ap == '\0') ap = argv[++a];
	    def_FloodMark = atoi(ap);
            break;
         case 'p':
            plain_format++;
            break;
	 case 'r':
            if (*ap == '\0') ap = argv[++a];
	    strcpy(rfname, ap);  /* Rule file name */
	    break;
	 case 's':
	    syntax++;
	    break;
	 case 't':
	    testing++;
	    break;
	 case 'u':
	    def_sync = 0;  /* Unsynchronised */
	    break;
	 case 'v':
	    verbose++;
	    break;
         case 'w':
            if (*ap == '\0') ap = argv[++a];
	    def_download = atoi(ap);
            break;
	 case 'x':
	    def_no_write_meter++;
	    break;
         case 'C':  /* It's a CoralReef tracefile meter */
	    def_trace_interval = 1;
            break;
         case 'E':
            if (*ap == '\0') ap = argv[++a];
	    def_ci_Timeout = atoi(ap);
            break;
	 case 'L':
            if (*ap == '\0') ap = argv[++a];
	    strcpy(user_logfile, ap);  /* Log file name */
	    break;
         case 'M':
            if (*ap == '\0') ap = argv[++a];
	    def_ci_MinPDUs = atoi(ap);
            break;
	 case 'N':
	    show_names = 1;
	    break;
	 case 'S':
	    standard++;
	    break;
 	 default:
	    fprintf(stderr,"Invalid option: -%c\n", argv[a][1]);
	    break;
	    }
	 continue;
	 }
      if (meter[0] == '\0') strcpy(meter,argv[a]);
      else if (community[0] == '\0') strcpy(community,argv[a]);
      else if (owner[0] == '\0') {
         strncpy(owner,argv[a],NAMESZ);  owner[NAMESZ] = '\0';
         }
      }

   if (syntax) {  /* Test a rule file */
      ms = (struct meter_status *)calloc(sizeof(struct meter_status), 1);
      strcpy(ms->c_ruleset.filename,rfname);
      if (ms->s_ruleset.filename[0] != '\0')
         parse_rulefile(ms,listrules,1,1,0);
      parse_rulefile(ms,1,1,0,0);
      fprintf(stderr,"\n%d errors in rule file(s) %s\n\n", rferrors,rfname);
      exit(0);
      }

   if (nd_flows < 1) nd_flows = 1;

   logfile[0] = '\0';
   if (user_logfile[0] != '\0') {
      /* nm_rc only write log messages if user explicitly asks for them */
      strcpy(logfile,user_logfile);
      if ((logfl = fopen(logfile,"w")) == 0) {
         fprintf(stderr, "Failed to open %s as log file", logfile);
         exit(10);
         }
      }

   init_mib();  /* Initialise SNMP handler */

   first_meter = (struct meter_status *)calloc
      (sizeof(struct meter_status), 1);
   strcpy(first_meter->c_ruleset.filename,rfname);
   strcpy(first_meter->name,meter);
   strcpy((char *)first_meter->owner_name,
      owner[0] != '\0' ? owner : "nm_rc");
   strcpy((char *)first_meter->community,
      community[0] != '\0' ? community : "public");
   first_meter->synchronised = def_sync;
   first_meter->lag_seconds = def_lag;
   first_meter->download_level = def_download;
   first_meter->no_write_meter = 
      rfname[0] == '\0' ? 1 : def_no_write_meter;
   first_meter->write_OK = 1;
   first_meter->ci_Timeout = def_ci_Timeout;
   first_meter->ci_MinPDUs = def_ci_MinPDUs;
   first_meter->GCIntervalReqd = def_GCIntervalReqd;
   first_meter->InactivityTime = def_InactivityTime;
   first_meter->lag_seconds = def_lag;
   first_meter->HighWaterMark = def_HighWaterMark;
   first_meter->FloodMark = def_FloodMark;
   if (first_meter->trace_interval = def_trace_interval)
      first_meter->sample_interval = 1;
   else first_meter->sample_interval = def_sample_interval;

   time(&t1);
   for (nmeters = 0, ms = first_meter; ms; ms = ms->next)
      create_meter(ms, t1, argv[0]);
   if (nmeters == 0) {
      fprintf(stderr,"No meters to monitor !!!\n");
      exit(0);
      }

   /* Allocate two extra flows since they start from 2 not 0 */
   if ((flow_table = (struct flow_data *)calloc(
         max_flows+2, sizeof(struct flow_data))) == NULL) {
      fprintf(stderr, "Failed to allocate memory for flow table!\n");
      exit(11);
      }
   if ((dflowi = (struct flow_info *)calloc(
         nd_flows, sizeof(struct flow_info))) == NULL) {
      fprintf(stderr, "Failed to allocate memory for display info!\n");
      exit(11);
      }
#if NEW_ATR
   if ((dsfdi = (struct subflow_data *)calloc(
         nd_flows, sizeof(struct subflow_data))) == NULL) {
      fprintf(stderr, "Failed to allocate memory for tcp attrib display!\n");
      exit(11);
      }
#endif
   if ((dflows = (struct display_data *)calloc(
         nd_flows, sizeof(struct display_data))) == NULL) {
      fprintf(stderr, "Failed to allocate memory for display data!\n");
      exit(11);
      }

   for (;;) {
      while (calendar != NULL && 
            calendar->next_event <= time(&t1)) {  /* Things to do */
         cp = calendar;  calendar = cp->next;  /* Take entry from queue */
         ms = cp->ms;

         if (ms->trace_interval == 0) {  /* Normal 'live' meter */
            if (ms->status & MT_MANAGE) monitor(ms);
            add_event(cp,ms);
            }
         else {  /* Meter reading tracefile (Dag or CoralReef) */
            if (!TrState_util(ms, CSU_READ, &cs)) {
               printf(">>> Couldn't read meter CrlState <<<\n");
               log_msg(LOG_INFO, FALSE, "Couldn't read CrlState!\n");
               }
            else {
#if DEBUG_TRACE_STATE
               if (cs != last_cs) {
                  printf(">>> trace: state=%d, ", cs);
                  last_cs = cs;
                  }
#endif
               if (cs == CT_WAIT_FLOWS_READ) {
                  if (ms->status & MT_MANAGE)
		     monitor(ms);  /* Read flow data from meter */
                  cs = CT_PROCESS_FLOWS;
                  if (!TrState_util(ms, CSU_WRITE, &cs))
                     log_msg(LOG_INFO, FALSE, "Couldn't write CrlState!\n");
                  }
               else if (cs == CT_TRACE_EOF) {
                  request_stop = 1;  /* Will read last set of flows */
 	          }
	       }
            if (!request_stop) {
               ms->next_event = ms->next_keepalive = t1 + 2;
               add_event(cp,ms);
               }
#if DEBUG_TRACE_STATE
            printf("new_cs=%d, req_stop=%d, next_event=%d\n",
               cs,request_stop,ms->next_event);
#endif
            }

         }      
      if (testing) fflush(stdout);
      time(&t1);

      if (!request_stop && calendar->next_event > t1)
         sleep(calendar->next_event-t1);

      if (request_stop) {  /* Set by sigint_handler() */
         reader_util(ms, CU_DESTROY, CU_CURRENT);
/*         reader_util(ms, CU_DESTROY, CU_DEFAULT);  */
         ms->mi_u_Index = ms->mi_Index;  task_util(ms, TU_DESTROY);
         ruleset_util(ms, RU_DESTROY, &ms->c_ruleset);

         if (ms->trace_interval != 0) {  /* Meter running tracefile */
            cs = CT_SHUTDOWN;
            if (!TrState_util(ms, CSU_WRITE, &cs))
               log_msg(LOG_INFO, FALSE, "Couldn't write CrlState!\n");
            }
         exit(1);
         }
      }
   }

int activeflows;  /* Nbr of flows active this collection */

void process_row(struct flow_info *fp)
{
   unsigned int fi, i,j,x;
   struct display_data *ddp, *mdp;
   unsigned long tb;
   int save_flow;

   fi = fp->FlowIndex;
   if (flow_table[fi].ruleset == 0 ||
         flow_table[fi].ruleset != fp->FlowRuleSet ||
         flow_table[fi].starttime != fp->FirstTime) {
      /* It's a new flow */
      memset(&flow_table[fi], 0, sizeof(struct flow_data));
      flow_table[fi].ruleset =  fp->FlowRuleSet;
      flow_table[fi].starttime = fp->FirstTime;
      }
   memset(&curr_rate, 0, sizeof(curr_rate));
   if (nz64(fp->FwdPackets)) {
      diff64(curr_rate.uppci, fp->FwdPackets,flow_table[fi].uppci);
      total_rate.uppci += curr_rate.uppci;
      assign64(flow_table[fi].uppci, fp->FwdPackets);
      }
   if (nz64(fp->BackPackets)) {
      diff64(curr_rate.dnpci, fp->BackPackets,flow_table[fi].dnpci);
      total_rate.dnpci += curr_rate.dnpci;
      assign64(flow_table[fi].dnpci, fp->BackPackets);
      }
   if (nz64(fp->FwdBytes)) {
      diff64(curr_rate.upbci, fp->FwdBytes,flow_table[fi].upbci);
      total_rate.upbci += curr_rate.upbci;
      assign64(flow_table[fi].upbci, fp->FwdBytes);
      }
   if (nz64(fp->BackBytes)) {
      diff64(curr_rate.dnbci, fp->BackBytes,flow_table[fi].dnbci);
      total_rate.dnbci += curr_rate.dnbci;
      assign64(flow_table[fi].dnbci, fp->BackBytes);
      }
#if NEW_ATR
   if (fp->sfd != NULL) {
      curr_rate.n_subflow_i =
         fp->sfd->n_subflows - flow_table[fi].n_subflows;
      flow_table[fi].n_subflows = fp->sfd->n_subflows;
      }
#endif
#if PERCENT_DEBUG
   printf("F+B bytes = %lu, total = %lu\n", 
      curr_rate.upbci+curr_rate.dnbci,
      total_rate.upbci+total_rate.dnbci);
#endif
   mdp = &dflows[0];  x = mdp->traffic;  j = 0;
   for (i = 1; i != nd_flows; ++i) {  /* Find min display flow */
      ddp = &dflows[i];
      if (ddp->traffic < x) {
         mdp = ddp;  x = ddp->traffic;  j = i;
         }
      }

   switch(criterion) {
   case C_TOTBYTES:
      tb = curr_rate.upbci+curr_rate.dnbci;
      save_flow = tb > x;
      break;
   case C_STREAMS:
#if NEW_ATR
      tb = curr_rate.n_subflow_i;
      save_flow = tb > x;
#endif
      break;
      }

   if (save_flow) {
      mdp->traffic = tb;

      mdp->fi = &dflowi[j];
      memcpy(mdp->fi, fp, sizeof(struct flow_info));
#if NEW_ATR
      if (fp->sfd != NULL) {
         memcpy(&dsfdi[j], fp->sfd, sizeof(struct subflow_data));
         mdp->fi->sfd = &dsfdi[j];
         }
#endif
#if SIZEOF_LONG_LONG == 8 || SIZEOF_LONG == 8
      mdp->fi->FwdPackets = curr_rate.uppci;
      mdp->fi->BackPackets = curr_rate.dnpci;
      mdp->fi->FwdBytes = curr_rate.upbci;
      mdp->fi->BackBytes = curr_rate.dnbci;
#else
      mdp->fi->FwdPackets.high = 0;
      mdp->fi->FwdPackets.low = curr_rate.uppci;
      mdp->fi->BackPackets.high = 0;
      mdp->fi->BackPackets.low = curr_rate.dnpci;
      mdp->fi->FwdBytes.high = 0;
      mdp->fi->FwdBytes.low = curr_rate.upbci;
      mdp->fi->BackBytes.high = 0;
      mdp->fi->BackBytes.low = curr_rate.dnbci;
#endif
      }

   ++activeflows;
   }

void monitor(struct meter_status *ms)
   /* Called every interval for each meter */
{
   time_t t, nst,si;  char *ts;
   int a,col, reachable, keepalive, s_statsreqd, i,j;
   unsigned long last_uptime, tb, tp;
   double pc, tpc;
   char fdfbuf[500], *fbp;
   struct flow_info *fp;
   unsigned int fi, t_count,x;
   struct display_data *ddp, *mdp;
   char buf[50], *bp;

   time(&t);  ts = fmt_time(&t);
   if (!ms->write_OK) no_write_warning(ms);
   last_uptime = ms->uptime;
   s_statsreqd = ms->statsreqd;  /* Save stats request through keepalive */

   reachable = 1;
   j = reader_util(ms,  /* Tell meter we want a collection */
      CU_SET_TIME, CU_CURRENT);
   if (j == 0) {  /* Lost contact */
      if (ms->status & MT_UP) {  /* Was up */
	 fprintf(stdout,"%s -- %s: No response\n", ts,ms->name);
	 }
      ms->status &= ~MT_UP;  /* Mark as 'down' */
      reachable = 0;
      }
   else if (!(ms->status & MT_UP)) {  /* Have contact now, was down */
      fprintf(stdout,"%s -- %s: Regained contact\n", ts,ms->name);
      ms->write_OK = 1;  ms->no_write_warned = 0;
      }
   ms->status |= MT_UP;
   ms->statsreqd = s_statsreqd;  /* Restore stats request from rule file */

   /* Meter processing .. */

   if (reachable) {
      meter_info(ms);  /* ms->uptime = LastTime */
      memset(&total_rate, 0, sizeof(total_rate));
      memset(dflows, 0, nd_flows*sizeof(struct display_data));
      activeflows = 0;

      if (ms->MIBver == 4) get_package_info(ms, 
         ms->c_ruleset.ri_Index, ms->OurLastCollectTime, process_row);
      else get_colblob_data(ms,
         ms->c_ruleset.ri_Index, ms->OurLastCollectTime, process_row);

      if (verbose) printf(
         "%s %s: %d active flows from %lu to %lu\n",
         ts,ms->name, activeflows, ms->OurLastCollectTime,ms->uptime);

      qsort(dflows, nd_flows,sizeof(struct display_data), sort_cmp);

      tb = total_rate.upbci+total_rate.dnbci;
      tp = total_rate.uppci+total_rate.dnpci;
      fprintf(stdout,"#---  %s %s  %u flows  ",
         ms->name,ms->interface, activeflows);
#if PERCENT_DEBUG
      printf("Total bytes = %lu\n", tb);
#endif
      tpc = 0.0;
      bp = sdisplay_number(buf,tp/ms->sample_interval);
      bp = strmov(bp,"pps ");
      bp = sdisplay_number(bp,tb/ms->sample_interval);
      *bp = '\0';  fprintf(stdout,buf);
      fprintf(stdout,"Bps   %s  ---\n", ts);
      for (i = 0; i != nd_flows; ++i) {
         ddp = &dflows[i];
         if (ddp->traffic == 0) break;
         fp = ddp->fi;
         fbp = fdfbuf;  /* Build flow data file line */
         if (!plain_format) {
#if SIZEOF_LONG_LONG == 8 || SIZEOF_LONG == 8
            pc = (fp->FwdBytes+fp->BackBytes)*100.0/tb;
#else
            pc = (fp->FwdBytes.low+fp->BackBytes.low)*100.0/tb;
#endif
            fprintf(stdout,"%2.0f%%  ", pc);
	    }
         for (col = ms->format[a = 0]; ; ) {
            if (col != '\0') {
               if (plain_format) fbp = sfmt_attrib(fbp, fp,col);
               else fbp = sdisplay_attrib(fbp, fp,col,ms);
               }
            if ((col = ms->format[a+1]) == '\0') {
               if (strcmp(ms->separator[a]," ") != 0)
                  fbp = strmov(fbp,ms->separator[a]);
               break;
               }
            fbp = strmov(fbp,ms->separator[a++]);
            }
         *fbp = '\0';  fprintf(stdout,fdfbuf);
         fprintf(stdout,"\n");
#if PERCENT_DEBUG
         printf("Flow bytes: fwd=%lu, back=%lu, %% = %.3f\n",
            fp->FwdBytes.low,fp->BackBytes.low, pc);
#endif
         tpc += pc;
	 }
      fprintf(stdout,"%2d%%  bytes in %d other flows\n",
         100-(int)tpc,activeflows-i);
      fflush(stdout);
      }

   ms->OurLastCollectTime = ms->uptime - 1L;
      /* -1 to make sure we don't miss any flows.  We may
         collect some of them twice, but we don't mind that */

   if (testing) printf("Sample: t=%u",ms->next_event);
   if (ms->synchronised) {
      nst = ms->next_sample + (si = ms->sample_interval);
      ms->next_sample = (nst / si) * si + ms->lag_seconds;
      }
   else ms->next_sample += ms->sample_interval;
   ms->next_event = ms->next_sample;
   if (testing) printf(", sync=%d, next_sample=%u, next_keepalive=%u\n",
      ms->synchronised, ms->next_sample, ms->next_keepalive);
   }
