static struct receiver *cutBackTo(struct receiver **rcvrs, u_int32_t size, u_int32_t max) {
  struct receiver *r, *tmp;
  int i=0;
  int count;

  if(size < max) //return the original table
    return *rcvrs;

  count = size - max;

  HASH_ITER(hh, *rcvrs, r, tmp) {
    if(i++ == count)
      return r;
    HASH_DEL(*rcvrs, r);
    free(r);
  }

  return(NULL);

}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int32_t max_num_packets_per_flow, max_packet_payload_dissection, max_num_reported_top_payloads;

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;


static void debug_printf(u_int32_t protocol, void *id_struct,
			 ndpi_log_level_t log_level,
			 const char *format, ...) {
  va_list va_ap;
  struct tm result;

  if(log_level <= nDPI_LogLevel) {
    char buf[8192], out_buf[8192];
    char theDate[32];
    const char *extra_msg = "";
    time_t theTime = time(NULL);

    va_start (va_ap, format);

    if(log_level == NDPI_LOG_ERROR)
      extra_msg = "ERROR: ";
    else if(log_level == NDPI_LOG_TRACE)
      extra_msg = "TRACE: ";
    else
      extra_msg = "DEBUG: ";

    memset(buf, 0, sizeof(buf));
    strftime(theDate, 32, "%d/%b/%Y %H:%M:%S", localtime_r(&theTime,&result));
    vsnprintf(buf, sizeof(buf)-1, format, va_ap);

    snprintf(out_buf, sizeof(out_buf), "%s %s%s", theDate, extra_msg, buf);
    printf("%s", out_buf);
    fflush(stdout);
  }

  va_end(va_ap);
}

Added fix to avoid potential heap buffer overflow in H.323 dissector
Modified HTTP report information to make it closer to the HTTP field names

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int32_t max_num_packets_per_flow, max_packet_payload_dissection, max_num_reported_top_payloads;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}


void init_h323_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask)
{
  ndpi_set_bitmask_protocol_detection("H323", ndpi_struct, detection_bitmask, *id,
				      NDPI_PROTOCOL_H323,
				      ndpi_search_h323,
				      NDPI_SELECTION_BITMASK_PROTOCOL_TCP_OR_UDP_WITH_PAYLOAD,
				      SAVE_DETECTION_BITMASK_AS_UNKNOWN,
				      ADD_TO_DETECTION_BITMASK);

  *id += 1;
}

#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_H323

struct tpkt {
  u_int8_t version, reserved;
  u_int16_t len;
}


static void updatePortStats(struct port_stats **stats, u_int32_t port,
			    u_int32_t addr, u_int8_t version,
                            u_int32_t num_pkts, u_int32_t num_bytes,
                            const char *proto) {

  struct port_stats *s = NULL;
  int count = 0;

  HASH_FIND_INT(*stats, &port, s);
  if(s == NULL) {
    s = (struct port_stats*)calloc(1, sizeof(struct port_stats));
    if(!s) return;

    s->port = port, s->num_pkts = num_pkts, s->num_bytes = num_bytes;
    s->num_addr = 1, s->cumulative_addr = 1; s->num_flows = 1;

    updateTopIpAddress(addr, version, proto, 1, s->top_ip_addrs, MAX_NUM_IP_ADDRESS);

    s->addr_tree = (addr_node *) malloc(sizeof(addr_node));
    if(!s->addr_tree) {
      free(s);
      return;
    }

    s->addr_tree->addr = addr;
    s->addr_tree->version = version;
    strncpy(s->addr_tree->proto, proto, sizeof(s->addr_tree->proto));
    s->addr_tree->count = 1;
    s->addr_tree->left = NULL;
    s->addr_tree->right = NULL;

    HASH_ADD_INT(*stats, port, s);
  }
  else{
    count = updateIpTree(addr, version, &(*s).addr_tree, proto);

    if(count == UPDATED_TREE) s->num_addr++;

    if(count) {
      s->cumulative_addr++;
      updateTopIpAddress(addr, version, proto, count, s->top_ip_addrs, MAX_NUM_IP_ADDRESS);
    }

    s->num_pkts += num_pkts, s->num_bytes += num_bytes, s->num_flows++;
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0;

u_int8_t verbose = 0, enable_joy_stats = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

u_int8_t human_readeable_string_len = 5;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int32_t max_num_packets_per_flow, max_packet_payload_dissection, max_num_reported_top_payloads;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}


flowGetBDMeanandVariance(struct ndpi_flow_info* flow) {
  FILE *out = results_file ? results_file : stdout;
  const uint32_t *array = NULL;
  uint32_t tmp[256], i;
  unsigned int num_bytes;
  double mean = 0.0, variance = 0.0;
  struct ndpi_entropy last_entropy = flow->last_entropy;

  fflush(out);

  /*
   * Sum up the byte_count array for outbound and inbound flows,
   * if this flow is bidirectional
   */
  if (!flow->bidirectional) {
    array = last_entropy.src2dst_byte_count;
    num_bytes = last_entropy.src2dst_l4_bytes;
    for (i=0; i<256; i++) {
      tmp[i] = last_entropy.src2dst_byte_count[i];
    }

    if (last_entropy.src2dst_num_bytes != 0) {
      mean = last_entropy.src2dst_bd_mean;
      variance = last_entropy.src2dst_bd_variance/(last_entropy.src2dst_num_bytes - 1);
      variance = sqrt(variance);

      if (last_entropy.src2dst_num_bytes == 1) {
        variance = 0.0;
      }
    }
  } else {
    for (i=0; i<256; i++) {
      tmp[i] = last_entropy.src2dst_byte_count[i] + last_entropy.dst2src_byte_count[i];
    }
    array = tmp;
    num_bytes = last_entropy.src2dst_l4_bytes + last_entropy.dst2src_l4_bytes;

    if (last_entropy.src2dst_num_bytes + last_entropy.dst2src_num_bytes != 0) {
      mean = ((double)last_entropy.src2dst_num_bytes)/((double)(last_entropy.src2dst_num_bytes+last_entropy.dst2src_num_bytes))*last_entropy.src2dst_bd_mean +
             ((double)last_entropy.dst2src_num_bytes)/((double)(last_entropy.dst2src_num_bytes+last_entropy.src2dst_num_bytes))*last_entropy.dst2src_bd_mean;

      variance = ((double)last_entropy.src2dst_num_bytes)/((double)(last_entropy.src2dst_num_bytes+last_entropy.dst2src_num_bytes))*last_entropy.src2dst_bd_variance +
                 ((double)last_entropy.dst2src_num_bytes)/((double)(last_entropy.dst2src_num_bytes+last_entropy.src2dst_num_bytes))*last_entropy.dst2src_bd_variance;

      variance = variance/((double)(last_entropy.src2dst_num_bytes + last_entropy.dst2src_num_bytes - 1));
      variance = sqrt(variance);
      if (last_entropy.src2dst_num_bytes + last_entropy.dst2src_num_bytes == 1) {
        variance = 0.0;
      }
    }
  }

  if(enable_joy_stats) {
#if 0
    if(verbose > 1) {
      reduceBDbits(tmp, 256);
      array = tmp;

      fprintf(out, " [byte_dist: ");
      for(i = 0; i < 255; i++)
	fprintf(out, "%u,", (unsigned char)array[i]);

      fprintf(out, "%u]", (unsigned char)array[i]);
    }
#endif

    /* Output the mean */
    if(num_bytes != 0) {
      double entropy = ndpi_flow_get_byte_count_entropy(array, num_bytes);

      if(csv_fp) {
	fprintf(csv_fp, ",%.3f,%.3f,%.3f,%.3f", mean, variance, entropy, entropy * num_bytes);
      } else {
	fprintf(out, "[byte_dist_mean: %f", mean);
	fprintf(out, "][byte_dist_std: %f]", variance);
	fprintf(out, "[entropy: %f]", entropy);
	fprintf(out, "[total_entropy: %f]", entropy * num_bytes);
      }
    } else {
      if(csv_fp)
	fprintf(csv_fp, ",%.3f,%.3f,%.3f,%.3f", 0.0, 0.0, 0.0, 0.0);
    }
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

u_int8_t verbose = 0, enable_joy_stats = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static pcap_dumper_t *extcap_dumper = NULL;

static char extcap_buf[16384];


void serializerUnitTest() {
  ndpi_serializer serializer, deserializer;
  int i;
  u_int8_t trace = 0;

  assert(ndpi_init_serializer(&serializer, ndpi_serialization_format_tlv) != -1);

  for(i=0; i<16; i++) {
    char kbuf[32], vbuf[32];
    assert(ndpi_serialize_uint32_uint32(&serializer, i, i*i) != -1);

    snprintf(kbuf, sizeof(kbuf), "Hello %d", i);
    snprintf(vbuf, sizeof(vbuf), "World %d", i);
    assert(ndpi_serialize_uint32_string(&serializer, i, "Hello") != -1);
    assert(ndpi_serialize_string_string(&serializer, kbuf, vbuf) != -1);
    assert(ndpi_serialize_string_uint32(&serializer, kbuf, i*i) != -1);
    assert(ndpi_serialize_string_float(&serializer,  kbuf, (float)(i*i), "%f") != -1);
  }

  if(trace)
    printf("Serialization size: %u\n", ndpi_serializer_get_buffer_len(&serializer));

  assert(ndpi_init_deserializer(&deserializer, &serializer) != -1);

  while(1) {
    ndpi_serialization_type kt, et;
    et = ndpi_deserialize_get_item_type(&deserializer, &kt);

    if(et == ndpi_serialization_unknown)
      break;
    else {
      u_int32_t k32, v32;
      ndpi_string ks, vs;
      float vf;

      switch(kt) {
        case ndpi_serialization_uint32:
          ndpi_deserialize_key_uint32(&deserializer, &k32);
	  if(trace) printf("%u=", k32);
          break;
        case ndpi_serialization_string:
          ndpi_deserialize_key_string(&deserializer, &ks);
          if (trace) {
	    u_int8_t bkp = ks.str[ks.str_len];
	    ks.str[ks.str_len] = '\0';
            printf("%s=", ks.str);
	    ks.str[ks.str_len] = bkp;
          }
          break;
        default:
          printf("Unsupported TLV key type %u\n", kt);
          return;
      }

      switch(et) {
      case ndpi_serialization_uint32:
	assert(ndpi_deserialize_value_uint32(&deserializer, &v32) != -1);
	if(trace) printf("%u\n", v32);
	break;

      case ndpi_serialization_string:
	assert(ndpi_deserialize_value_string(&deserializer, &vs) != -1);
	if(trace) {
	  u_int8_t bkp = vs.str[vs.str_len];
	  vs.str[vs.str_len] = '\0';
	  printf("%s\n", vs.str);
	  vs.str[vs.str_len] = bkp;
	}
	break;

      case ndpi_serialization_float:
	assert(ndpi_deserialize_value_float(&deserializer, &vf) != -1);
	if(trace) printf("%f\n", vf);
	break;

      default:
	if (trace) printf("\n");
        printf("serializerUnitTest: unsupported type %u detected!\n", et);
        return;
	break;
      }
    }

    ndpi_deserialize_next(&deserializer);
  }

  ndpi_term_serializer(&serializer);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0;

u_int8_t verbose = 0, enable_joy_stats = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

u_int8_t human_readeable_string_len = 5;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static int core_affinity[MAX_NUM_READER_THREADS];

static u_int32_t num_flows;

extern u_int32_t max_num_packets_per_flow, max_packet_payload_dissection, max_num_reported_top_payloads;

extern void ndpi_self_check_host_match();

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static char extcap_buf[16384];

static char *extcap_capture_fifo    = NULL;


static void terminateDetection(u_int16_t thread_id) {
  ndpi_workflow_free(ndpi_thread_info[thread_id].workflow);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int16_t min_pattern_len, max_pattern_len;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static pcap_dumper_t *extcap_dumper = NULL;

static char extcap_buf[16384];

static char *extcap_capture_fifo    = NULL;

static u_int16_t extcap_packet_filter = (u_int16_t)-1;

struct reader_thread {
  struct ndpi_workflow *workflow;
  pthread_t pthread;
  u_int64_t last_idle_scan_time;
  u_int32_t idle_scan_idx;
  u_int32_t num_idle_flows;
  struct ndpi_flow_info *idle_flows[IDLE_SCAN_BUDGET];
}

static struct reader_thread ndpi_thread_info[MAX_NUM_READER_THREADS];

static int dpdk_port_id = 0, dpdk_run_capture = 1;

void test_lib();

extern void ndpi_report_payload_stats();

static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle);


static void printFlowsStats() {
  int thread_id;
  u_int32_t total_flows = 0;
  FILE *out = results_file ? results_file : stdout;

  if(enable_payload_analyzer)
    ndpi_report_payload_stats();

  for(thread_id = 0; thread_id < num_threads; thread_id++)
    total_flows += ndpi_thread_info[thread_id].workflow->num_allocated_flows;

  if((all_flows = (struct flow_info*)malloc(sizeof(struct flow_info)*total_flows)) == NULL) {
    fprintf(out, "Fatal error: not enough memory\n");
    exit(-1);
  }

  if(verbose) {
    ndpi_host_ja3_fingerprints *ja3ByHostsHashT = NULL; // outer hash table
    ndpi_ja3_fingerprints_host *hostByJA3C_ht = NULL;   // for client
    ndpi_ja3_fingerprints_host *hostByJA3S_ht = NULL;   // for server
    int i;
    ndpi_host_ja3_fingerprints *ja3ByHost_element = NULL;
    ndpi_ja3_info *info_of_element = NULL;
    ndpi_host_ja3_fingerprints *tmp = NULL;
    ndpi_ja3_info *tmp2 = NULL;
    unsigned int num_ja3_client;
    unsigned int num_ja3_server;

    fprintf(out, "\n");

    num_flows = 0;
    for(thread_id = 0; thread_id < num_threads; thread_id++) {
      for(i=0; i<NUM_ROOTS; i++)
	ndpi_twalk(ndpi_thread_info[thread_id].workflow->ndpi_flows_root[i],
		   node_print_known_proto_walker, &thread_id);
    }

    if((verbose == 2) || (verbose == 3)) {
      for(i = 0; i < num_flows; i++) {
	ndpi_host_ja3_fingerprints *ja3ByHostFound = NULL;
	ndpi_ja3_fingerprints_host *hostByJA3Found = NULL;

	//check if this is a ssh-ssl flow
	if(all_flows[i].flow->ssh_tls.ja3_client[0] != '\0'){
	  //looking if the host is already in the hash table
	  HASH_FIND_INT(ja3ByHostsHashT, &(all_flows[i].flow->src_ip), ja3ByHostFound);

	  //host ip -> ja3
	  if(ja3ByHostFound == NULL){
	    //adding the new host
	    ndpi_host_ja3_fingerprints *newHost = malloc(sizeof(ndpi_host_ja3_fingerprints));
	    newHost->host_client_info_hasht = NULL;
	    newHost->host_server_info_hasht = NULL;
	    newHost->ip_string = all_flows[i].flow->src_name;
	    newHost->ip = all_flows[i].flow->src_ip;
	    newHost->dns_name = all_flows[i].flow->ssh_tls.client_requested_server_name;

	    ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info));
	    newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_client;
	    newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.client_unsafe_cipher;
	    //adding the new ja3 fingerprint
	    HASH_ADD_KEYPTR(hh, newHost->host_client_info_hasht,
			    newJA3->ja3, strlen(newJA3->ja3), newJA3);
	    //adding the new host
	    HASH_ADD_INT(ja3ByHostsHashT, ip, newHost);
	  } else {
	    //host already in the hash table
	    ndpi_ja3_info *infoFound = NULL;

	    HASH_FIND_STR(ja3ByHostFound->host_client_info_hasht,
			  all_flows[i].flow->ssh_tls.ja3_client, infoFound);

	    if(infoFound == NULL){
	      ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info));
	      newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_client;
	      newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.client_unsafe_cipher;
	      HASH_ADD_KEYPTR(hh, ja3ByHostFound->host_client_info_hasht,
			      newJA3->ja3, strlen(newJA3->ja3), newJA3);
	    }
	  }

	  //ja3 -> host ip
	  HASH_FIND_STR(hostByJA3C_ht, all_flows[i].flow->ssh_tls.ja3_client, hostByJA3Found);
	  if(hostByJA3Found == NULL){
	    ndpi_ip_dns *newHost = malloc(sizeof(ndpi_ip_dns));

	    newHost->ip = all_flows[i].flow->src_ip;
	    newHost->ip_string = all_flows[i].flow->src_name;
	    newHost->dns_name = all_flows[i].flow->ssh_tls.client_requested_server_name;;

	    ndpi_ja3_fingerprints_host *newElement = malloc(sizeof(ndpi_ja3_fingerprints_host));
	    newElement->ja3 = all_flows[i].flow->ssh_tls.ja3_client;
	    newElement->unsafe_cipher = all_flows[i].flow->ssh_tls.client_unsafe_cipher;
	    newElement->ipToDNS_ht = NULL;

	    HASH_ADD_INT(newElement->ipToDNS_ht, ip, newHost);
	    HASH_ADD_KEYPTR(hh, hostByJA3C_ht, newElement->ja3, strlen(newElement->ja3),
			    newElement);
	  } else {
	    ndpi_ip_dns *innerElement = NULL;
	    HASH_FIND_INT(hostByJA3Found->ipToDNS_ht, &(all_flows[i].flow->src_ip), innerElement);
	    if(innerElement == NULL){
	      ndpi_ip_dns *newInnerElement = malloc(sizeof(ndpi_ip_dns));
	      newInnerElement->ip = all_flows[i].flow->src_ip;
	      newInnerElement->ip_string = all_flows[i].flow->src_name;
	      newInnerElement->dns_name = all_flows[i].flow->ssh_tls.client_requested_server_name;
	      HASH_ADD_INT(hostByJA3Found->ipToDNS_ht, ip, newInnerElement);
	    }
	  }
	}

	if(all_flows[i].flow->ssh_tls.ja3_server[0] != '\0'){
	  //looking if the host is already in the hash table
	  HASH_FIND_INT(ja3ByHostsHashT, &(all_flows[i].flow->dst_ip), ja3ByHostFound);
	  if(ja3ByHostFound == NULL){
	    //adding the new host in the hash table
	    ndpi_host_ja3_fingerprints *newHost = malloc(sizeof(ndpi_host_ja3_fingerprints));
	    newHost->host_client_info_hasht = NULL;
	    newHost->host_server_info_hasht = NULL;
	    newHost->ip_string = all_flows[i].flow->dst_name;
	    newHost->ip = all_flows[i].flow->dst_ip;
	    newHost->dns_name = all_flows[i].flow->ssh_tls.server_info;

	    ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info));
	    newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_server;
	    newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.server_unsafe_cipher;
	    //adding the new ja3 fingerprint
	    HASH_ADD_KEYPTR(hh, newHost->host_server_info_hasht, newJA3->ja3,
			    strlen(newJA3->ja3), newJA3);
	    //adding the new host
	    HASH_ADD_INT(ja3ByHostsHashT, ip, newHost);
	  } else {
	    //host already in the hashtable
	    ndpi_ja3_info *infoFound = NULL;
	    HASH_FIND_STR(ja3ByHostFound->host_server_info_hasht,
			  all_flows[i].flow->ssh_tls.ja3_server, infoFound);
	    if(infoFound == NULL){
	      ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info));
	      newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_server;
	      newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.server_unsafe_cipher;
	      HASH_ADD_KEYPTR(hh, ja3ByHostFound->host_server_info_hasht,
			      newJA3->ja3, strlen(newJA3->ja3), newJA3);
	    }
	  }

	  HASH_FIND_STR(hostByJA3S_ht, all_flows[i].flow->ssh_tls.ja3_server, hostByJA3Found);
	  if(hostByJA3Found == NULL){
	    ndpi_ip_dns *newHost = malloc(sizeof(ndpi_ip_dns));

	    newHost->ip = all_flows[i].flow->dst_ip;
	    newHost->ip_string = all_flows[i].flow->dst_name;
	    newHost->dns_name = all_flows[i].flow->ssh_tls.server_info;;

	    ndpi_ja3_fingerprints_host *newElement = malloc(sizeof(ndpi_ja3_fingerprints_host));
	    newElement->ja3 = all_flows[i].flow->ssh_tls.ja3_server;
	    newElement->unsafe_cipher = all_flows[i].flow->ssh_tls.server_unsafe_cipher;
	    newElement->ipToDNS_ht = NULL;

	    HASH_ADD_INT(newElement->ipToDNS_ht, ip, newHost);
	    HASH_ADD_KEYPTR(hh, hostByJA3S_ht, newElement->ja3, strlen(newElement->ja3),
			    newElement);
	  } else {
	    ndpi_ip_dns *innerElement = NULL;

	    HASH_FIND_INT(hostByJA3Found->ipToDNS_ht, &(all_flows[i].flow->dst_ip), innerElement);
	    if(innerElement == NULL){
	      ndpi_ip_dns *newInnerElement = malloc(sizeof(ndpi_ip_dns));
	      newInnerElement->ip = all_flows[i].flow->dst_ip;
	      newInnerElement->ip_string = all_flows[i].flow->dst_name;
	      newInnerElement->dns_name = all_flows[i].flow->ssh_tls.server_info;
	      HASH_ADD_INT(hostByJA3Found->ipToDNS_ht, ip, newInnerElement);
	    }
	  }

	}
      }

      if(ja3ByHostsHashT) {
	ndpi_ja3_fingerprints_host *hostByJA3Element = NULL;
	ndpi_ja3_fingerprints_host *tmp3 = NULL;
	ndpi_ip_dns *innerHashEl = NULL;
	ndpi_ip_dns *tmp4 = NULL;

	if(verbose == 2) {
	  /* for each host the number of flow with a ja3 fingerprint is printed */
	  i = 1;

	  fprintf(out, "JA3 Host Stats: \n");
	  fprintf(out, "\t\t IP %-24s \t %-10s \n", "Address", "# JA3C");

	  for(ja3ByHost_element = ja3ByHostsHashT; ja3ByHost_element != NULL;
	      ja3ByHost_element = ja3ByHost_element->hh.next) {
	    num_ja3_client = HASH_COUNT(ja3ByHost_element->host_client_info_hasht);
	    num_ja3_server = HASH_COUNT(ja3ByHost_element->host_server_info_hasht);

	    if(num_ja3_client > 0) {
	      fprintf(out, "\t%d\t %-24s \t %-7u\n",
		      i,
		      ja3ByHost_element->ip_string,
		      num_ja3_client
		      );
	      i++;
	    }

	  }
	} else if(verbose == 3) {
	  int i = 1;
	  int againstRepeat;
	  ndpi_ja3_fingerprints_host *hostByJA3Element = NULL;
	  ndpi_ja3_fingerprints_host *tmp3 = NULL;
	  ndpi_ip_dns *innerHashEl = NULL;
	  ndpi_ip_dns *tmp4 = NULL;

	  //for each host it is printted the JA3C and JA3S, along the server name (if any)
	  //and the security status

	  fprintf(out, "JA3C/JA3S Host Stats: \n");
	  fprintf(out, "\t%-7s %-24s %-34s %s\n", "", "IP", "JA3C", "JA3S");

	  //reminder
	  //ja3ByHostsHashT: hash table <ip, (ja3, ht_client, ht_server)>
	  //ja3ByHost_element: element of ja3ByHostsHashT
	  //info_of_element: element of the inner hash table of ja3ByHost_element
	  HASH_ITER(hh, ja3ByHostsHashT, ja3ByHost_element, tmp) {
	    num_ja3_client = HASH_COUNT(ja3ByHost_element->host_client_info_hasht);
	    num_ja3_server = HASH_COUNT(ja3ByHost_element->host_server_info_hasht);
	    againstRepeat = 0;
	    if(num_ja3_client > 0) {
	      HASH_ITER(hh, ja3ByHost_element->host_client_info_hasht, info_of_element, tmp2) {
		fprintf(out, "\t%-7d %-24s %s %s\n",
			i,
			ja3ByHost_element->ip_string,
			info_of_element->ja3,
			print_cipher(info_of_element->unsafe_cipher)
			);
		againstRepeat = 1;
		i++;
	      }
	    }

	    if(num_ja3_server > 0) {
	      HASH_ITER(hh, ja3ByHost_element->host_server_info_hasht, info_of_element, tmp2) {
		fprintf(out, "\t%-7d %-24s %-34s %s %s %s%s%s\n",
			i,
			ja3ByHost_element->ip_string,
			"",
			info_of_element->ja3,
			print_cipher(info_of_element->unsafe_cipher),
			ja3ByHost_element->dns_name[0] ? "[" : "",
			ja3ByHost_element->dns_name,
			ja3ByHost_element->dns_name[0] ? "]" : ""
			);
		i++;
	      }
	    }
	  }

	  i = 1;

	  fprintf(out, "\nIP/JA3 Distribution:\n");
	  fprintf(out, "%-15s %-39s %-26s\n", "", "JA3", "IP");
	  HASH_ITER(hh, hostByJA3C_ht, hostByJA3Element, tmp3) {
	    againstRepeat = 0;
	    HASH_ITER(hh, hostByJA3Element->ipToDNS_ht, innerHashEl, tmp4) {
	      if(againstRepeat == 0) {
		fprintf(out, "\t%-7d JA3C %s",
			i,
			hostByJA3Element->ja3
			);
		fprintf(out, "   %-15s %s\n",
			innerHashEl->ip_string,
			print_cipher(hostByJA3Element->unsafe_cipher)
			);
		againstRepeat = 1;
		i++;
	      } else {
		fprintf(out, "\t%45s", "");
		fprintf(out, "   %-15s %s\n",
			innerHashEl->ip_string,
			print_cipher(hostByJA3Element->unsafe_cipher)
			);
	      }
	    }
	  }
	  HASH_ITER(hh, hostByJA3S_ht, hostByJA3Element, tmp3) {
	    againstRepeat = 0;
	    HASH_ITER(hh, hostByJA3Element->ipToDNS_ht, innerHashEl, tmp4) {
	      if(againstRepeat == 0) {
		fprintf(out, "\t%-7d JA3S %s",
			i,
			hostByJA3Element->ja3
			);
		fprintf(out, "   %-15s %-10s %s%s%s\n",
			innerHashEl->ip_string,
			print_cipher(hostByJA3Element->unsafe_cipher),
			innerHashEl->dns_name[0] ? "[" : "",
			innerHashEl->dns_name,
			innerHashEl->dns_name[0] ? "]" : ""
			);
		againstRepeat = 1;
		i++;
	      } else {
		fprintf(out, "\t%45s", "");
		fprintf(out, "   %-15s %-10s %s%s%s\n",
			innerHashEl->ip_string,
			print_cipher(hostByJA3Element->unsafe_cipher),
			innerHashEl->dns_name[0] ? "[" : "",
			innerHashEl->dns_name,
			innerHashEl->dns_name[0] ? "]" : ""
			);
	      }
	    }
	  }
	}
	fprintf(out, "\n\n");

	//freeing the hash table
	HASH_ITER(hh, ja3ByHostsHashT, ja3ByHost_element, tmp) {
	  HASH_ITER(hh, ja3ByHost_element->host_client_info_hasht, info_of_element, tmp2) {
	    if(ja3ByHost_element->host_client_info_hasht)
	      HASH_DEL(ja3ByHost_element->host_client_info_hasht, info_of_element);
	    free(info_of_element);
	  }
	  HASH_ITER(hh, ja3ByHost_element->host_server_info_hasht, info_of_element, tmp2) {
	    if(ja3ByHost_element->host_server_info_hasht)
	      HASH_DEL(ja3ByHost_element->host_server_info_hasht, info_of_element);
	    free(info_of_element);
	  }
	  HASH_DEL(ja3ByHostsHashT, ja3ByHost_element);
	  free(ja3ByHost_element);
	}

	HASH_ITER(hh, hostByJA3C_ht, hostByJA3Element, tmp3) {
	  HASH_ITER(hh, hostByJA3C_ht->ipToDNS_ht, innerHashEl, tmp4) {
	    if(hostByJA3Element->ipToDNS_ht)
	      HASH_DEL(hostByJA3Element->ipToDNS_ht, innerHashEl);
	    free(innerHashEl);
	  }
	  HASH_DEL(hostByJA3C_ht, hostByJA3Element);
	  free(hostByJA3Element);
	}

	hostByJA3Element = NULL;
	HASH_ITER(hh, hostByJA3S_ht, hostByJA3Element, tmp3) {
	  HASH_ITER(hh, hostByJA3S_ht->ipToDNS_ht, innerHashEl, tmp4) {
	    if(hostByJA3Element->ipToDNS_ht)
	      HASH_DEL(hostByJA3Element->ipToDNS_ht, innerHashEl);
	    free(innerHashEl);
	  }
	  HASH_DEL(hostByJA3S_ht, hostByJA3Element);
	  free(hostByJA3Element);
	}
      }
    }

    /* Print all flows stats */

    qsort(all_flows, num_flows, sizeof(struct flow_info), cmpFlows);

    if(verbose > 1) {
      for(i=0; i<num_flows; i++)
	printFlow(i+1, all_flows[i].flow, all_flows[i].thread_id);
    }

    for(thread_id = 0; thread_id < num_threads; thread_id++) {
      if(ndpi_thread_info[thread_id].workflow->stats.protocol_counter[0 /* 0 = Unknown */] > 0) {
	fprintf(out, "\n\nUndetected flows:%s\n",
		undetected_flows_deleted ? " (expired flows are not listed below)" : "");
	break;
      }
    }

    num_flows = 0;
    for(thread_id = 0; thread_id < num_threads; thread_id++) {
      if(ndpi_thread_info[thread_id].workflow->stats.protocol_counter[0] > 0) {
	for(i=0; i<NUM_ROOTS; i++)
	  ndpi_twalk(ndpi_thread_info[thread_id].workflow->ndpi_flows_root[i],
		     node_print_unknown_proto_walker, &thread_id);
      }
    }

    qsort(all_flows, num_flows, sizeof(struct flow_info), cmpFlows);

    for(i=0; i<num_flows; i++)
      printFlow(i+1, all_flows[i].flow, all_flows[i].thread_id);

  } else if(csv_fp != NULL) {
    int i;

    num_flows = 0;
    for(thread_id = 0; thread_id < num_threads; thread_id++) {
      for(i=0; i<NUM_ROOTS; i++)
	ndpi_twalk(ndpi_thread_info[thread_id].workflow->ndpi_flows_root[i],
		   node_print_known_proto_walker, &thread_id);
    }

    for(i=0; i<num_flows; i++)
	printFlow(i+1, all_flows[i].flow, all_flows[i].thread_id);
  }

  free(all_flows);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0;

u_int8_t verbose = 0, enable_joy_stats = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int32_t max_num_packets_per_flow, max_packet_payload_dissection, max_num_reported_top_payloads;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}


static int receivers_sort_asc(void *_a, void *_b) {
  struct receiver *a = (struct receiver *)_a;
  struct receiver *b = (struct receiver *)_b;

  return(a->num_pkts - b->num_pkts);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

int nDPI_LogLevel = 0;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;


char* formatTraffic(float numBits, int bits, char *buf) {
  char unit;

  if(bits)
    unit = 'b';
  else
    unit = 'B';

  if(numBits < 1024) {
    snprintf(buf, 32, "%lu %c", (unsigned long)numBits, unit);
  } else if(numBits < (1024*1024)) {
    snprintf(buf, 32, "%.2f K%c", (float)(numBits)/1024, unit);
  } else {
    float tmpMBits = ((float)numBits)/(1024*1024);

    if(tmpMBits < 1024) {
      snprintf(buf, 32, "%.2f M%c", tmpMBits, unit);
    } else {
      tmpMBits /= 1024;

      if(tmpMBits < 1024) {
	snprintf(buf, 32, "%.2f G%c", tmpMBits, unit);
      } else {
	snprintf(buf, 32, "%.2f T%c", (float)(tmpMBits)/1024, unit);
      }
    }
  }

  return(buf);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

static int core_affinity[MAX_NUM_READER_THREADS];

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static char extcap_buf[16384];

static char *extcap_capture_fifo    = NULL;

static int dpdk_port_id = 0, dpdk_run_capture = 1;

struct ndpi_proto_sorter {
  int id;
  char name[16];
}

struct timezone {
    int tz_minuteswest; /* minutes W of Greenwich */
    int tz_dsttime;     /* type of dst correction */
  }


static char* ipProto2Name(u_int16_t proto_id) {
  static char proto[8];

  switch(proto_id) {
  case IPPROTO_TCP:
    return("TCP");
    break;
  case IPPROTO_UDP:
    return("UDP");
    break;
  case IPPROTO_ICMP:
    return("ICMP");
    break;
  case IPPROTO_ICMPV6:
    return("ICMPV6");
    break;
  case 112:
    return("VRRP");
    break;
  case IPPROTO_IGMP:
    return("IGMP");
    break;
  }

  snprintf(proto, sizeof(proto), "%u", proto_id);
  return(proto);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

char *_debug_protocols = NULL;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int16_t min_pattern_len, max_pattern_len;

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static pcap_dumper_t *extcap_dumper = NULL;

static char extcap_buf[16384];

static char *extcap_capture_fifo    = NULL;

static u_int16_t extcap_packet_filter = (u_int16_t)-1;

static struct reader_thread ndpi_thread_info[MAX_NUM_READER_THREADS];

static int dpdk_port_id = 0, dpdk_run_capture = 1;

static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle);


int cmpFlows(const void *_a, const void *_b) {
  struct ndpi_flow_info *fa = ((struct flow_info*)_a)->flow;
  struct ndpi_flow_info *fb = ((struct flow_info*)_b)->flow;
  uint64_t a_size = fa->src2dst_bytes + fa->dst2src_bytes;
  uint64_t b_size = fb->src2dst_bytes + fb->dst2src_bytes;
  if(a_size != b_size)
    return a_size < b_size ? 1 : -1;

// copy from ndpi_workflow_node_cmp();

  if(fa->ip_version < fb->ip_version ) return(-1); else { if(fa->ip_version > fb->ip_version ) return(1); }
  if(fa->protocol   < fb->protocol   ) return(-1); else { if(fa->protocol   > fb->protocol   ) return(1); }
  if(htonl(fa->src_ip)   < htonl(fb->src_ip)  ) return(-1); else { if(htonl(fa->src_ip)   > htonl(fb->src_ip)  ) return(1); }
  if(htons(fa->src_port) < htons(fb->src_port)) return(-1); else { if(htons(fa->src_port) > htons(fb->src_port)) return(1); }
  if(htonl(fa->dst_ip)   < htonl(fb->dst_ip)  ) return(-1); else { if(htonl(fa->dst_ip)   > htonl(fb->dst_ip)  ) return(1); }
  if(htons(fa->dst_port) < htons(fb->dst_port)) return(-1); else { if(htons(fa->dst_port) > htons(fb->dst_port)) return(1); }
  return(0);
}

int nDPI_LogLevel = 0;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

struct reader_thread {
  struct ndpi_workflow *workflow;
  pthread_t pthread;
  u_int64_t last_idle_scan_time;
  u_int32_t idle_scan_idx;
  u_int32_t num_idle_flows;
  struct ndpi_flow_info *idle_flows[IDLE_SCAN_BUDGET];
}

static struct reader_thread ndpi_thread_info[MAX_NUM_READER_THREADS];

typedef struct ndpi_id {
  u_int8_t ip[4];		   // Ip address
  struct ndpi_id_struct *ndpi_id;  // nDpi worker structure
} ndpi_id_t;

static int dpdk_port_id = 0, dpdk_run_capture = 1;

void test_lib();

extern void ndpi_report_payload_stats();

static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle);


static void port_stats_walker(const void *node, ndpi_VISIT which, int depth, void *user_data) {
  if((which == ndpi_preorder) || (which == ndpi_leaf)) { /* Avoid walking the same node multiple times */
    struct ndpi_flow_info *flow = *(struct ndpi_flow_info **) node;
    u_int16_t thread_id = *(int *)user_data;
    u_int16_t sport, dport;
    char proto[16];
    int r;

    sport = ntohs(flow->src_port), dport = ntohs(flow->dst_port);

    /* get app level protocol */
    if(flow->detected_protocol.master_protocol)
      ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct,
			 flow->detected_protocol, proto, sizeof(proto));
    else
      strncpy(proto, ndpi_get_proto_name(ndpi_thread_info[thread_id].workflow->ndpi_struct,
					 flow->detected_protocol.app_protocol),sizeof(proto));

    if(((r = strcmp(ipProto2Name(flow->protocol), "TCP")) == 0)
       && (flow->src2dst_packets == 1) && (flow->dst2src_packets == 0)) {
      updateScanners(&scannerHosts, flow->src_ip, flow->ip_version, dport);
    }

    updateReceivers(&receivers, flow->dst_ip, flow->ip_version,
                    flow->src2dst_packets, &topReceivers);

    updatePortStats(&srcStats, sport, flow->src_ip, flow->ip_version,
                    flow->src2dst_packets, flow->src2dst_bytes, proto);

    updatePortStats(&dstStats, dport, flow->dst_ip, flow->ip_version,
                    flow->dst2src_packets, flow->dst2src_bytes, proto);
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int16_t min_pattern_len, max_pattern_len;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;


static void node_proto_guess_walker(const void *node, ndpi_VISIT which, int depth, void *user_data) {
  struct ndpi_flow_info *flow = *(struct ndpi_flow_info **) node;
  u_int16_t thread_id = *((u_int16_t *) user_data), proto;

  if((which == ndpi_preorder) || (which == ndpi_leaf)) { /* Avoid walking the same node multiple times */
    if((!flow->detection_completed) && flow->ndpi_flow) {
      u_int8_t proto_guessed;

      flow->detected_protocol = ndpi_detection_giveup(ndpi_thread_info[0].workflow->ndpi_struct,
						      flow->ndpi_flow, enable_protocol_guess, &proto_guessed);
    }

    process_ndpi_collected_info(ndpi_thread_info[thread_id].workflow, flow);

    proto = flow->detected_protocol.app_protocol ? flow->detected_protocol.app_protocol : flow->detected_protocol.master_protocol;

    ndpi_thread_info[thread_id].workflow->stats.protocol_counter[proto]       += flow->src2dst_packets + flow->dst2src_packets;
    ndpi_thread_info[thread_id].workflow->stats.protocol_counter_bytes[proto] += flow->src2dst_bytes + flow->dst2src_bytes;
    ndpi_thread_info[thread_id].workflow->stats.protocol_flows[proto]++;
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0;

u_int8_t verbose = 0, enable_joy_stats = 0;

int nDPI_LogLevel = 0;

u_int8_t human_readeable_string_len = 5;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int16_t min_pattern_len, max_pattern_len;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}


void extcap_capture() {
#ifdef DEBUG_TRACE
  if(trace) fprintf(trace, " #### %s #### \n", __FUNCTION__);
#endif

  if((extcap_dumper = pcap_dump_open(pcap_open_dead(DLT_EN10MB, 16384 /* MTU */),
				     extcap_capture_fifo)) == NULL) {
    fprintf(stderr, "Unable to open the pcap dumper on %s", extcap_capture_fifo);

#ifdef DEBUG_TRACE
    if(trace) fprintf(trace, "Unable to open the pcap dumper on %s\n",
		      extcap_capture_fifo);
#endif
    return;
  }

#ifdef DEBUG_TRACE
  if(trace) fprintf(trace, "Starting packet capture [%p]\n", extcap_dumper);
#endif
}

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

char *_debug_protocols = NULL;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct single_flow_info *scannerHosts = NULL;

struct receiver *receivers = NULL, *topReceivers = NULL;

static pcap_dumper_t *extcap_dumper = NULL;

static char *extcap_capture_fifo    = NULL;

void test_lib();

extern void ndpi_report_payload_stats();

FILE *trace = NULL;

static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle);


static void deleteReceivers(struct receiver *rcvrs) {
  struct receiver *current, *tmp;

  HASH_ITER(hh, rcvrs, current, tmp) {
    HASH_DEL(rcvrs, current);
    free(current);
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;


void printPortStats(struct port_stats *stats) {
  struct port_stats *s, *tmp;
  char addr_name[48];
  int i = 0, j = 0;

  HASH_ITER(hh, stats, s, tmp) {
    i++;
    printf("\t%2d\tPort %5u\t[%u IP address(es)/%u flows/%u pkts/%u bytes]\n\t\tTop IP Stats:\n",
	   i, s->port, s->num_addr, s->num_flows, s->num_pkts, s->num_bytes);

    qsort(&s->top_ip_addrs[0], MAX_NUM_IP_ADDRESS, sizeof(struct info_pair), info_pair_cmp);

    for(j=0; j<MAX_NUM_IP_ADDRESS; j++) {
      if(s->top_ip_addrs[j].count != 0) {
        if(s->top_ip_addrs[j].version == IPVERSION) {
          inet_ntop(AF_INET, &(s->top_ip_addrs[j].addr), addr_name, sizeof(addr_name));
        } else {
          inet_ntop(AF_INET6, &(s->top_ip_addrs[j].addr),  addr_name, sizeof(addr_name));
        }

	printf("\t\t%-36s ~ %.2f%%\n", addr_name,
	       ((s->top_ip_addrs[j].count) * 100.0) / s->cumulative_addr);
      }
    }

    printf("\n");
    if(i >= 10) break;
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static char extcap_buf[16384];

static char *extcap_capture_fifo    = NULL;


void freeIpTree(addr_node *root) {
  if(root == NULL)
    return;

  freeIpTree(root->left);
  freeIpTree(root->right);
  free(root);
}

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

char *_debug_protocols = NULL;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct single_flow_info *scannerHosts = NULL;

struct receiver *receivers = NULL, *topReceivers = NULL;

static pcap_dumper_t *extcap_dumper = NULL;

static char *extcap_capture_fifo    = NULL;

void test_lib();

extern void ndpi_report_payload_stats();

FILE *trace = NULL;

static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle);


static void mergeTables(struct receiver **primary, struct receiver **secondary) {
  struct receiver *r, *s, *tmp;

  HASH_ITER(hh, *primary, r, tmp) {
    HASH_FIND_INT(*secondary, (int *)&(r->addr), s);
    if(s == NULL){
      s = (struct receiver *)malloc(sizeof(struct receiver));
      if(!s) return;

      s->addr = r->addr;
      s->version = r->version;
      s->num_pkts = r->num_pkts;

      HASH_ADD_INT(*secondary, addr, s);
    }
    else
      s->num_pkts += r->num_pkts;

    HASH_DEL(*primary, r);
    free(r);
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;


static void updateReceivers(struct receiver **rcvrs, u_int32_t dst_addr,
                            u_int8_t version, u_int32_t num_pkts,
                            struct receiver **topRcvrs) {
  struct receiver *r;
  u_int32_t size;
  int a;

  HASH_FIND_INT(*rcvrs, (int *)&dst_addr, r);
  if(r == NULL) {
    if(((size = HASH_COUNT(*rcvrs)) < MAX_TABLE_SIZE_1)
       || ((a = acceptable(num_pkts)) != 0)){
      r = (struct receiver *)malloc(sizeof(struct receiver));
      if(!r) return;

      r->addr = dst_addr;
      r->version = version;
      r->num_pkts = num_pkts;

      HASH_ADD_INT(*rcvrs, addr, r);

      if((size = HASH_COUNT(*rcvrs)) > MAX_TABLE_SIZE_2){

        HASH_SORT(*rcvrs, receivers_sort_asc);
        *rcvrs = cutBackTo(rcvrs, size, MAX_TABLE_SIZE_1);
        mergeTables(rcvrs, topRcvrs);

        if((size = HASH_COUNT(*topRcvrs)) > MAX_TABLE_SIZE_1){
          HASH_SORT(*topRcvrs, receivers_sort_asc);
          *topRcvrs = cutBackTo(topRcvrs, size, MAX_TABLE_SIZE_1);
        }

        *rcvrs = NULL;
      }
    }
  }
  else
    r->num_pkts += num_pkts;
}


static void node_print_known_proto_walker(const void *node,
					  ndpi_VISIT which, int depth, void *user_data) {
  struct ndpi_flow_info *flow = *(struct ndpi_flow_info**)node;
  u_int16_t thread_id = *((u_int16_t*)user_data);

  if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_UNKNOWN)
     && (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN))
    return;

  if((which == ndpi_preorder) || (which == ndpi_leaf)) {
    /* Avoid walking the same node multiple times */
    all_flows[num_flows].thread_id = thread_id, all_flows[num_flows].flow = flow;
    num_flows++;
  }
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

int nDPI_LogLevel = 0;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int16_t min_pattern_len, max_pattern_len;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}

struct single_flow_info {
  u_int32_t saddr; /* key */
  u_int8_t version; /* IP version */
  struct port_flow_info *ports;
  u_int32_t tot_flows;
  UT_hash_handle hh;
}

struct single_flow_info *scannerHosts = NULL;

struct receiver {
  u_int32_t addr; /* key */
  u_int8_t version; /* IP version */
  u_int32_t num_pkts;
  UT_hash_handle hh;
}

struct receiver *receivers = NULL, *topReceivers = NULL;


char* formatPackets(float numPkts, char *buf) {

  if(numPkts < 1000) {
    snprintf(buf, 32, "%.2f", numPkts);
  } else if(numPkts < (1000*1000)) {
    snprintf(buf, 32, "%.2f K", numPkts/1000);
  } else {
    numPkts /= (1000*1000);
    snprintf(buf, 32, "%.2f M", numPkts);
  }

  return(buf);
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

char *_debug_protocols = NULL;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct ndpi_packet_trailer {
  u_int32_t magic; /* 0x19682017 */
  u_int16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */;
  char name[16];
}

static char extcap_buf[16384];

static char *extcap_capture_fifo    = NULL;

struct ndpi_proto_sorter {
  int id;
  char name[16];
}


static int info_pair_cmp (const void *_a, const void *_b)
{
  struct info_pair *a = (struct info_pair *)_a;
  struct info_pair *b = (struct info_pair *)_b;

  return b->count - a->count;
}


static void printFlow(u_int16_t id, struct ndpi_flow_info *flow, u_int16_t thread_id) {
  FILE *out = results_file ? results_file : stdout;
  u_int8_t known_tls;
  char buf[32], buf1[64];
  u_int i;

  double dos_ge_score;
  double dos_slow_score;
  double dos_hulk_score;
  double ddos_score;

  double hearthbleed_score;

  double ftp_patator_score;
  double ssh_patator_score;

  double inf_score;

  if(csv_fp != NULL) {
    float data_ratio = ndpi_data_ratio(flow->src2dst_bytes, flow->dst2src_bytes);
    double f = (double)flow->first_seen, l = (double)flow->last_seen;

    /* PLEASE KEEP IN SYNC WITH printCSVHeader() */
    dos_ge_score = Dos_goldeneye_score(flow);

    dos_slow_score = Dos_slow_score(flow);
    dos_hulk_score = Dos_hulk_score(flow);
    ddos_score = Ddos_score(flow);

    hearthbleed_score = Hearthbleed_score(flow);

    ftp_patator_score = Ftp_patator_score(flow);
    ssh_patator_score = Ssh_patator_score(flow);

    inf_score = Infiltration_score(flow);

    double benign_score = dos_ge_score < 1 && dos_slow_score < 1 && \
    dos_hulk_score < 1 && ddos_score < 1 && hearthbleed_score < 1 && \
    ftp_patator_score < 1 && ssh_patator_score < 1 && inf_score < 1 ? 1.1 : 0;

    fprintf(csv_fp, "%u,%u,%.3f,%.3f,%.3f,%s,%u,%s,%u,",
	    flow->flow_id,
	    flow->protocol,
	    f/1000.0, l/1000.0,
	    (l-f)/1000.0,
	    flow->src_name, ntohs(flow->src_port),
	    flow->dst_name, ntohs(flow->dst_port)
	    );

    fprintf(csv_fp, "%s,",
	    ndpi_protocol2id(ndpi_thread_info[thread_id].workflow->ndpi_struct,
			     flow->detected_protocol, buf, sizeof(buf)));

    fprintf(csv_fp, "%s,%s,",
	    ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct,
			       flow->detected_protocol, buf, sizeof(buf)),
	    flow->host_server_name);

    fprintf(csv_fp, "%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,", \
	    benign_score, dos_slow_score, dos_ge_score, dos_hulk_score, \
	    ddos_score, hearthbleed_score, ftp_patator_score,		\
	    ssh_patator_score, inf_score);

    fprintf(csv_fp, "%u,%llu,%llu,", flow->src2dst_packets,
	    (long long unsigned int) flow->src2dst_bytes, (long long unsigned int) flow->src2dst_goodput_bytes);
    fprintf(csv_fp, "%u,%llu,%llu,", flow->dst2src_packets,
	    (long long unsigned int) flow->dst2src_bytes, (long long unsigned int) flow->dst2src_goodput_bytes);
    fprintf(csv_fp, "%.3f,%s,", data_ratio, ndpi_data_ratio2str(data_ratio));
    fprintf(csv_fp, "%.1f,%.1f,", 100.0*((float)flow->src2dst_goodput_bytes / (float)(flow->src2dst_bytes+1)),
	    100.0*((float)flow->dst2src_goodput_bytes / (float)(flow->dst2src_bytes+1)));

    /* IAT (Inter Arrival Time) */
    fprintf(csv_fp, "%u,%.1f,%u,%.1f,",
	    ndpi_data_min(flow->iat_flow), ndpi_data_average(flow->iat_flow), ndpi_data_max(flow->iat_flow), ndpi_data_stddev(flow->iat_flow));

    fprintf(csv_fp, "%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,",
	    ndpi_data_min(flow->iat_c_to_s), ndpi_data_average(flow->iat_c_to_s), ndpi_data_max(flow->iat_c_to_s), ndpi_data_stddev(flow->iat_c_to_s),
	    ndpi_data_min(flow->iat_s_to_c), ndpi_data_average(flow->iat_s_to_c), ndpi_data_max(flow->iat_s_to_c), ndpi_data_stddev(flow->iat_s_to_c));

    /* Packet Length */
    fprintf(csv_fp, "%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,",
	    ndpi_data_min(flow->pktlen_c_to_s), ndpi_data_average(flow->pktlen_c_to_s), ndpi_data_max(flow->pktlen_c_to_s), ndpi_data_stddev(flow->pktlen_c_to_s),
	    ndpi_data_min(flow->pktlen_s_to_c), ndpi_data_average(flow->pktlen_s_to_c), ndpi_data_max(flow->pktlen_s_to_c), ndpi_data_stddev(flow->pktlen_s_to_c));

    /* TCP flags */
   fprintf(csv_fp, "%d,%d,%d,%d,%d,%d,%d,%d,", flow->cwr_count, flow->ece_count, flow->urg_count, flow->ack_count, flow->psh_count, flow->rst_count, flow->syn_count, flow->fin_count);

   fprintf(csv_fp, "%d,%d,%d,%d,%d,%d,%d,%d,", flow->src2dst_cwr_count, flow->src2dst_ece_count, flow->src2dst_urg_count, flow->src2dst_ack_count, flow->src2dst_psh_count, flow->src2dst_rst_count, flow->src2dst_syn_count, flow->src2dst_fin_count);

   fprintf(csv_fp, "%d,%d,%d,%d,%d,%d,%d,%d,", flow->dst2src_cwr_count, flow->ece_count, flow->urg_count, flow->ack_count, flow->psh_count, flow->rst_count, flow->syn_count, flow->fin_count);

   /* TCP window */
   fprintf(csv_fp, "%u,%u,", flow->c_to_s_init_win, flow->s_to_c_init_win);

    fprintf(csv_fp, "%s,%s,",
	    (flow->ssh_tls.client_requested_server_name[0] != '\0')  ? flow->ssh_tls.client_requested_server_name : "",
	    (flow->ssh_tls.server_info[0] != '\0')  ? flow->ssh_tls.server_info : "");

    fprintf(csv_fp, "%s,%s,%s,%s,%s,",
	    (flow->ssh_tls.ssl_version != 0)        ? ndpi_ssl_version2str(flow->ssh_tls.ssl_version, &known_tls) : "0",
	    (flow->ssh_tls.ja3_client[0] != '\0')   ? flow->ssh_tls.ja3_client : "",
	    (flow->ssh_tls.ja3_client[0] != '\0')   ? is_unsafe_cipher(flow->ssh_tls.client_unsafe_cipher) : "0",
	    (flow->ssh_tls.ja3_server[0] != '\0')   ? flow->ssh_tls.ja3_server : "",
	    (flow->ssh_tls.ja3_server[0] != '\0')   ? is_unsafe_cipher(flow->ssh_tls.server_unsafe_cipher) : "0");

    fprintf(csv_fp, "%s,%s,",
	    flow->ssh_tls.tls_alpn                  ? flow->ssh_tls.tls_alpn : "",
	    flow->ssh_tls.tls_supported_versions    ? flow->ssh_tls.tls_supported_versions : ""
	    );
    fprintf(csv_fp, "%s,%s,",
	    flow->ssh_tls.tls_issuerDN              ? flow->ssh_tls.tls_issuerDN : "",
	    flow->ssh_tls.tls_subjectDN             ? flow->ssh_tls.tls_subjectDN : ""
	    );
    fprintf(csv_fp, "%s,%s",
	    (flow->ssh_tls.client_hassh[0] != '\0') ? flow->ssh_tls.client_hassh : "",
	    (flow->ssh_tls.server_hassh[0] != '\0') ? flow->ssh_tls.server_hassh : ""
	    );

    fprintf(csv_fp, ",%s", flow->info);
  }

  if((verbose != 1) && (verbose != 2)) {
    if(csv_fp && enable_joy_stats) {
      flowGetBDMeanandVariance(flow);
    }

    if(csv_fp)
      fprintf(csv_fp, "\n");
    return;
  }

  if(csv_fp || (verbose > 1)) {
#if 1
  fprintf(out, "\t%u", id);
#else
  fprintf(out, "\t%u(%u)", id, flow->flow_id);
#endif

  fprintf(out, "\t%s ", ipProto2Name(flow->protocol));

  fprintf(out, "%s%s%s:%u %s %s%s%s:%u ",
	  (flow->ip_version == 6) ? "[" : "",
	  flow->src_name, (flow->ip_version == 6) ? "]" : "", ntohs(flow->src_port),
	  flow->bidirectional ? "<->" : "->",
	  (flow->ip_version == 6) ? "[" : "",
	  flow->dst_name, (flow->ip_version == 6) ? "]" : "", ntohs(flow->dst_port)
	  );

  if(flow->vlan_id > 0) fprintf(out, "[VLAN: %u]", flow->vlan_id);
  if(enable_payload_analyzer) fprintf(out, "[flowId: %u]", flow->flow_id);
  }

  if(enable_joy_stats) {
    /* Print entropy values for monitored flows. */
    flowGetBDMeanandVariance(flow);
    fflush(out);
    fprintf(out, "[score: %.4f]", flow->entropy.score);
  }

  if(csv_fp) fprintf(csv_fp, "\n");

  fprintf(out, "[proto: ");
  if(flow->tunnel_type != ndpi_no_tunnel)
    fprintf(out, "%s:", ndpi_tunnel2str(flow->tunnel_type));

  fprintf(out, "%s/%s]",
	  ndpi_protocol2id(ndpi_thread_info[thread_id].workflow->ndpi_struct,
			   flow->detected_protocol, buf, sizeof(buf)),
	  ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct,
			     flow->detected_protocol, buf1, sizeof(buf1)));

  if(flow->detected_protocol.category != 0)
    fprintf(out, "[cat: %s/%u]",
	    ndpi_category_get_name(ndpi_thread_info[thread_id].workflow->ndpi_struct,
				   flow->detected_protocol.category),
	    (unsigned int)flow->detected_protocol.category);

  fprintf(out, "[%u pkts/%llu bytes ", flow->src2dst_packets, (long long unsigned int) flow->src2dst_bytes);
  fprintf(out, "%s %u pkts/%llu bytes]",
	  (flow->dst2src_packets > 0) ? "<->" : "->",
	  flow->dst2src_packets, (long long unsigned int) flow->dst2src_bytes);

  fprintf(out, "[Goodput ratio: %.0f/%.0f]",
	  100.0*((float)flow->src2dst_goodput_bytes / (float)(flow->src2dst_bytes+1)),
	  100.0*((float)flow->dst2src_goodput_bytes / (float)(flow->dst2src_bytes+1)));

  if(flow->last_seen > flow->first_seen)
    fprintf(out, "[%.2f sec]", ((float)(flow->last_seen - flow->first_seen))/(float)1000);
  else
    fprintf(out, "[< 1 sec]");

  if(flow->telnet.username[0] != '\0')  fprintf(out, "[Username: %s]", flow->telnet.username);
  if(flow->telnet.password[0] != '\0')  fprintf(out, "[Password: %s]", flow->telnet.password);
  if(flow->host_server_name[0] != '\0') fprintf(out, "[Host: %s]", flow->host_server_name);

  if(flow->info[0] != '\0') fprintf(out, "[%s]", flow->info);
  if(flow->flow_extra_info[0] != '\0') fprintf(out, "[%s]", flow->flow_extra_info);

  if((flow->src2dst_packets+flow->dst2src_packets) > 5) {
    if(flow->iat_c_to_s && flow->iat_s_to_c) {
      float data_ratio = ndpi_data_ratio(flow->src2dst_bytes, flow->dst2src_bytes);

      fprintf(out, "[bytes ratio: %.3f (%s)]", data_ratio, ndpi_data_ratio2str(data_ratio));

      /* IAT (Inter Arrival Time) */
      fprintf(out, "[IAT c2s/s2c min/avg/max/stddev: %u/%u %.0f/%.0f %u/%u %.0f/%.0f]",
	      ndpi_data_min(flow->iat_c_to_s),     ndpi_data_min(flow->iat_s_to_c),
	      (float)ndpi_data_average(flow->iat_c_to_s), (float)ndpi_data_average(flow->iat_s_to_c),
	      ndpi_data_max(flow->iat_c_to_s),     ndpi_data_max(flow->iat_s_to_c),
	      (float)ndpi_data_stddev(flow->iat_c_to_s),  (float)ndpi_data_stddev(flow->iat_s_to_c));

      /* Packet Length */
      fprintf(out, "[Pkt Len c2s/s2c min/avg/max/stddev: %u/%u %.0f/%.0f %u/%u %.0f/%.0f]",
	      ndpi_data_min(flow->pktlen_c_to_s), ndpi_data_min(flow->pktlen_s_to_c),
	      ndpi_data_average(flow->pktlen_c_to_s), ndpi_data_average(flow->pktlen_s_to_c),
	      ndpi_data_max(flow->pktlen_c_to_s), ndpi_data_max(flow->pktlen_s_to_c),
	      ndpi_data_stddev(flow->pktlen_c_to_s),  ndpi_data_stddev(flow->pktlen_s_to_c));
    }
  }

  if(flow->http.url[0] != '\0') {
    ndpi_risk_enum risk = ndpi_validate_url(flow->http.url);

    if(risk != NDPI_NO_RISK)
      NDPI_SET_BIT(flow->risk, risk);

    fprintf(out, "[URL: %s][StatusCode: %u]",
	    flow->http.url, flow->http.response_status_code);

    if(flow->http.content_type[0] != '\0')
      fprintf(out, "[Content-Type: %s]", flow->http.content_type);

    if(flow->http.user_agent[0] != '\0')
      fprintf(out, "[User-Agent: %s]", flow->http.user_agent);
  }

  if(flow->risk) {
    u_int i;

    fprintf(out, "[Risk: ");

    for(i=0; i<NDPI_MAX_RISK; i++)
      if(NDPI_ISSET_BIT(flow->risk, i))
	fprintf(out, "** %s **", ndpi_risk2str(i));

    fprintf(out, "]");
  }

  if(flow->ssh_tls.ssl_version != 0) fprintf(out, "[%s]", ndpi_ssl_version2str(flow->ssh_tls.ssl_version, &known_tls));
  if(flow->ssh_tls.client_requested_server_name[0] != '\0') fprintf(out, "[Client: %s]", flow->ssh_tls.client_requested_server_name);
  if(flow->ssh_tls.client_hassh[0] != '\0') fprintf(out, "[HASSH-C: %s]", flow->ssh_tls.client_hassh);

  if(flow->ssh_tls.ja3_client[0] != '\0') fprintf(out, "[JA3C: %s%s]", flow->ssh_tls.ja3_client,
						  print_cipher(flow->ssh_tls.client_unsafe_cipher));

  if(flow->ssh_tls.server_info[0] != '\0') fprintf(out, "[Server: %s]", flow->ssh_tls.server_info);

  if(flow->ssh_tls.server_names) fprintf(out, "[ServerNames: %s]", flow->ssh_tls.server_names);
  if(flow->ssh_tls.server_hassh[0] != '\0') fprintf(out, "[HASSH-S: %s]", flow->ssh_tls.server_hassh);

  if(flow->ssh_tls.ja3_server[0] != '\0') fprintf(out, "[JA3S: %s%s]", flow->ssh_tls.ja3_server,
						  print_cipher(flow->ssh_tls.server_unsafe_cipher));

  if(flow->ssh_tls.tls_issuerDN)  fprintf(out, "[Issuer: %s]", flow->ssh_tls.tls_issuerDN);
  if(flow->ssh_tls.tls_subjectDN) fprintf(out, "[Subject: %s]", flow->ssh_tls.tls_subjectDN);

  if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS)
     || (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_TLS)) {
    if(flow->ssh_tls.sha1_cert_fingerprint_set) {
      fprintf(out, "[Certificate SHA-1: ");
      for(i=0; i<20; i++)
	fprintf(out, "%s%02X", (i > 0) ? ":" : "",
		flow->ssh_tls.sha1_cert_fingerprint[i] & 0xFF);
      fprintf(out, "]");
    }
  }

  if(flow->ssh_tls.notBefore && flow->ssh_tls.notAfter) {
    char notBefore[32], notAfter[32];
    struct tm a, b;
    struct tm *before = gmtime_r(&flow->ssh_tls.notBefore, &a);
    struct tm *after  = gmtime_r(&flow->ssh_tls.notAfter, &b);

    strftime(notBefore, sizeof(notBefore), "%F %T", before);
    strftime(notAfter, sizeof(notAfter), "%F %T", after);

    fprintf(out, "[Validity: %s - %s]", notBefore, notAfter);
  }

  if(flow->ssh_tls.server_cipher != '\0') fprintf(out, "[Cipher: %s]", ndpi_cipher2str(flow->ssh_tls.server_cipher));
  if(flow->bittorent_hash[0] != '\0') fprintf(out, "[BT Hash: %s]", flow->bittorent_hash);
  if(flow->dhcp_fingerprint[0] != '\0') fprintf(out, "[DHCP Fingerprint: %s]", flow->dhcp_fingerprint);

  if(flow->has_human_readeable_strings) fprintf(out, "[PLAIN TEXT (%s)]", flow->human_readeable_string_buffer);

  fprintf(out, "\n");
}

static char *_pcap_file[MAX_NUM_READER_THREADS];

static FILE *playlist_fp[MAX_NUM_READER_THREADS] = { NULL };

static FILE *results_file           = NULL;

static char *results_path           = NULL;

static char * bpfFilter             = NULL;

static char *_protoFilePath         = NULL;

static char *_customCategoryFilePath= NULL;

static FILE *csv_fp                 = NULL;

static u_int8_t live_capture = 0;

static u_int8_t undetected_flows_deleted = 0;

u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0;

u_int8_t verbose = 0, enable_joy_stats = 0;

int nDPI_LogLevel = 0;

char *_debug_protocols = NULL;

u_int8_t human_readeable_string_len = 5;

u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

static u_int32_t pcap_analysis_duration = (u_int32_t)-1;

static u_int16_t decode_tunnels = 0;

static u_int16_t num_loops = 1;

static u_int8_t shutdown_app = 0, quiet_mode = 0;

static u_int8_t num_threads = 1;

static struct timeval startup_time, begin, end;

static int core_affinity[MAX_NUM_READER_THREADS];

static struct timeval pcap_start = { 0, 0}, pcap_end = { 0, 0 };

static time_t capture_for = 0;

static time_t capture_until = 0;

static u_int32_t num_flows;

static struct ndpi_detection_module_struct *ndpi_info_mod = NULL;

extern u_int16_t min_pattern_len, max_pattern_len;

extern void ndpi_self_check_host_match();

struct flow_info {
  struct ndpi_flow_info *flow;
  u_int16_t thread_id;
}

static struct flow_info *all_flows;

struct info_pair {
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
}

typedef struct node_a{
  u_int32_t addr;
  u_int8_t version; /* IP version */
  char proto[16]; /*app level protocol*/
  int count;
  struct node_a *left, *right;
}addr_node;

struct port_stats {
  u_int32_t port; /* we'll use this field as the key */
  u_int32_t num_pkts, num_bytes;
  u_int32_t num_flows;
  u_int32_t num_addr; /*number of distinct IP addresses */
  u_int32_t cumulative_addr; /*cumulative some of IP addresses */
  addr_node *addr_tree; /* tree of distinct IP addresses */
  struct info_pair top_ip_addrs[MAX_NUM_IP_ADDRESS];
  u_int8_t hasTopHost; /* as boolean flag */
  u_int32_t top_host;  /* host that is contributed to > 95% of traffic */
  u_int8_t version;    /* top host's ip version */
  char proto[16];      /* application level protocol of top host */
  UT_hash_handle hh;   /* makes this structure hashable */
}

struct port_stats *srcStats = NULL, *dstStats = NULL;

struct port_flow_info {
  u_int32_t port; /* key */
  u_int32_t num_flows;
  UT_hash_handle hh;
}