/* 1157, Wed 12 Jan 00

   FD_FILTER.C:  Performs tidy-up operations on NeTraMet flow-data files
      * Compute flow rates [To/From ByteRate, To/From PduRate]
      * Change (NeTraMet) format for file
      * Filter out flows from file, tag those remaining [FlowTag]
      * Filter out #Stats records

   Copyright (C) 1994-2002 by Nevil Brownlee & Russell Fulton,
   CAIDA | University of Auckland */

/*
 * $Log: fd_filter.c,v $
 * Revision 1.1.1.2.2.12  2002/02/23 01:57:16  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.7  2000/11/29 21:40:08  nevil
 * Bug fixes for stream / packet queue handling
 *
 * Revision 1.1.1.2.2.6  2000/08/08 19:44:42  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.4  2000/06/06 03:38:08  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.1  2000/01/12 02:57:02  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  1999/10/03 21:06:14  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.9  1999/09/22 05:38:33  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.8  1999/09/14 00:46:45  nevil
 * 4.3 Release ..
 *  - Implement -D option to run NeMaC as a daemon
 *  - Tidy up the on-line help displays
 *  - Make IPv4 the default PeerType for sfmt_attribute()
 *
 * Revision 1.1.1.1.2.7  1999/05/18 03:36:24  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.6  1999/02/15 21:24:05  nevil
 * Distribution file for 4.3b9
 *
 * Revision 1.1.1.1.2.5  1999/02/03 04:41:41  nevil
 * Implementation of TCP attributes, part 5
 *
 * Revision 1.1.1.1.2.4  1999/01/27 04:26:12  nevil
 * Minor corrections to fix compiler warnings
 *
 * Revision 1.1.1.1.2.3  1999/01/20 04:01:32  nevil
 * Implementation of TCP attributes, part 4
 *
 * Revision 1.1.1.1.2.2  1999/01/08 01:37:02  nevil
 * Implement TCPdata and DScodepoint attribute
 *
 * Revision 1.1.1.1.2.1  1998/11/26 04:13:49  nevil
 * Fix problems in fd_filter and fd_extract when dealing with 'new'
 * attributes, especially those used with NETFLOW. In particular:
 * - Make sure attribs[] is referred to via attr_ix[]
 * - Get the separators correctly from a #Format record (no quotes)
 * - fd_extract wasn't writing out its data for the last sample!
 *
 * Revision 1.1.1.1  1998/11/16 03:57:26  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:36  nevil
 * Only include malloc.h if we HAVE_MALLOC_H
 *
 * Revision 1.1.1.1  1998/10/28 20:31:23  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.2.2.1  1998/10/27 04:39:13  nevil
 * 4.3b1 release
 *
 * Revision 1.1.3.2  1998/10/18 23:44:04  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:16  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.3  1998/05/07 04:28:53  rtfm
 * Implement NetFlowMet, the Cisco NetFlow RTFM meter
 */

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

#define DEBUG       0
#define TEST_DELTA  0

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if HAVE_MALLOC_H
# include <malloc.h>
#endif

/*
   filter fmt_file [ in_file [ trailer_file [ > out_file ]]]
*/

#include "ausnmp.h"
#include "asn1.h"

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

struct tag_info {
   unsigned char  /* Attributes to test, null terminated */
      att_nbr[1+LASTATTRIB];
   int cmp_eq[1+LASTATTRIB];
   val att_p[1+LASTATTRIB];  /* Pointers to attribute values */

   struct flow_info flow;  /* Attribute values */
   };

EXTERN struct tag_info *tags[MXTAGS];
EXTERN int ntags INIT(0);


struct tag_info *scan_tag(void)
{
   struct tag_info *t;
   unsigned int tn;
   int a, n, op;

   if ((t = (struct tag_info *)calloc(1, sizeof(struct tag_info))) == NULL) {
      fprintf(stderr,"Failed to allocate memory for tag info, exiting\n");
      exit(10);
      }

   tn = getnbr();  /* Get tag nbr */
   t->flow.TagNbr = tn;
   for (a = 0; ; ++a) {
      n = getnbr();  /* Get attribute */
      if (n < 0 || n > LASTATTRIB || attr_ix[n] == 0) {
         sprintf(ebuf, "Attribute %d not allowed in tag %d",n,tn);
         p_error(ebuf);
         }
      else {
         t->att_nbr[a] = n;
         t->att_p[a] = attrib_ptr(&t->flow,n); 
         }

      if (op = relop_next()) {
         t->cmp_eq[a] = (op == OP_EQ) ? 1 : 0;
         }

      /* Note: String attribue values (i.e. addresses) are held and compared
          as strings, so they have to appear in filter format files
          exactly as they do in flow data files */

      get_value(t->att_p[a], n, ibp);

      for (;;) {  /* Find next attribute */
         if (ic == ';')  /* End of attrib list */
            return t;
         if (isalpha(ic)) break;
         else nextchar();
         }
      }
   }
 
struct flow_data {
   unsigned short ruleset;
   unsigned int flownum, starttime;
   counter64
      upbc, dnbc, uppc, dnpc;  /* Counts from last sample */
#if NEW_ATR
   struct subflow_data sfd;
   unsigned int
      ToLostPDUs, FromLostPDUs,
      ToPQOverflows, FromPQOverflows;
   struct distribution *dist;
#endif  /* NEW_ATR */
   };
struct flow_data *flow;

int next_flow = 1, max_flows;
int stats_reqd = 0;

FILE *input, *output;
struct flow_stats in_set, out_set;
char format_fn[STR_LEN], input_fn[STR_LEN];
char trailer_fn[STR_LEN], handling_trailer;
int recno;

void quack(int n) {;}  /* For gdb */

int Data_seen = 0;  /* #Data records (follwing a Time record) */

int nextrecord(void)
{
   if (handling_trailer) {
      if (fgets(inbuf, sizeof(inbuf), input) == NULL)
         return 0;  /* (Unexpected) end of trailer file */
#if 0
      /*      if (inbuf[0] == '#') {
         if (recno > 4 && inbuf[1] == 'T')
	    return 0;  /* Time record other than first one in file */
         } */
#endif
      return 1;
      }
   else {
      if (fgets(inbuf, sizeof(inbuf), input) != NULL) return 1;
      fclose(input);
      if (trailer_fn[0] == '\0') return 0;  /* No trailer file */
      if ((input=fopen(trailer_fn, "r")) == NULL) {
         fprintf(stderr, "can't open trailer %s:",trailer_fn); perror("");
         return 0;
         }
      Data_seen = 0;  handling_trailer = 1;  /* Handling trailer file */
      
      recno = 0;
      if (fgets(inbuf, sizeof(inbuf), input) == NULL)  /* Read header record */
         return 0;
      if (strncmp(inbuf, "##NeTraMet ", 10) != 0) {
         fprintf(stderr,"Trailer not a NeTraMet file!\n");
         return 0;
         }

      for (;;) {
         ++recno;
         if (fgets(inbuf, sizeof(inbuf), input) == NULL) {
            fprintf(stderr,"Failed to find format record in trailer file\n");
            return 0;;
            }
         if (inbuf[0] != '#') {
            fprintf(stderr,"Non-# record found in trailer file header\n");
            return 0;;
            }
         if (inbuf[1] == 'F')  /* Should be #Format */
            break;
         }
      ic = ' ';  ibp = inbuf+8;  /* Scan format line */
      scan_format(&in_set,1,listformat);  /* single_record */
      if (!check_formats(&in_set, &out_set)) return 0;
      ++recno;
      return fgets(inbuf, sizeof(inbuf), input) != NULL;
      }
   }

#if NEW_ATR
void compute_deltas(struct distribution *d_rate,
   struct distribution *cf_dist, struct distribution *fi_dist)
   /* delta = cf_dist - fi_dist;  Diff from previous sample
      fi_dist = cf_dist;          Save for next diff        */
{
   struct distribution *d, *cfd, *fid;
   int a, j, t;

   for (d = d_rate, fid = fi_dist; d != NULL; d = d->next, fid = fid->next) {
      a = d->selector - (DELTA_BASE-DISTRIB_BASE);
      for (cfd = cf_dist; cfd != NULL; cfd = cfd->next) {
         if (cfd->selector == a) break;
         }
      if (cfd == NULL) {
         fprintf(stderr, "Couldn't find distribution attrib %d\n", a);
         exit(12);
         }

      d->Transform = cfd->Transform;
      if (d->Transform != 0) {  /* We have a non-empty distribution */
         d->ScaleFactor = cfd->ScaleFactor;  /* cfd may have different dist params !!! */
         d->LowerLimit = cfd->LowerLimit;  d->UpperLimit = cfd->UpperLimit;
         d->Buckets = cfd->Buckets;  d->Parameter1 = cfd->Parameter1;
         d->Parameter2 = cfd->Parameter2;  d->Parameter3 = cfd->Parameter3;
         for (j = 0; j != d->Buckets+1; ++j)
            d->counts[j] = cfd->counts[j] - fid->counts[j];
         }

      t = fid->Transform;
      fid->Transform = cfd->Transform;   
      if (t == 0) {  /* Flow's 'last values' params don't change */
         fid->ScaleFactor = cfd->ScaleFactor;
         fid->LowerLimit = cfd->LowerLimit;  fid->UpperLimit = cfd->UpperLimit;
         fid->Buckets = cfd->Buckets;  fid->Parameter1 = cfd->Parameter1;
         fid->Parameter2 = cfd->Parameter2;  fid->Parameter3 = cfd->Parameter3;
         }
      if (fid->Transform != 0) {
         for (j = 0; j != d->Buckets+1; ++j)
            fid->counts[j] = cfd->counts[j];
         }
      }
   }
#endif  /* NEW_ATR */

int main(int argc, char *argv[])
{
   char *pb, *p;
   int x, l = 0, n, a, b, i, fail;
   int fi, ruleset=0, first_sample=1;
   struct tag_info *tip;

   int Time_seen = 0;
      /* Don't write #Time or #Endata until we've seen real data! */
   char last_Time_record[sizeof(inbuf)];

#if NEW_ATR
   struct distribution *d, *ld;
#endif  /* NEW_ATR */

   if (argc < 2) {
      fprintf(stderr, 
         "%s format_file [ input_file [ trailer_file [ > output_file ]]]\n",
         argv[0]);
      exit(0);
      }

   memset(attr_ix, 0, sizeof(attr_ix));  /* Make attribute index */
   for (x = 0; x != SZ_ATTRIBS; ++x)
      attr_ix[attribs[x].index] = x; 

   format_fn[0] = input_fn[0] = trailer_fn[STR_LEN] = '\0';
   quiet = 0;
   listrules = 1;  /* List tags and format (from flow data file) */
   listformat = 0;  /* From the format file */

   for (a = 1; a != argc; ++a) {
      if (argv[a][0] == '-') {
	 switch (argv[a][1]) {
	 case 'f':
	    listformat = 1;
	    break;
	 case 'q':
	    quiet = 1;  /* No 'comment' listing */
	    break;
	 default:
	    fprintf(stderr,"Invalid option: -%c\n", argv[a][1]);
	    exit(0);
	    }
         continue;
	 }
      if (format_fn[0] == '\0') strcpy(format_fn, argv[a]);
      else if (input_fn[0] == '\0') strcpy(input_fn, argv[a]);
      else if (trailer_fn[0] == '\0') strcpy(trailer_fn, argv[a]);
      }

   if (format_fn[0] == '\0') {
      fprintf(stderr,"No format file specified!\n");
      exit(1);
      }
   if (!parse_open(format_fn)) {
      fprintf(stderr,"Can't open format file %s !\n", format_fn);
      exit(1);
      }
   out_set.MIBver = 4;

   for (;;) {  /* Read the format file */
      do {  /* First char of a line */
	 nextchar();
	 if (ic == EOF) break;
	 } while (lic != '\n');
      if (ic == EOF) break;
      n = getnbr();  /* What kind of line is it? */
      if (n == RF_SET) {
         ruleset = getnbr();
         continue;
         }
      if (n == RF_FORMAT) {
         scan_format(&out_set,0,listformat);  /* Not single_record */
         continue;
	 }
      if (n == RF_TAG) {
         tags[ntags++] = scan_tag();
         continue;
	 }
      if (n == RF_STATS) {
         stats_reqd = 1;
         }
      else p_error("Unexpected line in format file");
      }
   fclose(rfp);
   if (rferrors != 0) {
      fprintf(stderr,"%d errors in format file!\n", rferrors);
      exit(10);
      }

   if (listrules && !quiet) {
      for (n = 0; n != ntags; ++n) {  /* List the tags */
         if ((tip = tags[n]) == NULL) continue;
         fprintf(stderr,"tag %d: ",tip->flow.TagNbr);
         for (i = 0; ; ++i) {
            if ((a = tip->att_nbr[i]) == 0) break;
            x = attr_ix[a];  /* Index into attribs[] */
            fprintf(stderr, " %s %s ", attribs[x].name,
               tip->cmp_eq[i] ? "==" : "!=");
            put_value(stderr, a, tip->att_p[i]);
            }
         fprintf(stderr,"\n");
         }
      }

   if (argc > 2) {
      if ((input=fopen(input_fn, "r")) == NULL) {
         fprintf(stderr, "Can't open input %s:",input_fn);
         exit(1);
         }
      }
   else input = stdin;
   handling_trailer = 0;  /* Handling input file */

/* Read the header line and check it */

   recno = 0;
   if (fgets(inbuf, sizeof(inbuf), input) == NULL) {
      fprintf(stderr, "initial read failed on input file\n");
      exit(10);
      }
   ++recno;
   if (strncmp(inbuf, "##NeTraMet ", 10) != 0) {
      fprintf(stderr,
	      "Hmm ... this does not appear to be a NeTraMet file, exiting\n");
      exit(10);
      }
   in_set.MIBver = 4;
   if (strncmp(inbuf+11, ver_str ":", 5) != 0) {
      fprintf(stderr, 
         "Caution: this program was written for version " ver_str " files,"
	 " continuing\n");
      if (strncmp(inbuf+11, "v3.", 3) == 0)
         in_set.MIBver = 3;
      }
   if ((p = strstr(inbuf+12, "flows")) == NULL) {
      fprintf(stderr, "Can't find maximum flow number! exiting\n");
      exit(10);
      }
   for (--p; isspace(*p); ) --p;
   for ( ; isdigit(*p); ) --p;
   sscanf(p,"%d",&max_flows);

   /* Allocate two extra flows since for v3 they started from 2 */

   if ((flow=(struct flow_data *)calloc(  /* Initially zeroed */
         max_flows+2, sizeof(struct flow_data))) == NULL) {
      fprintf(stderr, "Failed to allocate memory for flow table, exiting\n");
      exit(10);
      }
   output = stdout;
   fputs(inbuf, output);  /* Copy header record to new file */

   for (;;) {
      if (fgets(inbuf, sizeof(inbuf), input) == NULL) {
         fprintf(stderr,"Failed to find format record in flows file\n");
         exit(10);
         }
      ++recno;
      if (inbuf[0] != '#') {
         fprintf(stderr,"Non-# record found in flows file header\n");
         exit(10);
         }
      if (inbuf[1] == 'F')  /* Should be #Format */
         break;
      else fputs(inbuf, output);  /* Copy unknown header record(s) to new file */
      }
   ic = ' ';  ibp = inbuf+8;  /* Scan format line */
   scan_format(&in_set,1,listrules);  /* single_record */
   if (!check_formats(&in_set, &out_set)) exit(1);

   fputs("#Format: ", output);
   for (i = 0; i != out_set.n_attr; ++i) {
      a = out_set.format[i];
      x = attr_ix[a];  /* Index into attribs[] */
      fputs(attribs[x].name, output);
      if (i != out_set.n_attr-1
            || strcmp(out_set.separator[i]," ") != 0)
         fputs(out_set.separator[i], output);
      }
   fputs("\n", output);

   if (!in_set.required[FTFLOWINDEX] || !in_set.required[FTRULESET] ||
         !in_set.required[FTFIRSTTIME])
      p_error("Input file must have FlowIndex, FlowRuleSet and\n"
         "   FirstTime to uniquely identify flows");
   if (rferrors != 0) exit(1);

#if NEW_ATR
   if (out_set.delta_bits != 0) {
      for (i = 0, ld = NULL; i != out_set.n_attr; ++i) {
         a = out_set.format[i];
         if (DELTA_ATTRIB(a)) {
            if ((d = get_dist()) == NULL) {
               fprintf(stderr, "Couldn't get space for distribution!\n");
               exit(12);
               }
#if TEST_DELTA
            fprintf(stderr, "dist_rate(0x%0lx, %d) -> 0x%0lx\n", ld,a, d);
#endif
            if (ld == NULL) curr_flow.dist_rate = d;
            else ld->next = d;
            d->next = NULL;  d->selector = a;
            d->Transform = 0;
            ld = d;
            }
         }
      }
#endif  /* NEW_ATR */
   for (a = 1; a <= LASTATTRIB; ++a) {  /* Make ptrs to curr_flow attribs */
      x = attr_ix[a];
      if (x != 0) attribs[x].value = attrib_ptr(&curr_flow, a);
      }

   while (nextrecord()) {
      recno++;
      if (inbuf[0] == '#') {
         if (strncmp(inbuf, "#Time", 5) == 0) {
            if (Data_seen) {
	      if (handling_trailer) break;  /* #Time following data */
               first_sample = 0;
               }
            strcpy(last_Time_record, inbuf);  Time_seen = 1;
            Data_seen = 0;
            }
         else if (strncmp(inbuf, "#EndData", 8) == 0) {
            if (Data_seen) fputs(inbuf, output);
            }
	 /*          if (first_sample && recno > 4 && inbuf[1] == 'T')
		     first_sample = 0; */
         else if (strncmp(inbuf, "#Statistics", 11) == 0) {
            if (stats_reqd) fputs(inbuf, output);
            }
         else fputs(inbuf, output);
         }
      else {  /* It's a flow */
         if (!Data_seen) {
            if (Time_seen && !first_sample) fputs(last_Time_record, output);
            Data_seen = 1;
            }
         pb = inbuf; 
#if NEW_ATR
         ld = curr_flow.dist;  /* Don't re-allocate distrib chains! */
         d = curr_flow.dist_rate;
#endif
         memset(&curr_flow, 0, sizeof(curr_flow)); /* Zero current values */
#if NEW_ATR
         curr_flow.dist = ld;
         curr_flow.dist_rate = d;
#endif

         for (i = 0; i != in_set.n_attr; ++i) {  /* Get the values */
	    a = in_set.format[i];
            x = attr_ix[a];  /* Index into attribs[] */
            pb = get_value(attribs[attr_ix[a]].value, a, pb);
            if (i != in_set.n_attr-1)
               pb += strlen(in_set.separator[i]);
	    }

         if (in_set.MIBver == 3) {
            curr_flow.LowPeerType = new_addr_type[curr_flow.LowPeerType];
            curr_flow.HighPeerType = new_addr_type[curr_flow.HighPeerType];
            }
         if ((fi=curr_flow.FlowIndex) > max_flows+1) {
	    fprintf(stderr,
	       "flow index (%d) greater than number of flows from "
	       "header (%d) at record number %d\n",
	       fi, max_flows, recno);
            exit(10);
            }
#if DEBUG
         fprintf(output,"<< ");
         for (i = 0; i != in_set.n_attr; ++i) {
	    a = in_set.format[i];
            x = attr_ix[a];  /* Index into attribs[] */
	    put_value(output, a, attribs[x].value);
            if (i != in_set.n_attr-1
                  || strcmp(in_set.separator[i]," ") != 0)
               fprintf(output,"%s",in_set.separator[i]);
	    }
         fprintf(output,"\n");
#endif
#if TEST_DELTA
         fprintf(stderr, "Read <%d> ", fi);
#endif

         if (ruleset == curr_flow.FlowRuleSet || ruleset == 0) {

            for (fail = 0, n = 0;  n != ntags;  ++n) {  /* Search tag list */
               if ((tip = tags[n]) == NULL) continue;
               for (fail = i = 0; ; ++i) {
                  if ((a = tip->att_nbr[i]) == 0) break;
                  x = attr_ix[a];  /* Index into attribs[] */

                  if (Counter64_value(x)) {  /* Counter64 */
		     if (tip->cmp_eq[i]) {
                        if (!eq64p(attribs[x].value.c64val,
                                tip->att_p[i].c64val)) {
                           fail = 1;  break;
                           }
                        }
		     else {
                        if (eq64p(attribs[x].value.c64val,
                                tip->att_p[i].c64val)) {
                           fail = 1;  break;
			   }
                        }
		     }
                  else if (string_value(x)) {
		     if (tip->cmp_eq[i]) {
                        if (strcmp((char *)attribs[x].value.charval,
                              (char *)tip->att_p[i].charval) != 0) {
                           fail = 1;  break;
                           }
                        }
		     else {
                        if (strcmp((char *)attribs[x].value.charval,
                              (char *)tip->att_p[i].charval) == 0) {
                           fail = 1;  break;
                           }
                        }
		     }
#if 0
#if NEW_ATR
                  else if (tcp_value(x)) {
                     ;  /* Can't use tcpdata in a tag */
		     }
                  else if (distrib_value(x)) {
                     ;  /* Can't use distributions in a tag */
		     }
#endif  /* NEW_ATR */
#endif
                  else {  /* Integer (1, 2 or 4 bytes) */
		     if (tip->cmp_eq[i]) {
                        if (*attribs[x].value.intval != *tip->att_p[i].intval) {
                           fail = 1;  break;
                           }
		        }
		     else {
                        if (*attribs[x].value.intval == *tip->att_p[i].intval) {
                           fail = 1;  break;
                           }
                        }
		     }
                  }
               if (!fail) break;
               }
#if DEBUG
            if (fail) fprintf(output,"\n");  /* Blank line */
#endif
            if (fail) continue;  /* Flow not selected by tags - ignore it */

            if (flow[fi].ruleset == 0 || 
                  flow[fi].ruleset != curr_flow.FlowRuleSet ||
                  flow[fi].starttime != curr_flow.FirstTime) {
               /* It's a new flow */
#if TEST_DELTA
               fprintf(stderr, "New flow: fi=%d, ld=%0lx\n",
                  fi, flow[fi].dist);
#endif
#if NEW_ATR
               ld = flow[fi].dist;
#endif  /* NEW_ATR */
	       memset(&flow[fi], 0, sizeof(flow[fi]));
#if NEW_ATR
               flow[fi].dist = ld;
#endif  /* NEW_ATR */
	       flow[fi].ruleset =  curr_flow.FlowRuleSet;
	       flow[fi].starttime = curr_flow.FirstTime;
	       flow[fi].flownum = next_flow++;
#if NEW_ATR
               if (out_set.delta_bits != 0 && ld == NULL) {
                  for (i = 0; i != out_set.n_attr; ++i) {
                     a = out_set.format[i];
                     if (DELTA_ATTRIB(a)) {
                        if ((d = get_dist()) == NULL) {
                           fprintf(stderr, 
                              "Couldn't get space for distribution!\n");
                           exit(12);
                           }
#if TEST_DELTA
            fprintf(stderr, "fi[%d](0x%0lx, %d) -> 0x%0lx\n", fi, ld,a, d);
#endif
                        if (ld == NULL) flow[fi].dist = d;
                        else ld->next = d;
                        d->next = NULL;  d->selector = a;
                        d->Transform = 0;
                        ld = d;
                        }

                     }
		  }
#endif  /* NEW_ATR */
	       }

            if (nz64(curr_flow.FwdBytes)) {
	       subtr64(curr_flow.upbci, curr_flow.FwdBytes,flow[fi].upbc);
	       assign64(flow[fi].upbc, curr_flow.FwdBytes);
	       }
            if (nz64(curr_flow.BackBytes)) {
	       subtr64(curr_flow.dnbci, curr_flow.BackBytes, flow[fi].dnbc);
	       assign64(flow[fi].dnbc, curr_flow.BackBytes);
	       }
            if (nz64(curr_flow.FwdPackets)) {
	       subtr64(curr_flow.uppci, curr_flow.FwdPackets, flow[fi].uppc);
	       assign64(flow[fi].uppc, curr_flow.FwdPackets);
	       }
            if (nz64(curr_flow.BackPackets)) {
	       subtr64(curr_flow.dnpci, curr_flow.BackPackets, flow[fi].dnpc);
	       assign64(flow[fi].dnpc, curr_flow.BackPackets);
	       }
            if (ntags != 0) curr_flow.TagNbr = tip->flow.TagNbr;

#if NEW_ATR
            if (curr_flow.ToLostPDUs != 0) {
               curr_flow.d_ToLostPDUs =
                  curr_flow.ToLostPDUs - flow[fi].ToLostPDUs;
               flow[fi].ToLostPDUs = curr_flow.ToLostPDUs;
               }
            if (curr_flow.FromLostPDUs != 0) {
               curr_flow.d_FromLostPDUs =
                  curr_flow.FromLostPDUs - flow[fi].FromLostPDUs;
               flow[fi].FromLostPDUs = curr_flow.FromLostPDUs;
               }
            if (curr_flow.ToPQOverflows != 0) {
               curr_flow.d_ToPQOverflows =
                  curr_flow.ToPQOverflows - flow[fi].ToPQOverflows;
               flow[fi].ToPQOverflows = curr_flow.ToPQOverflows;
               }
            if (curr_flow.FromPQOverflows != 0) {
               curr_flow.d_FromPQOverflows =
                  curr_flow.FromPQOverflows - flow[fi].FromPQOverflows;
               flow[fi].FromPQOverflows = curr_flow.FromPQOverflows;
               }

            curr_flow.sfd_rate.n_subflows =
               curr_flow.sfd.n_subflows - flow[fi].sfd.n_subflows;
            flow[fi].sfd.n_subflows = curr_flow.sfd.n_subflows;
            curr_flow.sfd_rate.mx_active_subflows =
                  curr_flow.sfd.mx_active_subflows;
            if (nz64(curr_flow.sfd.ToTCPLenOctets)) {
	       subtr64(curr_flow.sfd_rate.ToTCPLenOctets,
                  curr_flow.sfd.ToTCPLenOctets,
                     flow[fi].sfd.ToTCPLenOctets);
	       assign64(flow[fi].sfd.ToTCPLenOctets,
                  curr_flow.sfd.ToTCPLenOctets);
               }
            if (nz64(curr_flow.sfd.ToTCPSeqOctets)) {
	       subtr64(curr_flow.sfd_rate.ToTCPSeqOctets,
                  curr_flow.sfd.ToTCPSeqOctets,
                     flow[fi].sfd.ToTCPSeqOctets);
	       assign64(flow[fi].sfd.ToTCPSeqOctets,
                  curr_flow.sfd.ToTCPSeqOctets);
               }
            if (nz64(curr_flow.sfd.ToTCPAckOctets)) {
	       subtr64(curr_flow.sfd_rate.ToTCPAckOctets,
                  curr_flow.sfd.ToTCPAckOctets,
                     flow[fi].sfd.ToTCPAckOctets);
	       assign64(flow[fi].sfd.ToTCPAckOctets,
                  curr_flow.sfd.ToTCPAckOctets);
               }
            if (nz64(curr_flow.sfd.FromTCPLenOctets)) {
	       subtr64(curr_flow.sfd_rate.FromTCPLenOctets,
                  curr_flow.sfd.FromTCPLenOctets,
                     flow[fi].sfd.FromTCPLenOctets);
	       assign64(flow[fi].sfd.FromTCPLenOctets,
                  curr_flow.sfd.FromTCPLenOctets);
               }
            if (nz64(curr_flow.sfd.FromTCPSeqOctets)) {
	       subtr64(curr_flow.sfd_rate.FromTCPSeqOctets,
                  curr_flow.sfd.FromTCPSeqOctets,
                     flow[fi].sfd.FromTCPSeqOctets);
	       assign64(flow[fi].sfd.FromTCPSeqOctets,
                  curr_flow.sfd.FromTCPSeqOctets);
	       }
            if (nz64(curr_flow.sfd.FromTCPAckOctets)) {
	       subtr64(curr_flow.sfd_rate.FromTCPAckOctets,
                  curr_flow.sfd.FromTCPAckOctets,
                     flow[fi].sfd.FromTCPAckOctets);
	       assign64(flow[fi].sfd.FromTCPAckOctets,
                  curr_flow.sfd.FromTCPAckOctets);
	       }
            curr_flow.sfd_rate.ToTCPDecrSeq =
               curr_flow.sfd.ToTCPDecrSeq - flow[fi].sfd.ToTCPDecrSeq;
            flow[fi].sfd.ToTCPDecrSeq = curr_flow.sfd.ToTCPDecrSeq;
            curr_flow.sfd_rate.FromTCPDecrSeq =
               curr_flow.sfd.FromTCPDecrSeq - flow[fi].sfd.FromTCPDecrSeq;
            flow[fi].sfd.FromTCPDecrSeq = curr_flow.sfd.FromTCPDecrSeq;

            if (out_set.delta_bits != 0) {
                     compute_deltas(curr_flow.dist_rate,
                        curr_flow.dist, flow[fi].dist);
#if 0
               ld = flow[fi].dist;
               for (i = 0; i != out_set.n_attr; ++i) {
                  a = out_set.format[i];
                  if (DELTA_ATTRIB(a)) {
                     compute_deltas(curr_flow.dist_rate,
                        curr_flow.dist, flow[fi].dist);
                     }
		  }
#endif
	       }
#endif  /* NEW_ATR */

            if (!first_sample) {  /* Write the values */
#if DEBUG
            fprintf(output,">> ");
#endif
	       for (i = 0; i != out_set.n_attr; ++i) {
	          a = out_set.format[i];
                  x = attr_ix[a];  /* Index into attribs[] */
	          put_value(output, a, attribs[x].value);
                  if (i != out_set.n_attr-1
                        || strcmp(out_set.separator[i]," ") != 0)
                     fprintf(output,"%s",out_set.separator[i]);
	          }
	       fprintf(output, "\n");
	       }
            }
         }
      /*    fflush(stdout);  /* !!!! */
      }
   exit(0);
   }

int check_formats(struct flow_stats *in, struct flow_stats *out)
{
   return 1;
   }
