/* 1600, Wed 3 Jun 98

   X_NM_RC.C:  An X/Motif remote display console for NeTraMet

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

/*
 * $Log: x_nm_rc.c,v $
 * Revision 1.1.1.2.2.9  2002/02/23 01:57:23  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.5  2000/08/08 19:44:47  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.3  2000/06/06 03:38:14  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2  1999/10/03 21:06:19  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.4  1999/09/22 05:38:38  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.3  1999/05/25 22:30:51  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.2  1999/01/27 04:26:15  nevil
 * Minor corrections to fix compiler warnings
 *
 * Revision 1.1.1.1.2.1  1999/01/08 01:38:33  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1  1998/11/16 03:57:28  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:01  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1.2.1  1998/11/11 23:14:41  nevil
 * Only include malloc.h if we HAVE_MALLOC_H
 *
 * Revision 1.1.1.1  1998/10/28 20:31:25  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2  1998/10/18 23:44:15  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:30  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:29  nguba
 * NetraMet 4.2 Original Distribution
 *
 * Revision 1.6  1998/06/03 04:52:11  rtfm
 * Don't set snmp_delay (only done in nmc_snmp.c)
 * Use LastTime instead of sysUptime
 */

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

#define TEST_GET_FLOWS  0

#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 <syslog.h>
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#include <string.h>

#include <Xm/Xm.h>
#include "n_plot.h"

#define EXTSNMP
#include "ausnmp.h"

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

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

#define XNMRC
#include "x_nm_rc.h"

struct meter_status *nms;

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

void shutdown_nifty(void)
{
   reader_util(nms, CU_DESTROY, CU_CURRENT);
   nms->mi_u_Index = nms->mi_Index;  task_util(nms, TU_DESTROY);
   ruleset_util(nms, RU_DESTROY, &nms->c_ruleset);
   exit(1);
   }

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;
int no_user_format;

unsigned char rc_format[1+N_ATTRIBS], rc_required[1+N_ATTRIBS],
   /* Min attribs needed to determine 'top n' flows */
   user_format[1+N_ATTRIBS], user_required[1+N_ATTRIBS];
   /* Attributes for user's format */

void set_sample_columns(void)
{
  /* Note: we get both packet and byte info so that the user can
           look at both types of plot without having to wait until
           we collect the next sample!  */

   memset(rc_required, 0, 1+N_ATTRIBS);
   rc_format[0] = FTRULESET;     rc_required[FTRULESET] = 1;
   rc_format[1] = FTFLOWINDEX;   rc_required[FTFLOWINDEX] = 1;
   rc_format[2] = FTFIRSTTIME;   rc_required[FTFIRSTTIME] = 1;

   rc_format[3] = FTFLOWKIND;    rc_required[FTFLOWKIND] = 1;
   rc_format[4] = FTLASTTIME;    rc_required[FTLASTTIME] = 1;
   rc_format[5] = FTUPOCTETS;    rc_required[FTUPOCTETS] = 1;
   rc_format[6] = FTDOWNOCTETS;  rc_required[FTDOWNOCTETS] = 1;
   rc_format[7] = FTUPPDUS;      rc_required[FTUPPDUS] = 1;
   rc_format[8] = FTDOWNPDUS;    rc_required[FTDOWNPDUS] = 1;

   rc_format[9] = 0;
   }

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;
   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)
{
   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)
{
   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;
      }

   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 set specified !!!\n");
      return 0;  /* Can't just pick one already running! */
      }
   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);

   if (no_user_format = ms->format[0] == 0) {  /* No format specified */
      ms->format[0] = FTLOWPEERTYPE;      ms->required[FTLOWPEERTYPE] = 1;
      ms->format[1] = FTLOWPEERADDRESS;   ms->required[FTLOWPEERADDRESS] = 1;
      ms->format[2] = FTHIPEERADDRESS;    ms->required[FTHIPEERADDRESS] = 1;
      ms->format[3] = FTLOWTRANSTYPE;     ms->required[FTLOWTRANSTYPE] = 1;
      ms->format[4] = FTLOWTRANSADDRESS;  ms->required[FTLOWTRANSADDRESS] = 1;
      ms->format[5] = FTHITRANSADDRESS;   ms->required[FTHITRANSADDRESS] = 1;
      ms->format[6] = 0;
      for (a = 0; a != 6; ++a) ms->separator[a] = " ";
      }
   memcpy(user_format, ms->format, 1+N_ATTRIBS);
   memcpy(user_required, ms->required, 1+N_ATTRIBS);

   ++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) break;
      fprintf(stdout,ms->separator[a++]);
      }
   fprintf(stdout,"\n");
   }

int main(int argc, char *argv[])
{
   int syntax;
   char *ap, arg[NAME_LN];
   int a, j;
   time_t t1,t2;  int busy_seconds;
   struct meter_status *ms;
   struct calendar_entry *cp;
   struct stat stat_buf;
   char have_config_file;  FILE *cnf;
   char download_only;

   if (argc < 2) {
      fprintf(stderr,"%s [options] meter-name community\n\n", argv[0]);
      exit(0);
      }

   request_stop = 0;
   signal(SIGINT, sigint_handler);
   signal(SIGTERM, sigint_handler);

   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 = DEF_ND_FLOWS;
   def_HighWaterMark = 35;  /* 35% for nm_rc and nifty */
   def_ci_Timeout = 0;
   def_ci_MinPDUs = 20;  /* Reader MinPDU filter value */
   def_GCIntervalReqd =  /* Use meter defaults for MIB variables */ 
   def_InactivityTime = def_FloodMark = 0;
   logging_samples = logging_points = 0;
   show_names = 0;
   n_recent = 8;
   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 '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 '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 '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 '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 */
            logging_samples = logging_points = 1;
	    break;
         case 'M':
            if (*ap == '\0') ap = argv[++a];
	    def_ci_MinPDUs = atoi(ap);
            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);  /* syntax, standby */
      parse_rulefile(ms,1,1,0,0);  /* list, syntax, current, restart */
      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') {
      strcpy(logfile,user_logfile);
      if ((logfl = fopen(logfile,"w")) == 0) {
         fprintf(stderr, "Failed to open %s as log file", logfile);
         exit(10);
         }
      }
   else logfl = wfopen(logfile, LOGFILE);

   init_mib();  /* Initialise SNMP handler */

   time(&t1);
   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 : "nifty");
   strcpy((char *)first_meter->community,
      community[0] != '\0' ? community : "public");
   first_meter->synchronised = def_sync;
   first_meter->sample_interval = def_sample_interval;
   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;

   time(&t1);
   nms = first_meter;
   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);
      }

   /* Make sure we get the info we need to select 'top n' flows */
   selection = STLAST;
   ordinate = PQBYTES;
   set_sample_columns();
   plot_ms = NULL;

   /* 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 ((select_table = (struct flow_select *)calloc(
         max_flows+2, sizeof(struct flow_select))) == NULL) {
      fprintf(stderr, "Failed to allocate memory for select table!\n");
      exit(11);
      }
   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 (j = 1; j != max_flows+2; ++j)
      flow_table[j].active = MXIDLE;

   for (;;) {
      for (;;) {
         time(&t1);
         if (calendar->next_event > t1) break;  /* Wait a while */
         cp = calendar;  calendar = cp->next;  /* Take entry from queue */
         ms = cp->ms;
         if (ms->status & MT_MANAGE) monitor(ms);
         add_event(cp,ms);
         }      
      if (testing) fflush(stdout);
      time(&t1);
      if (calendar->next_event > t1) break;
      if (request_stop)  /* Set by sigint_handler() */
         shutdown_nifty();
      }

   nm_x_startup(1,argv, calendar->next_event-t1);
   /* We only go round this loop once to get communication
      with the target meter established properly */
   }

static int display_ready = 0;
static char *display_msg;

int nm_timer_callback(char *msg, 
   int OK_to_display)  /* Returns nbr of seconds to sleep */
{
   time_t t1,t2;  int busy_seconds;
   struct meter_status *ms;
   struct calendar_entry *cp;

   display_ready = OK_to_display;
   display_msg = msg;   
   for (;;) {
      for (;;) {
         time(&t1);
         if (calendar->next_event > t1) break;  /* Wait a while */
         cp = calendar;  calendar = cp->next;  /* Take entry from queue */
         ms = cp->ms;
         if (ms->status & MT_MANAGE) monitor(ms);
         add_event(cp,ms);
         }      
      if (testing) fflush(stdout);
      time(&t1);
      if (calendar->next_event > t1) break;
      if (request_stop)  /* Set by sigint_handler() */
         shutdown_nifty();
      }
   return calendar->next_event-t1;  /* Seconds before next event */
   }

int activeflows;      /* Nbr of flows active this sample */
unsigned long sb,sp;  /* Total bytes/packets this sample */
unsigned long sample_LastCollectTime;

void process_row(struct flow_info *fp)
{
   struct flow_data *fdp;
   unsigned int fi;

   fdp = &flow_table[fi = fp->FlowIndex];
   if (fdp->ruleset != fp->FlowRuleSet ||
         fdp->starttime != fp->FirstTime) {
               /* It's a new flow */
      memset(fdp, 0, sizeof(struct flow_data));
      fdp->ruleset =  fp->FlowRuleSet;
      fdp->flowkind =  fp->FlowKind;
      fdp->starttime = fp->FirstTime;
      fdp->interval = fp->LastTime-fp->FirstTime;
      }
   else {
      fdp->active = 0;  /* Active this sample */
      fdp->interval = fp->LastTime - fdp->lasttime;
      }
   fdp->lasttime = fp->LastTime;
#if TEST_GET_FLOWS
   printf("flow %d, topkt=%lu, frompkt=%lu\n", 
      fi, fp->FwdPackets.low, fp->BackPackets.low);
#endif
   if (display_ready) {  /* 64-bit counts => 32-bit rates !!! */
      diff64(fdp->up_pkt_rate, fp->FwdPackets,fdp->up_pkt_count);
      if (fdp->up_pkt_rate != 0) {
         diff64(fdp->up_byte_rate, fp->FwdBytes,fdp->up_byte_count);
         if (fdp->up_byte_rate/fdp->up_pkt_rate > 1500)
            fdp->up_byte_rate = fdp->up_pkt_rate = 0L;
         }
      else fdp->up_byte_rate = 0;
      diff64(fdp->down_pkt_rate, fp->BackPackets,fdp->down_pkt_count);
      if (fdp->down_pkt_rate != 0) {
         diff64(fdp->down_byte_rate, fp->BackBytes,fdp->down_byte_count);
         if (fdp->down_byte_rate/fdp->down_pkt_rate > 1500)
            fdp->down_byte_rate = fdp->down_pkt_rate = 0L;
         }
      else fdp->down_byte_rate = 0;
      }
   assign64(fdp->up_pkt_count, fp->FwdPackets);
   assign64(fdp->up_byte_count, fp->FwdBytes);
   assign64(fdp->down_pkt_count, fp->BackPackets);
   assign64(fdp->down_byte_count, fp->BackBytes);

   sb += (fdp->up_byte_rate+fdp->down_byte_rate);  /* Sample totals */
   sp += (fdp->up_pkt_rate+fdp->down_pkt_rate);
   ++activeflows;
   }

void monitor(  /* Called every interval for each meter */
   struct meter_status *ms)
{
   time_t t, nst,si;  char *ts;
   int mir, reachable, n,r;
   unsigned long last_uptime;
   struct flow_info *fp;
   struct flow_data *fdp;
   char buf[150], *dbp;

   time(&t);  ts = fmt_time(&t);
   if (!ms->write_OK) no_write_warning(ms);
   last_uptime = ms->uptime;

   reachable = 1;
   ms->ci_TimeMark = ms->OurLastCollectTime;
      /* Associate TimeFilter with rdr row */
   if (ms->MIBver == 4) {  /* Tell meter we want to start a collection */
      if (ms->c_ruleset.ri_Index != 0) {
         mir = reader_util(ms, CU_SET_TIME, CU_CURRENT);
         }
      else mir = 1;  /* No current ruleset (yet) */
      }
   else {  /* V3 MIB */
      mir = reader_util(ms, CU_SET_TIME, CU_CURRENT);
      }
   if (mir) {  /* LastTime was set OK */
      mir = meter_info(ms);  /* ms->uptime = LastTime */
      }

   if (mir == 0) {  /* Lost contact */
      if (restart_snmp_session(ms)) {
         ms->uptime = 0;
         mir = meter_info(ms);
         }
      }
   if (mir == 0) {  /* Couldn't re-establish contact */
      fprintf(stderr, "%s -- %s: No response\n", ts,ms->name);
      reachable = 0;
      }

   /* Meter processing .. */

   if (reachable) {
      if (ms->uptime < last_uptime ) {  /* Meter has restarted */
	 fprintf(stderr, "%s: Meter has restarted\n", ms->name);

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

         set_meter_params(ms);

         /* Clear old (e.g. NeTraMet) tasks from meter */
         search_table(ms, ST_READER,  reader_row);
         ms->use_meter_rows = 0;
         search_table(ms, ST_MANAGER, manager_row);
         search_table(ms, ST_RULESET, ruleset_row);

         /* Forget our control rows - mustn't confuse the download */
         ms->ci_c_Index = 0;
         ms->mi_Index = 0;
         ms->c_ruleset.ri_Index = 0;
         n = parse_rulefile(ms,listrules,0,0,1);  /* Reload */
         if (!n || rferrors != 0) return;

         if (ms->mi_Index != 0) {
            reader_util(ms, CU_INIT, CU_CURRENT);
            if (!ms->write_OK) no_write_warning(ms);
	    }

         ms->ci_TimeMark = ms->OurLastCollectTime;
            /* Associate TimeFilter with rdr row */
         if (ms->MIBver == 4) {  /* Tell meter we want to start a collection */
            if (ms->c_ruleset.ri_Index != 0)
               reader_util(ms, CU_SET_TIME, CU_CURRENT);
            }
         else reader_util(ms, CU_SET_TIME, CU_CURRENT);  /* V3 MIB */
         meter_info(ms);
         }

      if (ms->mi_Index == 0 && ms->MIBver == 4)
         ms->uptime = last_uptime;  /* Haven't got our task running yet */

      plot_ms = ms;
      for (r = 0; r != max_flows+2; ++r) {
         fdp = &flow_table[r];
         if (fdp->active != MXIDLE) ++fdp->active;
         }
      tt = 0;  /* Used by x_support.c */
      sb = sp = 0;
      sample_LastCollectTime = ms->OurLastCollectTime;
      memset(dflows, 0, nd_flows*sizeof(struct display_data));
      activeflows = 0;

      memcpy(ms->format, rc_format, 1+N_ATTRIBS);
      memcpy(ms->required, rc_required, 1+N_ATTRIBS);
         /* Only get the attribs needed to determine 'top n' flows */
      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);
      memcpy(ms->format, user_format, 1+N_ATTRIBS);
      memcpy(ms->required, user_required, 1+N_ATTRIBS);

      if (display_ready) {
         sprintf(buf,"%s, %s %s, %s, %us sample, ",
            ts, ms->name,ms->interface, 
            ms->c_ruleset.filename, ms->sample_interval);
         dbp = strmov(display_msg,buf);
         if (ms->statsreqd) {
            sprintf(buf,"%u flows, %u active, ", ms->NbrFlows,activeflows);
            dbp = strmov(dbp,buf);
	    }
         else {
            sprintf(buf,"%u flows (%u read), ", ms->ActiveFlows, activeflows);
/* $$$      sprintf(buf,"%u active flows, ", activeflows); */
            dbp = strmov(dbp,buf);
	    }
         if (ordinate == PQBYTES) sprintf(buf,"%.3f Mbps",
            ((float)sb*8.0)/((float)ms->sample_interval*1000000.0) );
         else sprintf(buf,"%.3f kpps",
            (float)sp/((float)ms->sample_interval*1000.0) );
         dbp = strmov(dbp,buf);
         if (logging_samples) {
            log_msg(LOG_INFO, FALSE, "%s\n",display_msg);
            }
         }
      }

   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);
   }
