/*
 * reader_util.c
 *
 * Copyright (C) 2011-22 - ntop.org
 *
 * This file is part of nDPI, an open source deep packet inspection
 * library based on the OpenDPI and PACE technology by ipoque GmbH
 *
 * nDPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * nDPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifdef HAVE_CONFIG_H
#include "ndpi_config.h"
#endif

#include <stdlib.h>
#include <math.h>
#include <float.h>

#ifdef WIN32
#include <winsock2.h> /* winsock.h is included automatically */
#include <process.h>
#include <io.h>
#include <ip6_misc.h>
#else
#include <unistd.h>
#include <netinet/in.h>
#endif

#include "reader_util.h"

#define SNAP                   0XAA
#define BSTP                   0x42     /* Bridge Spanning Tree Protocol */

/* Keep last 32 packets */
#define DATA_ANALUYSIS_SLIDING_WINDOW    32

/* mask for FCF */
#define	WIFI_DATA                        0x2    /* 0000 0010 */
#define FCF_TYPE(fc)     (((fc) >> 2) & 0x3)    /* 0000 0011 = 0x3 */
#define FCF_SUBTYPE(fc)  (((fc) >> 4) & 0xF)    /* 0000 1111 = 0xF */
#define FCF_TO_DS(fc)        ((fc) & 0x0100)
#define FCF_FROM_DS(fc)      ((fc) & 0x0200)

/* mask for Bad FCF presence */
#define BAD_FCS                         0x50    /* 0101 0000 */

#define GTP_U_V1_PORT                  2152
#define NDPI_CAPWAP_DATA_PORT          5247
#define TZSP_PORT                      37008

#ifndef DLT_LINUX_SLL
#define DLT_LINUX_SLL  113
#endif

#include "ndpi_main.h"
#include "reader_util.h"
#include "ndpi_classify.h"

extern u_int8_t enable_protocol_guess, enable_flow_stats, enable_payload_analyzer;
extern u_int8_t verbose, human_readeable_string_len;
extern u_int8_t max_num_udp_dissected_pkts /* 24 */, max_num_tcp_dissected_pkts /* 80 */;
static u_int32_t flow_id = 0;

u_int8_t enable_doh_dot_detection = 0;
u_int8_t enable_ja3_plus = 0;

/* ****************************************************** */

struct flow_id_stats {
  u_int32_t flow_id;
  UT_hash_handle hh;   /* makes this structure hashable */
};

struct packet_id_stats {
  u_int32_t packet_id;
  UT_hash_handle hh;   /* makes this structure hashable */
};

struct payload_stats {
  u_int8_t *pattern;
  u_int8_t pattern_len;
  u_int16_t num_occurrencies;
  struct flow_id_stats *flows;
  struct packet_id_stats *packets;
  UT_hash_handle hh;   /* makes this structure hashable */
};


struct payload_stats *pstats = NULL;
u_int32_t max_num_packets_per_flow      = 10; /* ETTA requires min 10 pkts for record. */
u_int32_t max_packet_payload_dissection = 128;
u_int32_t max_num_reported_top_payloads = 25;
u_int16_t min_pattern_len = 4;
u_int16_t max_pattern_len = 8;

/* *********************************************************** */

void ndpi_analyze_payload(struct ndpi_flow_info *flow,
			  u_int8_t src_to_dst_direction,
			  u_int8_t *payload,
			  u_int16_t payload_len,
			  u_int32_t packet_id) {
  struct payload_stats *ret;
  struct flow_id_stats *f;
  struct packet_id_stats *p;

#ifdef DEBUG_PAYLOAD
  for(i=0; i<payload_len; i++)
    printf("%c", isprint(payload[i]) ? payload[i] : '.');
  printf("\n");
#endif

  HASH_FIND(hh, pstats, payload, payload_len, ret);
  if(ret == NULL) {
    if((ret = (struct payload_stats*)ndpi_calloc(1, sizeof(struct payload_stats))) == NULL)
      return; /* OOM */

    if((ret->pattern = (u_int8_t*)ndpi_malloc(payload_len)) == NULL) {
      ndpi_free(ret);
      return;
    }

    memcpy(ret->pattern, payload, payload_len);
    ret->pattern_len = payload_len;
    ret->num_occurrencies = 1;

    HASH_ADD(hh, pstats, pattern[0], payload_len, ret);

#ifdef DEBUG_PAYLOAD
    printf("Added element [total: %u]\n", HASH_COUNT(pstats));
#endif
  } else {
    ret->num_occurrencies++;
    // printf("==> %u\n", ret->num_occurrencies);
  }

  HASH_FIND_INT(ret->flows, &flow->flow_id, f);
  if(f == NULL) {
    if((f = (struct flow_id_stats*)ndpi_calloc(1, sizeof(struct flow_id_stats))) == NULL)
      return; /* OOM */

    f->flow_id = flow->flow_id;
    HASH_ADD_INT(ret->flows, flow_id, f);
  }

  HASH_FIND_INT(ret->packets, &packet_id, p);
  if(p == NULL) {
    if((p = (struct packet_id_stats*)ndpi_calloc(1, sizeof(struct packet_id_stats))) == NULL)
      return; /* OOM */
    p->packet_id = packet_id;

    HASH_ADD_INT(ret->packets, packet_id, p);
  }
}

/* *********************************************************** */

void ndpi_payload_analyzer(struct ndpi_flow_info *flow,
			   u_int8_t src_to_dst_direction,
			   u_int8_t *payload, u_int16_t payload_len,
			   u_int32_t packet_id) {
  u_int16_t i, j;
  u_int16_t scan_len = ndpi_min(max_packet_payload_dissection, payload_len);

  if((flow->src2dst_packets+flow->dst2src_packets) <= max_num_packets_per_flow) {
#ifdef DEBUG_PAYLOAD
    printf("[hashval: %u][proto: %u][vlan: %u][%s:%u <-> %s:%u][direction: %s][payload_len: %u]\n",
	   flow->hashval, flow->protocol, flow->vlan_id,
	   flow->src_name, flow->src_port,
	   flow->dst_name, flow->dst_port,
	   src_to_dst_direction ? "s2d" : "d2s",
	   payload_len);
#endif
  } else
    return;

  for(i=0; i<scan_len; i++) {
    for(j=min_pattern_len; j <= max_pattern_len; j++) {
      if((i+j) < payload_len) {
	ndpi_analyze_payload(flow, src_to_dst_direction, &payload[i], j, packet_id);
      }
    }
  }
}

/* ***************************************************** */

static int payload_stats_sort_asc(void *_a, void *_b) {
  struct payload_stats *a = (struct payload_stats *)_a;
  struct payload_stats *b = (struct payload_stats *)_b;

  //return(a->num_occurrencies - b->num_occurrencies);
  return(b->num_occurrencies - a->num_occurrencies);
}

/* ***************************************************** */

void print_payload_stat(struct payload_stats *p) {
  u_int i;
  struct flow_id_stats *s, *tmp;
  struct packet_id_stats *s1, *tmp1;

  printf("\t[");

  for(i=0; i<p->pattern_len; i++) {
    printf("%c", isprint(p->pattern[i]) ? p->pattern[i] : '.');
  }

  printf("]");
  for(; i<16; i++) printf(" ");
  printf("[");

  for(i=0; i<p->pattern_len; i++) {
    printf("%s%02X", (i > 0) ? " " : "", isprint(p->pattern[i]) ? p->pattern[i] : '.');
  }

  printf("]");

  for(; i<16; i++) printf("  ");
  for(i=p->pattern_len; i<max_pattern_len; i++) printf(" ");

  printf("[len: %u][num_occurrencies: %u][flowId: ",
	 p->pattern_len, p->num_occurrencies);

  i = 0;
  HASH_ITER(hh, p->flows, s, tmp) {
    printf("%s%u", (i > 0) ? " " : "", s->flow_id);
    i++;
  }

  printf("][packetIds: ");

  /* ******************************** */

  i = 0;
  HASH_ITER(hh, p->packets, s1, tmp1) {
    printf("%s%u", (i > 0) ? " " : "", s1->packet_id);
    i++;
  }

  printf("]\n");


}

/* ***************************************************** */

void ndpi_report_payload_stats() {
  struct payload_stats *p, *tmp;
  u_int num = 0;

  printf("\n\nPayload Analysis\n");

  HASH_SORT(pstats, payload_stats_sort_asc);

  HASH_ITER(hh, pstats, p, tmp) {
    if(num <= max_num_reported_top_payloads)
      print_payload_stat(p);

    ndpi_free(p->pattern);

    {
      struct flow_id_stats *p1, *tmp1;

      HASH_ITER(hh, p->flows, p1, tmp1) {
	HASH_DEL(p->flows, p1);
	ndpi_free(p1);
      }
    }

    {
      struct packet_id_stats *p1, *tmp1;

      HASH_ITER(hh, p->packets, p1, tmp1) {
	HASH_DEL(p->packets, p1);
	ndpi_free(p1);
      }
    }

    HASH_DEL(pstats, p);
    ndpi_free(p);
    num++;
  }
}

/* ***************************************************** */

void ndpi_free_flow_info_half(struct ndpi_flow_info *flow) {
  if(flow->ndpi_flow) { ndpi_flow_free(flow->ndpi_flow); flow->ndpi_flow = NULL; }
}

/* ***************************************************** */

extern u_int32_t current_ndpi_memory, max_ndpi_memory;

/**
 * @brief ndpi_malloc wrapper function
 */
static void *ndpi_malloc_wrapper(size_t size) {
  current_ndpi_memory += size;

  if(current_ndpi_memory > max_ndpi_memory)
    max_ndpi_memory = current_ndpi_memory;

  return(malloc(size)); /* Don't change to ndpi_malloc !!!!! */
}

/* ***************************************************** */

/**
 * @brief free wrapper function
 */
static void free_wrapper(void *freeable) {
  free(freeable); /* Don't change to ndpi_free !!!!! */
}

/* ***************************************************** */

static uint16_t ndpi_get_proto_id(struct ndpi_detection_module_struct *ndpi_mod, const char *name) {
  uint16_t proto_id;
  char *e;
  unsigned long p = strtol(name,&e,0);
  ndpi_proto_defaults_t *proto_defaults = ndpi_get_proto_defaults(ndpi_mod);

  if(e && !*e) {
    if(p < NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS &&
       proto_defaults[p].protoName) return (uint16_t)p;
    return NDPI_PROTOCOL_UNKNOWN;
  }

  for(proto_id=NDPI_PROTOCOL_UNKNOWN; proto_id < NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS; proto_id++) {
    if(proto_defaults[proto_id].protoName &&
       !strcasecmp(proto_defaults[proto_id].protoName,name))
      return proto_id;
  }
  return NDPI_PROTOCOL_UNKNOWN;
}

/* ***************************************************** */

static NDPI_PROTOCOL_BITMASK debug_bitmask;
static char _proto_delim[] = " \t,:;";
static int parse_debug_proto(struct ndpi_detection_module_struct *ndpi_mod, char *str) {
  char *n;
  uint16_t proto;
  char op=1;
  for(n = strtok(str,_proto_delim); n && *n; n = strtok(NULL,_proto_delim)) {
    if(*n == '-') {
      op = 0;
      n++;
    } else if(*n == '+') {
      op = 1;
      n++;
    }
    if(!strcmp(n,"all")) {
      if(op)
	NDPI_BITMASK_SET_ALL(debug_bitmask);
      else
	NDPI_BITMASK_RESET(debug_bitmask);
      continue;
    }
    proto = ndpi_get_proto_id(ndpi_mod, n);
    if(proto == NDPI_PROTOCOL_UNKNOWN && strcmp(n,"unknown") && strcmp(n,"0")) {
      fprintf(stderr,"Invalid protocol %s\n",n);
      return 1;
    }
    if(op)
      NDPI_BITMASK_ADD(debug_bitmask,proto);
    else
      NDPI_BITMASK_DEL(debug_bitmask,proto);
  }
  return 0;
}

/* ***************************************************** */

extern char *_debug_protocols;
static int _debug_protocols_ok = 0;

struct ndpi_workflow* ndpi_workflow_init(const struct ndpi_workflow_prefs * prefs,
					 pcap_t * pcap_handle) {
  struct ndpi_detection_module_struct * module;
  struct ndpi_workflow * workflow;

  set_ndpi_malloc(ndpi_malloc_wrapper), set_ndpi_free(free_wrapper);
  set_ndpi_flow_malloc(NULL), set_ndpi_flow_free(NULL);

  /* TODO: just needed here to init ndpi ndpi_malloc wrapper */
  module = ndpi_init_detection_module(enable_ja3_plus ? ndpi_enable_ja3_plus : ndpi_no_prefs);

  if(module == NULL) {
    LOG(NDPI_LOG_ERROR, "global structure initialization failed\n");
    exit(-1);
  }

  workflow = ndpi_calloc(1, sizeof(struct ndpi_workflow));
  if(workflow == NULL) {
    LOG(NDPI_LOG_ERROR, "global structure initialization failed\n");
    ndpi_free(module);
    exit(-1);
  }

  workflow->pcap_handle = pcap_handle;
  workflow->prefs       = *prefs;
  workflow->ndpi_struct = module;

  ndpi_set_log_level(module, nDPI_LogLevel);

  if(_debug_protocols != NULL && ! _debug_protocols_ok) {
    if(parse_debug_proto(module,_debug_protocols))
      exit(-1);
    _debug_protocols_ok = 1;
  }
  if(_debug_protocols_ok)
    ndpi_set_debug_bitmask(module, debug_bitmask);

  workflow->ndpi_flows_root = ndpi_calloc(workflow->prefs.num_roots, sizeof(void *));

  return workflow;
}

/* ***************************************************** */

void ndpi_flow_info_freer(void *node) {
  struct ndpi_flow_info *flow = (struct ndpi_flow_info*)node;

  ndpi_flow_info_free_data(flow);
  ndpi_free(flow);
}

/* ***************************************************** */

static void ndpi_free_flow_tls_data(struct ndpi_flow_info *flow) {

  if(flow->dhcp_fingerprint) {
    ndpi_free(flow->dhcp_fingerprint);
    flow->dhcp_fingerprint = NULL;
  }
  if(flow->dhcp_class_ident) {
    ndpi_free(flow->dhcp_class_ident);
    flow->dhcp_class_ident = NULL;
  }

  if(flow->bittorent_hash) {
    ndpi_free(flow->bittorent_hash);
    flow->bittorent_hash = NULL;
  }

  if(flow->telnet.username) {
    ndpi_free(flow->telnet.username);
    flow->telnet.username = NULL;
  }
  if(flow->telnet.password) {
    ndpi_free(flow->telnet.password);
    flow->telnet.password = NULL;
  }

  if(flow->ssh_tls.server_names) {
    ndpi_free(flow->ssh_tls.server_names);
    flow->ssh_tls.server_names = NULL;
  }

  if(flow->ssh_tls.tls_alpn) {
    ndpi_free(flow->ssh_tls.tls_alpn);
    flow->ssh_tls.tls_alpn = NULL;
  }

  if(flow->ssh_tls.tls_supported_versions) {
    ndpi_free(flow->ssh_tls.tls_supported_versions);
    flow->ssh_tls.tls_supported_versions = NULL;
  }

  if(flow->ssh_tls.tls_issuerDN) {
    ndpi_free(flow->ssh_tls.tls_issuerDN);
    flow->ssh_tls.tls_issuerDN = NULL;
  }

  if(flow->ssh_tls.tls_subjectDN) {
    ndpi_free(flow->ssh_tls.tls_subjectDN);
    flow->ssh_tls.tls_subjectDN = NULL;
  }

  if(flow->ssh_tls.encrypted_sni.esni) {
    ndpi_free(flow->ssh_tls.encrypted_sni.esni);
    flow->ssh_tls.encrypted_sni.esni = NULL;
  }
}

/* ***************************************************** */

static void ndpi_free_flow_data_analysis(struct ndpi_flow_info *flow) {
  if(flow->iat_c_to_s) ndpi_free_data_analysis(flow->iat_c_to_s, 1);
  if(flow->iat_s_to_c) ndpi_free_data_analysis(flow->iat_s_to_c, 1);

  if(flow->pktlen_c_to_s) ndpi_free_data_analysis(flow->pktlen_c_to_s, 1);
  if(flow->pktlen_s_to_c) ndpi_free_data_analysis(flow->pktlen_s_to_c, 1);

  if(flow->iat_flow) ndpi_free_data_analysis(flow->iat_flow, 1);

  if(flow->entropy) ndpi_free(flow->entropy);
  if(flow->last_entropy) ndpi_free(flow->last_entropy);
}

/* ***************************************************** */

void ndpi_flow_info_free_data(struct ndpi_flow_info *flow) {

  ndpi_free_flow_info_half(flow);
  ndpi_free_flow_data_analysis(flow);
  ndpi_free_flow_tls_data(flow);

#ifdef DIRECTION_BINS
  ndpi_free_bin(&flow->payload_len_bin_src2dst);
  ndpi_free_bin(&flow->payload_len_bin_dst2src);
#else
  ndpi_free_bin(&flow->payload_len_bin);
#endif
}

/* ***************************************************** */

void ndpi_workflow_free(struct ndpi_workflow * workflow) {
  u_int i;

  for(i=0; i<workflow->prefs.num_roots; i++)
    ndpi_tdestroy(workflow->ndpi_flows_root[i], ndpi_flow_info_freer);

  ndpi_exit_detection_module(workflow->ndpi_struct);
  ndpi_free(workflow->ndpi_flows_root);
  ndpi_free(workflow);
}

/* ***************************************************** */

int ndpi_workflow_node_cmp(const void *a, const void *b) {
  const struct ndpi_flow_info *fa = (const struct ndpi_flow_info*)a;
  const struct ndpi_flow_info *fb = (const struct ndpi_flow_info*)b;

  if(fa->hashval < fb->hashval) return(-1); else if(fa->hashval > fb->hashval) return(1);

  /* Flows have the same hash */

  if(fa->vlan_id   < fb->vlan_id   ) return(-1); else { if(fa->vlan_id    > fb->vlan_id   ) return(1); }
  if(fa->protocol  < fb->protocol  ) return(-1); else { if(fa->protocol   > fb->protocol  ) return(1); }

  if(
     (
      (fa->src_ip      == fb->src_ip  )
      && (fa->src_port == fb->src_port)
      && (fa->dst_ip   == fb->dst_ip  )
      && (fa->dst_port == fb->dst_port)
      )
     ||
     (
      (fa->src_ip      == fb->dst_ip  )
      && (fa->src_port == fb->dst_port)
      && (fa->dst_ip   == fb->src_ip  )
      && (fa->dst_port == fb->src_port)
      )
     )
    return(0);

  if(fa->src_ip   < fb->src_ip  ) return(-1); else { if(fa->src_ip   > fb->src_ip  ) return(1); }
  if(fa->src_port < fb->src_port) return(-1); else { if(fa->src_port > fb->src_port) return(1); }
  if(fa->dst_ip   < fb->dst_ip  ) return(-1); else { if(fa->dst_ip   > fb->dst_ip  ) return(1); }
  if(fa->dst_port < fb->dst_port) return(-1); else { if(fa->dst_port > fb->dst_port) return(1); }

  return(0); /* notreached */
}

/* ***************************************************** */

/**
 * \brief Update the byte count for the flow record.
 * \param f Flow data
 * \param x Data to use for update
 * \param len Length of the data (in bytes)
 * \return none
 */
static void
ndpi_flow_update_byte_count(struct ndpi_flow_info *flow, const void *x,
                            unsigned int len, u_int8_t src_to_dst_direction) {
  /*
   * implementation note: The spec says that 4000 octets is enough of a
   * sample size to accurately reflect the byte distribution. Also, to avoid
   * wrapping of the byte count at the 16-bit boundry, we stop counting once
   * the 4000th octet has been seen for a flow.
   */

  if((flow->entropy->src2dst_pkt_count+flow->entropy->dst2src_pkt_count) <= max_num_packets_per_flow) {
    /* octet count was already incremented before processing this payload */
    u_int32_t current_count;

    if(src_to_dst_direction) {
      current_count = flow->entropy->src2dst_l4_bytes - len;
    } else {
      current_count = flow->entropy->dst2src_l4_bytes - len;
    }

    if(current_count < ETTA_MIN_OCTETS) {
      u_int32_t i;
      const unsigned char *data = x;

      for(i=0; i<len; i++) {
        if(src_to_dst_direction) {
          flow->entropy->src2dst_byte_count[data[i]]++;
        } else {
          flow->entropy->dst2src_byte_count[data[i]]++;
        }
        current_count++;
        if(current_count >= ETTA_MIN_OCTETS) {
          break;
        }
      }
    }
  }
}

/* ***************************************************** */

/**
 * \brief Update the byte distribution mean for the flow record.
 * \param f Flow record
 * \param x Data to use for update
 * \param len Length of the data (in bytes)
 * \return none
 */
static void
ndpi_flow_update_byte_dist_mean_var(ndpi_flow_info_t *flow, const void *x,
                                    unsigned int len, u_int8_t src_to_dst_direction) {
  const unsigned char *data = x;

  if((flow->entropy->src2dst_pkt_count+flow->entropy->dst2src_pkt_count) <= max_num_packets_per_flow) {
    unsigned int i;

    for(i=0; i<len; i++) {
      double delta;

      if(src_to_dst_direction) {
        flow->entropy->src2dst_num_bytes += 1;
        delta = ((double)data[i] - flow->entropy->src2dst_bd_mean);
        flow->entropy->src2dst_bd_mean += delta/((double)flow->entropy->src2dst_num_bytes);
        flow->entropy->src2dst_bd_variance += delta*((double)data[i] - flow->entropy->src2dst_bd_mean);
      } else {
        flow->entropy->dst2src_num_bytes += 1;
        delta = ((double)data[i] - flow->entropy->dst2src_bd_mean);
        flow->entropy->dst2src_bd_mean += delta/((double)flow->entropy->dst2src_num_bytes);
        flow->entropy->dst2src_bd_variance += delta*((double)data[i] - flow->entropy->dst2src_bd_mean);
      }
    }
  }
}

/* ***************************************************** */

float ndpi_flow_get_byte_count_entropy(const uint32_t byte_count[256],
				       unsigned int num_bytes)
{
  int i;
  float sum = 0.0;

  for(i=0; i<256; i++) {
    float tmp = (float) byte_count[i] / (float) num_bytes;

    if(tmp > FLT_EPSILON) {
      sum -= tmp * logf(tmp);
    }
  }
  return(sum / logf(2.0));
}

/* ***************************************************** */

static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow,
						 const u_int8_t version,
						 u_int16_t vlan_id,
						 ndpi_packet_tunnel tunnel_type,
						 const struct ndpi_iphdr *iph,
						 const struct ndpi_ipv6hdr *iph6,
						 u_int16_t ip_offset,
						 u_int16_t ipsize,
						 u_int16_t l4_packet_len,
						 u_int16_t l4_offset,
						 struct ndpi_tcphdr **tcph,
						 struct ndpi_udphdr **udph,
						 u_int16_t *sport, u_int16_t *dport,
						 u_int8_t *proto,
						 u_int8_t **payload,
						 u_int16_t *payload_len,
						 u_int8_t *src_to_dst_direction,
                                                 pkt_timeval when) {
  u_int32_t idx, hashval;
  struct ndpi_flow_info flow;
  void *ret;
  const u_int8_t *l3, *l4;
  u_int32_t l4_data_len = 0XFEEDFACE;

  /*
    Note: to keep things simple (ndpiReader is just a demo app)
    we handle IPv6 a-la-IPv4.
  */
  if(version == IPVERSION) {
    if(ipsize < 20)
      return NULL;

    if((iph->ihl * 4) > ipsize || ipsize < ntohs(iph->tot_len)
       /* || (iph->frag_off & htons(0x1FFF)) != 0 */)
      return NULL;

    l3 = (const u_int8_t*)iph;
  } else {
    if(l4_offset > ipsize)
      return NULL;

    l3 = (const u_int8_t*)iph6;
  }
  if(ipsize < l4_offset + l4_packet_len)
    return NULL;

  *proto = iph->protocol;

  if(l4_packet_len < 64)
    workflow->stats.packet_len[0]++;
  else if(l4_packet_len >= 64 && l4_packet_len < 128)
    workflow->stats.packet_len[1]++;
  else if(l4_packet_len >= 128 && l4_packet_len < 256)
    workflow->stats.packet_len[2]++;
  else if(l4_packet_len >= 256 && l4_packet_len < 1024)
    workflow->stats.packet_len[3]++;
  else if(l4_packet_len >= 1024 && l4_packet_len < 1500)
    workflow->stats.packet_len[4]++;
  else if(l4_packet_len >= 1500)
    workflow->stats.packet_len[5]++;

  if(l4_packet_len > workflow->stats.max_packet_len)
    workflow->stats.max_packet_len = l4_packet_len;

  l4 =& ((const u_int8_t *) l3)[l4_offset];

  if(*proto == IPPROTO_TCP && l4_packet_len >= sizeof(struct ndpi_tcphdr)) {
    u_int tcp_len;

    // TCP
    workflow->stats.tcp_count++;
    *tcph = (struct ndpi_tcphdr *)l4;
    *sport = ntohs((*tcph)->source), *dport = ntohs((*tcph)->dest);
    tcp_len = ndpi_min(4*(*tcph)->doff, l4_packet_len);
    *payload = (u_int8_t*)&l4[tcp_len];
    *payload_len = ndpi_max(0, l4_packet_len-4*(*tcph)->doff);
    l4_data_len = l4_packet_len - sizeof(struct ndpi_tcphdr);
  } else if(*proto == IPPROTO_UDP && l4_packet_len >= sizeof(struct ndpi_udphdr)) {
    // UDP
    workflow->stats.udp_count++;
    *udph = (struct ndpi_udphdr *)l4;
    *sport = ntohs((*udph)->source), *dport = ntohs((*udph)->dest);
    *payload = (u_int8_t*)&l4[sizeof(struct ndpi_udphdr)];
    *payload_len = (l4_packet_len > sizeof(struct ndpi_udphdr)) ? l4_packet_len-sizeof(struct ndpi_udphdr) : 0;
    l4_data_len = l4_packet_len - sizeof(struct ndpi_udphdr);
  } else if(*proto == IPPROTO_ICMP) {
    *payload = (u_int8_t*)&l4[sizeof(struct ndpi_icmphdr )];
    *payload_len = (l4_packet_len > sizeof(struct ndpi_icmphdr)) ? l4_packet_len-sizeof(struct ndpi_icmphdr) : 0;
    l4_data_len = l4_packet_len - sizeof(struct ndpi_icmphdr);
    *sport = *dport = 0;
  } else if(*proto == IPPROTO_ICMPV6) {
    *payload = (u_int8_t*)&l4[sizeof(struct ndpi_icmp6hdr)];
    *payload_len = (l4_packet_len > sizeof(struct ndpi_icmp6hdr)) ? l4_packet_len-sizeof(struct ndpi_icmp6hdr) : 0;
    l4_data_len = l4_packet_len - sizeof(struct ndpi_icmp6hdr);
    *sport = *dport = 0;
  } else {
    // non tcp/udp protocols
    *sport = *dport = 0;
    l4_data_len = 0;
  }

  flow.protocol = iph->protocol, flow.vlan_id = vlan_id;
  flow.src_ip = iph->saddr, flow.dst_ip = iph->daddr;
  flow.src_port = htons(*sport), flow.dst_port = htons(*dport);
  flow.hashval = hashval = flow.protocol + flow.src_ip + flow.dst_ip + flow.src_port + flow.dst_port;

#if 0
  printf("hashval=%u [%u][%u][%u:%u][%u:%u]\n", hashval, flow.protocol, flow.vlan_id,
	 flow.src_ip, flow.src_port, ntohs(flow.dst_ip), ntohs(flow.dst_port));
#endif

  idx = hashval % workflow->prefs.num_roots;
  ret = ndpi_tfind(&flow, &workflow->ndpi_flows_root[idx], ndpi_workflow_node_cmp);

  /* to avoid two nodes in one binary tree for a flow */
  int is_changed = 0;
  if(ret == NULL) {
    u_int32_t orig_src_ip = flow.src_ip;
    u_int16_t orig_src_port = flow.src_port;
    u_int32_t orig_dst_ip = flow.dst_ip;
    u_int16_t orig_dst_port = flow.dst_port;

    flow.src_ip = orig_dst_ip;
    flow.src_port = orig_dst_port;
    flow.dst_ip = orig_src_ip;
    flow.dst_port = orig_src_port;

    is_changed = 1;

    ret = ndpi_tfind(&flow, &workflow->ndpi_flows_root[idx], ndpi_workflow_node_cmp);
  }

  if(ret == NULL) {
    if(workflow->stats.ndpi_flow_count == workflow->prefs.max_ndpi_flows) {
      LOG(NDPI_LOG_ERROR,
	       "maximum flow count (%u) has been exceeded\n",
	       workflow->prefs.max_ndpi_flows);
      return NULL;
    } else {
      struct ndpi_flow_info *newflow = (struct ndpi_flow_info*)ndpi_malloc(sizeof(struct ndpi_flow_info));

      if(newflow == NULL) {
	LOG(NDPI_LOG_ERROR, "[NDPI] %s(1): not enough memory\n", __FUNCTION__);
	return(NULL);
      } else
        workflow->num_allocated_flows++;

      memset(newflow, 0, sizeof(struct ndpi_flow_info));
      newflow->flow_id = flow_id++;
      newflow->hashval = hashval;
      newflow->tunnel_type = tunnel_type;
      newflow->protocol = iph->protocol, newflow->vlan_id = vlan_id;
      newflow->src_ip = iph->saddr, newflow->dst_ip = iph->daddr;
      newflow->src_port = htons(*sport), newflow->dst_port = htons(*dport);
      newflow->ip_version = version;
      newflow->iat_c_to_s = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW),
	newflow->iat_s_to_c =  ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW);
      newflow->pktlen_c_to_s = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW),
	newflow->pktlen_s_to_c =  ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW),
	newflow->iat_flow = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW);

#ifdef DIRECTION_BINS
      ndpi_init_bin(&newflow->payload_len_bin_src2dst, ndpi_bin_family8, PLEN_NUM_BINS);
      ndpi_init_bin(&newflow->payload_len_bin_dst2src, ndpi_bin_family8, PLEN_NUM_BINS);
#else
      ndpi_init_bin(&newflow->payload_len_bin, ndpi_bin_family8, PLEN_NUM_BINS);
#endif

      if(version == IPVERSION) {
	inet_ntop(AF_INET, &newflow->src_ip, newflow->src_name, sizeof(newflow->src_name));
	inet_ntop(AF_INET, &newflow->dst_ip, newflow->dst_name, sizeof(newflow->dst_name));
      } else {
	inet_ntop(AF_INET6, &iph6->ip6_src, newflow->src_name, sizeof(newflow->src_name));
	inet_ntop(AF_INET6, &iph6->ip6_dst, newflow->dst_name, sizeof(newflow->dst_name));
	/* For consistency across platforms replace :0: with :: */
	ndpi_patchIPv6Address(newflow->src_name), ndpi_patchIPv6Address(newflow->dst_name);
      }

      if((newflow->ndpi_flow = ndpi_flow_malloc(SIZEOF_FLOW_STRUCT)) == NULL) {
	LOG(NDPI_LOG_ERROR, "[NDPI] %s(2): not enough memory\n", __FUNCTION__);
#ifdef DIRECTION_BINS
	ndpi_free_bin(&newflow->payload_len_bin_src2dst), ndpi_free_bin(&newflow->payload_len_bin_dst2src);
#else
	ndpi_free_bin(&newflow->payload_len_bin);
#endif
	ndpi_free(newflow);
	return(NULL);
      } else
	memset(newflow->ndpi_flow, 0, SIZEOF_FLOW_STRUCT);

      ndpi_tsearch(newflow, &workflow->ndpi_flows_root[idx], ndpi_workflow_node_cmp); /* Add */
      workflow->stats.ndpi_flow_count++;
      if(*proto == IPPROTO_TCP)
        workflow->stats.flow_count[0]++;
      else if(*proto == IPPROTO_UDP)
        workflow->stats.flow_count[1]++;
      else
        workflow->stats.flow_count[2]++;

      if(enable_flow_stats) {
        newflow->entropy = ndpi_calloc(1, sizeof(struct ndpi_entropy));
        newflow->last_entropy = ndpi_calloc(1, sizeof(struct ndpi_entropy));
        newflow->entropy->src2dst_pkt_len[newflow->entropy->src2dst_pkt_count] = l4_data_len;
        newflow->entropy->src2dst_pkt_time[newflow->entropy->src2dst_pkt_count] = when;
        if(newflow->entropy->src2dst_pkt_count == 0) {
          newflow->entropy->src2dst_start = when;
        }
        newflow->entropy->src2dst_pkt_count++;
        // Non zero app data.
        if(l4_data_len != 0XFEEDFACE && l4_data_len != 0) {
          newflow->entropy->src2dst_opackets++;
          newflow->entropy->src2dst_l4_bytes += l4_data_len;
        }
      }
      return newflow;
    }
  } else {
    struct ndpi_flow_info *rflow = *(struct ndpi_flow_info**)ret;

    if(is_changed) {
      if(rflow->src_ip == iph->saddr
	 && rflow->dst_ip == iph->daddr
	 && rflow->src_port == htons(*sport)
	 && rflow->dst_port == htons(*dport)
	 )
	*src_to_dst_direction = 0, rflow->bidirectional = 1;
      else
	*src_to_dst_direction = 1;
    }
    else {
      if(rflow->src_ip == iph->saddr
	 && rflow->dst_ip == iph->daddr
	 && rflow->src_port == htons(*sport)
	 && rflow->dst_port == htons(*dport)
	 )
	*src_to_dst_direction = 1;
      else
	*src_to_dst_direction = 0, rflow->bidirectional = 1;
    }
    if(enable_flow_stats) {
      if(src_to_dst_direction) {
        if(rflow->entropy->src2dst_pkt_count < max_num_packets_per_flow) {
          rflow->entropy->src2dst_pkt_len[rflow->entropy->src2dst_pkt_count] = l4_data_len;
          rflow->entropy->src2dst_pkt_time[rflow->entropy->src2dst_pkt_count] = when;
          rflow->entropy->src2dst_l4_bytes += l4_data_len;
          rflow->entropy->src2dst_pkt_count++;
        }
        // Non zero app data.
        if(l4_data_len != 0XFEEDFACE && l4_data_len != 0) {
          rflow->entropy->src2dst_opackets++;
        }
      } else {
        if(rflow->entropy->dst2src_pkt_count < max_num_packets_per_flow) {
          rflow->entropy->dst2src_pkt_len[rflow->entropy->dst2src_pkt_count] = l4_data_len;
          rflow->entropy->dst2src_pkt_time[rflow->entropy->dst2src_pkt_count] = when;
          if(rflow->entropy->dst2src_pkt_count == 0) {
            rflow->entropy->dst2src_start = when;
          }
          rflow->entropy->dst2src_l4_bytes += l4_data_len;
          rflow->entropy->dst2src_pkt_count++;
        }
        // Non zero app data.
        if(l4_data_len != 0XFEEDFACE && l4_data_len != 0) {
          rflow->entropy->dst2src_opackets++;
        }
      }
    }

    return(rflow);
  }
}

/* ****************************************************** */

static struct ndpi_flow_info *get_ndpi_flow_info6(struct ndpi_workflow * workflow,
						  u_int16_t vlan_id,
						  ndpi_packet_tunnel tunnel_type,
						  const struct ndpi_ipv6hdr *iph6,
						  u_int16_t ip_offset,
						  u_int16_t ipsize,
						  struct ndpi_tcphdr **tcph,
						  struct ndpi_udphdr **udph,
						  u_int16_t *sport, u_int16_t *dport,
						  u_int8_t *proto,
						  u_int8_t **payload,
						  u_int16_t *payload_len,
						  u_int8_t *src_to_dst_direction,
                                                  pkt_timeval when) {
  struct ndpi_iphdr iph;

  if(ipsize < 40)
    return(NULL);
  memset(&iph, 0, sizeof(iph));
  iph.version = IPVERSION;
  iph.saddr = iph6->ip6_src.u6_addr.u6_addr32[2] + iph6->ip6_src.u6_addr.u6_addr32[3];
  iph.daddr = iph6->ip6_dst.u6_addr.u6_addr32[2] + iph6->ip6_dst.u6_addr.u6_addr32[3];
  u_int8_t l4proto = iph6->ip6_hdr.ip6_un1_nxt;
  u_int16_t ip_len = ntohs(iph6->ip6_hdr.ip6_un1_plen);
  const u_int8_t *l4ptr = (((const u_int8_t *) iph6) + sizeof(struct ndpi_ipv6hdr));
  if(ndpi_handle_ipv6_extension_headers(ipsize - sizeof(struct ndpi_ipv6hdr), &l4ptr, &ip_len, &l4proto) != 0) {
    return(NULL);
  }
  iph.protocol = l4proto;

  return(get_ndpi_flow_info(workflow, 6, vlan_id, tunnel_type,
			    &iph, iph6, ip_offset, ipsize,
			    ip_len, l4ptr - (const u_int8_t *)iph6,
			    tcph, udph, sport, dport,
			    proto, payload,
			    payload_len, src_to_dst_direction, when));
}

/* ****************************************************** */

static u_int8_t is_ndpi_proto(struct ndpi_flow_info *flow, u_int16_t id) {
  if((flow->detected_protocol.master_protocol == id)
     || (flow->detected_protocol.app_protocol == id))
    return(1);
  else
    return(0);
}

/* ****************************************************** */

void correct_csv_data_field(char* data) {
  /* Replace , with ; to avoid issues with CSVs */
  u_int i;
  for(i=0; data[i] != '\0'; i++) if(data[i] == ',') data[i] = ';';
}

/* ****************************************************** */

u_int8_t plen2slot(u_int16_t plen) {
  /*
     Slots [32 bytes lenght]
     0..31, 32..63 ...
  */

  if(plen > PLEN_MAX)
    return(PLEN_NUM_BINS-1);
  else
    return(plen/PLEN_BIN_LEN);
}

/* ****************************************************** */

void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_flow_info *flow, FILE * csv_fp) {
  u_int i, is_quic = 0;

  if(!flow->ndpi_flow) return;

  flow->confidence = flow->ndpi_flow->confidence;

  snprintf(flow->host_server_name, sizeof(flow->host_server_name), "%s",
	   flow->ndpi_flow->host_server_name);

  snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s",
	   flow->ndpi_flow->flow_extra_info);

  flow->risk = flow->ndpi_flow->risk;

  if(is_ndpi_proto(flow, NDPI_PROTOCOL_DHCP)) {
    if(flow->ndpi_flow->protos.dhcp.fingerprint[0] != '\0')
      flow->dhcp_fingerprint = ndpi_strdup(flow->ndpi_flow->protos.dhcp.fingerprint);
    if(flow->ndpi_flow->protos.dhcp.class_ident[0] != '\0')
      flow->dhcp_class_ident = ndpi_strdup(flow->ndpi_flow->protos.dhcp.class_ident);
  } else if(is_ndpi_proto(flow, NDPI_PROTOCOL_BITTORRENT) &&
            !is_ndpi_proto(flow, NDPI_PROTOCOL_TLS)) {
    u_int j;

    if(flow->ndpi_flow->protos.bittorrent.hash[0] != '\0') {
      flow->bittorent_hash = ndpi_malloc(sizeof(flow->ndpi_flow->protos.bittorrent.hash) * 2 + 1);
      for(i=0, j = 0; i < sizeof(flow->ndpi_flow->protos.bittorrent.hash); i++) {
        sprintf(&flow->bittorent_hash[j], "%02x",
	        flow->ndpi_flow->protos.bittorrent.hash[i]);

        j += 2;
      }
      flow->bittorent_hash[j] = '\0';
    }
  }
  /* DNS */
  else if(is_ndpi_proto(flow, NDPI_PROTOCOL_DNS)) {
    if(flow->ndpi_flow->protos.dns.rsp_type == 0x1)
      inet_ntop(AF_INET, &flow->ndpi_flow->protos.dns.rsp_addr.ipv4, flow->info, sizeof(flow->info));
    else {
      inet_ntop(AF_INET6, &flow->ndpi_flow->protos.dns.rsp_addr.ipv6, flow->info, sizeof(flow->info));

      /* For consistency across platforms replace :0: with :: */
      ndpi_patchIPv6Address(flow->info);
    }
  }
  /* MDNS */
  else if(is_ndpi_proto(flow, NDPI_PROTOCOL_MDNS)) {
    snprintf(flow->info, sizeof(flow->info), "%s", flow->ndpi_flow->host_server_name);
  }
  /* UBNTAC2 */
  else if(is_ndpi_proto(flow, NDPI_PROTOCOL_UBNTAC2)) {
    snprintf(flow->info, sizeof(flow->info), "%s", flow->ndpi_flow->protos.ubntac2.version);
  }
  /* FTP */
  else if((is_ndpi_proto(flow, NDPI_PROTOCOL_FTP_CONTROL))
	  || /* IMAP */ is_ndpi_proto(flow, NDPI_PROTOCOL_MAIL_IMAP)
	  || /* POP */  is_ndpi_proto(flow, NDPI_PROTOCOL_MAIL_POP)
	  || /* SMTP */ is_ndpi_proto(flow, NDPI_PROTOCOL_MAIL_SMTP)) {
    if(flow->ndpi_flow->l4.tcp.ftp_imap_pop_smtp.username[0] != '\0')
      snprintf(flow->info, sizeof(flow->info), "User: %s][Pwd: %s%s",
	       flow->ndpi_flow->l4.tcp.ftp_imap_pop_smtp.username,
	       flow->ndpi_flow->l4.tcp.ftp_imap_pop_smtp.password,
	       flow->ndpi_flow->l4.tcp.ftp_imap_pop_smtp.auth_failed ? "][Auth Failed" : "");
  }
  /* KERBEROS */
  else if(is_ndpi_proto(flow, NDPI_PROTOCOL_KERBEROS)) {
    if((flow->ndpi_flow->protos.kerberos.hostname[0] != '\0')
       || (flow->ndpi_flow->protos.kerberos.username[0] != '\0')) {
      snprintf(flow->info, sizeof(flow->info), "%s%s%s%s",
	       flow->ndpi_flow->protos.kerberos.domain /* = realm */,
	       flow->ndpi_flow->protos.kerberos.domain[0] != '\0' ? "\\" : "",
	       flow->ndpi_flow->protos.kerberos.hostname,
	       flow->ndpi_flow->protos.kerberos.username);
    } else if(flow->ndpi_flow->protos.kerberos.domain[0] != '\0')
      snprintf(flow->info, sizeof(flow->info), "%s",
	       flow->ndpi_flow->protos.kerberos.domain);

#if 0
    if(flow->info[0] != '\0')
      printf("->> (%d) [%s][%s][%s]<<--\n",
	     htons(flow->src_port),
	     flow->ndpi_flow->protos.kerberos.domain,
	     flow->ndpi_flow->protos.kerberos.hostname,
	     flow->ndpi_flow->protos.kerberos.username);
#endif
  }
  /* HTTP */
  else if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_HTTP)
	  || is_ndpi_proto(flow, NDPI_PROTOCOL_HTTP)) {
    if(flow->ndpi_flow->http.url != NULL) {
      snprintf(flow->http.url, sizeof(flow->http.url), "%s", flow->ndpi_flow->http.url);
      flow->http.response_status_code = flow->ndpi_flow->http.response_status_code;
      snprintf(flow->http.content_type, sizeof(flow->http.content_type), "%s", flow->ndpi_flow->http.content_type ? flow->ndpi_flow->http.content_type : "");
      snprintf(flow->http.request_content_type, sizeof(flow->http.request_content_type), "%s", flow->ndpi_flow->http.request_content_type ? flow->ndpi_flow->http.request_content_type : "");
      snprintf(flow->http.user_agent, sizeof(flow->http.user_agent), "%s", flow->ndpi_flow->http.user_agent ? flow->ndpi_flow->http.user_agent : "");
    }
  } else if(is_ndpi_proto(flow, NDPI_PROTOCOL_TELNET)) {
    if(flow->ndpi_flow->protos.telnet.username[0] != '\0')
      flow->telnet.username = ndpi_strdup(flow->ndpi_flow->protos.telnet.username);
    if(flow->ndpi_flow->protos.telnet.password[0] != '\0')
      flow->telnet.password = ndpi_strdup(flow->ndpi_flow->protos.telnet.password);
  } else if(is_ndpi_proto(flow, NDPI_PROTOCOL_SSH)) {
    snprintf(flow->host_server_name,
	     sizeof(flow->host_server_name), "%s",
	     flow->ndpi_flow->protos.ssh.client_signature);
    snprintf(flow->ssh_tls.server_info, sizeof(flow->ssh_tls.server_info), "%s",
	     flow->ndpi_flow->protos.ssh.server_signature);
    snprintf(flow->ssh_tls.client_hassh, sizeof(flow->ssh_tls.client_hassh), "%s",
	     flow->ndpi_flow->protos.ssh.hassh_client);
    snprintf(flow->ssh_tls.server_hassh, sizeof(flow->ssh_tls.server_hassh), "%s",
	     flow->ndpi_flow->protos.ssh.hassh_server);
  }
  /* TLS */
  else if((is_ndpi_proto(flow, NDPI_PROTOCOL_TLS))
	  || ((is_quic = is_ndpi_proto(flow, NDPI_PROTOCOL_QUIC)))
	  || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS)
	  || (flow->ndpi_flow->protos.tls_quic.ja3_client[0] != '\0')
	  ) {
    flow->ssh_tls.ssl_version = flow->ndpi_flow->protos.tls_quic.ssl_version;

    snprintf(flow->http.user_agent, sizeof(flow->http.user_agent), "%s", flow->ndpi_flow->http.user_agent ? flow->ndpi_flow->http.user_agent : "");

    if(flow->ndpi_flow->protos.tls_quic.server_names_len > 0 && flow->ndpi_flow->protos.tls_quic.server_names)
      flow->ssh_tls.server_names = ndpi_strdup(flow->ndpi_flow->protos.tls_quic.server_names);

    flow->ssh_tls.notBefore = flow->ndpi_flow->protos.tls_quic.notBefore;
    flow->ssh_tls.notAfter = flow->ndpi_flow->protos.tls_quic.notAfter;
    snprintf(flow->ssh_tls.ja3_client, sizeof(flow->ssh_tls.ja3_client), "%s",
	     flow->ndpi_flow->protos.tls_quic.ja3_client);
    snprintf(flow->ssh_tls.ja3_server, sizeof(flow->ssh_tls.ja3_server), "%s",
	     flow->ndpi_flow->protos.tls_quic.ja3_server);
    flow->ssh_tls.server_unsafe_cipher = flow->ndpi_flow->protos.tls_quic.server_unsafe_cipher;
    flow->ssh_tls.server_cipher = flow->ndpi_flow->protos.tls_quic.server_cipher;

    if(flow->ndpi_flow->l4.tcp.tls.fingerprint_set) {
      memcpy(flow->ssh_tls.sha1_cert_fingerprint,
	     flow->ndpi_flow->protos.tls_quic.sha1_certificate_fingerprint, 20);
      flow->ssh_tls.sha1_cert_fingerprint_set = 1;
    }

    flow->ssh_tls.browser_heuristics = flow->ndpi_flow->protos.tls_quic.browser_heuristics;

    if(flow->ndpi_flow->protos.tls_quic.alpn) {
      if((flow->ssh_tls.tls_alpn = ndpi_strdup(flow->ndpi_flow->protos.tls_quic.alpn)) != NULL)
	correct_csv_data_field(flow->ssh_tls.tls_alpn);
    }

    if(flow->ndpi_flow->protos.tls_quic.issuerDN)
      flow->ssh_tls.tls_issuerDN = strdup(flow->ndpi_flow->protos.tls_quic.issuerDN);

    if(flow->ndpi_flow->protos.tls_quic.subjectDN)
      flow->ssh_tls.tls_subjectDN = strdup(flow->ndpi_flow->protos.tls_quic.subjectDN);

    if(flow->ndpi_flow->protos.tls_quic.encrypted_sni.esni) {
      flow->ssh_tls.encrypted_sni.esni = strdup(flow->ndpi_flow->protos.tls_quic.encrypted_sni.esni);
      flow->ssh_tls.encrypted_sni.cipher_suite = flow->ndpi_flow->protos.tls_quic.encrypted_sni.cipher_suite;
    }

    if(flow->ssh_tls.tls_supported_versions) {
      if((flow->ssh_tls.tls_supported_versions = ndpi_strdup(flow->ndpi_flow->protos.tls_quic.tls_supported_versions)) != NULL)
	correct_csv_data_field(flow->ssh_tls.tls_supported_versions);
    }

    if(flow->ndpi_flow->protos.tls_quic.alpn
       && flow->ndpi_flow->protos.tls_quic.tls_supported_versions) {
      correct_csv_data_field(flow->ndpi_flow->protos.tls_quic.alpn);
      correct_csv_data_field(flow->ndpi_flow->protos.tls_quic.tls_supported_versions);

      if(csv_fp)
	snprintf(flow->info, sizeof(flow->info), "%s",
		 flow->ndpi_flow->protos.tls_quic.alpn);
      else
	snprintf(flow->info, sizeof(flow->info), "ALPN: %s][TLS Supported Versions: %s",
		 flow->ndpi_flow->protos.tls_quic.alpn,
		 flow->ndpi_flow->protos.tls_quic.tls_supported_versions);
    } else if(flow->ndpi_flow->protos.tls_quic.alpn) {
      correct_csv_data_field(flow->ndpi_flow->protos.tls_quic.alpn);

      if(csv_fp)
	snprintf(flow->info, sizeof(flow->info), "%s,",
		 flow->ndpi_flow->protos.tls_quic.alpn);
      else
	snprintf(flow->info, sizeof(flow->info), "ALPN: %s",
		 flow->ndpi_flow->protos.tls_quic.alpn);
    }

    if(enable_doh_dot_detection) {
      /* For TLS we use TLS block lenght instead of payload lenght */
      ndpi_reset_bin(&flow->payload_len_bin);

      for(i=0; i<flow->ndpi_flow->l4.tcp.tls.num_tls_blocks; i++) {
	u_int16_t len = abs(flow->ndpi_flow->l4.tcp.tls.tls_application_blocks_len[i]);

	/* printf("[TLS_LEN] %u\n", len); */
	ndpi_inc_bin(&flow->payload_len_bin, plen2slot(len), 1);
      }
    }
  }

  if(flow->detection_completed && (!flow->check_extra_packets)) {
    if(is_ndpi_proto(flow, NDPI_PROTOCOL_UNKNOWN)) {
      if(workflow->__flow_giveup_callback != NULL)
	workflow->__flow_giveup_callback(workflow, flow, workflow->__flow_giveup_udata);
    } else {
      if(workflow->__flow_detected_callback != NULL)
	workflow->__flow_detected_callback(workflow, flow, workflow->__flow_detected_udata);
    }

    ndpi_free_flow_info_half(flow);
  }
}

/* ****************************************************** */

/**
 * @brief Clear entropy stats if it meets prereq.
 */
static void
ndpi_clear_entropy_stats(struct ndpi_flow_info *flow) {
  if(enable_flow_stats) {
    if(flow->entropy->src2dst_pkt_count + flow->entropy->dst2src_pkt_count == max_num_packets_per_flow) {
      memcpy(flow->last_entropy, flow->entropy,  sizeof(struct ndpi_entropy));
      memset(flow->entropy, 0x00, sizeof(struct ndpi_entropy));
    }
  }
}

void update_tcp_flags_count(struct ndpi_flow_info* flow, struct ndpi_tcphdr* tcp, u_int8_t src_to_dst_direction){
  if(tcp->cwr){
    flow->cwr_count++;
    src_to_dst_direction ? flow->src2dst_cwr_count++ : flow->dst2src_cwr_count++;
  }
  if(tcp->ece){
    flow->ece_count++;
    src_to_dst_direction ? flow->src2dst_ece_count++ : flow->dst2src_ece_count++;
  }
  if(tcp->rst){
    flow->rst_count++;
    src_to_dst_direction ? flow->src2dst_rst_count++ : flow->dst2src_rst_count++;
  }
  if(tcp->ack){
    flow->ack_count++;
    src_to_dst_direction ? flow->src2dst_ack_count++ : flow->dst2src_ack_count++;
  }
  if(tcp->fin){
    flow->fin_count++;
    src_to_dst_direction ? flow->src2dst_fin_count++ : flow->dst2src_fin_count++;
  }
  if(tcp->syn){
    flow->syn_count++;
    src_to_dst_direction ? flow->src2dst_syn_count++ : flow->dst2src_syn_count++;
  }
  if(tcp->psh){
    flow->psh_count++;
    src_to_dst_direction ? flow->src2dst_psh_count++ : flow->dst2src_psh_count++;
  }
  if(tcp->urg){
    flow->urg_count++;
    src_to_dst_direction ? flow->src2dst_urg_count++ : flow->dst2src_urg_count++;
  }
}

/* ****************************************************** */

/**
   Function to process the packet:
   determine the flow of a packet and try to decode it
   @return: 0 if success; else != 0

   @Note: ipsize = header->len - ip_offset ; rawsize = header->len
*/
static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow,
					   const u_int64_t time_ms,
					   u_int16_t vlan_id,
					   ndpi_packet_tunnel tunnel_type,
					   const struct ndpi_iphdr *iph,
					   struct ndpi_ipv6hdr *iph6,
					   u_int16_t ip_offset,
					   u_int16_t ipsize, u_int16_t rawsize,
					   const struct pcap_pkthdr *header,
					   const u_char *packet,
					   pkt_timeval when,
					   ndpi_risk *flow_risk,
					   FILE * csv_fp) {
  struct ndpi_flow_info *flow = NULL;
  struct ndpi_flow_struct *ndpi_flow = NULL;
  u_int8_t proto;
  struct ndpi_tcphdr *tcph = NULL;
  struct ndpi_udphdr *udph = NULL;
  u_int16_t sport, dport, payload_len = 0;
  u_int8_t *payload;
  u_int8_t src_to_dst_direction = 1;
  u_int8_t begin_or_end_tcp = 0;
  struct ndpi_proto nproto = NDPI_PROTOCOL_NULL;

  if(workflow->prefs.ignore_vlanid)
    vlan_id = 0;

  if(iph)
    flow = get_ndpi_flow_info(workflow, IPVERSION, vlan_id,
			      tunnel_type, iph, NULL,
			      ip_offset, ipsize,
			      ntohs(iph->tot_len) - (iph->ihl * 4),
			      iph->ihl * 4,
			      &tcph, &udph, &sport, &dport,
			      &proto,
			      &payload, &payload_len, &src_to_dst_direction, when);
  else
    flow = get_ndpi_flow_info6(workflow, vlan_id,
			       tunnel_type, iph6, ip_offset, ipsize,
			       &tcph, &udph, &sport, &dport,
			       &proto,
			       &payload, &payload_len, &src_to_dst_direction, when);

  if(flow != NULL) {
    pkt_timeval tdiff;

    workflow->stats.ip_packet_count++;
    workflow->stats.total_wire_bytes += rawsize + 24 /* CRC etc */,
      workflow->stats.total_ip_bytes += rawsize;
    ndpi_flow = flow->ndpi_flow;

    if(tcph != NULL){
      update_tcp_flags_count(flow, tcph, src_to_dst_direction);
      if(tcph->syn && !flow->src2dst_bytes){
	flow->c_to_s_init_win = rawsize;
      }else if(tcph->syn && tcph->ack && flow->src2dst_bytes == flow->c_to_s_init_win){
	flow->s_to_c_init_win = rawsize;
      }
    }

    if((tcph != NULL) && (tcph->fin || tcph->rst || tcph->syn))
      begin_or_end_tcp = 1;

    if(flow->flow_last_pkt_time.tv_sec) {
      ndpi_timer_sub(&when, &flow->flow_last_pkt_time, &tdiff);

      if(flow->iat_flow
	 && (tdiff.tv_sec >= 0) /* Discard backward time */
	 ) {
	u_int32_t ms = ndpi_timeval_to_milliseconds(tdiff);

	if(ms > 0)
	  ndpi_data_add_value(flow->iat_flow, ms);
      }
    }

    memcpy(&flow->flow_last_pkt_time, &when, sizeof(when));

    if(src_to_dst_direction) {
      if(flow->src2dst_last_pkt_time.tv_sec) {
	ndpi_timer_sub(&when, &flow->src2dst_last_pkt_time, &tdiff);

	if(flow->iat_c_to_s
	   && (tdiff.tv_sec >= 0) /* Discard backward time */
	   ) {
	  u_int32_t ms = ndpi_timeval_to_milliseconds(tdiff);

	  ndpi_data_add_value(flow->iat_c_to_s, ms);
	}
      }

      ndpi_data_add_value(flow->pktlen_c_to_s, rawsize);
      flow->src2dst_packets++, flow->src2dst_bytes += rawsize, flow->src2dst_goodput_bytes += payload_len;
      memcpy(&flow->src2dst_last_pkt_time, &when, sizeof(when));

#ifdef DIRECTION_BINS
      if(payload_len && (flow->src2dst_packets < MAX_NUM_BIN_PKTS))
	ndpi_inc_bin(&flow->payload_len_bin_src2dst, plen2slot(payload_len));
#endif
    } else {
      if(flow->dst2src_last_pkt_time.tv_sec && (!begin_or_end_tcp)) {
	ndpi_timer_sub(&when, &flow->dst2src_last_pkt_time, &tdiff);

	if(flow->iat_s_to_c) {
	  u_int32_t ms = ndpi_timeval_to_milliseconds(tdiff);

	  ndpi_data_add_value(flow->iat_s_to_c, ms);
	}
      }
      ndpi_data_add_value(flow->pktlen_s_to_c, rawsize);
      flow->dst2src_packets++, flow->dst2src_bytes += rawsize, flow->dst2src_goodput_bytes += payload_len;
      memcpy(&flow->dst2src_last_pkt_time, &when, sizeof(when));

#ifdef DIRECTION_BINS
      if(payload_len && (flow->dst2src_packets < MAX_NUM_BIN_PKTS))
	ndpi_inc_bin(&flow->payload_len_bin_dst2src, plen2slot(payload_len));
#endif
    }

#ifndef DIRECTION_BINS
    if(payload_len && ((flow->src2dst_packets+flow->dst2src_packets) < MAX_NUM_BIN_PKTS)) {
#if 0
      /* Discard packets until the protocol is detected */
      if(flow->detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN)
#endif
	ndpi_inc_bin(&flow->payload_len_bin, plen2slot(payload_len), 1);
    }
#endif

    if(enable_payload_analyzer && (payload_len > 0))
      ndpi_payload_analyzer(flow, src_to_dst_direction,
			    payload, payload_len,
			    workflow->stats.ip_packet_count);

    if(enable_flow_stats) {
      /* Update BD, distribution and mean. */
      ndpi_flow_update_byte_count(flow, payload, payload_len, src_to_dst_direction);
      ndpi_flow_update_byte_dist_mean_var(flow, payload, payload_len, src_to_dst_direction);
      /* Update SPLT scores for first 32 packets. */
      if((flow->entropy->src2dst_pkt_count+flow->entropy->dst2src_pkt_count) <= max_num_packets_per_flow) {
        if(flow->bidirectional)
          flow->entropy->score = ndpi_classify(flow->entropy->src2dst_pkt_len, flow->entropy->src2dst_pkt_time,
					      flow->entropy->dst2src_pkt_len, flow->entropy->dst2src_pkt_time,
					      flow->entropy->src2dst_start, flow->entropy->dst2src_start,
					      max_num_packets_per_flow, flow->src_port, flow->dst_port,
					      flow->src2dst_packets, flow->dst2src_packets,
					      flow->entropy->src2dst_opackets, flow->entropy->dst2src_opackets,
					      flow->entropy->src2dst_l4_bytes, flow->entropy->dst2src_l4_bytes, 1,
					      flow->entropy->src2dst_byte_count, flow->entropy->dst2src_byte_count);
	else
	  flow->entropy->score = ndpi_classify(flow->entropy->src2dst_pkt_len, flow->entropy->src2dst_pkt_time,
					      NULL, NULL, flow->entropy->src2dst_start, flow->entropy->src2dst_start,
					      max_num_packets_per_flow, flow->src_port, flow->dst_port,
					      flow->src2dst_packets, 0,
					      flow->entropy->src2dst_opackets, 0,
					      flow->entropy->src2dst_l4_bytes, 0, 1,
					      flow->entropy->src2dst_byte_count, NULL);
      }
    }

    if(flow->first_seen_ms == 0)
      flow->first_seen_ms = time_ms;

    flow->last_seen_ms = time_ms;

    /* Copy packets entropy if num packets count == 10 */
    ndpi_clear_entropy_stats(flow);
    /* Reset IAT reeference times (see https://github.com/ntop/nDPI/pull/1316) */
    if(((flow->src2dst_packets + flow->dst2src_packets) % max_num_packets_per_flow) == 0) {
      memset(&flow->src2dst_last_pkt_time, '\0', sizeof(flow->src2dst_last_pkt_time));
      memset(&flow->dst2src_last_pkt_time, '\0', sizeof(flow->dst2src_last_pkt_time));
      memset(&flow->flow_last_pkt_time, '\0', sizeof(flow->flow_last_pkt_time));
    }

    if((human_readeable_string_len != 0) && (!flow->has_human_readeable_strings)) {
      u_int8_t skip = 0;

      if((proto == IPPROTO_TCP)
	 && (
	     is_ndpi_proto(flow, NDPI_PROTOCOL_TLS)
	     || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS)
	     || is_ndpi_proto(flow, NDPI_PROTOCOL_SSH)
	     || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_SSH))
	 ) {
	if((flow->src2dst_packets+flow->dst2src_packets) < 10 /* MIN_NUM_ENCRYPT_SKIP_PACKETS */)
	  skip = 1; /* Skip initial negotiation packets */
      }

      if((!skip) && ((flow->src2dst_packets+flow->dst2src_packets) < 100)) {
	if(ndpi_has_human_readeable_string(workflow->ndpi_struct, (char*)packet, header->caplen,
					   human_readeable_string_len,
					   flow->human_readeable_string_buffer,
					   sizeof(flow->human_readeable_string_buffer)) == 1)
	  flow->has_human_readeable_strings = 1;
      }
    } else {
      if((proto == IPPROTO_TCP)
	 && (
	     is_ndpi_proto(flow, NDPI_PROTOCOL_TLS)
	     || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS)
	     || is_ndpi_proto(flow, NDPI_PROTOCOL_SSH)
	     || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_SSH))
	 )
	flow->has_human_readeable_strings = 0;
    }
  } else { // flow is NULL
    workflow->stats.total_discarded_bytes++;
    return(nproto);
  }

  if(!flow->detection_completed) {
    u_int enough_packets =
      (((proto == IPPROTO_UDP) && ((flow->src2dst_packets + flow->dst2src_packets) > max_num_udp_dissected_pkts))
       || ((proto == IPPROTO_TCP) && ((flow->src2dst_packets + flow->dst2src_packets) > max_num_tcp_dissected_pkts))) ? 1 : 0;

#if 0
    printf("%s()\n", __FUNCTION__);
#endif

    if(proto == IPPROTO_TCP)
      workflow->stats.dpi_packet_count[0]++;
    else if(proto == IPPROTO_UDP)
      workflow->stats.dpi_packet_count[1]++;
    else
      workflow->stats.dpi_packet_count[2]++;

    flow->detected_protocol = ndpi_detection_process_packet(workflow->ndpi_struct, ndpi_flow,
							    iph ? (uint8_t *)iph : (uint8_t *)iph6,
							    ipsize, time_ms);

    if(enough_packets || (flow->detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN)) {
      if((!enough_packets)
	 && ndpi_extra_dissection_possible(workflow->ndpi_struct, ndpi_flow))
	; /* Wait for certificate fingerprint */
      else {
	/* New protocol detected or give up */
	flow->detection_completed = 1;

#if 0
	/* Check if we should keep checking extra packets */
	if(ndpi_flow && ndpi_flow->check_extra_packets)
	  flow->check_extra_packets = 1;
#endif

	if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN) {
	  u_int8_t proto_guessed;

	  flow->detected_protocol = ndpi_detection_giveup(workflow->ndpi_struct, flow->ndpi_flow,
							  enable_protocol_guess, &proto_guessed);
	  if(enable_protocol_guess) workflow->stats.guessed_flow_protocols++;
	}

	process_ndpi_collected_info(workflow, flow, csv_fp);
      }
    }
  }

#if 0
  if(flow->risk != 0) {
    FILE *r = fopen("/tmp/e", "a");

    if(r) {
      fprintf(r, "->>> %u [%08X]\n", flow->risk, flow->risk);
      fclose(r);
    }
  }
#endif

  *flow_risk = flow->risk;

  return(flow->detected_protocol);
}

/* ****************************************************** */

int ndpi_is_datalink_supported(int datalink_type) {
  /* Keep in sync with the similar switch in ndpi_workflow_process_packet */
  switch(datalink_type) {
  case DLT_NULL:
  case DLT_PPP_SERIAL:
  case DLT_C_HDLC:
  case DLT_PPP:
#ifdef DLT_IPV4
  case DLT_IPV4:
#endif
#ifdef DLT_IPV6
  case DLT_IPV6:
#endif
  case DLT_EN10MB:
  case DLT_LINUX_SLL:
  case DLT_IEEE802_11_RADIO:
  case DLT_RAW:
    return 1;
  default:
    return 0;
  }
}

/* ****************************************************** */

struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow,
					       const struct pcap_pkthdr *header,
					       const u_char *packet,
					       ndpi_risk *flow_risk,
					       FILE * csv_fp) {
  /*
   * Declare pointers to packet headers
   */
  /* --- Ethernet header --- */
  const struct ndpi_ethhdr *ethernet;
  /* --- LLC header --- */
  const struct ndpi_llc_header_snap *llc;

  /* --- Cisco HDLC header --- */
  const struct ndpi_chdlc *chdlc;

  /* --- Radio Tap header --- */
  const struct ndpi_radiotap_header *radiotap;
  /* --- Wifi header --- */
  const struct ndpi_wifi_header *wifi;

  /* --- MPLS header --- */
  union mpls {
    uint32_t u32;
    struct ndpi_mpls_header mpls;
  } mpls;

  /** --- IP header --- **/
  struct ndpi_iphdr *iph;
  /** --- IPv6 header --- **/
  struct ndpi_ipv6hdr *iph6;

  struct ndpi_proto nproto = NDPI_PROTOCOL_NULL;
  ndpi_packet_tunnel tunnel_type = ndpi_no_tunnel;

  /* lengths and offsets */
  u_int32_t eth_offset = 0;
  u_int16_t radio_len;
  u_int16_t fc;
  u_int16_t type = 0;
  int wifi_len = 0;
  int pyld_eth_len = 0;
  int check;
  u_int64_t time_ms;
  u_int16_t ip_offset = 0, ip_len;
  u_int16_t frag_off = 0, vlan_id = 0;
  u_int8_t proto = 0, recheck_type;
  /*u_int32_t label;*/

  /* counters */
  u_int8_t vlan_packet = 0;

  *flow_risk = 0 /* NDPI_NO_RISK */;

  /* Increment raw packet counter */
  workflow->stats.raw_packet_count++;

  /* setting time */
  time_ms = ((uint64_t) header->ts.tv_sec) * TICK_RESOLUTION + header->ts.tv_usec / (1000000 / TICK_RESOLUTION);

  /* safety check */
  if(workflow->last_time > time_ms) {
    /* printf("\nWARNING: timestamp bug in the pcap file (ts delta: %llu, repairing)\n", ndpi_thread_info[thread_id].last_time - time); */
    time_ms = workflow->last_time;
  }
  /* update last time value */
  workflow->last_time = time_ms;

  /*** check Data Link type ***/
  int datalink_type;

#ifdef USE_DPDK
  datalink_type = DLT_EN10MB;
#else
  datalink_type = (int)pcap_datalink(workflow->pcap_handle);
#endif

 datalink_check:
  // 20 for min iph and 8 for min UDP
  if(header->caplen < eth_offset + 28)
    return(nproto); /* Too short */

  /* Keep in sync with ndpi_is_datalink_supported() */
  switch(datalink_type) {
  case DLT_NULL:
    if(ntohl(*((u_int32_t*)&packet[eth_offset])) == 2)
      type = ETH_P_IP;
    else
      type = ETH_P_IPV6;

    ip_offset = 4 + eth_offset;
    break;

    /* Cisco PPP in HDLC-like framing - 50 */
  case DLT_PPP_SERIAL:
    chdlc = (struct ndpi_chdlc *) &packet[eth_offset];
    ip_offset = eth_offset + sizeof(struct ndpi_chdlc); /* CHDLC_OFF = 4 */
    type = ntohs(chdlc->proto_code);
    break;

    /* Cisco PPP - 9 or 104 */
  case DLT_C_HDLC:
  case DLT_PPP:
    if(packet[0] == 0x0f || packet[0] == 0x8f) {
      chdlc = (struct ndpi_chdlc *) &packet[eth_offset];
      ip_offset = eth_offset + sizeof(struct ndpi_chdlc); /* CHDLC_OFF = 4 */
      type = ntohs(chdlc->proto_code);
    } else {
      ip_offset = eth_offset + 2;
      type = ntohs(*((u_int16_t*)&packet[eth_offset]));
    }
    break;

#ifdef DLT_IPV4
  case DLT_IPV4:
    type = ETH_P_IP;
    ip_offset = 0;
    break;
#endif

#ifdef DLT_IPV6
  case DLT_IPV6:
    type = ETH_P_IPV6;
    ip_offset = 0;
    break;
#endif

    /* IEEE 802.3 Ethernet - 1 */
  case DLT_EN10MB:
    ethernet = (struct ndpi_ethhdr *) &packet[eth_offset];
    ip_offset = sizeof(struct ndpi_ethhdr) + eth_offset;
    check = ntohs(ethernet->h_proto);

    if(check <= 1500)
      pyld_eth_len = check;
    else if(check >= 1536)
      type = check;

    if(pyld_eth_len != 0) {
      llc = (struct ndpi_llc_header_snap *)(&packet[ip_offset]);
      /* check for LLC layer with SNAP extension */
      if(llc->dsap == SNAP || llc->ssap == SNAP) {
	type = llc->snap.proto_ID;
	ip_offset += + 8;
      }
      /* No SNAP extension - Spanning Tree pkt must be discarted */
      else if(llc->dsap == BSTP || llc->ssap == BSTP) {
	goto v4_warning;
      }
    }
    break;

    /* Linux Cooked Capture - 113 */
  case DLT_LINUX_SLL:
    type = (packet[eth_offset+14] << 8) + packet[eth_offset+15];
    ip_offset = 16 + eth_offset;
    break;

    /* Radiotap link-layer - 127 */
  case DLT_IEEE802_11_RADIO:
    radiotap = (struct ndpi_radiotap_header *) &packet[eth_offset];
    radio_len = radiotap->len;

    /* Check Bad FCS presence */
    if((radiotap->flags & BAD_FCS) == BAD_FCS) {
      workflow->stats.total_discarded_bytes +=  header->len;
      return(nproto);
    }

    if(header->caplen < (eth_offset + radio_len + sizeof(struct ndpi_wifi_header)))
      return(nproto);

    /* Calculate 802.11 header length (variable) */
    wifi = (struct ndpi_wifi_header*)( packet + eth_offset + radio_len);
    fc = wifi->fc;

    /* check wifi data presence */
    if(FCF_TYPE(fc) == WIFI_DATA) {
      if((FCF_TO_DS(fc) && FCF_FROM_DS(fc) == 0x0) ||
	 (FCF_TO_DS(fc) == 0x0 && FCF_FROM_DS(fc)))
	wifi_len = 26; /* + 4 byte fcs */
    } else   /* no data frames */
      break;

    /* Check ether_type from LLC */
    if(header->caplen < (eth_offset + wifi_len + radio_len + sizeof(struct ndpi_llc_header_snap)))
      return(nproto);
    llc = (struct ndpi_llc_header_snap*)(packet + eth_offset + wifi_len + radio_len);
    if(llc->dsap == SNAP)
      type = ntohs(llc->snap.proto_ID);

    /* Set IP header offset */
    ip_offset = wifi_len + radio_len + sizeof(struct ndpi_llc_header_snap) + eth_offset;
    break;

  case DLT_RAW:
    ip_offset = eth_offset = 0;
    break;

  default:
    /*
     * We shoudn't be here, because we already checked that this datalink is supported.
     * Should ndpi_is_datalink_supported() be updated?
     */
    printf("Unknown datalink %d\n", datalink_type);
    return(nproto);
  }

 ether_type_check:
  recheck_type = 0;

  /* check ether type */
  switch(type) {
  case ETH_P_VLAN:
    if(ip_offset+4 >= (int)header->caplen)
      return(nproto);
    vlan_id = ((packet[ip_offset] << 8) + packet[ip_offset+1]) & 0xFFF;
    type = (packet[ip_offset+2] << 8) + packet[ip_offset+3];
    ip_offset += 4;
    vlan_packet = 1;

    // double tagging for 802.1Q
    while((type == 0x8100) && (((bpf_u_int32)ip_offset+4) < header->caplen)) {
      vlan_id = ((packet[ip_offset] << 8) + packet[ip_offset+1]) & 0xFFF;
      type = (packet[ip_offset+2] << 8) + packet[ip_offset+3];
      ip_offset += 4;
    }
    recheck_type = 1;
    break;

  case ETH_P_MPLS_UNI:
  case ETH_P_MPLS_MULTI:
    mpls.u32 = *((uint32_t *) &packet[ip_offset]);
    mpls.u32 = ntohl(mpls.u32);
    workflow->stats.mpls_count++;
    type = ETH_P_IP, ip_offset += 4;

    while(!mpls.mpls.s && (((bpf_u_int32)ip_offset) + 4 < header->caplen)) {
      mpls.u32 = *((uint32_t *) &packet[ip_offset]);
      mpls.u32 = ntohl(mpls.u32);
      ip_offset += 4;
    }
    recheck_type = 1;
    break;

  case ETH_P_PPPoE:
    workflow->stats.pppoe_count++;
    type = ETH_P_IP;
    ip_offset += 8;
    recheck_type = 1;
    break;

  default:
    break;
  }

  if(recheck_type)
    goto ether_type_check;

  workflow->stats.vlan_count += vlan_packet;

 iph_check:
  /* Check and set IP header size and total packet length */
  if(header->caplen < ip_offset + sizeof(struct ndpi_iphdr))
    return(nproto); /* Too short for next IP header*/

  iph = (struct ndpi_iphdr *) &packet[ip_offset];

  /* just work on Ethernet packets that contain IP */
  if(type == ETH_P_IP && header->caplen >= ip_offset) {
    frag_off = ntohs(iph->frag_off);

    proto = iph->protocol;
    if(header->caplen < header->len) {
      static u_int8_t cap_warning_used = 0;

      if(cap_warning_used == 0) {
	if(!workflow->prefs.quiet_mode)
	  LOG(NDPI_LOG_DEBUG,
		   "\n\nWARNING: packet capture size is smaller than packet size, DETECTION MIGHT NOT WORK CORRECTLY\n\n");
	cap_warning_used = 1;
      }
    }
  }

  if(iph->version == IPVERSION) {
    ip_len = ((u_int16_t)iph->ihl * 4);
    iph6 = NULL;

    if(iph->protocol == IPPROTO_IPV6
#ifdef IPPROTO_IPIP
       || iph->protocol == IPPROTO_IPIP
#endif
       ) {
      ip_offset += ip_len;
      if(ip_len > 0)
        goto iph_check;
    }

    if((frag_off & 0x1FFF) != 0) {
      static u_int8_t ipv4_frags_warning_used = 0;
      workflow->stats.fragmented_count++;

      if(ipv4_frags_warning_used == 0) {
	if(!workflow->prefs.quiet_mode)
	  LOG(NDPI_LOG_DEBUG, "\n\nWARNING: IPv4 fragments are not handled by this demo (nDPI supports them)\n");
	ipv4_frags_warning_used = 1;
      }

      workflow->stats.total_discarded_bytes +=  header->len;
      return(nproto);
    }
  } else if(iph->version == 6) {
    if(header->caplen < ip_offset + sizeof(struct ndpi_ipv6hdr))
      return(nproto); /* Too short for IPv6 header*/

    iph6 = (struct ndpi_ipv6hdr *)&packet[ip_offset];
    proto = iph6->ip6_hdr.ip6_un1_nxt;
    ip_len = ntohs(iph6->ip6_hdr.ip6_un1_plen);

    if(header->caplen < (ip_offset + sizeof(struct ndpi_ipv6hdr) + ntohs(iph6->ip6_hdr.ip6_un1_plen)))
      return(nproto); /* Too short for IPv6 payload*/

    const u_int8_t *l4ptr = (((const u_int8_t *) iph6) + sizeof(struct ndpi_ipv6hdr));
    u_int16_t ipsize = header->caplen - ip_offset;

    if(ndpi_handle_ipv6_extension_headers(ipsize - sizeof(struct ndpi_ipv6hdr), &l4ptr, &ip_len, &proto) != 0) {
      return(nproto);
    }

    if(proto == IPPROTO_IPV6
#ifdef IPPROTO_IPIP
       || proto == IPPROTO_IPIP
#endif
       ) {
      if(l4ptr > packet) { /* Better safe than sorry */
        ip_offset = (l4ptr - packet);
        goto iph_check;
      }
    }

    iph = NULL;
  } else {
    static u_int8_t ipv4_warning_used = 0;

  v4_warning:
    if(ipv4_warning_used == 0) {
      if(!workflow->prefs.quiet_mode)
        LOG(NDPI_LOG_DEBUG,
		 "\n\nWARNING: only IPv4/IPv6 packets are supported in this demo (nDPI supports both IPv4 and IPv6), all other packets will be discarded\n\n");
      ipv4_warning_used = 1;
    }

    workflow->stats.total_discarded_bytes +=  header->len;
    return(nproto);
  }

  if(workflow->prefs.decode_tunnels && (proto == IPPROTO_UDP)) {
    if(header->caplen < ip_offset + ip_len + sizeof(struct ndpi_udphdr))
      return(nproto); /* Too short for UDP header*/
    else {
      struct ndpi_udphdr *udp = (struct ndpi_udphdr *)&packet[ip_offset+ip_len];
      u_int16_t sport = ntohs(udp->source), dport = ntohs(udp->dest);

      if(((sport == GTP_U_V1_PORT) || (dport == GTP_U_V1_PORT)) &&
         (ip_offset + ip_len + sizeof(struct ndpi_udphdr) + 8 /* Minimum GTPv1 header len */ < header->caplen)) {
	/* Check if it's GTPv1 */
	u_int offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr);
	u_int8_t flags = packet[offset];
	u_int8_t message_type = packet[offset+1];
	u_int8_t exts_parsing_error = 0;

	if((((flags & 0xE0) >> 5) == 1 /* GTPv1 */) &&
	   (message_type == 0xFF /* T-PDU */)) {

	  offset += 8; /* GTPv1 header len */
	  if(flags & 0x07)
	    offset += 4; /* sequence_number + pdu_number + next_ext_header fields */
	  /* Extensions parsing */
	  if(flags & 0x04) {
	    unsigned int ext_length = 0;

	    while(offset < header->caplen) {
	      ext_length = packet[offset] << 2;
	      offset += ext_length;
	      if(offset >= header->caplen || ext_length == 0) {
	        exts_parsing_error = 1;
	        break;
	      }
	      if(packet[offset - 1] == 0)
	        break;
	    }
	  }

	  if(offset < header->caplen && !exts_parsing_error) {
	    /* Ok, valid GTP-U */
	    tunnel_type = ndpi_gtp_tunnel;
	    ip_offset = offset;
	    iph = (struct ndpi_iphdr *)&packet[ip_offset];
	    if(iph->version == 6) {
	      iph6 = (struct ndpi_ipv6hdr *)&packet[ip_offset];
	      iph = NULL;
              if(header->caplen < ip_offset + sizeof(struct ndpi_ipv6hdr))
	        return(nproto);
	    } else if(iph->version != IPVERSION) {
	      // printf("WARNING: not good (packet_id=%u)!\n", (unsigned int)workflow->stats.raw_packet_count);
	      goto v4_warning;
	    } else {
              if(header->caplen < ip_offset + sizeof(struct ndpi_iphdr))
	        return(nproto);
	    }
	  }
	}
      } else if((sport == TZSP_PORT) || (dport == TZSP_PORT)) {
	/* https://en.wikipedia.org/wiki/TZSP */
	if(header->caplen < ip_offset + ip_len + sizeof(struct ndpi_udphdr) + 4)
	  return(nproto); /* Too short for TZSP*/

	u_int offset           = ip_offset+ip_len+sizeof(struct ndpi_udphdr);
	u_int8_t version       = packet[offset];
	u_int8_t ts_type       = packet[offset+1];
	u_int16_t encapsulates = ntohs(*((u_int16_t*)&packet[offset+2]));

	tunnel_type = ndpi_tzsp_tunnel;

	if((version == 1) && (ts_type == 0) && (encapsulates == 1)) {
	  u_int8_t stop = 0;

	  offset += 4;

	  while((!stop) && (offset < header->caplen)) {
	    u_int8_t tag_type = packet[offset];
	    u_int8_t tag_len;

	    switch(tag_type) {
	    case 0: /* PADDING Tag */
	      tag_len = 1;
	      break;
	    case 1: /* END Tag */
	      tag_len = 1, stop = 1;
	      break;
	    default:
	      tag_len = packet[offset+1];
	      break;
	    }

	    offset += tag_len;

	    if(offset >= header->caplen)
	      return(nproto); /* Invalid packet */
	    else {
	      eth_offset = offset;
	      goto datalink_check;
	    }
	  }
	}
      } else if((sport == NDPI_CAPWAP_DATA_PORT) || (dport == NDPI_CAPWAP_DATA_PORT)) {
	/* We dissect ONLY CAPWAP traffic */
	u_int offset           = ip_offset+ip_len+sizeof(struct ndpi_udphdr);

	if((offset+1) < header->caplen) {
	  uint8_t preamble = packet[offset];

	  if((preamble & 0x0F) == 0) { /* CAPWAP header */
	    u_int16_t msg_len = (packet[offset+1] & 0xF8) >> 1;

	    offset += msg_len;

	    if((offset + 32 < header->caplen)) {
	      /* IEEE 802.11 Data */
	      offset += 24;
	      /* LLC header is 8 bytes */
	      type = ntohs((u_int16_t)*((u_int16_t*)&packet[offset+6]));

	      ip_offset = offset + 8;

	      tunnel_type = ndpi_capwap_tunnel;
	      goto iph_check;
	    }
	  }
	}
      }
    }
  }

  /* process the packet */
  return(packet_processing(workflow, time_ms, vlan_id, tunnel_type, iph, iph6,
			   ip_offset, header->caplen - ip_offset,
			   header->caplen, header, packet, header->ts,
			   flow_risk, csv_fp));
}

/* ********************************************************** */
/*       http://home.thep.lu.se/~bjorn/crc/crc32_fast.c       */
/* ********************************************************** */

static uint32_t crc32_for_byte(uint32_t r) {
  int j;
  for(j = 0; j < 8; ++j)
    r = ((r & 1) ? 0 : (uint32_t)0xEDB88320L) ^ r >> 1;
  return r ^ (uint32_t)0xFF000000L;
}

/* Any unsigned integer type with at least 32 bits may be used as
 * accumulator type for fast crc32-calulation, but unsigned long is
 * probably the optimal choice for most systems. */
typedef unsigned long accum_t;

static void init_tables(uint32_t* table, uint32_t* wtable) {
  size_t i, j, k, w;
  for(i = 0; i < 0x100; ++i)
    table[i] = crc32_for_byte(i);
  for(k = 0; k < sizeof(accum_t); ++k)
    for(i = 0; i < 0x100; ++i) {
      for(j = w = 0; j < sizeof(accum_t); ++j)
	w = table[(uint8_t)(j == k? w ^ i: w)] ^ w >> 8;
      wtable[(k << 8) + i] = w ^ (k? wtable[0]: 0);
    }
}

static void __crc32(const void* data, size_t n_bytes, uint32_t* crc) {
  static uint32_t table[0x100], wtable[0x100*sizeof(accum_t)];
  size_t n_accum = n_bytes/sizeof(accum_t);
  size_t i, j;
  if(!*table)
    init_tables(table, wtable);
  for(i = 0; i < n_accum; ++i) {
    accum_t a = *crc ^ ((accum_t*)data)[i];
    for(j = *crc = 0; j < sizeof(accum_t); ++j)
      *crc ^= wtable[(j << 8) + (uint8_t)(a >> 8*j)];
  }
  for(i = n_accum*sizeof(accum_t); i < n_bytes; ++i)
    *crc = table[(uint8_t)*crc ^ ((uint8_t*)data)[i]] ^ *crc >> 8;
}

u_int32_t ethernet_crc32(const void* data, size_t n_bytes) {
  u_int32_t crc = 0;
  __crc32(data, n_bytes, &crc);
  return crc;
}

/* *********************************************** */

#ifdef USE_DPDK

#include <rte_version.h>
#include <rte_ether.h>

static const struct rte_eth_conf port_conf_default = {
#if(RTE_VERSION < RTE_VERSION_NUM(19, 8, 0, 0))
						      .rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN }
#else
						      .rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN }
#endif
};

/* ************************************ */

int dpdk_port_init(int port, struct rte_mempool *mbuf_pool) {
  struct rte_eth_conf port_conf = port_conf_default;
  const u_int16_t rx_rings = 1, tx_rings = 1;
  int retval;
  u_int16_t q;

  /* 1 RX queue */
  retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);

  if(retval != 0)
    return retval;

  for(q = 0; q < rx_rings; q++) {
    retval = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE, rte_eth_dev_socket_id(port), NULL, mbuf_pool);
    if(retval < 0)
      return retval;
  }

  for(q = 0; q < tx_rings; q++) {
    retval = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE, rte_eth_dev_socket_id(port), NULL);
    if(retval < 0)
      return retval;
  }

  retval = rte_eth_dev_start(port);

  if(retval < 0)
    return retval;

  rte_eth_promiscuous_enable(port);

  return 0;
}

int dpdk_port_deinit(int port) {
  rte_eth_dev_stop(port);
  rte_eth_dev_close(port);
  return 0;
}

#endif
