/*
 *
 * (C) 2013-25 - ntop.org
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#include "ntop_includes.h"

/* static so default is zero-initialization, let's just define it */

const ndpi_protocol Flow::ndpiUnknownProtocol = {
  { NDPI_PROTOCOL_UNKNOWN /* master_protocol */, NDPI_PROTOCOL_UNKNOWN /* app_protocol */ },
  NDPI_PROTOCOL_UNKNOWN, /* protocol_by_ip */
  NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NULL};
// #define DEBUG_DISCOVERY
// #define DEBUG_UA
// #define DEBUG_SCORE

/* #define BLACKLISTED_FLOWS_DEBUG */

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

Flow::Flow(NetworkInterface *_iface,
	   int32_t _iface_idx, u_int16_t _vlanId,
           u_int16_t _observation_point_id, u_int32_t _private_flow_id,
           u_int8_t _protocol, Mac *_cli_mac, IpAddress *_cli_ip,
           u_int16_t _cli_port, Mac *_srv_mac, IpAddress *_srv_ip,
           u_int16_t _srv_port, const ICMPinfo *const _icmp_info,
           time_t _first_seen, time_t _last_seen, u_int8_t *_view_cli_mac,
           u_int8_t *_view_srv_mac)
  : GenericHashEntry(_iface) {
  if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[new] %s", __FILE__);

  creation_time = iface->getTimeLastPktRcvd(),
    iface_index = _iface_idx,
    vlanId = _vlanId, protocol = _protocol, cli_port = _cli_port,
    srv_port = _srv_port, privateFlowId = _private_flow_id;
  flow_dropped_counts_increased = 0, protocolErrorCode = 0;
  srcAS = dstAS = 0, rttSec = 0;
  src2dst_tcp_flags = dst2src_tcp_flags = 0;

  collected_qoe.src_to_dst = collected_qoe.dst_to_src = NTOP_QOE_UNKNOWN, has_collected_qoe = 0;
  tcp = NULL;

#ifdef NTOPNG_PRO
  udp = NULL;
#endif

  if(ntop->getPrefs()->fullStatsEnabled() && getInterface()->isPacketInterface()) {
    if(protocol == IPPROTO_TCP) {
      tcp = (FlowTCP*)calloc(1, sizeof(FlowTCP));

      if(tcp != NULL) {
	ndpi_init_data_analysis(&tcp->tcpWin.cli_to_srv, 0);
	ndpi_init_data_analysis(&tcp->tcpWin.srv_to_cli, 0);
	ndpi_init_data_analysis(&tcp->rtt.cli_to_srv, 0);
	ndpi_init_data_analysis(&tcp->rtt.srv_to_cli, 0);
      }
    }
#ifdef NTOPNG_PRO
    else {
      if(protocol == IPPROTO_UDP) {
	udp = (FlowUDP*)calloc(1, sizeof(FlowUDP));

	if(udp != NULL) {
	  ndpi_init_data_analysis(&udp->rtt.cli_min_rtt, 0);
	  ndpi_init_data_analysis(&udp->rtt.srv_min_rtt, 0);
	}
      }
    }
#endif
  }

  collection = NULL;

  predominant_alert.id = flow_alert_normal,
    predominant_alert.category = alert_category_other,
    predominant_alert_score = 0;
  alert_info.is_cli_attacker =
    alert_info.is_cli_victim =
    alert_info.is_srv_attacker =
    alert_info.is_srv_victim = 0;
  alert_info.auto_acknowledge = 1;
  pending_alerts = refresh_triggered_alerts = false;
  category_list_name_shared_pointer = NULL;
  ndpiAddressFamilyProtocol = NULL;
  ndpi_confidence = NDPI_CONFIDENCE_UNKNOWN;
  clearRisks();
  /* Note is_periodic_flow is updated by the updateFlowPeriodicity() call */
  detection_completed = 0, non_zero_payload_observed = 0, is_periodic_flow = 0,
    extra_dissection_completed = 0,
    has_malicious_cli_signature = has_malicious_srv_signature = 0,
    iface_flow_accounted = 0, periodicity = 0;
  ndpiDetectedProtocol = ndpiUnknownProtocol;
  cli2srv_tos = srv2cli_tos = 0;
  src2dst_tcp_zero_window = dst2src_tcp_zero_window = 0;
  swap_done = swap_requested = 0;
  current_c_state = MINOR_NO_STATE;
#ifdef HAVE_NEDGE
  last_conntrack_update = 0;
  marker = MARKER_NO_ACTION;
#endif

  host_server_name = NULL, flow_source = packet_to_flow;
  icmp_info = _icmp_info ? new (std::nothrow) ICMPinfo(*_icmp_info) : NULL;
  ndpiFlow = NULL, confidence = NDPI_CONFIDENCE_UNKNOWN;
  json_info = NULL, tlv_info = NULL, twh_over = 0,
    dissect_next_http_packet = 0;
  periodic_stats_update_partial = NULL;
  bt_hash = NULL, ebpf = NULL, iec104 = NULL, stun_mapped_address = NULL;
  twh_over_view = false;
  flow_verdict = 0;
  operating_system = ndpi_os_unknown;
  last_update_time.tv_sec = 0, last_update_time.tv_usec = 0;
  bytes_thpt = 0, goodput_bytes_thpt = 0;
  pkts_thpt = 0;
  top_bytes_thpt = 0, top_pkts_thpt = 0, top_goodput_bytes_thpt = 0,
    applLatencyMsec = 0;
  external_alert.json = NULL, external_alert.source = NULL;
  trigger_immediate_periodic_update = false;
  next_call_periodic_update = 0;

  riskInfo = NULL, json_protocol_info = NULL;
  alerts_json = NULL, alerts_json_shadow = NULL;
  end_reason = NULL;
  ndpiFlowRiskName = NULL;
#ifdef NTOPNG_PRO
  viewFlowStats = NULL;
#endif
  suspicious_dga_domain = NULL;
  flow_payload = NULL, flow_payload_len = 0;

  last_db_dump.partial = NULL;
  last_db_dump.first_seen = last_db_dump.last_seen = 0;
  last_db_dump.in_progress = false;

  memset(&protos, 0, sizeof(protos));
  memset(&flow_device, 0, sizeof(flow_device));

  flow_score = 0;

  rtp_stream_type = ndpi_multimedia_unknown_flow;
#ifdef NTOPNG_PRO
  rtp = NULL;
#endif

  cli_ip_addr = srv_ip_addr = NULL;
  cli_host = srv_host = NULL;

  INTERFACE_PROFILING_SUB_SECTION_ENTER(iface,
                                        "Flow::Flow: iface->findFlowHosts", 7);
  iface->findFlowHosts(_iface_idx, _vlanId, _observation_point_id, _private_flow_id,
                       _cli_mac, _cli_ip, &cli_host, _srv_mac, _srv_ip, &srv_host);
  INTERFACE_PROFILING_SUB_SECTION_EXIT(iface, 7);

  if(_observation_point_id) {
    iface->incObservationPointIdFlows(_observation_point_id);
    flow_device.observation_point_id = _observation_point_id;
  }

#ifdef NTOPNG_PRO
  if(_iface->isViewed()) {
    memset(view_cli_mac, 0, sizeof(view_cli_mac));
    memset(view_srv_mac, 0, sizeof(view_srv_mac));

    if(_view_cli_mac)
      memcpy(view_cli_mac, _view_cli_mac, sizeof(view_cli_mac));

    if(_view_srv_mac)
      memcpy(view_srv_mac, _view_srv_mac, sizeof(view_srv_mac));
  }
#endif

  /*
    Standard Interface
    Increase the hosts counter or Init the IP Addresses in case hosts are NULL
  */
  if(cli_host) {
    cli_host->incUses(), cli_host->incNumFlows(last_seen, true, isTCP());
    cli_host->incCliContactedPorts(_srv_port);
    cli_ip_addr = cli_host->get_ip();
  } else {
    /*
      View Interface
      Client host has not been allocated, let's keep the info in an IpAddress.
    */
    if((cli_ip_addr = new (std::nothrow) IpAddress(*_cli_ip)))
      cli_ip_addr->reloadBlacklist(iface->get_ndpi_struct());
  }

  if(srv_host) {
    /* Standard Interface */
    srv_host->incUses(), srv_host->incNumFlows(last_seen, false, isTCP());
    srv_host->incSrvPortsContacts(htons(_cli_port));
    srv_host->incSrvHostContacts(_cli_ip);
    srv_ip_addr = srv_host->get_ip();

    if(cli_host) {
      if(srv_ip_addr->isBroadcastAddress() || srv_ip_addr->isMulticastAddress()) {
	cli_host->setMACmeaningful();
      } else {
	if(cli_ip_addr->isIPv4()) {
	  u_int32_t c_ip = cli_ip_addr->get_ipv4();
	  u_int32_t d_ip = srv_ip_addr->get_ipv4();

	  if((ntohl(c_ip) & 0xFFFFFF00) == (ntohl(d_ip) & 0xFFFFFF00))
	    cli_host->setMACmeaningful(), srv_host->setMACmeaningful();
	}
      }
    }
  } else {
    /*
      View Interface
      Server host has not been allocated, let's keep the info in an IpAddress
    */
    if((srv_ip_addr = new (std::nothrow) IpAddress(*_srv_ip)))
      srv_ip_addr->reloadBlacklist(iface->get_ndpi_struct());
  }

  /* Update broadcast domain, if destination MAC address is broadcast */
  if(_cli_mac && _srv_mac &&
      _srv_mac->isBroadcast() /* Broadcast MAC address */
      && get_cli_ip_addr()->isIPv4() &&
      get_srv_ip_addr()->isIPv4() /* IPv4 only */
      && !get_srv_ip_addr()->isBroadcastAddress() /* Avoid 255.255.255.255 */)
    getInterface()->updateBroadcastDomains(_vlanId, _cli_mac->get_mac(), _srv_mac->get_mac(),
					   ntohl(_cli_ip->get_ipv4()), ntohl(_srv_ip->get_ipv4()));

  memset(&custom_app, 0, sizeof(custom_app));

#ifdef HAVE_NEDGE
  quota_exceeded = 0;
  HostPools *hp = iface->getHostPools();

  routing_table_id = DEFAULT_ROUTING_TABLE_ID;

  if(hp) {
    if(cli_host)
      routing_table_id = hp->getRoutingPolicy(cli_host->get_host_pool());
    if(srv_host)
      routing_table_id = max_val(routing_table_id, hp->getRoutingPolicy(srv_host->get_host_pool()));
  }
#endif

  passVerdict = 1;
  dropVerdictReason = DROP_REASON_UNKNOWN;
#ifdef ALERTED_FLOWS_DEBUG
  iface_alert_inc = iface_alert_dec = false;
#endif
  if((_first_seen > _last_seen) || (_first_seen == 0))
    _first_seen = _last_seen;

  first_seen = _first_seen, last_seen = _last_seen;
  bytes_thpt_trend = trend_unknown, pkts_thpt_trend = trend_unknown;

  memset(&ip_stats_s2d, 0, sizeof(ip_stats_s2d)),
    memset(&ip_stats_d2s, 0, sizeof(ip_stats_d2s));
  memset(&customFlowAlert, 0, sizeof(customFlowAlert));

  if(ntop->getPrefs()->fullStatsEnabled()
     && iface->isPacketInterface()
     && !iface->isSampledTraffic()) {
    /* Is this necessary? */
    cli2srvPktTime = new (std::nothrow) InterarrivalStats();
    srv2cliPktTime = new (std::nothrow) InterarrivalStats();

#ifdef ENABLE_ENTROPHY_CALCULATION
    initial_bytes_entropy.c2s = ndpi_alloc_data_analysis(256);
    initial_bytes_entropy.s2c = ndpi_alloc_data_analysis(256);
#endif
  } else {
    cli2srvPktTime = srv2cliPktTime = NULL;
#ifdef ENABLE_ENTROPHY_CALCULATION
    initial_bytes_entropy.c2s = initial_bytes_entropy.s2c = NULL;
#endif
  }

  c2sFirstGoodputTime.tv_sec = c2sFirstGoodputTime.tv_usec = 0;

#ifdef NTOPNG_PRO
  modbus = NULL;
  lateral_movement = false;
  periodicity_status = periodicity_status_unknown;
#ifndef HAVE_NEDGE
  trafficProfile = NULL;
#else
  cli2srv_in = cli2srv_out = srv2cli_in = srv2cli_out = DEFAULT_SHAPER_ID;
  memset(&flowShaperIds, 0, sizeof(flowShaperIds));
  cli_quota_source = srv_quota_source = policy_source_default;
#endif
#endif

  /* Reset the initial state */
  hash_entry_id = 0;
  set_hash_entry_state_allocated();

  switch (protocol) {
  case IPPROTO_TCP:
  case IPPROTO_UDP:
    if(iface->is_ndpi_enabled()) allocDPIMemory();

    if(protocol == IPPROTO_UDP) set_hash_entry_state_flow_notyetdetected();
    break;

  case IPPROTO_ICMP:
    ndpiDetectedProtocol.proto.app_protocol = NDPI_PROTOCOL_IP_ICMP,
      ndpiDetectedProtocol.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN;

    /* Use nDPI to check potential flow risks */
    if(iface->is_ndpi_enabled()) allocDPIMemory();
    set_hash_entry_state_flow_notyetdetected();
    break;

  case IPPROTO_ICMPV6:
    ndpiDetectedProtocol.proto.app_protocol = NDPI_PROTOCOL_IP_ICMPV6,
      ndpiDetectedProtocol.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN;

    /* Use nDPI to check potential flow risks */
    if(iface->is_ndpi_enabled()) allocDPIMemory();
    set_hash_entry_state_flow_notyetdetected();
    break;

  default:
    setDetectedProtocol(ndpi_guess_undetected_protocol(iface->get_ndpi_struct(), NULL, protocol), true);
    break;
  }

  computeKey();
  deferredInitialization();
}

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

/*
 * This function is used to initialize those info
 * that are not really necessary at the creation of the flow
 */
void Flow::deferredInitialization() {
  if(cli_host && srv_host) {
    char country[64];

    if(cli_host->isLocalHost()) {
      srv_host->get_country(country, sizeof(country));
      if(country[0] != '\0') cli_host->incCountriesContacts(country);

      Mac *srv_mac = srv_host->getMac();
      /* Add client gateway */
      if(srv_mac && !srv_mac->isNull() && !srv_host->isLocalHost()) {
        LocalHost *lh = (LocalHost *)cli_host;

        lh->setRouterMac(srv_mac);
      }
    }

    if(srv_host->isLocalHost()) {
      cli_host->get_country(country, sizeof(country));
      if(country[0] != '\0') srv_host->incCountriesContacts(country);
    }
  }

  if(isBlacklistedClient()) {
    if(srv_host) srv_host->inc_num_blacklisted_flows(false);
  } else if(isBlacklistedServer()) {
    if(cli_host) cli_host->inc_num_blacklisted_flows(true);
  }

  iface->execFlowBeginChecks(this);
}

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

void Flow::allocDPIMemory() {
  if((ndpiFlow = (ndpi_flow_struct *)calloc(1, iface->get_flow_size())) == NULL)
    throw "Not enough memory";
}

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

void Flow::freeDPIMemory() {
  if(ndpiFlow) {
    if(isDNS()) {
      if(ndpiFlow && iface->isPacketInterface()) {
	if((stats.get_cli2srv_packets() > 0) && (stats.get_srv2cli_packets() == 0)
	   && (ndpiFlow->protos.dns.is_query == 0)) {
	  request_swap(); /* This flow will be swapped */
	  swap();
	}
      }
    } else if(/* !isDNS() */ ntop->getPrefs()->is_dns_cache_enabled()) {
      if(srv_host) {
        /* Standard Interface */
        updateServerName(srv_host);
      }
    }

    if(ntop->getPrefs()->are_sites_collection_enabled() && (host_server_name != NULL)) {
      if(strchr(host_server_name, ':') == NULL /* No IPv6 or IP:port */) {
        const char *domain = ndpi_get_host_domain(iface->get_ndpi_struct(), host_server_name);
        int len = strlen(domain);

        if((len > 0)
          && (!isdigit(domain[len-1]))
          && (domain[0] != '_')
          && (strchr(domain, '.') != NULL)
          && (ndpi_strrstr(domain, ".local") == NULL)
          && (ndpi_strrstr(domain, ".arpa") == NULL)
          ) {
#ifdef DEBUG
          const char *ja4r = ndpiFlow->protos.tls_quic.ja4_client_raw ? ndpiFlow->protos.tls_quic.ja4_client_raw : "";
          char buf[64];
          char *client = get_cli_host()->get_ip()->printMask(buf, sizeof(buf), true);

          ntop->getTrace()->traceEvent(TRACE_INFO, "%s\t%s [%s] [%s]", client, domain, host_server_name, ja4r);
#endif

          ntop->getRedis()->hashSet("ntopng.domains", domain, host_server_name);
        }
      }
    }

    setRisk(ndpi_flow_risk_bitmap | ndpiFlow->risk);
    ndpi_confidence = ndpiFlow->confidence;

    if(tcp != NULL) {
      if((tcp->tcp_fingerprint == NULL) && ndpiFlow->tcp.fingerprint
	 && (cli_host || getViewSharedClient())) {
	Host *h = cli_host ? cli_host : getViewSharedClient();

#if 0
	char buf[64];

	ntop->getTrace()->traceEvent(TRACE_WARNING, "->> %s [%s][%s@%d]",
				     ndpiFlow->tcp.fingerprint,
				     ndpi_print_os_hint(ndpiFlow->tcp.os_hint),
				     h->get_ip()->print(buf, sizeof(buf)),
				     vlanId);
#endif

	h->setTCPfingerprint(ndpiFlow->tcp.fingerprint, ndpiFlow->tcp.os_hint);

	if((ndpiFlow->tcp.os_hint == ndpi_os_unknown)
	   && h->isLocalHost()
	   && ntop->getPrefs()->areFingerprintStatsEnabled()) {
	  char buf[64], log[128];

	  snprintf(log, sizeof(log), "%s,%s",
		   h->get_ip()->print(buf, sizeof(buf)),
		   Utils::OS2Str(h->getOS()));

	  ntop->getTrace()->traceEvent(TRACE_DEBUG, "** Unknown TCP fingerprint %s [%s]",
				       ndpiFlow->tcp.fingerprint, log);

	  ntop->getRedis()->hashSet(CONST_STR_UNKNOWN_TCP_FINGERPRINTS,
				    ndpiFlow->tcp.fingerprint, log);
	}

	tcp->tcp_fingerprint = strdup(ndpiFlow->tcp.fingerprint);
      }
    }

    ndpi_free_flow(ndpiFlow);
    ndpiFlow = NULL;
  }
}

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

Flow::~Flow() {
  bool is_oneway_tcp_udp_flow =
    (((protocol == IPPROTO_TCP) || (protocol == IPPROTO_UDP)) && isOneWay()) ? true : false;

#ifdef NTOPNG_PRO
#if 0  // DEBUG
  if (rtp)
    rtp->computeMOS(this);
#endif
#endif

  if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[delete] %s", __FILE__);
  if(getUses() != 0 && !ntop->getGlobals()->isShutdown())
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s] Deleting flow [%u]",
                                 __FUNCTION__, getUses());

  /* Delete triggered alerts for the flow */
  for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++)
    delete it->second;

  accountFlowTraffic(true);

#ifdef ALERTED_FLOWS_DEBUG
  if(iface_alert_inc && !iface_alert_dec) {
    char buf[256];

    ntop->getTrace()->traceEvent(TRACE_WARNING, "[MISMATCH][inc but not dec][alerted: %u] %s",
				 isFlowAlerted() ? 1 : 0, print(buf, sizeof(buf)));
  }
#endif

  /*
    Get client and server hosts. Use unsafe* methods to get the client and
    server also for 'viewed' interfaces. For 'Viewed' interfaces, host pointers
    are shared across multiple 'viewed' interfaces and thus they are termed as
    unsafe.

    IMPORTANT: only call here methods that are safe (e.g., locked or atomic-ed).

    It is fundamental to only call
  */
  Host *cli_u = getViewSharedClient(), *srv_u = getViewSharedServer();

#ifdef NTOPNG_PRO
  if(getInterface()->isViewed()) /* Score decrements done here for 'viewed'
                                     interfaces to avoid races. */
    decAllFlowScores();
#endif

  if(cli_u) {
    cli_u->updateView(cli_ip_addr);
    cli_u->decUses(); /* Decrease the number of uses */
    cli_u->decNumFlows(get_last_seen(), true, isTCP(), twh_over);

    if(is_oneway_tcp_udp_flow) cli_u->incUnidirectionalEgressTCPUDPFlows();
  }

  if(!cli_host && cli_ip_addr) /* Dynamically allocated only when cli_host was NULL in Flow
				   constructor (viewed interfaces) */
    delete cli_ip_addr;

  if(srv_u) {
    srv_u->updateView(srv_ip_addr);
    srv_u->decUses(); /* Decrease the number of uses */
    srv_u->decNumFlows(get_last_seen(), false, isTCP(), twh_over);

    if(is_oneway_tcp_udp_flow) {
      srv_u->incUnidirectionalIngressTCPUDPFlows();

      if(cli_u) {
        u_int16_t s_port = ntohs(srv_port);

        cli_u->setUnidirectionalTCPUDPNoTXEgressFlow(srv_u->get_ip(), s_port);
        srv_u->setContactedTCPUDPServerPortNoTX(s_port);
        srv_u->setUnidirectionalTCPUDPNoTXIngressFlow(cli_u->get_ip(),
                                                      ntohs(srv_port));
      }
    }
  }

  if(!srv_host && srv_ip_addr) /* Dynamically allocated only when srv_host was NULL in Flow
				  constructor (viewed interfaces) */
    delete srv_ip_addr;

  /*
    Finish deleting other flow data structures
  */

  freeDPIMemory();

  if(tcp != NULL) {
    ndpi_free_data_analysis(&tcp->tcpWin.cli_to_srv, 0);
    ndpi_free_data_analysis(&tcp->tcpWin.srv_to_cli, 0);
    ndpi_free_data_analysis(&tcp->rtt.cli_to_srv, 0);
    ndpi_free_data_analysis(&tcp->rtt.srv_to_cli, 0);

    if(tcp->tcp_fingerprint) free(tcp->tcp_fingerprint);

    free(tcp);
    tcp = NULL;
  }

#ifdef NTOPNG_PRO
  if(udp != NULL) {
    ndpi_free_data_analysis(&udp->rtt.cli_min_rtt, 0);
    ndpi_free_data_analysis(&udp->rtt.srv_min_rtt, 0);

    free(udp);
    udp = NULL;
  }
#endif

#ifdef NTOPNG_PRO
  if (rtp) delete(rtp);
#endif

  if(riskInfo) free(riskInfo);
  if(end_reason) free(end_reason);

  if(collection) {
    if(collection->wifi.wlan_ssid) free(collection->wifi.wlan_ssid);
    free(collection);
  }

#ifdef NTOPNG_PRO
  if(viewFlowStats) delete (viewFlowStats);
#endif

  if(periodic_stats_update_partial) delete (periodic_stats_update_partial);
  if(last_db_dump.partial) delete (last_db_dump.partial);
  if(json_info) json_object_put(json_info);

  if(tlv_info) {
    ndpi_term_serializer(tlv_info);
    free(tlv_info);
  }

  if(host_server_name) free(host_server_name);
  if(iec104) delete iec104;
#ifdef NTOPNG_PRO
  if(modbus) delete modbus;
#endif

  if(suspicious_dga_domain) free(suspicious_dga_domain);
  if(ndpiAddressFamilyProtocol) free(ndpiAddressFamilyProtocol);
  if(ebpf) delete ebpf;

  if(cli2srvPktTime) delete cli2srvPktTime;
  if(srv2cliPktTime) delete srv2cliPktTime;

#ifdef ENABLE_ENTROPHY_CALCULATION
  if(initial_bytes_entropy.c2s)
    ndpi_free_data_analysis(initial_bytes_entropy.c2s, 1);
  if(initial_bytes_entropy.s2c)
    ndpi_free_data_analysis(initial_bytes_entropy.s2c, 1);
#endif
  
  if(flow_payload) free(flow_payload);

  if(isHTTP() || isHTTP_PROXY()) {
    if(protos.http.last_url)        free(protos.http.last_url);
    if(protos.http.last_user_agent) free(protos.http.last_user_agent);
    if(protos.http.last_server)     free(protos.http.last_server);
  } else if(isDNS()) {
    if(protos.dns.last_query)         free(protos.dns.last_query);
    if(protos.dns.last_query_shadow)  free(protos.dns.last_query_shadow);
    if(protos.dns.last_rsp)           free(protos.dns.last_rsp);
    if(protos.dns.last_rsp_shadow)    free(protos.dns.last_rsp_shadow);
  } else if(isMDNS()) {
    if(protos.mdns.answer)   free(protos.mdns.answer);
    if(protos.mdns.name)     free(protos.mdns.name);
    if(protos.mdns.name_txt) free(protos.mdns.name_txt);
    if(protos.mdns.ssid)     free(protos.mdns.ssid);
  } else if(isSSDP()) {
    if(protos.ssdp.location) free(protos.ssdp.location);
  } else if(isNetBIOS()) {
    if(protos.netbios.name)  free(protos.netbios.name);
  } else if(isSIP()) {
    if(protos.sip.call_id)   free(protos.sip.call_id);
  } else if(isSSH()) {
    if(protos.ssh.client_signature)  free(protos.ssh.client_signature);
    if(protos.ssh.server_signature)  free(protos.ssh.server_signature);
    if(protos.ssh.hassh.client_hash) free(protos.ssh.hassh.client_hash);
    if(protos.ssh.hassh.server_hash) free(protos.ssh.hassh.server_hash);
  } else if(isTLS()) {
    if(protos.tls.client_requested_server_name)
      free(protos.tls.client_requested_server_name);
    if(protos.tls.server_names) free(protos.tls.server_names);
    if(protos.tls.ja4.client_hash) free(protos.tls.ja4.client_hash);
    if(protos.tls.client_alpn) free(protos.tls.client_alpn);
    if(protos.tls.client_tls_supported_versions)
      free(protos.tls.client_tls_supported_versions);
    if(protos.tls.issuerDN) free(protos.tls.issuerDN);
    if(protos.tls.subjectDN) free(protos.tls.subjectDN);
  } else if(isMining()) {
    if(protos.mining.currency) free(protos.mining.currency);
  } else if(isDHCP()) {
    if(protos.dhcp.name) free(protos.dhcp.name);
  } else if(isSIP()) {
    if(protos.sip.call_id) free(protos.sip.call_id);
  } else if(isSMTP()) {
    if(protos.smtp.mail_from) free(protos.smtp.mail_from);
    if(protos.smtp.rcpt_to)   free(protos.smtp.rcpt_to);
  }

  if(ndpiFlowRiskName) free(ndpiFlowRiskName);

  if(bt_hash) free(bt_hash);
  if(stun_mapped_address)   free(stun_mapped_address);

  if(icmp_info)             delete (icmp_info);
  if(json_protocol_info)    free(json_protocol_info);
  if(alerts_json)           free(alerts_json);
  if(alerts_json_shadow)    free(alerts_json_shadow);
  if(external_alert.json)   json_object_put(external_alert.json);
  if(external_alert.source) free(external_alert.source);

  if(customFlowAlert.msg)   free(customFlowAlert.msg);
}

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

u_int16_t Flow::getStatsProtocol() const {
  u_int16_t stats_protocol;

  if(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN &&
      !ndpi_is_subprotocol_informative(ndpiDetectedProtocol.proto.master_protocol))
    stats_protocol = ndpiDetectedProtocol.proto.app_protocol;
  else
    stats_protocol = ndpiDetectedProtocol.proto.master_protocol;

  return (stats_protocol);
}

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

/* This function is called as soon as the protocol detection is
 * completed. See processExtraDissectedInformation for a later check.
 * NOTE: does NOT need ndpiFlow
 */
void Flow::processDetectedProtocol(u_int8_t *payload, u_int16_t payload_len) {
  u_int16_t l7proto;
  u_int16_t stats_protocol;
  Host *cli_h = NULL, *srv_h = NULL;
  char *domain_name = NULL;

  /*
    If peers should be swapped, then pointers are inverted.
    NOTE: only function pointers are inverted, not pointers in the flow.
  */
  get_actual_peers(&cli_h, &srv_h);

  stats_protocol = getStatsProtocol();

  /* Update the active flows stats */
  if(cli_h) cli_h->incnDPIFlows(stats_protocol);
  if(srv_h) srv_h->incnDPIFlows(stats_protocol);
  iface->incnDPIFlows(stats_protocol);

  l7proto = ndpi_get_lower_proto(ndpiDetectedProtocol);

  /* Domain Concats Alert */
  if(ndpiFlow)
    domain_name = ndpi_get_flow_name(ndpiFlow),
      confidence = ndpiFlow->confidence;

  if(cli_h && domain_name && domain_name[0] != '\0')
    cli_h->addContactedDomainName(domain_name);

  /* NOTE: UDP flows are updated when the flow ends */
  if(tcp != NULL)
    updateTCPHostServices(cli_h, srv_h);
  else if(isUDP()) {
    /* Check direction first */
    if(payload && (get_packets() == 1)) {
      switch (l7proto) {
      case NDPI_PROTOCOL_NTP:
	if(payload_len > 1) {
	  u_int8_t mode = payload[0] & 0x07;

	  if(mode == 4 /* server -> client */) {
	    request_swap(); /* This flow will be swapped */
	    swap();
	  }
	}
	break;

      case NDPI_PROTOCOL_DNS:
	if(payload_len > 4) {
	  if((payload[3] & 0x80) == 0x80) {
	    /* server -> client */
	    request_swap(); /* This flow will be swapped */
	    swap();
	  }
	}
	break;
      }
    }
  } else if(isICMP()) {
    if(payload_len > 0) {
      u_int8_t icmp_type = payload[0];
      
      if(icmp_type == 0 /* ICMP Echo Reply */) {
	/* server -> client */
	request_swap(); /* This flow will be swapped */
	swap();
      }
    }
  }
  
  if(ndpiFlow && (ndpiFlow->tcp.os_hint != ndpi_os_unknown)) {
    Host *h = cli_h ? cli_h : getViewSharedClient() /* View interface */;

    if(h != NULL)
      h->setnDPIOS(ndpiFlow->tcp.os_hint);
  }
}

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

/* This function is called as soon as the protocol detection is
 * completed to process nDPI-dissected data (only for packet interfaces).
 * NOTE: needs ndpiFlow
 */
void Flow::processDetectedProtocolData() {
  u_int16_t l7proto;
  Host *cli_h = NULL, *srv_h = NULL;
  /*
    Make sure to actual client and server to avoid setting wrong names (e.g.,
    set the server name to the client)
    https://github.com/ntop/ntopng/issues/5506
  */
  get_actual_peers(&cli_h, &srv_h);

  if(ndpiFlow == NULL) return;

  l7proto = ndpi_get_lower_proto(ndpiDetectedProtocol);

  if((l7proto != NDPI_PROTOCOL_DNS)
      && (l7proto != NDPI_PROTOCOL_DHCP) /* host_server_name in DHCP is for the
					    client name, not the server */
      && (ndpiFlow->host_server_name[0] != '\0') && (!host_server_name)) {
    Utils::sanitizeHostName((char *)ndpiFlow->host_server_name);

    if(ndpi_is_proto(ndpiDetectedProtocol.proto, NDPI_PROTOCOL_HTTP)) {
      char *double_column = strrchr((char *)ndpiFlow->host_server_name, ':');

      if(double_column) double_column[0] = '\0';
    }

    /*
      Host server name equals the Host: HTTP header field.
    */
    host_server_name = strdup((char *)ndpiFlow->host_server_name);
  }

  switch (l7proto) {
  case NDPI_PROTOCOL_BITTORRENT:
    if(!bt_hash) setBittorrentHash((char *)ndpiFlow->protos.bittorrent.hash,
				   sizeof(ndpiFlow->protos.bittorrent.hash));
    break;

  case NDPI_PROTOCOL_MDNS:
    /* protos.mdns.{answer,name} already propagated to hosts in
     * Flow::hosts_periodic_stats_update  */
    break;

  case NDPI_PROTOCOL_TOR:
  case NDPI_PROTOCOL_TLS:
  case NDPI_PROTOCOL_QUIC:
    if(ndpiFlow->host_server_name[0] != '\0') {
      if((ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_DOH_DOT)
	  && cli_h
	  && cli_h->isLocalHost())
	cli_h->incrVisitedWebSite(ndpiFlow->host_server_name);

      if(cli_h) cli_h->incContactedService(ndpiFlow->host_server_name);
#if 0
      /*
	Commented out as it will be eventually set below by (***)
	This should prevent misclassifying host names
      */
      if(srv_h) srv_h->setServerName(host_server_name);
#endif
    }
    break;

  case NDPI_PROTOCOL_HTTP:
  case NDPI_PROTOCOL_HTTP_PROXY:
    if(ndpiFlow->http.url) {
      if(!protos.http.last_url)
	protos.http.last_url = strdup(ndpiFlow->http.url);

      if((!protos.http.last_user_agent) && ndpiFlow->http.user_agent)
	protos.http.last_user_agent = strdup(ndpiFlow->http.user_agent);

      setHTTPMethod(ndpiFlow->http.method);
    }

    break;
  } /* switch */
}

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

/* This is called only once per Flow, when all the protocol information,
 * including extra dissection information (e.g. the TLS certificate), is
 * available. */
void Flow::processExtraDissectedInformation() {
  if(ndpiFlow) {
    if(isSSH()) {
      if(protos.ssh.client_signature == NULL &&
          !Utils::isEmptyString(ndpiFlow->protos.ssh.client_signature))
	protos.ssh.client_signature =
	  strdup(ndpiFlow->protos.ssh.client_signature);
      if(protos.ssh.server_signature == NULL &&
          !Utils::isEmptyString(ndpiFlow->protos.ssh.server_signature))
	protos.ssh.server_signature =
	  strdup(ndpiFlow->protos.ssh.server_signature);

      if(protos.ssh.hassh.client_hash == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.ssh.hassh_client)) {
	protos.ssh.hassh.client_hash =
	  strdup(ndpiFlow->protos.ssh.hassh_client);
	updateHASSH(true /* As client */);
      }

      if(protos.ssh.hassh.server_hash == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.ssh.hassh_server)) {
	protos.ssh.hassh.server_hash =
	  strdup(ndpiFlow->protos.ssh.hassh_server);
	updateHASSH(false /* As server */);
      }
    } else if(isTLS()) {
      protos.tls.tls_version = ndpiFlow->protos.tls_quic.ssl_version;

      protos.tls.notBefore = ndpiFlow->protos.tls_quic.notBefore,
        protos.tls.notAfter = ndpiFlow->protos.tls_quic.notAfter;

      if(protos.tls.client_requested_server_name == NULL &&
         !Utils::isEmptyString(ndpiFlow->host_server_name)) {
	protos.tls.client_requested_server_name = strdup(ndpiFlow->host_server_name);
	/* Now some minor cleanup */
	char *c;

	if((c = strchr(protos.tls.client_requested_server_name, ',')) !=   NULL)
	  c[0] = '\0';
	else if((c = strchr(protos.tls.client_requested_server_name, ' ')) !=
		 NULL)
	  c[0] = '\0';
      }

      if(protos.tls.server_names == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.tls_quic.server_names))
	protos.tls.server_names = strdup(ndpiFlow->protos.tls_quic.server_names);

      if(protos.tls.client_alpn == NULL) {
	if(!Utils::isEmptyString(ndpiFlow->protos.tls_quic.negotiated_alpn))
	  protos.tls.client_alpn = strdup(ndpiFlow->protos.tls_quic.negotiated_alpn);
	else if(!Utils::isEmptyString(ndpiFlow->protos.tls_quic.advertised_alpns))
	  protos.tls.client_alpn = strdup(ndpiFlow->protos.tls_quic.advertised_alpns);
      }

      if(protos.tls.client_tls_supported_versions == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.tls_quic.tls_supported_versions))
	protos.tls.client_tls_supported_versions = strdup(ndpiFlow->protos.tls_quic.tls_supported_versions);

      if(protos.tls.issuerDN == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.tls_quic.issuerDN))
	protos.tls.issuerDN = strdup(ndpiFlow->protos.tls_quic.issuerDN);

      if(protos.tls.subjectDN == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.tls_quic.subjectDN))
	protos.tls.subjectDN = strdup(ndpiFlow->protos.tls_quic.subjectDN);

      if(protos.tls.ja4.client_hash == NULL &&
	  !Utils::isEmptyString(ndpiFlow->protos.tls_quic.ja4_client)) {
	protos.tls.ja4.client_hash = strdup(ndpiFlow->protos.tls_quic.ja4_client);
	updateCliJA4();
      }

    } else if(isDNS()) {
      if(srv_host && (ndpiFlow->protos.dns.reply_code == 0 /* No Error */)) {
	/* Now we need to check if the requested IP matches the server host */

	if((ndpiFlow->protos.dns.rsp_type == 0x0C /* PTR */) &&
	    (ndpiFlow->protos.dns.ptr_domain_name[0] != '\0')) {
	  u_int len = strlen((char *)ndpiFlow->host_server_name);
	  IpAddress *addr = srv_host->get_ip();

	  if(len > 13) {
	    if(addr->isIPv4() &&
		(strcmp(&ndpiFlow->host_server_name[len - 13],
			".in-addr.arpa") == 0)) {
	      /* 130.197.62.178.in-addr.arpa */
	      int a, b, c, d;

	      if(sscanf(ndpiFlow->host_server_name, "%d.%d.%d.%d", &d, &c,
			 &b, &a) == 4) {
		char buf[32];
		u_int32_t ipv4_addr;

		snprintf(buf, sizeof(buf), "%u.%u.%u.%u", a, b, c, d);
		ipv4_addr = ntohl(inet_addr(buf));

		if(addr->equal(ipv4_addr))
		  srv_host->setResolvedName((char *)ndpiFlow->protos.dns.ptr_domain_name);
		else {
		  /* This is not the right IPv4 host: let's cache it for later */

		  ntop->getRedis()->setResolvedAddress(buf, (char *)ndpiFlow->protos.dns.ptr_domain_name);
		}
	      }
	    } else if(strcmp(&ndpiFlow->host_server_name[len - 9],
			      ".ip6.arpa") == 0) {
	      /* 1.0.0.4.0.6.3.0.0.0.0.0.0.0.0.0.0.d.0.0.2.0.0.0.0.c.0.b.3.0.a.2.ip6.arpa
	       */
	      int a, b;
	      int i = 15;
	      char *tmp,
		*item = strtok_r(ndpiFlow->host_server_name, ".", &tmp);
	      struct ndpi_in6_addr ipv6_addr;

	      while (item != NULL) {
		a = strtol(item, NULL, 16);
		item = strtok_r(NULL, ".", &tmp);
		if(item) {
		  b = strtol(item, NULL, 16);

		  ipv6_addr.u6_addr.u6_addr8[i] = (b << 4) + a;

		  if(--i < 0)
		    break;
		  else
		    item = strtok_r(NULL, ".", &tmp);
		}
	      } /* while */

	      if(addr->equal(&ipv6_addr))
		srv_host->setResolvedName((char *)ndpiFlow->protos.dns.ptr_domain_name);
	      else {
		char buf[64];

		/* This is not the right IPv6 host: let's cache it for later
		 */
		ntop->getRedis()->setResolvedAddress(Utils::intoaV6(ipv6_addr, 128, buf, sizeof(buf)),
						     (char *)ndpiFlow->protos.dns.ptr_domain_name);
	      }
	    }
	  }
	}
      }
    } else if(isMining()) {
      if(protos.mining.currency == NULL &&
          !Utils::isEmptyString(ndpiFlow->protos.mining.currency))
        protos.mining.currency = strdup(ndpiFlow->protos.mining.currency);

      /* ntop->getTrace()->traceEvent(TRACE_NORMAL, "-->>> %s", ndpiFlow->protos.mining.currency); */
    } else if(isHTTP() || isHTTP_PROXY()) {
      if(protos.http.last_url) {
	ndpi_risk_enum risk = ndpi_validate_url(protos.http.last_url);

	if((risk != NDPI_NO_RISK) && (risk < NDPI_MAX_RISK))
	  addRisk(((ndpi_risk)2) << (risk - 1));
      }

      if(protos.http.last_server == NULL &&
          !Utils::isEmptyString(ndpiFlow->http.server))
	protos.http.last_server = strdup(ndpiFlow->http.server);

      if(ndpiFlow->http.response_status_code == 200) {
	if(srv_host
	    && (ndpiFlow->host_server_name[0] != '\0')
	    && (ndpiFlow->http.nat_ip == NULL) /* THis is not a proxy */
	    )
	  srv_host->setServerName(host_server_name);

	if(ndpiFlow->host_server_name[0] != '\0') {
	  char *doublecol, delimiter = ':';

	  /* If <host>:<port> we need to remove ':' */
	  if((doublecol = (char *)strchr((const char *)ndpiFlow->host_server_name, delimiter)) != NULL)
	    doublecol[0] = '\0';

	  if(cli_host) {
	    cli_host->incContactedService((char *)ndpiFlow->host_server_name);

	    if(ndpiFlow->http.detected_os)
	      cli_host->inlineSetOSDetail((char *)ndpiFlow->http.detected_os); /* Learnt from User-Agent */

	    if(cli_host->isLocalHost())
	      cli_host->incrVisitedWebSite((char *)ndpiFlow->host_server_name);
	  }
	}
      }
    } else if(isSTUN()) {
      if(stun_mapped_address == NULL &&
         ndpiFlow->stun.mapped_address.port != 0) {
	IpAddress ip;
	char tmp[96], ipb[64];

	if(ndpiFlow->stun.mapped_address.is_ipv6)
	  ip.set((struct in6_addr*)&ndpiFlow->stun.mapped_address.address);
	else
	  ip.set(ndpiFlow->stun.mapped_address.address.v4);

	snprintf(tmp, sizeof(tmp),"%s:%u",
		 ip.print(ipb, sizeof(ipb)), ndpiFlow->stun.mapped_address.port);
	stun_mapped_address = strdup(tmp);
      }
    }

    updateSuspiciousDGADomain();
    // updateHostBlacklists();
  }

#if defined(NTOPNG_PRO)
  getInterface()->updateFlowPeriodicity(this); /* <- here we eventually set is_periodic_flow */

  if(is_periodic_flow) {
    ndpi_risk_enum risk = NDPI_PERIODIC_FLOW;
    ndpi_risk r_bitmap = ((ndpi_risk)2) << (risk - 1);

    if(get_ndpi_flow() != NULL) {
      char msg[32];

      snprintf(msg, sizeof(msg), "%u sec", periodicity);
      ndpi_set_risk(iface->get_ndpi_struct(), get_ndpi_flow(), NDPI_PERIODIC_FLOW, msg);
    }

    setRisk(r_bitmap);
  }

  getInterface()->updateServiceMap(this);
#endif

  if(get_ndpi_flow()) {
    /* Save riskInfo */
    char *out, buf[512];
    struct ndpi_flow_struct *f = get_ndpi_flow();

    out = ndpi_get_flow_risk_info(f, buf, sizeof(buf), 1 /* JSON */);

    if(out != NULL) setJSONRiskInfo(out);

    flow_payload = f->flow_payload, flow_payload_len = f->flow_payload_len;
    ndpiFlow->flow_payload = NULL; /* ntopng will free this memory not nDPI */
  }

  /* Read this data before freeing nDPI */
  if(get_ndpi_flow() != NULL) {
    u_int8_t flow_types = get_ndpi_flow()->flow_multimedia_types;

    if(flow_types != ndpi_multimedia_unknown_flow) {
      if(flow_types & ndpi_multimedia_audio_flow)
	setRTPStreamType(ndpi_multimedia_audio_flow);
      else if(flow_types & ndpi_multimedia_video_flow)
	setRTPStreamType(ndpi_multimedia_video_flow);
      else if(flow_types & ndpi_multimedia_screen_sharing_flow)
	setRTPStreamType(ndpi_multimedia_screen_sharing_flow);
    }
  }

  /*
    We need to change state here as in Lua scripts we need to know
    all metadata available
  */
  set_hash_entry_state_flow_protocoldetected();
}

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

bool Flow::needsExtraDissection() {
  ndpi_flow_struct *ndpif;

  /* NOTE: do not check hasDissectedTooManyPackets() here, otherwise
   * ndpi_detection_giveup won't be called. */
  return ((ndpif = get_ndpi_flow())
	  && (!extra_dissection_completed)
	  && (ndpi_extra_dissection_possible(iface->get_ndpi_struct(), ndpif)));
}

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

/* Process a packet and advance the flow detection state. */
void Flow::processPacket(bool src2dst_direction,
			 const struct pcap_pkthdr *h, const u_char *ip_packet,
                         u_int16_t ip_len, u_int64_t packet_time,
                         u_int8_t *payload, u_int16_t payload_len,
                         u_int16_t src_port) {
  bool detected;
  ndpi_protocol proto_id;
  /* Note: do not call endProtocolDissection before
   * ndpi_detection_process_packet. In case of early giveup (e.g. sampled
   * traffic), nDPI should process at least one packet in order to be able to
   * guess the protocol. */

  proto_id = ndpi_detection_process_packet(iface->get_ndpi_struct(), ndpiFlow, ip_packet, ip_len, packet_time, NULL);

  if((ndpi_flow_risk_bitmap != 0) && (ndpiFlow->risk == 0)) {
    /*
      Probably an exception has cleared the risk that was previously
      stored in ntopng so we need to rollback also the risk in ntopng
    */

    ndpi_flow_risk_bitmap = 0;
  }

  detected = ndpi_is_protocol_detected(proto_id);

  if(!detected && hasDissectedTooManyPackets()) {
    /*
      Perform a giveup and finalize all additional operations such as
      the processing of extra dissection data.
    */
    endProtocolDissection(src2dst_direction);
  }

#ifdef NTOPNG_PRO
  // Update the profile even if the detection is not yet completed.
  // Indeed, even if the L7 detection is not yet completed
  // the flow already carries information on all the other fields,
  // e.g., IP src and DST, vlan, L4 proto, etc
#ifndef HAVE_NEDGE
  updateProfile();
#endif
#endif

  if(payload_len > 0) non_zero_payload_observed = 1;

  if(detected) {
    if(ndpiFlow->risk != 0) {
      if(srv_host) {
        /* Ignore unsafe protocols for broadcast packets (e.g. SMBv1) */
        Mac *srv_mac = srv_host->getMac();

        if(srv_mac && srv_mac->isBroadcast()) {
          ndpi_risk r = ((ndpi_risk)1) << NDPI_UNSAFE_PROTOCOL;

          if((ndpiFlow->risk & r) == r)
            ndpiFlow->risk &= ~r; /* Clear the bit */
        }
      }

      if(cli_host) {
        if(NDPI_ISSET_BIT(ndpiFlow->risk, NDPI_HTTP_CRAWLER_BOT))
          cli_host->setCrawlerBotScannerHost();
      }

      addRisk(ndpiFlow->risk);
    }

    updateProtocol(proto_id);
    setProtocolDetectionCompleted(payload, payload_len, h->ts.tv_sec);
  }

  /*
    Perform other protocol-specific processing before marking the detection as
    completed. For example, for the DNS, additional processing on the query is
    performed and later used by checks.
  */

  if(isDNS())
    processDNSPacket(ip_packet, ip_len, packet_time);
  else if(isIEC60870())
    processIEC60870Packet((htons(src_port) == 2404) ? true : false, payload,
                          payload_len, h);

  if(detection_completed) {
    if(!needsExtraDissection()) {
      setExtraDissectionCompleted(src2dst_direction);
      updateProtocol(proto_id);
    }
  }
}

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

extern "C" {
  int current_pkt_from_client_to_server(const struct ndpi_detection_module_struct *ndpi_str, const struct ndpi_flow_struct *flow);
};

/* Special handling of DNS which is always performed. */
void Flow::processDNSPacket(const u_char *ip_packet, u_int16_t ip_len,
                            u_int64_t packet_time) {
  ndpi_protocol proto_id;

  /* Exits if the flow isn't DNS or it the interface is not a packet-interface */
  if((!isDNS())
      || (!getInterface()->isPacketInterface())
      || (ndpiFlow == NULL))
    return;

  /* Instruct nDPI to continue the dissection
     See
     https://github.com/ntop/ntopng/commit/30f52179d9f7a1eb774534def93d55c77d6070bc#diff-20b1df29540b6de59ceb6c6d2f3afdb5R387
  */
  ndpiFlow->max_extra_packets_to_check = 10;

  proto_id = ndpi_detection_process_packet(iface->get_ndpi_struct(), ndpiFlow, ip_packet, ip_len, packet_time, NULL);

  /*
    A DNS flow won't change to a non-DNS flow. However, this check is
    just in case for safety. What can change is the application protocol, e.g.,
    a DNS.Google can become DNS.Facebook.
  */
  switch (ndpi_get_lower_proto(proto_id)) {
  case NDPI_PROTOCOL_DNS:
    ndpiDetectedProtocol = proto_id; /* Override! */

    if(ndpiFlow->host_server_name[0] != '\0') {
      std::string addresses;

      if(cli_host && (ndpiFlow->protos.dns.reply_code == 0 /* no Error */)) {
	cli_host->incContactedService((char *)ndpiFlow->host_server_name);
	cli_host->incrVisitedWebSite((char *)ndpiFlow->host_server_name);
      }

      for(u_int i=0; i<ndpiFlow->protos.dns.num_rsp_addr; i++) {
	char buf[64];

	if(ndpiFlow->protos.dns.is_rsp_addr_ipv6[i] == 0)
	  inet_ntop(AF_INET, &ndpiFlow->protos.dns.rsp_addr[i].ipv4, buf, sizeof(buf));
	else
	  inet_ntop(AF_INET6, &ndpiFlow->protos.dns.rsp_addr[i].ipv6, buf, sizeof(buf));

	if(i > 0) addresses += ",";
	addresses += buf;
      }

      setDNSQuery(ndpiFlow->host_server_name, (char*)addresses.c_str(), true);

      if(ndpiFlow->protos.dns.query_type != 0)
	protos.dns.last_query_type = ndpiFlow->protos.dns.query_type;

      if(!ndpiFlow->protos.dns.is_query) {
	/* this is a response... */
	if(ntop->getPrefs()->is_dns_decoding_enabled()) {
	  char delimiter = '@', *name = NULL;
	  char *at = (char *)strchr((const char *)ndpiFlow->host_server_name, delimiter);

	  protos.dns.last_return_code = ndpiFlow->protos.dns.reply_code;

	  /* Consider only positive DNS replies */
	  if(at != NULL)
	    name = &at[1], at[0] = '\0';
	  else if((!strstr((const char *)ndpiFlow->host_server_name,  ".in-addr.arpa")) &&
		   (!strstr((const char *)ndpiFlow->host_server_name, ".ip6.arpa")))
	    name = (char *)ndpiFlow->host_server_name;

	  if(name) {
#if 0
	    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[DNS] %s [query_type: %u][reply_code: %u][is_query: %u][num_queries: %u][num_answers: %u]",
					 (char*)ndpiFlow->host_server_name,
					 ndpiFlow->protos.dns.query_type,
					 ndpiFlow->protos.dns.reply_code,
					 ndpiFlow->protos.dns.is_query ? 1 : 0,
					 ndpiFlow->protos.dns.num_queries,
					 ndpiFlow->protos.dns.num_answers);
#endif

	    if(ndpiFlow->protos.dns.reply_code == 0) {
	      if(ndpiFlow->protos.dns.num_answers > 0) {
		if(at != NULL) {
		  // ntop->getTrace()->traceEvent(TRACE_NORMAL, "[_NS] %s <-> %s", name, (char*)ndpiFlow->host_server_name);
		  ntop->getRedis()->setResolvedAddress(name, (char *)ndpiFlow->host_server_name);
		}
	      }
	    }
	  }
	}
      }
    }

#ifdef HAVE_NEDGE
    updateFlowShapers(false);
#endif
    break;
  default:
    break;
  }

#if 0
  char buf[256];
  ntop->getTrace()->traceEvent(TRACE_ERROR, "%s %s", ndpiFlow->host_server_name[0] != '\0' ? ndpiFlow->host_server_name : (unsigned char*)"", print(buf, sizeof(buf)));
#endif
}

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

/* Special handling of IEC60870 which is always performed. */
void Flow::processIEC60870Packet(bool tx_direction, const u_char *payload,
                                 u_int16_t payload_len,
				 const struct pcap_pkthdr *h) {
  /* Exits if the flow isn't IEC60870 or it the interface is not a
   * packet-interface */
  if(!isIEC60870() || (!getInterface()->isPacketInterface()) ||
      (payload_len < 6))
    return;

  if(!iec104) iec104 = new (std::nothrow) IEC104Stats();

  if(iec104)
    iec104->processPacket(this, tx_direction, payload, payload_len, h);
}

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

/* End the nDPI dissection on a flow. Guess the protocol if not already
 * detected. It is safe to call endProtocolDissection() multiple times. */
void Flow::endProtocolDissection(bool src2dst_direction) {
  if(!detection_completed) {
    u_int8_t proto_guessed;

    updateProtocol(ndpi_detection_giveup(iface->get_ndpi_struct(), ndpiFlow,
                                         &proto_guessed));
    setRisk(ndpi_flow_risk_bitmap | ndpiFlow->risk);
    setProtocolDetectionCompleted(NULL, 0, iface->getTimeLastPktRcvd());
  }

  if(!extra_dissection_completed) setExtraDissectionCompleted(src2dst_direction);
}

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

/* Manually set a protocol on the flow and terminate the dissection. */
void Flow::setDetectedProtocol(ndpi_protocol proto_id, bool src2dst_direction) {
  updateProtocol(proto_id);
  setProtocolDetectionCompleted(NULL, 0, iface->getTimeLastPktRcvd());

  endProtocolDissection(src2dst_direction);
}

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

/* Called when the extra dissection on the flow is completed. */
void Flow::setExtraDissectionCompleted(bool src2dst_direction) {
  if(extra_dissection_completed)
    return;

  if(!detection_completed) {
    ntop->getTrace()->traceEvent(TRACE_ERROR,
				 "Bad internal status: setExtraDissectionCompleted called before "
				 "setDetectedProtocol");
    return;
  } else if(needsExtraDissection()) {
    u_int8_t proto_guessed;

    updateProtocol(ndpi_detection_giveup(iface->get_ndpi_struct(), ndpiFlow,
                                         &proto_guessed));
    setRisk(ndpi_flow_risk_bitmap | ndpiFlow->risk);
  }

  if(ndpiFlow) {
    if((protocol == IPPROTO_TCP) || (protocol == IPPROTO_UDP) ||
       (protocol == IPPROTO_ICMP) || (protocol == IPPROTO_ICMPV6)) {
      /*
	nDPI is not allocated for non-TCP non-UDP flows so, in order to
	make sure custom cateories are properly populated, function
	ndpi_fill_ip_protocol_category must be called explicitly.
      */
      u_int16_t p, *p_ptr;

      if(get_cli_ip_addr()->get_ipv4() /* && get_srv_ip_addr()->get_ipv4() */)
	ndpi_fill_ip_protocol_category(iface->get_ndpi_struct(),
				       ndpiFlow,
				       get_cli_ip_addr()->get_ipv4(),
				       get_srv_ip_addr()->get_ipv4(),
				       &ndpiDetectedProtocol);
      else if(get_cli_ip_addr()->get_ipv6()) /* IPv6 */
	ndpi_fill_ipv6_protocol_category(iface->get_ndpi_struct(),
					 ndpiFlow,
					 (struct in6_addr *)get_cli_ip_addr()->get_ipv6(),
					 (struct in6_addr *)get_srv_ip_addr()->get_ipv6(),
					 &ndpiDetectedProtocol);

      p_ptr = (u_int16_t*)&ndpiDetectedProtocol.category;
      p = (*p_ptr) & 0xFF; /* See Ntop::nDPILoadHostnameCategory */

      ndpiDetectedProtocol.category = (ndpi_protocol_category_t)p;

      /* We have used the trick to save in the protocolId both the list name and the protocol */
      if(ndpiDetectedProtocol.custom_category_userdata == NULL) {
	u_int8_t list_id = (ndpiDetectedProtocol.category & 0xFF00) >> 8; /* See Ntop::nDPILoadHostnameCategory */

	ndpiDetectedProtocol.category = (ndpi_protocol_category_t)(ndpiDetectedProtocol.category & 0xFF);
	ndpiDetectedProtocol.custom_category_userdata = (void*)ntop->getPersistentCustomListNameById(list_id);
      }

      stats.setDetectedProtocol(&ndpiDetectedProtocol);
      updateHostBlacklists();
    }

    setErrorCode(ndpi_get_flow_error_code(ndpiFlow));

    if(ndpiDetectedProtocol.protocol_by_ip != NDPI_PROTOCOL_UNKNOWN) {
      char *p = ndpi_get_proto_name(iface->get_ndpi_struct(),
                                    ndpiDetectedProtocol.protocol_by_ip);

      setAddressFamilyProtocol(p);
    }
  }

  /*
    NOTE
    TCP host services are updated in Flow::processDetectedProtocol
    UDP sevices are updated here as we need to know if the flow is
    unidirectional and this can be sorted out only after a few packets
    as doing it during the flow processing it is too early as nDPI
    returns immediately the protocol name (e.g. with DNS) without
    waiting the response to be received
  */
  if(protocol == IPPROTO_UDP)
    updateUDPHostServices(src2dst_direction);

  processExtraDissectedInformation();

  extra_dissection_completed = 1;

  /* Free the nDPI memory */
  freeDPIMemory();
}

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

void Flow::updateHostBlacklists() {
  if(get_custom_category_file()) {
    char *cat = get_custom_category_file();

    if(isBlacklistedClient()) {
      if(cli_host)
	cli_host->setBlacklistName(cat); /* Standard Interface */
      else if(cli_ip_addr != NULL) {
	/*
	  View interface

	  Will be updated by ViewInterface::viewed_flows_walker
	*/
      }
    } else if(isBlacklistedServer()) {
      if(srv_host)
	srv_host->setBlacklistName(cat); /* Standard Interface */
      else if(srv_ip_addr != NULL) {
	/*
	  View interface

	  Will be updated by ViewInterface::viewed_flows_walker
	*/
      }
    }
  }
}

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

void Flow::updateProtocol(ndpi_protocol proto_id) {
  u_int16_t *ptr16;

  /* NOTE: in order to avoid inconsistent states, only overwrite the
   * protocools if UNKNOWN. */
  if(ndpiDetectedProtocol.proto.master_protocol == NDPI_PROTOCOL_UNKNOWN)
    ndpiDetectedProtocol.proto.master_protocol = proto_id.proto.master_protocol;

  if((ndpiDetectedProtocol.proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) ||
      (/*
         Update the protocols when adding a subprotocol, not when things
         are totally different
       */
       (ndpiDetectedProtocol.proto.master_protocol ==
        ndpiDetectedProtocol.proto.app_protocol) &&
       (ndpiDetectedProtocol.proto.app_protocol != proto_id.proto.app_protocol)))
    ndpiDetectedProtocol.proto.app_protocol = proto_id.proto.app_protocol;

  if(ndpiDetectedProtocol.proto.master_protocol == ndpiDetectedProtocol.proto.app_protocol)
    ndpiDetectedProtocol.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN;

  ndpiDetectedProtocol.protocol_by_ip = proto_id.protocol_by_ip;

  /* NOTE: only overwrite the category if it was not set.
   * This prevents overwriting already determined category (e.g. by IP or Host)
   */
  if (ndpiDetectedProtocol.category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) {
    u_int16_t *a = (u_int16_t*)&ndpiDetectedProtocol.category;
    u_int16_t *b = (u_int16_t*)&proto_id.category;

    *a = *b; /* trick to avoid runtime errors with custom categories */
  }

  ptr16 = (u_int16_t*)&ndpiDetectedProtocol.category;  /* trick to avoid runtime errors with custom categories */

  if ((*ptr16 > 1024) && ((*ptr16 - 1024) == CUSTOM_CATEGORY_MALWARE))
    ndpiDetectedProtocol.category = CUSTOM_CATEGORY_MALWARE;
}

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

/*
 * Called to update the flow protocol and possibly advance the flow to
 * the protocol_detected state.
 */
void Flow::setProtocolDetectionCompleted(u_int8_t *payload,
                                         u_int16_t payload_len, time_t when_seen) {
  if(detection_completed) return;

  stats.setDetectedProtocol(&ndpiDetectedProtocol);

  /* Process detected protocol and doesn't need ndpiFlow not allocated for
   * non-packet interfaces */
  processDetectedProtocol(payload, payload_len);

  /* Process detected protocol data and needs ndpiFlow only allocated for packet interfaces */
  processDetectedProtocolData();

  detection_completed = 1;

  if(cli_host && srv_host) {
    /*
      Hosts are NULL for view interfaces and thus they are updated
      in Flow::hosts_periodic_stats_update()
    */
    updateServerPortsStats(srv_host, &ndpiDetectedProtocol, when_seen);
    updateClientContactedPorts(cli_host, &ndpiDetectedProtocol);
  }

#ifdef BLACKLISTED_FLOWS_DEBUG
  if(ndpiDetectedProtocol.category == CUSTOM_CATEGORY_MALWARE) {
    char buf[512];

    print(buf, sizeof(buf));
    snprintf(&buf[strlen(buf)], sizeof(buf) - strlen(buf),
             "Malware category detected. [cli_blacklisted: "
             "%u][srv_blacklisted: %u][category: %s][blacklist: %s]",
             isBlacklistedClient(), isBlacklistedServer(),
             get_protocol_category_name(),
	     get_custom_category_file() ? get_custom_category_file() : "");

    ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s", buf);
  }
#endif

#ifdef NTOPNG_PRO
#ifdef HAVE_NEDGE
  updateFlowShapers(true);
#else
  updateProfile();
#endif
#endif
}

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

void Flow::setJSONInfo(json_object *json) {
  if(json == NULL) return;

  if(json_info != NULL) json_object_put(json_info);

  json_info = json_object_get(json);
}

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

void Flow::setTLVInfo(ndpi_serializer *tlv) {
  if(tlv == NULL) return;

  if(tlv_info != NULL) {
    ndpi_term_serializer(tlv_info);
    free(tlv_info);
  }

  tlv_info = tlv;
}

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

/*
 * A faster replacement for inet_ntoa().
 */
char *Flow::intoaV4(unsigned int addr, char *buf, u_short bufLen) {
  char *cp, *retStr;
  int n;

  cp = &buf[bufLen];
  *--cp = '\0';

  n = 4;
  do {
    u_int byte = addr & 0xff;

    *--cp = byte % 10 + '0';
    byte /= 10;
    if(byte > 0) {
      *--cp = byte % 10 + '0';
      byte /= 10;
      if(byte > 0) *--cp = byte + '0';
    }
    *--cp = '.';
    addr >>= 8;
  } while (--n > 0);

  /* Convert the string to srccase */
  retStr = (char *)(cp + 1);

  return (retStr);
}

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

u_int64_t Flow::get_current_bytes_cli2srv() const {
  int64_t diff = get_bytes_cli2srv() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_cli2srv_bytes() : 0);

  /*
    We need to do this as due to concurrency issues,
    we might have a negative value
  */
  return ((diff > 0) ? diff : 0);
};

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

u_int64_t Flow::get_current_bytes_srv2cli() const {
  int64_t diff = get_bytes_srv2cli() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_srv2cli_bytes() : 0);

  /*
    We need to do this as due to concurrency issues,
    we might have a negative value
  */
  return ((diff > 0) ? diff : 0);
};

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

u_int64_t Flow::get_current_goodput_bytes_cli2srv() const {
  int64_t diff =
    get_goodput_bytes_cli2srv() -
    (periodic_stats_update_partial
     ? periodic_stats_update_partial->get_cli2srv_goodput_bytes()
     : 0);

  /*
    We need to do this as due to concurrency issues,
    we might have a negative value
  */
  return ((diff > 0) ? diff : 0);
};

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

u_int64_t Flow::get_current_goodput_bytes_srv2cli() const {
  int64_t diff =
    get_goodput_bytes_srv2cli() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_srv2cli_goodput_bytes() : 0);

  /*
    We need to do this as due to concurrency issues,
    we might have a negative value
  */
  return ((diff > 0) ? diff : 0);
};

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

u_int64_t Flow::get_current_packets_cli2srv() const {
  int64_t diff = get_packets_cli2srv() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_cli2srv_packets() : 0);

  /*
    We need to do this as due to concurrency issues,
    we might have a negative value
  */
  return ((diff > 0) ? diff : 0);
};

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

u_int64_t Flow::get_current_packets_srv2cli() const {
  int64_t diff = get_packets_srv2cli() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_srv2cli_packets() : 0);

  /*
    We need to do this as due to concurrency issues,
    we might have a negative value
  */
  return ((diff > 0) ? diff : 0);
};

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

char *Flow::printTCPflags(u_int8_t flags, char *const buf, u_int buf_len) {
  snprintf(buf, buf_len, "%s%s%s%s%s%s%s%s", (flags & TH_SYN) ? " SYN" : "",
           (flags & TH_ACK) ? " ACK" : "", (flags & TH_FIN) ? " FIN" : "",
           (flags & TH_RST) ? " RST" : "", (flags & TH_PUSH) ? " PUSH" : "",
           (flags & TH_URG) ? " URG" : "", (flags & TH_ECE) ? " ECE" : "",
           (flags & TH_CWR) ? " CWR" : "");

  if(buf[0] == ' ')
    return (&buf[1]);
  else
    return (buf);
}

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

char *Flow::printTCPState(char *const buf, u_int buf_len) const {
  snprintf(buf, buf_len, "%s%s%s%s", isTCPEstablished() ? " est" : "",
           isTCPConnecting() ? " conn" : "", isTCPClosed() ? " closed" : "",
           isTCPReset() ? " reset" : "");

  if(buf[0] == ' ')
    return (&buf[1]);
  else
    return (buf);
}

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

char* Flow::print(char *buf, u_int buf_len, bool full_report) const {
  char buf1[32], buf2[32], buf3[32], buf4[32], buf5[32], pbuf[32], tcp_buf[64];

  buf[0] = '\0';

  if(!full_report) {
    Mac *cli_mac = get_cli_host() ? get_cli_host()->getMac() : NULL;
    Mac *srv_mac = get_srv_host() ? get_srv_host()->getMac() : NULL;
    char b1[32], b2[32];

    snprintf(buf, buf_len, "%s %s:%u [%s] <-> %s:%u [%s][%u/%u pkts][%llu/%llu bytes]",
	     get_protocol_name(),
	     get_cli_ip_addr() ? get_cli_ip_addr()->print(buf1, sizeof(buf1)) : "",
	     ntohs(cli_port),
	     cli_mac ? cli_mac->print(b1, sizeof(b1)) : "",
	     get_srv_ip_addr() ? get_srv_ip_addr()->print(buf2, sizeof(buf2)) : "",
	     ntohs(srv_port),
	     srv_mac ? srv_mac->print(b2, sizeof(b2)) : "",
	     get_packets_cli2srv(), get_packets_srv2cli(),
	     (long long unsigned)get_bytes_cli2srv(),
	     (long long unsigned)get_bytes_srv2cli());
    return(buf);
  }

#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
  char shapers[64];

  TrafficShaper *cli2srv_in  = flowShaperIds.cli2srv.ingress;
  TrafficShaper *cli2srv_out = flowShaperIds.cli2srv.egress;
  TrafficShaper *srv2cli_in  = flowShaperIds.srv2cli.ingress;
  TrafficShaper *srv2cli_out = flowShaperIds.srv2cli.egress;

  if(iface->is_bridge_interface()) {
    snprintf(
	     shapers, sizeof(shapers),
	     "[pass_verdict: %s (%d)] "
	     "[shapers: cli2srv=%u/%u, srv2cli=%u/%u] "
	     "[cli2srv_ingress shaping_enabled: %i max_rate: %lu] "
	     "[cli2srv_egress shaping_enabled: %i max_rate: %lu] "
	     "[srv2cli_ingress shaping_enabled: %i max_rate: %lu] "
	     "[srv2cli_egress shaping_enabled: %i max_rate: %lu] ",
	     passVerdict ? "PASS" : "DROP",
             (int) dropVerdictReason,
	     flowShaperIds.cli2srv.ingress ? flowShaperIds.cli2srv.ingress->get_shaper_id() : DEFAULT_SHAPER_ID,
	     flowShaperIds.cli2srv.egress  ? flowShaperIds.cli2srv.egress->get_shaper_id()  : DEFAULT_SHAPER_ID,
	     flowShaperIds.srv2cli.ingress ? flowShaperIds.srv2cli.ingress->get_shaper_id() : DEFAULT_SHAPER_ID,
	     flowShaperIds.srv2cli.egress  ? flowShaperIds.srv2cli.egress->get_shaper_id()  : DEFAULT_SHAPER_ID,
	     cli2srv_in->shaping_enabled(), cli2srv_in->get_max_rate_kbit_sec(),
	     cli2srv_out->shaping_enabled(), cli2srv_out->get_max_rate_kbit_sec(),
	     srv2cli_in->shaping_enabled(), srv2cli_in->get_max_rate_kbit_sec(),
	     srv2cli_out->shaping_enabled(), srv2cli_out->get_max_rate_kbit_sec());
  } else
    shapers[0] = '\0';

#endif

  tcp_buf[0] = '\0';
  if(tcp != NULL) {
    int len = 0;

    if((stats.get_cli2srv_tcp_ooo() + stats.get_srv2cli_tcp_ooo()) > 0)
      len += snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[OOO=%u/%u]",
                      stats.get_cli2srv_tcp_ooo(), stats.get_srv2cli_tcp_ooo());

    if((stats.get_cli2srv_tcp_lost() + stats.get_srv2cli_tcp_lost()) > 0)
      len +=
	snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[Lost=%u/%u]",
		 stats.get_cli2srv_tcp_lost(), stats.get_srv2cli_tcp_lost());

    if((stats.get_cli2srv_tcp_retr() + stats.get_srv2cli_tcp_retr()) > 0)
      len +=
	snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[Retr=%u/%u]",
		 stats.get_cli2srv_tcp_retr(), stats.get_srv2cli_tcp_retr());

    if((stats.get_cli2srv_tcp_keepalive() +
         stats.get_srv2cli_tcp_keepalive()) > 0)
      len += snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[KeepAlive=%u/%u]",
                      stats.get_cli2srv_tcp_keepalive(),
                      stats.get_srv2cli_tcp_keepalive());
  }

  snprintf(
	   buf, buf_len,
	   "%s %s:%u &gt; %s:%u [first: %u][last: %u][proto: %u.%u/%s][cat: "
	   "%u/%s][device: %u in: %u out:%u]"
	   "[%u/%u pkts][%llu/%llu bytes][flags src2dst: %s][flags dst2stc: "
	   "%s][state: %s]"
	   "%s%s%s"
#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
	   "%s"
#endif
	   ,
	   get_protocol_name(),
	   get_cli_ip_addr() ? get_cli_ip_addr()->print(buf1, sizeof(buf1)) : "",
	   ntohs(cli_port),
	   get_srv_ip_addr() ? get_srv_ip_addr()->print(buf2, sizeof(buf2)) : "",
	   ntohs(srv_port), (u_int32_t)first_seen, (u_int32_t)last_seen,
	   ndpiDetectedProtocol.proto.master_protocol, ndpiDetectedProtocol.proto.app_protocol,
	   get_detected_protocol_name(pbuf, sizeof(pbuf)), get_protocol_category(),
	   get_protocol_category_name(), flow_device.device_ip, flow_device.in_index,
	   flow_device.out_index, get_packets_cli2srv(), get_packets_srv2cli(),
	   (long long unsigned)get_bytes_cli2srv(),
	   (long long unsigned)get_bytes_srv2cli(),
	   printTCPflags(src2dst_tcp_flags, buf3, sizeof(buf3)),
	   printTCPflags(dst2src_tcp_flags, buf4, sizeof(buf4)),
	   printTCPState(buf5, sizeof(buf5)),
	   (isTLS() && protos.tls.server_names) ? "[" : "",
	   (isTLS() && protos.tls.server_names) ? protos.tls.server_names : "",
	   (isTLS() && protos.tls.server_names) ? "]" : ""
#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
	   ,
	   shapers
#endif
	   );

  return (buf);
}

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

bool Flow::dump(time_t t, bool last_dump_before_free) {
  bool rc = false;

  if(!ntop->getPrefs()->is_tiny_flows_export_enabled() && isTiny()) {
#ifdef TINY_FLOWS_DEBUG
    char buf[256];
    
    ntop->getTrace()->traceEvent(TRACE_NORMAL,
				 "Skipping tiny flow dump [flow key: %u]"
				 "[packets current/max: %i/%i] "
				 "[bytes current/max: %i/%i]"
				 ": %s",
				 key(), get_packets(),
				 ntop->getPrefs()->get_max_num_packets_per_tiny_flow(), get_bytes(),
				 ntop->getPrefs()->get_max_num_bytes_per_tiny_flow(),
				 print(buf, sizeof(buf)));
#endif
    return (rc);
  }

  if(!last_dump_before_free) {
    if((getInterface()->getIfType() == interface_type_PCAP_DUMP &&
         (!getInterface()->read_from_pcap_dump_done())) ||
        !timeToPeriodicDump(t) || last_db_dump.in_progress) {
      return (rc); /* Don't call too often periodic flow dump */
    }
  }

  /* Add protocol information in the JSON field (if not already set by alerts) */
  setProtocolJSONInfo();
  getInterface()->dumpFlow(get_last_seen(), this);

  return (true);
}

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

void Flow::setDropVerdict(DropReason reason) {
#if defined(HAVE_NEDGE)
  if((iface->getIfType() == interface_type_NETFILTER) && (passVerdict == 1))
    ((NetfilterInterface *)iface)->setPolicyChanged();
#endif

  passVerdict = 0;
  dropVerdictReason = reason;
}

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

#ifdef HAVE_NEDGE
void Flow::incFlowDroppedCounters() {
  if(!flow_dropped_counts_increased) {
    if(cli_host) {
      cli_host->incNumDroppedFlows();

      if(cli_host->getMac())
	cli_host->getMac()->incNumDroppedFlows();
    }

#ifdef NTOPNG_PRO
    HostPools *h = iface ? iface->getHostPools() : NULL;
    u_int16_t cli_pool = NO_HOST_POOL_ID;

    if(h) {
      cli_pool = cli_host ? cli_host->get_host_pool() : NO_HOST_POOL_ID;

      if(cli_pool != NO_HOST_POOL_ID) h->incPoolNumDroppedFlows(cli_pool);
    }
#endif

    /* Increasing stats on the server is pointless.
       If a flow is dropped, the server doesn't even see it,
       it is just the client that gets a drop. */
    flow_dropped_counts_increased = 1;
  }
}
#endif

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

/* This function is called when deleting the Flow, so if some counter/value
 * needs to be updated on flow_end, it's possible to do it from here
 */
void Flow::flow_end_stats_update() {
  if(isTCP()) {
    accountBidirectionalTCPProtocolServices();
  } else if(isUDP()) {
    accountBidirectionalUDPProtocolServices();
  }
#ifdef NTOPNG_PRO
  QoEType qoe_type = getQoEType();
  Host *cli_u = getViewSharedClient(), *srv_u = getViewSharedServer();
  AutonomousSystem *cli_as = cli_u ? cli_u->get_as() : NULL;
  AutonomousSystem *srv_as = srv_u ? srv_u->get_as() : NULL;
  int32_t cli_network_id = 0, srv_network_id = 0;

  if (cli_u) cli_u->incQoEStats(qoe_type);
  if (srv_u) srv_u->incQoEStats(qoe_type);
  if (cli_as) cli_as->incQoEStats(qoe_type);
  if (srv_as) {
    /* In case cli and srv ASN are the same do not increase, already increased above */
    if (cli_as && (cli_as == srv_as)) { ; }
    else { srv_as->incQoEStats(qoe_type); }
  }
  if (cli_u) {
    cli_network_id = cli_u->get_local_network_id();
    NetworkStats *cli_network_stats = cli_u->getNetworkStats(cli_network_id);
    if (cli_network_stats) cli_network_stats->incQoEStats(qoe_type);
  }
  if (srv_u) {
    /* Same as ASN, increase only if they are different networks */
    srv_network_id = srv_u->get_local_network_id();
    if (srv_network_id != cli_network_id) {
        NetworkStats *srv_network_stats = srv_u->getNetworkStats(srv_network_id);
        if (srv_network_stats) srv_network_stats->incQoEStats(qoe_type);
    }
  }
  if (iface) iface->incQoEStats(qoe_type);
#endif
}

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

/* NOTE: this function is periodically executed both on normal interfaces
 * and ViewInterface. On ViewInterface, the cli_host and srv_host *do not*
 * correspond to the flow hosts (which remain NULL). This is the correct
 * place to increment stats on cli/srv hosts and make them work with
 * ViewInterfaces.
 *
 * const is *required* here as the flow must not be modified (as it could go in
 * concuncurrency with the subinterfaces). */

void Flow::hosts_periodic_stats_update(NetworkInterface *iface, Host *cli_host,
                                       Host *srv_host,
                                       PartializableFlowTrafficStats *partial,
                                       bool first_partial,
                                       const struct timeval *tv) {
  if((!isDNS()) && ntop->getPrefs()->is_dns_cache_enabled()) {
    if(!srv_host) {
      /* Standard Interface */
      /* Handled in Flow::freeDPIMemory() */
    } else {
      /* View interface (****) */
      updateServerName(getViewSharedServer());
    }
  }

  update_pools_stats(iface, cli_host, srv_host, tv, partial->get_cli2srv_packets(),
		     partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
		     partial->get_srv2cli_bytes());

  if(cli_host && srv_host) {
    bool cli_and_srv_in_same_subnet = false;
    bool cli_and_srv_in_same_country = false;
    VLAN *vl;
    int32_t cli_network_id = cli_host->get_local_network_id();
    int32_t srv_network_id = srv_host->get_local_network_id();
    int16_t stats_protocol =
      getStatsProtocol(); /* The protocol (among ndpi master_ and app_) that
			     is chosen to increase stats */
    NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;

    updateServerPortsStats(srv_host, &ndpiDetectedProtocol, get_first_seen());
    updateClientContactedPorts(cli_host, &ndpiDetectedProtocol);

    if(cli_network_id >= 0 && (cli_network_id == srv_network_id))
      cli_and_srv_in_same_subnet = true;

    cli_host->incCliContactedHosts(srv_host->get_ip());
    srv_host->incSrvHostContacts(cli_host->get_ip());

    if(iface &&
        (vl = iface->getVLAN(vlanId, false, false /* NOT an inline call */))) {
      /* Note: source and destination hosts have, by definition, the same VLAN
       * so the increase is done only one time. */
      /* Note: vl will never be null as we're in a flow with that vlan. Hence,
         it is guaranteed that at least two hosts exists for that vlan and that
         any purge attempt will be prevented. */
#ifdef VLAN_DEBUG
      ntop->getTrace()->traceEvent(TRACE_NORMAL, "Increasing VLAN %u stats",
                                   u_int16_t);
#endif
      vl->incStats(tv->tv_sec, stats_protocol, partial->get_cli2srv_packets(),
                   partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
                   partial->get_srv2cli_bytes());
    }

    // Update local stats (local vs remote)
    // this replaces the old call to Flow::updateInterfaceLocalStats from packet
    // processing
    iface->incLocalStats(partial->get_cli2srv_packets(),
                         partial->get_cli2srv_bytes(), cli_host->isLocalHost(),
                         srv_host->isLocalHost());
    iface->incLocalStats(partial->get_srv2cli_packets(),
                         partial->get_srv2cli_bytes(), srv_host->isLocalHost(),
                         cli_host->isLocalHost());

    // Update network stats
    cli_network_stats = cli_host->getNetworkStats(cli_network_id);
    cli_host->incStats(
		       tv->tv_sec, get_protocol(), stats_protocol, get_protocol_category(),
		       custom_app, partial->get_cli2srv_packets(),
		       partial->get_cli2srv_bytes(), partial->get_cli2srv_goodput_bytes(),
		       partial->get_srv2cli_packets(), partial->get_srv2cli_bytes(),
		       partial->get_srv2cli_goodput_bytes(),
		       srv_host->get_ip()->isNonEmptyUnicastAddress());

    // update per-subnet byte counters
    if(cli_network_stats) {  // only if the network is known and local
      if(!cli_and_srv_in_same_subnet) {
        cli_network_stats->incEgress(tv->tv_sec, partial->get_cli2srv_packets(),
                                     partial->get_cli2srv_bytes(),
                                     srv_host->get_ip()->isBroadcastAddress());
        cli_network_stats->incIngress(tv->tv_sec,
                                      partial->get_srv2cli_packets(),
                                      partial->get_srv2cli_bytes(),
                                      cli_host->get_ip()->isBroadcastAddress());
      } else  // client and server ARE in the same subnet
        // need to update the inner counter (just one time, will intentionally
        // skip this for srv_host)
        cli_network_stats->incInner(tv->tv_sec,
				    partial->get_cli2srv_packets() + partial->get_srv2cli_packets(),
				    partial->get_cli2srv_bytes() + partial->get_srv2cli_bytes(),
				    srv_host->get_ip()->isBroadcastAddress() ||
				    cli_host->get_ip()->isBroadcastAddress());
    }

    srv_network_stats = srv_host->getNetworkStats(srv_network_id);
    srv_host->incStats(tv->tv_sec, get_protocol(), stats_protocol, get_protocol_category(),
		       custom_app, partial->get_srv2cli_packets(),
		       partial->get_srv2cli_bytes(), partial->get_srv2cli_goodput_bytes(),
		       partial->get_cli2srv_packets(), partial->get_cli2srv_bytes(),
		       partial->get_cli2srv_goodput_bytes(),
		       cli_host->get_ip()->isNonEmptyUnicastAddress());

    if(srv_network_stats) {
      // local and known server network
      if(!cli_and_srv_in_same_subnet) {
        srv_network_stats->incIngress(tv->tv_sec,
                                      partial->get_cli2srv_packets(),
                                      partial->get_cli2srv_bytes(),
                                      srv_host->get_ip()->isBroadcastAddress());
        srv_network_stats->incEgress(tv->tv_sec, partial->get_srv2cli_packets(),
                                     partial->get_srv2cli_bytes(),
                                     cli_host->get_ip()->isBroadcastAddress());
      }
    }

    if(cli_host->get_asn() != srv_host->get_asn()) {
      AutonomousSystem *cli_as = cli_host ? cli_host->get_as() : NULL,
	*srv_as = srv_host ? srv_host->get_as() : NULL;

      if(cli_as)
        cli_as->incStats(tv->tv_sec, stats_protocol, partial->get_cli2srv_packets(),
			 partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
			 partial->get_srv2cli_bytes());
      if(srv_as)
        srv_as->incStats(tv->tv_sec, stats_protocol, partial->get_srv2cli_packets(),
			 partial->get_srv2cli_bytes(), partial->get_cli2srv_packets(),
			 partial->get_cli2srv_bytes());
    }

    if(cli_host->get_observation_point_id() &&
        srv_host->get_observation_point_id()) {
      ObservationPoint *cli_obs_point = cli_host ? cli_host->get_obs_point() : NULL,
	*srv_obs_point = srv_host ? srv_host->get_obs_point() : NULL;

      if(cli_obs_point)
        cli_obs_point->incStats(tv->tv_sec, stats_protocol, partial->get_cli2srv_packets(),
				partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
				partial->get_srv2cli_bytes());
      if(srv_obs_point)
        srv_obs_point->incStats(tv->tv_sec, stats_protocol, partial->get_srv2cli_packets(),
				partial->get_srv2cli_bytes(), partial->get_cli2srv_packets(),
				partial->get_cli2srv_bytes());
    }

    // Update client DSCP stats
    cli_host->incDSCPStats(getCli2SrvDSCP(), partial->get_cli2srv_packets(),
                           partial->get_cli2srv_bytes(),
                           partial->get_srv2cli_packets(),
                           partial->get_srv2cli_bytes());

    // Update server DSCP stats
    srv_host->incDSCPStats(getSrv2CliDSCP(), partial->get_srv2cli_packets(),
                           partial->get_srv2cli_bytes(),
                           partial->get_cli2srv_packets(),
                           partial->get_cli2srv_bytes());

    // Update Country stats
    Country *cli_country_stats = cli_host->getCountryStats();
    Country *srv_country_stats = srv_host->getCountryStats();

    if(cli_country_stats && srv_country_stats &&
        cli_country_stats->equal(srv_country_stats))
      cli_and_srv_in_same_country = true;

    if(cli_country_stats) {
      if(!cli_and_srv_in_same_country) {
        cli_country_stats->incEgress(tv->tv_sec, partial->get_cli2srv_packets(),
                                     partial->get_cli2srv_bytes(),
                                     srv_host->get_ip()->isBroadcastAddress());
        cli_country_stats->incIngress(tv->tv_sec,
                                      partial->get_srv2cli_packets(),
                                      partial->get_srv2cli_bytes(),
                                      cli_host->get_ip()->isBroadcastAddress());
      } else  // client and server ARE in the same country
        // need to update the inner counter (just one time, will intentionally
        // skip this for srv_host)
        cli_country_stats->incInner(tv->tv_sec,
				    partial->get_cli2srv_packets() + partial->get_srv2cli_packets(),
				    partial->get_cli2srv_bytes() + partial->get_srv2cli_bytes(),
				    srv_host->get_ip()->isBroadcastAddress() ||
				    cli_host->get_ip()->isBroadcastAddress());
    }

    if(srv_country_stats) {
      if(!cli_and_srv_in_same_country) {
        srv_country_stats->incIngress(tv->tv_sec,
                                      partial->get_cli2srv_packets(),
                                      partial->get_cli2srv_bytes(),
                                      srv_host->get_ip()->isBroadcastAddress());
        srv_country_stats->incEgress(tv->tv_sec, partial->get_srv2cli_packets(),
                                     partial->get_srv2cli_bytes(),
                                     cli_host->get_ip()->isBroadcastAddress());
      }
    }
  }

  // Update interface DSCP stats
  if(iface) {
    iface->incDSCPStats(getCli2SrvDSCP(), partial->get_cli2srv_packets(),
                        partial->get_cli2srv_bytes(),
                        partial->get_srv2cli_packets(),
                        partial->get_srv2cli_bytes());
  }

  switch (get_protocol()) {
  case IPPROTO_TCP:
    incTcpBadStats(true, cli_host, srv_host, iface, partial->get_cli2srv_tcp_ooo(),
		   partial->get_cli2srv_tcp_retr(), partial->get_cli2srv_tcp_lost(),
		   partial->get_cli2srv_tcp_keepalive());
    incTcpBadStats(false, cli_host, srv_host, iface, partial->get_srv2cli_tcp_ooo(),
		   partial->get_srv2cli_tcp_retr(), partial->get_srv2cli_tcp_lost(),
		   partial->get_srv2cli_tcp_keepalive());
    break;

  case IPPROTO_ICMP:
    if(iface) {
      if(partial->get_cli2srv_packets())
	iface->incICMPStats(false /* icmp v4 */,
			    partial->get_cli2srv_packets(),
			    protos.icmp.cli2srv.icmp_type,
			    protos.icmp.cli2srv.icmp_code, true);

      if(partial->get_srv2cli_packets())
	iface->incICMPStats(false /* icmp v4 */,
			    partial->get_srv2cli_packets(),
			    protos.icmp.srv2cli.icmp_type,
			    protos.icmp.srv2cli.icmp_code, true);
    }
    break;

  case IPPROTO_ICMPV6:
    if(iface) {
      if(partial->get_cli2srv_packets())
	iface->incICMPStats(true /* icmp v6 */,
			    partial->get_cli2srv_packets(),
			    protos.icmp.cli2srv.icmp_type,
			    protos.icmp.cli2srv.icmp_code, true);

      if(partial->get_srv2cli_packets())
	iface->incICMPStats(true /* icmp v6 */,
			    partial->get_srv2cli_packets(),
			    protos.icmp.srv2cli.icmp_type,
			    protos.icmp.srv2cli.icmp_code, true);
    }

    break;
  default:
    break;
  }

  switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
  case NDPI_PROTOCOL_HTTP:
    if(cli_host && cli_host->getHTTPstats())
      cli_host->getHTTPstats()->incStats(true /* Client */,
					 partial->get_flow_http_stats());
    if(srv_host && srv_host->getHTTPstats())
      srv_host->getHTTPstats()->incStats(false /* Server */,
					 partial->get_flow_http_stats());

    if(operating_system != ndpi_os_unknown) {
      if(cli_host && !(get_cli_ip_addr()->isBroadcastAddress() ||
			get_cli_ip_addr()->isMulticastAddress()))
	cli_host->setOS(operating_system, os_learning_http_user_agent);
    }

    /* Don't break, let's process also HTTP_PROXY */
  case NDPI_PROTOCOL_HTTP_PROXY:
    if(srv_host) {
      if((!Utils::isIPAddress(host_server_name))
	 /* && hasRisk(NDPI_NUMERIC_IP_HOST) */
	 ) {
	srv_host->offlineSetHTTPName(host_server_name);
      }

      if(srv_host->getHTTPstats() && host_server_name &&
	  isThreeWayHandshakeOK()) {
	srv_host->getHTTPstats()->updateHTTPHostRequest(tv->tv_sec, host_server_name, partial->get_num_http_requests(),
							partial->get_cli2srv_bytes(), partial->get_srv2cli_bytes());
      }
    }
    break;

  case NDPI_PROTOCOL_DNS:
    if(cli_host && cli_host->getDNSstats())
      cli_host->getDNSstats()->incStats(true /* Client */,  partial->get_flow_dns_stats());

    if(srv_host && srv_host->getDNSstats())
      srv_host->getDNSstats()->incStats(false /* Server */, partial->get_flow_dns_stats());

    if(cli_host && srv_host)
      cli_host->incDNSContactCardinality(srv_host);
    break;

  case NDPI_PROTOCOL_MDNS:
    if(cli_host) {
      if(protos.mdns.answer)    cli_host->offlineSetMDNSInfo(protos.mdns.answer);
      if(protos.mdns.name)      cli_host->offlineSetMDNSName(protos.mdns.name);
      if(protos.mdns.name_txt)  cli_host->offlineSetMDNSTXTName(protos.mdns.name_txt);
    }
    break;

  case NDPI_PROTOCOL_SSDP:
    if(cli_host) {
      if(protos.ssdp.location)
	cli_host->offlineSetSSDPLocation(protos.ssdp.location);
    }
    break;

  case NDPI_PROTOCOL_NETBIOS:
    if(cli_host) {
      if(protos.netbios.name)
	cli_host->offlineSetNetbiosName(protos.netbios.name);
    }
    break;

  case NDPI_PROTOCOL_DHCP:
    if(cli_host) {
      if(protos.dhcp.name) {
	cli_host->offlineSetDHCPName(protos.dhcp.name);
      }
    }
    break;

  case NDPI_PROTOCOL_NTP:
    if(cli_host && srv_host) {
      cli_host->incNTPContactCardinality(srv_host);
    }
    break;

  case NDPI_PROTOCOL_IP_ICMP:
  case NDPI_PROTOCOL_IP_ICMPV6:
    if(cli_host && cli_host->getICMPstats()) {
      if(partial->get_cli2srv_packets())
	cli_host->getICMPstats()->incStats(
					   partial->get_cli2srv_packets(), protos.icmp.cli2srv.icmp_type,
					   protos.icmp.cli2srv.icmp_code, true /* Sent */, srv_host);

      if(partial->get_srv2cli_packets())
	cli_host->getICMPstats()->incStats(
					   partial->get_srv2cli_packets(), protos.icmp.srv2cli.icmp_type,
					   protos.icmp.srv2cli.icmp_code, false /* Rcvd */, srv_host);
    }
    if(srv_host && srv_host->getICMPstats()) {
      if(partial->get_cli2srv_packets())
	srv_host->getICMPstats()->incStats(
					   partial->get_cli2srv_packets(), protos.icmp.cli2srv.icmp_type,
					   protos.icmp.cli2srv.icmp_code, false /* Rcvd */, cli_host);

      if(partial->get_srv2cli_packets())
	srv_host->getICMPstats()->incStats(
					   partial->get_srv2cli_packets(), protos.icmp.srv2cli.icmp_type,
					   protos.icmp.srv2cli.icmp_code, true /* Sent */, cli_host);
    }

    if(first_partial && icmp_info) {
      if(icmp_info->isPortUnreachable()) {  // Port unreachable icmpv6/icmpv4

	if(srv_host) srv_host->incNumUnreachableFlows(true /* as server */);
	if(cli_host) cli_host->incNumUnreachableFlows(false /* as client */);
      } else if(icmp_info->isHostUnreachable(protocol)) {
	if(srv_host)
	  srv_host->incNumHostUnreachableFlows(true /* as server */);
	if(cli_host)
	  cli_host->incNumHostUnreachableFlows(false /* as client */);
      }
    }
    break;

  case NDPI_PROTOCOL_MAIL_SMTPS:
  case NDPI_PROTOCOL_MAIL_SMTP:
    if(cli_host && srv_host) {
      cli_host->incSMTPContactCardinality(srv_host);
    }
    break;

  case NDPI_PROTOCOL_MAIL_IMAPS:
  case NDPI_PROTOCOL_MAIL_IMAP:
    if(cli_host && srv_host) {
      cli_host->incIMAPContactCardinality(srv_host);
    }
    break;

  case NDPI_PROTOCOL_MAIL_POPS:
  case NDPI_PROTOCOL_MAIL_POP:
    if(cli_host && srv_host) {
      cli_host->incPOPContactCardinality(srv_host);
    }
    break;

  default:
    break;
  }

  if(srv_host && isTLS()) {
#ifdef DEBUG
    char buf2[64];

    ntop->getTrace()->traceEvent(TRACE_WARNING, "$$$ %s <-> %s [pkts: %u][%s]",
				 get_srv_ip_addr() ? get_srv_ip_addr()->print(buf2, sizeof(buf2)) : "",
				 protos.tls.client_requested_server_name,
				 get_packets(),
				 protos.tls.server_names ? protos.tls.server_names : "");
#endif

    if((protos.tls.server_names != NULL)
       /* Ignore hostnames with wildcard or multiple comma-separated values */
       && (strchr(protos.tls.server_names, '*') == NULL)
       && (strchr(protos.tls.server_names, ',') == NULL))
      srv_host->offlineSetTLSName(protos.tls.server_names);
    else if((protos.tls.client_requested_server_name != NULL)
	    && (!hasRisk(NDPI_TLS_CERTIFICATE_MISMATCH)) /* Certificates (if present) do not mismatch */
	    && (!Utils::isIPAddress(protos.tls.client_requested_server_name))
	    && (get_packets() >= 16) /*
				       Avoid micro-flows that might be an indication that
				       the response page is too short and thus that
				       it might be a denied page or similar
				     */
	    && (!srv_host->isLocalHost()
		/*
		  As in TLS we cannot check if the connection reported
		  some mismatches we do not set TLS names for local hosts
		  that are more subject to naming errors, and that whose
		  name could be set via other protocols
		*/
		))
      srv_host->offlineSetTLSName(protos.tls.client_requested_server_name); /* (***) */
  }
}

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

void Flow::updateThroughputStats(float tdiff_msec, u_int32_t diff_sent_packets,
                                 u_int64_t diff_sent_bytes,
                                 u_int64_t diff_sent_goodput_bytes,
                                 u_int32_t diff_rcvd_packets,
                                 u_int64_t diff_rcvd_bytes,
                                 u_int64_t diff_rcvd_goodput_bytes) {
  if(tdiff_msec == 0) return;

  /* In order to avoid overestimating the throughput, we scale small intervals
   * to at least one second */
  if(tdiff_msec < 1000) tdiff_msec = 1000.;

  // bps
  float bytes_msec = ((float)((diff_sent_bytes+diff_rcvd_bytes) * 1000)) / tdiff_msec;
  float goodput_bytes_msec = ((float)((diff_sent_goodput_bytes+diff_rcvd_goodput_bytes) * 1000)) / tdiff_msec;

  /* Just to be safe */
  if(bytes_msec < 0) bytes_msec = 0;
  if(goodput_bytes_msec < 0) goodput_bytes_msec = 0;

  if(bytes_msec > 0) {
    // refresh trend stats for the overall throughput
    if(get_bytes_thpt() < bytes_msec)
      bytes_thpt_trend = trend_up;
    else if(get_bytes_thpt() > bytes_msec)
      bytes_thpt_trend = trend_down;
    else
      bytes_thpt_trend = trend_stable;

    // refresh goodput stats for the overall throughput
    if(get_goodput_bytes_thpt() < goodput_bytes_msec)
      goodput_bytes_thpt_trend = trend_up;
    else if(get_goodput_bytes_thpt() > goodput_bytes_msec)
      goodput_bytes_thpt_trend = trend_down;
    else
      goodput_bytes_thpt_trend = trend_stable;

    // update the old values with the newly calculated ones
    bytes_thpt = bytes_msec;
    goodput_bytes_thpt = goodput_bytes_msec;

#if DEBUG_TREND
    u_int64_t diff_bytes = diff_sent_bytes + diff_rcvd_bytes;

    ntop->getTrace()->traceEvent(TRACE_NORMAL,
				 "[tdiff_msec: %.2f][diff_bytes: %lu][diff_sent_bytes: "
				 "%lu][diff_rcvd_bytes: %lu][bytes_thpt: %.4f Mbit]",
				 tdiff_msec, diff_bytes, diff_sent_bytes, diff_rcvd_bytes,
				 (get_bytes_thpt() * 8)/1000000.);
#endif

    if(top_bytes_thpt < get_bytes_thpt())
      top_bytes_thpt = get_bytes_thpt();

    if(top_goodput_bytes_thpt < get_goodput_bytes_thpt())
      top_goodput_bytes_thpt = get_goodput_bytes_thpt();

#ifdef NTOPNG_PRO
    throughputTrend.update(get_bytes_thpt()),
      goodputTrend.update(get_goodput_bytes_thpt());
    thptRatioTrend.update((bytes_msec != 0) ? (((double)(goodput_bytes_msec * 100)) / (double)bytes_msec) : 0);

#ifdef DEBUG_TREND
    if((get_goodput_bytes_cli2srv() + get_goodput_bytes_srv2cli()) > 0) {
      char buf[256];

      ntop->getTrace()->traceEvent(TRACE_NORMAL,
				   "%s [Goodput long/mid/short %.3f/%.3f/%.3f][ratio: %s][goodput/thpt: %.3f]",
				   print(buf, sizeof(buf)), goodputTrend.getLongTerm(),
				   goodputTrend.getMidTerm(), goodputTrend.getShortTerm(),
				   goodputTrend.getTrendMsg(),
				   ((float)(100 * (get_goodput_bytes))) /
				   (float)(get_bytes));
    }
#endif
#endif

    // pps
    float pkts_msec = ((float)((diff_sent_packets+ diff_rcvd_packets) * 1000)) / tdiff_msec;

    /* Just to be safe */
    if(pkts_msec < 0) pkts_msec = 0;

    if(get_pkts_thpt() < pkts_msec)
      pkts_thpt_trend = trend_up;
    else if(get_pkts_thpt() > pkts_msec)
      pkts_thpt_trend = trend_down;
    else
      pkts_thpt_trend = trend_stable;

    pkts_thpt = pkts_msec;
    if(top_pkts_thpt < get_pkts_thpt()) top_pkts_thpt = get_pkts_thpt();

#if DEBUG_TREND
    u_int64_t diff_pkts = diff_sent_packets + diff_rcvd_packets;

    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[msec: %.1f][tdiff: %f][pkts: %lu][pkts_thpt: %.2f pps]",
				 pkts_msec, tdiff_msec, diff_pkts, get_pkts_thpt());
#endif
  }
}

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

/* This function is called every second by the purgeIdle function */
void Flow::periodic_stats_update(const struct timeval *tv) {
  bool first_partial;
  PartializableFlowTrafficStats partial;
  Host *cli_h = NULL, *srv_h = NULL;

#if 0
  ntop->getTrace()->traceEvent(TRACE_NORMAL, "Periodic flow update");
#endif

  if((last_update_time.tv_sec > 0)
     && ((tv->tv_sec - last_update_time.tv_sec) < 3.)) {

    return; /* Too early */
  }

  get_partial_traffic_stats(&periodic_stats_update_partial, &partial, &first_partial);

  u_int32_t diff_sent_packets = partial.get_cli2srv_packets();
  u_int64_t diff_sent_bytes = partial.get_cli2srv_bytes();
  u_int64_t diff_sent_goodput_bytes = partial.get_cli2srv_goodput_bytes();

  u_int32_t diff_rcvd_packets = partial.get_srv2cli_packets();
  u_int64_t diff_rcvd_bytes = partial.get_srv2cli_bytes();
  u_int64_t diff_rcvd_goodput_bytes = partial.get_srv2cli_goodput_bytes();

  /*
    Do the stats update on the actual peers, i.e.,
    peers possibly swapped due to the heuristic
  */
  get_actual_peers(&cli_h, &srv_h);

  Mac *cli_mac = cli_h ? cli_h->getMac() : NULL;
  Mac *srv_mac = srv_h ? srv_h->getMac() : NULL;

  hosts_periodic_stats_update(getInterface(), cli_h, srv_h, &partial, first_partial, tv);

  if(cli_h && srv_h) {
    if(diff_sent_bytes || diff_rcvd_bytes) {
      /* Update L2 Device stats */

      if(srv_mac) {
	//#ifdef HAVE_NEDGE
        srv_mac->incSentStats(tv->tv_sec, diff_rcvd_packets, diff_rcvd_bytes);
        srv_mac->incRcvdStats(tv->tv_sec, diff_sent_packets, diff_sent_bytes);
	//#endif

        if(ntop->getPrefs()->areMacNdpiStatsEnabled()) {
          srv_mac->incnDPIStats(tv->tv_sec, get_protocol_category(),
                                diff_rcvd_packets, diff_rcvd_bytes,
                                diff_rcvd_goodput_bytes, diff_sent_packets,
                                diff_sent_bytes, diff_sent_goodput_bytes);
        }
      }

      if(cli_mac) {
	//#ifdef HAVE_NEDGE
        cli_mac->incSentStats(tv->tv_sec, diff_sent_packets, diff_sent_bytes);
        cli_mac->incRcvdStats(tv->tv_sec, diff_rcvd_packets, diff_rcvd_bytes);
	//#endif

        if(ntop->getPrefs()->areMacNdpiStatsEnabled()) {
          cli_mac->incnDPIStats(tv->tv_sec, get_protocol_category(),
                                diff_sent_packets, diff_sent_bytes,
                                diff_sent_goodput_bytes, diff_rcvd_packets,
                                diff_rcvd_bytes, diff_rcvd_goodput_bytes);
        }
      }

#ifdef NTOPNG_PRO
      if(ntop->getPro()->has_valid_license()) {
#ifndef HAVE_NEDGE
        if(trafficProfile)
          trafficProfile->incBytes(diff_sent_bytes + diff_rcvd_bytes);
#endif
      }
#endif
    }
  } /* Closes if(cli_h && srv_h) */

#ifndef HAVE_NEDGE
  /* For nEdge check Flow::setPacketsBytes */
  /* Update throughput */
  if(getFlowSource() != collected_netflow_ipfix) {
    /*
      In case of NetFlow/IPFIX throughput is computed whenever a new
      flow update is received for this flow
      by Flow::addFlowStats()
    */

    if(last_update_time.tv_sec > 0) {
      float tdiff_msec = Utils::msTimevalDiff(tv, &last_update_time);
#ifdef DEBUG
      u_int32_t total = diff_sent_bytes+diff_rcvd_bytes;
#endif

      updateThroughputStats(tdiff_msec, diff_sent_packets, diff_sent_bytes,
			    diff_sent_goodput_bytes, diff_rcvd_packets,
			    diff_rcvd_bytes, diff_rcvd_goodput_bytes);

#ifdef DEBUG
      ntop->getTrace()->traceEvent(TRACE_NORMAL, "%.1f ms [%.3f Mbit][%u]", tdiff_msec,
				   ((float)(total*8)) / (tdiff_msec*1000.), total);
#endif
    }
  }
#endif

  memcpy(&last_update_time, tv, sizeof(struct timeval));
  GenericHashEntry::periodic_stats_update(tv);
}

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

void Flow::dumpCheck(time_t t, bool last_dump_before_free) {
  if((ntop->getPrefs()->is_flows_dump_enabled()
#ifdef HAVE_ZMQ
#ifndef HAVE_NEDGE
       || ntop->get_export_interface()
#endif
#endif
       )
#ifdef NTOPNG_PRO
      && (getInterface()->isPacketInterface() /* Not a ZMQ interface */
          || (!ntop->getPrefs()->do_dump_flows_direct() /* Direct dump not enabled */))
#endif
      ) {
    dump(t, last_dump_before_free);
  }
}

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

void Flow::update_pools_stats(NetworkInterface *iface, Host *cli_host,
                              Host *srv_host, const struct timeval *tv,
                              u_int64_t diff_sent_packets,
                              u_int64_t diff_sent_bytes,
                              u_int64_t diff_rcvd_packets,
                              u_int64_t diff_rcvd_bytes) const {
  if(!diff_sent_bytes && !diff_rcvd_bytes) return; /* Nothing to update */

  HostPools *hp;
  u_int16_t cli_host_pool_id = 0, srv_host_pool_id;
  ndpi_protocol_category_t category_id = get_protocol_category();

  hp = iface->getHostPools();
  if(hp) {
    /* Client host */
    if(cli_host
#ifdef HAVE_NEDGE
        && cli_host->getMac() &&
        (cli_host->getMac()->locate() == located_on_lan_interface)
#endif
	) {
      cli_host_pool_id = cli_host->get_host_pool();

      /* Overall host pool stats */
      if(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN &&
          !ndpi_is_subprotocol_informative(ndpiDetectedProtocol.proto.master_protocol))
        hp->incPoolStats(tv->tv_sec, cli_host_pool_id,
                         ndpiDetectedProtocol.proto.app_protocol, category_id,
                         diff_sent_packets, diff_sent_bytes, diff_rcvd_packets,
                         diff_rcvd_bytes);
      else
        hp->incPoolStats(tv->tv_sec, cli_host_pool_id,
                         ndpiDetectedProtocol.proto.master_protocol, category_id,
                         diff_sent_packets, diff_sent_bytes, diff_rcvd_packets,
                         diff_rcvd_bytes);

#ifdef NTOPNG_PRO
      /* Per host quota-enforcement stats */
      if(hp->enforceQuotasPerPoolMember(cli_host_pool_id)) {
        cli_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.master_protocol, diff_sent_packets,
					   diff_sent_bytes, diff_rcvd_packets, diff_rcvd_bytes);
        cli_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.app_protocol, diff_sent_packets,
					   diff_sent_bytes, diff_rcvd_packets, diff_rcvd_bytes);
        cli_host->incQuotaEnforcementCategoryStats(tv->tv_sec, category_id, diff_sent_bytes, diff_rcvd_bytes);
      }
#endif
    }

    /* Server host */
    if(srv_host
#ifdef HAVE_NEDGE
        && srv_host->getMac() &&
        (srv_host->getMac()->locate() == located_on_lan_interface)
#endif
	) {
      srv_host_pool_id = srv_host->get_host_pool();

      /* Update server pool stats only if the pool is not equal to the client
       * pool */
      if(!cli_host || (srv_host_pool_id != cli_host_pool_id)) {
        if(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN &&
            !ndpi_is_subprotocol_informative(ndpiDetectedProtocol.proto.master_protocol))
          hp->incPoolStats(tv->tv_sec, srv_host_pool_id,
                           ndpiDetectedProtocol.proto.app_protocol, category_id,
                           diff_rcvd_packets, diff_rcvd_bytes,
                           diff_sent_packets, diff_sent_bytes);
        else
          hp->incPoolStats(tv->tv_sec, srv_host_pool_id,
                           ndpiDetectedProtocol.proto.master_protocol, category_id,
                           diff_rcvd_packets, diff_rcvd_bytes,
                           diff_sent_packets, diff_sent_bytes);
      }

      /* When quotas have to be enforced per pool member, stats must be
       * increased even if cli and srv are on the same pool */
#ifdef NTOPNG_PRO
      if(hp->enforceQuotasPerPoolMember(srv_host_pool_id)) {
        srv_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.master_protocol, diff_rcvd_packets,
					   diff_rcvd_bytes, diff_sent_packets, diff_sent_bytes);
        srv_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.app_protocol, diff_rcvd_packets,
					   diff_rcvd_bytes, diff_sent_packets, diff_sent_bytes);
        srv_host->incQuotaEnforcementCategoryStats(tv->tv_sec, category_id, diff_rcvd_bytes, diff_sent_bytes);
      }
#endif
    }
  }
}

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

bool Flow::equal(const Mac *_src_pkt_mac, const Mac *_dst_pkt_mac,
                 const IpAddress *_cli_ip, const IpAddress *_srv_ip,
                 u_int16_t _cli_port, u_int16_t _srv_port, u_int16_t _u_int16_t,
                 u_int16_t _observation_point_id, u_int32_t _private_flow_id,
                 u_int8_t _protocol, const ICMPinfo *const _icmp_info,
                 bool *src2srv_direction) const {
  const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
  const Mac *src_mac, *dst_mac;
  bool useMacAddressInFlowKey = ntop->getPrefs()->useMacAddressInFlowKey();

#if 0
  if(ntohs(_cli_port) == 17446) {
    char buf1[64],buf2[64],buf3[64],buf4[64];

    ntop->getTrace()->traceEvent(TRACE_WARNING, "[%s][%s][%s][%s]",
				 cli_ip->print(buf1, sizeof(buf1)),
				 srv_ip->print(buf2, sizeof(buf2)),
				 _cli_ip->print(buf3, sizeof(buf3)),
				 _srv_ip->print(buf4, sizeof(buf4)));
  }
#endif

  if(getPrivateFlowId() != _private_flow_id) return (false);

  if((get_vlan_id() != _u_int16_t)
#ifdef MAKE_OBSERVATION_POINT_KEY
      /*
        Uncomment the line below if you want the same host
        seen from various observation points, to be considered
        a unique host */
      || (get_observation_point_id() != _observation_point_id)
#endif
      )
    return (false);

  if(_protocol != protocol) return (false);

#ifdef DONT_MERGE_ICMP_FLOWS
  if(icmp_info && !icmp_info->equal(_icmp_info)) return (false);
#endif

  if(cli_ip && cli_ip->equal(_cli_ip) && srv_ip && srv_ip->equal(_srv_ip) &&
      _cli_port == cli_port && _srv_port == srv_port) {
    *src2srv_direction = true, src_mac = _src_pkt_mac, dst_mac = _dst_pkt_mac;
  } else if(srv_ip && srv_ip->equal(_cli_ip) && cli_ip &&
             cli_ip->equal(_srv_ip) && _srv_port == cli_port &&
             _cli_port == srv_port) {
    *src2srv_direction = false, src_mac = _dst_pkt_mac, dst_mac = _src_pkt_mac;
    cli_ip = get_srv_ip_addr(), srv_ip = get_cli_ip_addr();
  } else
    return (false);

#ifdef USE_MAC_IN_KEY_WITH_DHCP
  /* Check if MAC address needs to be used in flow key */
  if((cli_ip->key() == 0) && (srv_ip->key() == 0xFFFFFFFF)) {
    useMacAddressInFlowKey = true;
  }
#endif

  if(useMacAddressInFlowKey) {
    if(cli_host && src_mac) {
      Mac *cli_mac = cli_host->getMac();

      if(cli_mac != src_mac) {
#ifdef DEBUG
        Mac *srv_mac = srv_host->getMac();
        char buf[64], buf0[64], buf1[64], buf2[64], buf3[64], buf4[64];

        ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s (%s) <-> %s (%s) [%s / %s]",
				     cli_ip->print(buf, sizeof(buf)), cli_mac->print(buf0, sizeof(buf0)),
				     srv_ip->print(buf1, sizeof(buf1)),
				     srv_mac->print(buf2, sizeof(buf2)),
				     ((Mac *)_src_pkt_mac)->print(buf3, sizeof(buf3)),
				     ((Mac *)_dst_pkt_mac)->print(buf4, sizeof(buf4)));
#endif
        return (false);
      }
    }

    if(src_mac && dst_mac && cli_host && srv_host) {
      Mac *srv_mac = srv_host->getMac();

      if(srv_mac != dst_mac) return (false);
    }
  }

  return (true);
}

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

const char *Flow::cipher_weakness2str(ndpi_cipher_weakness w) const {
  switch (w) {
  case ndpi_cipher_safe:
    return ("safe");
    break;

  case ndpi_cipher_weak:
    return ("weak");
    break;

  case ndpi_cipher_insecure:
    return ("insecure");
    break;
  }

  return (""); /* NOTREACHED */
}

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

void Flow::luaScore(lua_State *vm) {
  u_int32_t tot;

  lua_newtable(vm);

  lua_push_int32_table_entry(vm, "flow_score", getScore());

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

  lua_newtable(vm);
  for (u_int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
    ScoreCategory score_category = (ScoreCategory)i;
    char tmp[8];

    snprintf(tmp, sizeof(tmp), "%u", i);
    lua_push_int32_table_entry(vm, tmp,
                               stats.get_cli_score(score_category) +
			       stats.get_srv_score(score_category));
  }

  lua_pushstring(vm, "host_categories_total");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

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

  lua_newtable(vm);

  tot = 0;
  for (u_int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
    ScoreCategory score_category = (ScoreCategory)i;
    tot += stats.get_cli_score(score_category);
  }
  lua_push_int32_table_entry(vm, "client_score", tot);

  tot = 0;
  for (u_int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
    ScoreCategory score_category = (ScoreCategory)i;

    tot += stats.get_srv_score(score_category);
  }
  lua_push_int32_table_entry(vm, "server_score", tot);

  lua_pushstring(vm, "host_score_total");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

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

  /* Individual alerts score */

  lua_newtable(vm);
  for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++) {
    char tmp[8];
    FlowAlert *alert = it->second;

    snprintf(tmp, sizeof(tmp), "%u", it->first);

    lua_push_int32_table_entry(vm, tmp, alert->getCliScore() + alert->getSrvScore());
  }

  lua_pushstring(vm, "alert_score");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

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

  lua_pushstring(vm, "score");
  lua_insert(vm, -2);
  lua_settable(vm, -3);
}

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

void Flow::lua(lua_State *vm, AddressTree *ptree,
	       DetailsLevel details_level, bool skipNewTable) {
  const IpAddress *src_ip = get_cli_ip_addr(), *dst_ip = get_srv_ip_addr();
  bool src_match = true, dst_match = true;
  bool mask_flow;
  bool has_json_info = false;
  u_char community_id[200];
  char buf[64];
  Mac *cli_mac = get_cli_host() ? get_cli_host()->getMac() : NULL;

  if(ptree) {
    if(src_ip) src_match = src_ip->match(ptree);
    if(dst_ip) dst_match = dst_ip->match(ptree);
    if(!src_match && !dst_match) return;
  }

  if(!skipNewTable) lua_newtable(vm);

  lua_get_ip(vm, true /* Client */);
  lua_get_ip(vm, false /* Server */);
  lua_push_uint32_table_entry(vm, "vlan", get_vlan_id());
  lua_push_int32_table_entry(vm, "iface_index", getInterface() ? getInterface()->get_id() : -1);
  lua_get_port(vm, true /* Client */);
  lua_get_port(vm, false /* Server */);

  mask_flow = isMaskedFlow();  // mask_cli_host || mask_dst_host;

  lua_get_bytes(vm);

  if (json_protocol_info)
    lua_push_str_table_entry(vm, "json_protocol_info", json_protocol_info);

  char *json = alerts_json; /* Copying ref as it may be moved to the shadow meantime */
  if (json) lua_push_str_table_entry(vm, "json_alert", json);

  if(details_level >= details_high) {
    if(tcp && tcp->tcp_fingerprint)
      lua_push_str_table_entry(vm, "tcp_fingerprint", tcp->tcp_fingerprint);

    if(swap_done) lua_push_bool_table_entry(vm, "flow_swapped", true);
    lua_push_uint32_table_entry(vm, "flow_source", flow_source);
    lua_push_bool_table_entry(vm, "cli.allowed_host", src_match);
    lua_push_bool_table_entry(vm, "srv.allowed_host", dst_match);

    lua_get_info(vm, true /* Client */);
    lua_get_info(vm, false /* Server */);

    if(collection && collection->vrfId)
      lua_push_uint64_table_entry(vm, "vrfId", collection->vrfId);

    /* See VLANAddressTree.h for details */
    lua_push_uint32_table_entry(vm, "observation_point_id",
                                get_observation_point_id());

    if(srcAS)
      lua_push_int32_table_entry(vm, "src_as", srcAS);
    else {
      Host *h = get_cli_host();

      if(h) {
        lua_push_int32_table_entry(vm, "src_as", h->get_asn());
        lua_push_str_table_entry(vm, "src_as_name", h->get_asname());
      } else {
	/* View interfaces */
	u_int32_t asn;
	char *asname;

	ntop->getGeolocation()->getAS(get_cli_ip_addr(), &asn, &asname);

	if(asn != 0) lua_push_int32_table_entry(vm, "src_as", asn);
        if(asname)   lua_push_str_table_entry(vm, "src_as_name", asname);
      }
    }

    if(dstAS)
      lua_push_int32_table_entry(vm, "dst_as", dstAS);
    else {
      Host *h = get_srv_host();

      if(h) {
        lua_push_int32_table_entry(vm, "dst_as", h->get_asn());
        lua_push_str_table_entry(vm, "dst_as_name", h->get_asname());
      } else {
	/* View interfaces */
	u_int32_t asn;
	char *asname;

	ntop->getGeolocation()->getAS(get_srv_ip_addr(), &asn, &asname);
	if(asn != 0) lua_push_int32_table_entry(vm, "dst_as", asn);
        if(asname)   lua_push_str_table_entry(vm, "dst_as_name", asname);
      }
    }

    if(collection) {
      if(collection->prevAdjacentAS)
	lua_push_int32_table_entry(vm, "prev_adjacent_as", collection->prevAdjacentAS);

      if(collection->nextAdjacentAS)
	lua_push_int32_table_entry(vm, "next_adjacent_as", collection->nextAdjacentAS);
    }

    lua_tos(vm);
    lua_get_protocols(vm);
    lua_push_str_table_entry(vm, "community_id",
			     (char *)getCommunityId(community_id, sizeof(community_id)));

#ifdef NTOPNG_PRO
#ifndef HAVE_NEDGE
    if((!mask_flow) && trafficProfile && ntop->getPro()->has_valid_license())
      lua_push_str_table_entry(vm, "profile", trafficProfile->getName());
#endif
#endif

    lua_get_packets(vm);
    lua_get_time(vm);

    lua_get_dir_traffic(vm, true /* Client to Server */);
    lua_get_dir_traffic(vm, false /* Server to Client */);

    lua_get_flow_connection_state(vm);

    luaScore(vm);

    if(isICMP()) {
      lua_newtable(vm);

      if(isBidirectional()) {
        lua_push_uint64_table_entry(vm, "type", protos.icmp.srv2cli.icmp_type);
        lua_push_uint64_table_entry(vm, "code", protos.icmp.srv2cli.icmp_code);
      } else {
        lua_push_uint64_table_entry(vm, "type", protos.icmp.cli2srv.icmp_type);
        lua_push_uint64_table_entry(vm, "code", protos.icmp.cli2srv.icmp_code);
      }

      if(icmp_info) icmp_info->lua(vm, NULL, iface, get_vlan_id());

      lua_pushstring(vm, "icmp");
      lua_insert(vm, -2);
      lua_settable(vm, -3);
    }

    lua_push_int32_table_entry(vm, "cli.devtype",
                               (cli_host && cli_host->getMac())
			       ? cli_host->getMac()->getDeviceType()
			       : device_unknown);
    lua_push_int32_table_entry(vm, "srv.devtype",
                               (srv_host && srv_host->getMac())
			       ? srv_host->getMac()->getDeviceType()
			       : device_unknown);

#ifdef HAVE_NEDGE
    if(iface->is_bridge_interface()) {
      lua_push_bool_table_entry(vm, "verdict.pass", isPassVerdict() ? 1 : 0);
      lua_push_int32_table_entry(vm, "verdict.reason", (int) dropVerdictReason);
    }
#else
    if(!passVerdict) {
      lua_push_bool_table_entry(vm, "verdict.pass", 0);
    }
#endif

    if(get_protocol() == IPPROTO_TCP) lua_get_tcp_info(vm);

    if(!mask_flow) {
      if(host_server_name)
        lua_push_str_table_entry(vm, "host_server_name", host_server_name);
      if(suspicious_dga_domain)
        lua_push_str_table_entry(vm, "suspicious_dga_domain",
                                 suspicious_dga_domain);
      if(bt_hash) lua_push_str_table_entry(vm, "bittorrent_hash", bt_hash);
      lua_push_str_table_entry(vm, "info", getFlowInfo(true).c_str());
    }

    if(stun_mapped_address)
      lua_push_str_table_entry(vm, "protos.stun.mapped_ip_address", stun_mapped_address);

    if(isDNS() && protos.dns.last_query) {
      lua_push_uint64_table_entry(vm, "protos.dns.last_query_type",
                                  protos.dns.last_query_type);
      lua_push_uint64_table_entry(vm, "protos.dns.last_return_code",
                                  protos.dns.last_return_code);
    }

    if(l7_json.length() > 0)
      lua_push_str_table_entry(vm, "l7_json", l7_json.c_str());

#ifdef HAVE_NEDGE
    lua_push_uint64_table_entry(vm, "marker", marker);

    if(cli_host && srv_host) {
      /* Shapers */
      lua_push_uint64_table_entry(vm, "shaper.cli2srv_ingress",
				  flowShaperIds.cli2srv.ingress ? flowShaperIds.cli2srv.ingress->get_shaper_id() : DEFAULT_SHAPER_ID);
      lua_push_uint64_table_entry(vm, "shaper.cli2srv_egress",
				  flowShaperIds.cli2srv.egress  ? flowShaperIds.cli2srv.egress->get_shaper_id()  : DEFAULT_SHAPER_ID);
      lua_push_uint64_table_entry(vm, "shaper.srv2cli_ingress",
				  flowShaperIds.srv2cli.ingress ? flowShaperIds.srv2cli.ingress->get_shaper_id() : DEFAULT_SHAPER_ID);
      lua_push_uint64_table_entry(vm, "shaper.srv2cli_egress",
				  flowShaperIds.srv2cli.egress  ? flowShaperIds.srv2cli.egress->get_shaper_id()  : DEFAULT_SHAPER_ID);

      /* Quota */
      lua_push_str_table_entry(vm, "cli.quota_source",
                               Utils::policySource2Str(cli_quota_source));
      lua_push_str_table_entry(vm, "srv.quota_source",
                               Utils::policySource2Str(srv_quota_source));
    }
#endif

    if(!mask_flow) {
      if(isHTTP()) lua_get_http_info(vm);
      if(isDNS()) lua_get_dns_info(vm);
      if(isSSH()) lua_get_ssh_info(vm);
      if(isTLS()) lua_get_tls_info(vm);
      if(isSIP()) lua_get_sip_info(vm);
    }

    if(!getInterface()->isPacketInterface()) lua_snmp_info(vm);

    lua_push_int32_table_entry(vm, "l7_error_code", getErrorCode());
    lua_push_int32_table_entry(vm, "flow_verdict", flow_verdict);
    lua_push_bool_table_entry(vm, "periodic_flow",
                              is_periodic_flow ? true : false);
    if(end_reason)
      lua_push_str_table_entry(vm, "flow_end_reason", getEndReason());

    if(collection && collection->wifi.wlan_ssid) {
      char mac_buf[20];

      lua_newtable(vm);

      lua_push_str_table_entry(vm, "ssid", collection->wifi.wlan_ssid);
      lua_push_str_table_entry(vm, "wtp_mac_address",
			       Utils::formatMac(collection->wifi.wtp_mac_address, mac_buf, sizeof(mac_buf)));

      lua_pushstring(vm, "wlan");
      lua_insert(vm, -2);
      lua_settable(vm, -3);
    }

    if(isSMTP()
	/* Discard SMTP connections that become TLS as the SMTP part is not populated */
	&& (!isSMTPS())) {
      if(getSMTPMailFrom())
        lua_push_str_table_entry(vm, "smtp_mail_from", getSMTPMailFrom());

      if(getSMTPRcptTo())
        lua_push_str_table_entry(vm, "smtp_rcpt_to", getSMTPRcptTo());
    }

    if(rtp_stream_type != ndpi_multimedia_unknown_flow) {
      if(rtp_stream_type & ndpi_multimedia_audio_flow)
	lua_push_str_table_entry(vm, "rtp_stream_type", "audio");
      else if(rtp_stream_type & ndpi_multimedia_video_flow)
	lua_push_str_table_entry(vm, "rtp_stream_type", "video");
      else if(rtp_stream_type & ndpi_multimedia_screen_sharing_flow)
	lua_push_str_table_entry(vm, "rtp_stream_type", "screen_share");
    }

    if(flow_payload != NULL)
      lua_push_str_len_table_entry(vm, "flow_payload", flow_payload, flow_payload_len);

    if(get_json_info()) {
      lua_push_str_table_entry(vm, "moreinfo.json",
                               json_object_to_json_string(get_json_info()));
      has_json_info = true;
    } else if(get_tlv_info()) {
      ndpi_deserializer deserializer;

      if(ndpi_init_deserializer(&deserializer, get_tlv_info()) == 0) {
        ndpi_serializer serializer;

        if(ndpi_init_serializer(&serializer, ndpi_serialization_format_json) >=  0) {
          char *buffer;
          u_int32_t buffer_len;

          ndpi_deserialize_clone_all(&deserializer, &serializer);
          buffer = ndpi_serializer_get_buffer(&serializer, &buffer_len);

          if(buffer) {
            lua_push_str_table_entry(vm, "moreinfo.json", buffer);
            has_json_info = true;
          }

          ndpi_term_serializer(&serializer);
        }
      }
    }

    if(iec104) iec104->lua(vm);

#ifdef NTOPNG_PRO
    if(modbus) modbus->lua(vm);
#endif

    if(!has_json_info) lua_push_str_table_entry(vm, "moreinfo.json", "{}");

    if(ebpf) ebpf->lua(vm);

    lua_get_throughput(vm);

    /* Interarrival Times */
    lua_get_dir_iat(vm, true /* Client to Server */);
    lua_get_dir_iat(vm, false /* Server to Client */);

    if((!mask_flow) && (details_level >= details_higher)) {
      lua_get_geoloc(vm, true /* Client */, true /* Coordinates */,
                     false /* Country and City */);
      lua_get_geoloc(vm, false /* Server */, true /* Coordinates */,
                     false /* Country and City */);

      if(details_level >= details_max) {
        lua_get_geoloc(vm, true /* Client */, false /* Coordinates */,
                       true /* Country and City */);
        lua_get_geoloc(vm, false /* Server */, false /* Coordinates */,
                       true /* Country and City */);
      }
    }

    lua_confidence(vm);
    lua_get_risk_info(vm);
#ifdef ENABLE_ENTROPHY_CALCULATION
    lua_entropy(vm);
#endif
    if(riskInfo)
      lua_push_str_table_entry(vm, "riskInfo", riskInfo);

#if defined(NTOPNG_PRO)
    lua_get_qoe_score(vm);
#endif
  }

  lua_get_status(vm);

  lua_push_str_table_entry(vm, "proto.ndpi",
                           detection_completed
			   ? get_detected_protocol_name(buf, sizeof(buf))
			   : (char *)CONST_TOO_EARLY);

  lua_push_str_table_entry(vm, "proto.ndpi_confidence",
			   ndpi_confidence_get_name(ndpi_confidence));

  // this is used to dynamicall update entries in the GUI
  lua_push_uint64_table_entry(vm, "ntopng.key", key());  // Key
  lua_push_uint64_table_entry(vm, "hash_entry_id", get_hash_entry_id());

  if(cli_mac != NULL) {
    if(cli_mac->getDHCPfingerprint())
      lua_push_str_table_entry(vm,
			       "dhcp_fingerprint",
			       cli_mac->getDHCPfingerprint());
  }
}

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

void Flow::lua_confidence(lua_State *vm) {
  if(isDPIDetectedFlow())
    lua_push_uint32_table_entry(vm, "confidence",
				(ndpiConfidence)confidence_dpi);
  else
    lua_push_uint32_table_entry(vm, "confidence",
				(ndpiConfidence)confidence_guessed);
}

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

void Flow::lua_tos(lua_State *vm) {
  lua_newtable(vm);

  lua_newtable(vm);
  lua_push_int32_table_entry(vm, "DSCP", getCli2SrvDSCP());
  lua_push_int32_table_entry(vm, "ECN", getCli2SrvECN());
  lua_pushstring(vm, "client");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

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

  lua_newtable(vm);
  lua_push_int32_table_entry(vm, "DSCP", getSrv2CliDSCP());
  lua_push_int32_table_entry(vm, "ECN", getSrv2CliECN());
  lua_pushstring(vm, "server");
  lua_insert(vm, -2);
  lua_settable(vm, -3);

  lua_pushstring(vm, "tos");
  lua_insert(vm, -2);
  lua_settable(vm, -3);
}

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

void Flow::lua_get_risk_info(lua_State *vm) {
  if(ndpi_flow_risk_bitmap != 0) {
    ndpi_risk unhandled_ndpi_risks = ntop->getUnhandledRisks();

    if(unhandled_ndpi_risks & ndpi_flow_risk_bitmap) {
      /* This flow has some unhandled risks, that is, risks set by nDPI but not
       * handled by flow checks */

      lua_newtable(vm);

      for (u_int i = 0; i < NDPI_MAX_RISK; i++)
        if(hasRisk((ndpi_risk_enum)i) &&
            NDPI_ISSET_BIT(unhandled_ndpi_risks, (ndpi_risk_enum)i))
          lua_push_uint64_table_entry(vm, ndpi_risk2str((ndpi_risk_enum)i), i);

      lua_pushstring(vm, "unhandled_flow_risk");
      lua_insert(vm, -2);
      lua_settable(vm, -3);
    }
  }
}

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

void Flow::setRisk(ndpi_risk risk_bitmap) {
  /* Handle OR of risks with no risk */
  NDPI_CLR_BIT(risk_bitmap, NDPI_NO_RISK);

  if(risk_bitmap == 0) NDPI_SET_BIT(risk_bitmap, NDPI_NO_RISK);

  ndpi_flow_risk_bitmap = risk_bitmap;

  has_malicious_cli_signature =
    NDPI_ISSET_BIT(ndpi_flow_risk_bitmap, NDPI_MALICIOUS_FINGERPRINT);
}

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

void Flow::addRisk(ndpi_risk risk_bitmap) {
  setRisk(ndpi_flow_risk_bitmap | risk_bitmap);
}

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

bool Flow::hasRisk(ndpi_risk_enum r) const {
  if(r < NDPI_MAX_RISK) return NDPI_ISSET_BIT(ndpi_flow_risk_bitmap, r);

  return false;
}

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

/* Returns true if at least one nDPI flow risk is set */
bool Flow::hasRisks() const {
  for (int i = 0; i < NDPI_MAX_RISK; i++) {
    if(hasRisk((ndpi_risk_enum)i)) return true;
  }

  return false;
}

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

void Flow::clearRisks() {
  ndpi_flow_risk_bitmap = 0;
  NDPI_SET_BIT(ndpi_flow_risk_bitmap, NDPI_NO_RISK);
}

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

void Flow::computeKey() {
  u_int32_t k = cli_port + srv_port + vlanId + protocol + privateFlowId;

#ifdef MAKE_OBSERVATION_POINT_KEY
  k += get_observation_point_id();
#endif

  if(get_cli_ip_addr()) k += get_cli_ip_addr()->key();
  if(get_srv_ip_addr()) k += get_srv_ip_addr()->key();
  if(icmp_info) k += icmp_info->key();

#if USE_MAC_IN_KEY_WITH_DHCP
  if(get_cli_ip_addr() && get_srv_ip_addr()) {
    if((get_cli_ip_addr()->key() == 0) && (get_srv_ip_addr()->key() == 0xFFFFFFFF)) {
      /* Add the MAC address of the source host (dst_mac is not necessary as it's FF:FF:FF:FF:FF:FF) */
      if(get_cli_host() != NULL) {
	Mac *cli_mac = get_cli_host()->getMac();

	if(cli_mac != NULL)
	  k += cli_mac->key();
      }
    }
  }
#endif

  flow_key = k;
}

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

u_int32_t Flow::key(Host *_cli, u_int16_t _cli_port, Host *_srv,
                    u_int16_t _srv_port, u_int16_t _vlan_id,
                    u_int16_t _observation_point_id, u_int16_t _protocol) {
  u_int32_t k = _cli_port + _srv_port + _vlan_id + _protocol;

#ifdef MAKE_OBSERVATION_POINT_KEY
  k += _observation_point_id;
#endif

  if(_cli) k += _cli->key();
  if(_srv) k += _srv->key();

  return (k);
}

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

void Flow::set_hash_entry_id(u_int32_t assigned_hash_entry_id) {
  hash_entry_id = assigned_hash_entry_id;
};

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

u_int32_t Flow::get_hash_entry_id() const { return (hash_entry_id); };

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

bool Flow::is_hash_entry_state_idle_transition_ready() {
  bool ret = false;

#ifdef EXPIRE_FLOWS_IMMEDIATELY
  return (true); /* Debug only */
#endif

#ifdef HAVE_NEDGE
  if(iface->getIfType() == interface_type_NETFILTER)
    return (isNetfilterIdleFlow());
#endif

  if(iface->getIfType() == interface_type_ZMQ ||
      iface->getIfType() == interface_type_SYSLOG) {
    ret = is_active_entry_now_idle(iface->getFlowMaxIdle());
  } else {
    if(tcp != NULL) {
      u_int8_t tcp_flags = src2dst_tcp_flags | dst2src_tcp_flags;

      /*
       * The flow is considered idle after a MAX_TCP_FLOW_IDLE
       * when RST/FIN are set or when the TWH is not completed.
       * This prevents finalized/reset flows, or flows with an imcomplete
       * TWH from staying in memory for too long.
       */
      if((tcp_flags & TH_FIN
	   || tcp_flags & TH_RST
	   || ((iface->isPacketInterface()
		|| tcp_flags /* If not a packet interfaces, we expect flags to be set to be sure they've been exported */)
	       && !isThreeWayHandshakeOK()))
	  && is_active_entry_now_idle(iface->getFlowMaxIdle())) {
        ret = true;
      }
    }

    if(!ret) ret = is_active_entry_now_idle(iface->getFlowMaxIdle());
  }

#if 0
  ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s() [uses: %u][time: %d][idle: %s]",
			       __FUNCTION__, getUses(),
			       (last_seen + iface->getFlowMaxIdle()) - iface->getTimeLastPktRcvd(),
			       ret ? "true" : "false");

  if(ret)
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s] Idle flow found", iface->get_name());
#endif

  return (ret);
}

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

void Flow::sumStats(nDPIStats *ndpi_stats, FlowStats *status_stats) {
  ndpi_protocol detected_protocol = get_detected_protocol();

  /* Increase Application stats */
  if(detected_protocol.proto.app_protocol != detected_protocol.proto.master_protocol &&
      detected_protocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) {
    ndpi_stats->incStats(0, detected_protocol.proto.app_protocol,
                         get_packets_cli2srv(), get_bytes_cli2srv(),
                         get_packets_srv2cli(), get_bytes_srv2cli());
    ndpi_stats->incFlowsStats(detected_protocol.proto.app_protocol);
  } else {
    ndpi_stats->incStats(0, detected_protocol.proto.master_protocol,
                         get_packets_cli2srv(), get_bytes_cli2srv(),
                         get_packets_srv2cli(), get_bytes_srv2cli());
    ndpi_stats->incFlowsStats(detected_protocol.proto.master_protocol);
  }

  /* Increase Category stats */
  ndpi_stats->incCategoryStats(0, get_protocol_category(),
			       get_bytes_cli2srv(), get_bytes_srv2cli());

  status_stats->incStats(getAlertsBitmap(), protocol,
                         Utils::mapScoreToSeverity(getPredominantAlertScore()),
                         getCli2SrvDSCP(), getSrv2CliDSCP(), this);
}

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

char* Flow::serialize(bool use_labels) {
  json_object *my_object;
  char *rsp = NULL;

  ntop->getPrefs()->set_json_symbolic_labels_format(use_labels);

  my_object = flow2JSON();
  // ntop->getTrace()->traceEvent(TRACE_WARNING, "Flow Length: %d",
  // json_object_object_length(my_object));

  if(my_object != NULL) {
    /* JSON string */
    const char *json = json_object_to_json_string(my_object);
    // ntop->getTrace()->traceEvent(TRACE_WARNING, "Emitting Flow: %s", json);

    if(json) {
      rsp = json ? strdup(json) : NULL;

      /* Free memory */
      json_object_put(my_object);
    }
  }

  return (rsp);
}

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

void Flow::formatECSObserver(json_object *my_object) {
  json_object *observer_object;
  if((observer_object = json_object_new_object()) != NULL) {
    json_object_object_add(observer_object, "product",
                           json_object_new_string("ntopng"));
    json_object_object_add(observer_object, "vendor",
                           json_object_new_string("ntop"));

    if(ntop->getPrefs() && ntop->getPrefs()->get_instance_name())
      json_object_object_add(
			     observer_object, "name",
			     json_object_new_string(ntop->getPrefs()->get_instance_name()));

    json_object_object_add(my_object, "observer", observer_object);
  }
}

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

void Flow::formatECSEvent(json_object *my_object) {
  json_object *event_object;
  if((event_object = json_object_new_object()) != NULL) {
    json_object_object_add(event_object, "risk_score",
                           json_object_new_int(getScore()));
    json_object_object_add(my_object, "event", event_object);
  }
}

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

void Flow::formatECSInterface(json_object *my_object) {
  json_object *interface_object;
  if((interface_object = json_object_new_object()) != NULL) {
    json_object_object_add(interface_object, "id",
                           json_object_new_int(iface->get_id()));

    if(iface && iface->get_name())
      json_object_object_add(interface_object, "name",
                             json_object_new_string(iface->get_name()));

    json_object_object_add(my_object, "interface", interface_object);
  }
}

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

void Flow::formatECSExtraInfo(json_object *my_object) {
  json_object *interface_object;

  if(ntop->getPrefs()->do_dump_extended_json()) {
    if((interface_object = json_object_new_object()) != NULL) {
      json_object_object_add(my_object, "flow_time",
                             json_object_new_int(last_seen));

#if defined(NTOPNG_PRO) && !defined(HAVE_NEDGE)
      json_object_object_add(my_object, "profile",
                             json_object_new_string(get_profile_name()));
#endif

      json_object_object_add(my_object, "interface", interface_object);
    }
  }
}

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

void Flow::formatECSAppProto(json_object *my_object) {
  json_object *application_object;

  if(isDNS() && protos.dns.last_query) {
    if((application_object = json_object_new_object()) != NULL) {
      json_object_object_add(application_object, "question.name",
                             json_object_new_string(protos.dns.last_query));
      json_object_object_add(my_object, "dns", application_object);
    }

  } else if(isTLS() && protos.tls.client_requested_server_name) {
    if((application_object = json_object_new_object()) != NULL) {
      json_object_object_add(
			     application_object, "server_name",
			     json_object_new_string(protos.tls.client_requested_server_name));
      json_object_object_add(my_object, "tls", application_object);
    }

  } else if(isHTTP()) {
    if((application_object = json_object_new_object()) != NULL) {
      if(protos.http.last_url && protos.http.last_url[0] != '\0')
        json_object_object_add(application_object, "request.url",
                               json_object_new_string(protos.http.last_url));
      if(protos.http.last_user_agent && protos.http.last_user_agent[0] != '\0')
        json_object_object_add(
			       application_object, "user_agent",
			       json_object_new_string(protos.http.last_user_agent));
      if(protos.http.last_method != NDPI_HTTP_METHOD_UNKNOWN)
        json_object_object_add(application_object, "request.method",
                               json_object_new_string(ndpi_http_method2str(
									   protos.http.last_method)));
      if(protos.http.last_return_code > 0)
        json_object_object_add(
			       application_object, "response.status_code",
			       json_object_new_int((u_int32_t)protos.http.last_return_code));
      if(protos.http.last_server != NULL)
        json_object_object_add(application_object, "response.server",
                               json_object_new_string(protos.http.last_server));

      json_object_object_add(my_object, "http", application_object);
    }

  } else if(bt_hash) {
    if((application_object = json_object_new_object()) != NULL) {
      json_object_object_add(application_object, "hash",
                             json_object_new_string(bt_hash));
      json_object_object_add(my_object, "bittorrent", application_object);
    }
  }
}

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

void Flow::formatECSNetwork(json_object *my_object, const IpAddress *addr) {
  json_object *network_object;
  if((network_object = json_object_new_object()) != NULL) {
    char buf[128], jsonbuf[64];
    u_char community_id[200];

    json_object_object_add(
			   network_object,
			   Utils::jsonLabel(PROTOCOL, "iana_number", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int(protocol));

    if(((get_packets_cli2srv() + get_packets_srv2cli()) >
         NDPI_MIN_NUM_PACKETS) ||
        (ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN))
      json_object_object_add(network_object,
			     Utils::jsonLabel(L7_PROTO_NAME, "protocol", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(get_detected_protocol_name(buf, sizeof(buf))));

    if(tcp != NULL)
      json_object_object_add(network_object,
			     Utils::jsonLabel(TCP_FLAGS, "tcp_flags", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_int(src2dst_tcp_flags | dst2src_tcp_flags));

    json_object_object_add(network_object,
                           Utils::jsonLabel(FIRST_SWITCHED, "first_seen",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int(get_partial_first_seen()));

    json_object_object_add(network_object,
			   Utils::jsonLabel(LAST_SWITCHED, "last_seen", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int(get_partial_last_seen()));

    json_object *category_object;
    if((category_object = json_object_new_object()) != NULL) {
      json_object_object_add(category_object,
			     Utils::jsonLabel(L7_CATEGORY, "name", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(get_protocol_category_name()));
      json_object_object_add(category_object,
			     Utils::jsonLabel(L7_CATEGORY_ID, "id", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_int((u_int32_t)get_protocol_category()));
      json_object_object_add(network_object, "category", category_object);
    }

    json_object_object_add(my_object, "community_id",
                           json_object_new_string((char *)getCommunityId(
									 community_id, sizeof(community_id))));

#ifdef NTOPNG_PRO
#ifndef HAVE_NEDGE
    // Traffic profile information, if any
    if(trafficProfile && trafficProfile->getName())
      json_object_object_add(network_object, "profile",
                             json_object_new_string(trafficProfile->getName()));
#endif
#endif

#ifdef HAVE_NEDGE
    if(iface && iface->is_bridge_interface()) {
      json_object_object_add(my_object, "verdict.pass",
			     json_object_new_boolean(isPassVerdict() ? (json_bool)1
						     : (json_bool)0));
      json_object_object_add(my_object, "verdict.reason",
			     json_object_new_int((int) dropVerdictReason));
    }
#else
    if(!passVerdict)
      json_object_object_add(my_object, "pass_verdict",
                             json_object_new_boolean((json_bool)0));
#endif

    if(addr)
      json_object_object_add(
			     network_object,
			     Utils::jsonLabel(IP_PROTOCOL_VERSION, "type", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(addr->isIPv4() ? "ipv4" : "ipv6"));

    if(flow_device.device_ip)
      json_object_object_add(network_object, "exporter",
                             json_object_new_string(intoaV4(
							    flow_device.device_ip, buf, sizeof(buf))));

    json_object_object_add(network_object, "info",
			   json_object_new_string(getFlowInfo(false).c_str()));

    json_object_object_add(my_object, "network", network_object);
  }
}

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

void Flow::formatECSHost(json_object *my_object, bool is_client,
                         const IpAddress *addr, Host *host) {
  json_object *host_object;
  bool use_nat = false;

  if((host_object = json_object_new_object()) != NULL) {
    char buf[64], jsonbuf[64], *c;
    IpAddress tmp_ip;

    /* Adding MAC */
    if(host && host->getMac() && !host->getMac()->isNull())
      json_object_object_add(host_object,
			     Utils::jsonLabel(is_client ? IN_SRC_MAC : IN_DST_MAC, "mac", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(Utils::formatMac(host ? host->get_mac() : NULL,
								     buf, sizeof(buf))));

    /* Adding IP */
    if(addr) {
      if(getPreNATSrcIp()) {
        tmp_ip.set(getPreNATSrcIp());
        if(is_client && (getPreNATSrcIp() != getPostNATSrcIp()))
          use_nat = true;
      } else {
        tmp_ip.set(addr);
      }

      /* With NAT they are IPv4 */
      json_object_object_add(host_object,
			     Utils::jsonLabel(is_client ? (tmp_ip.isIPv4() ? IPV4_SRC_ADDR : IPV6_SRC_ADDR)
					      : (tmp_ip.isIPv4() ? IPV4_DST_ADDR : IPV6_DST_ADDR),
					      "ip", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(tmp_ip.print(buf, sizeof(buf))));

      /* Custom information elements, Local, Blacklisted, Has Services and
       * domain name */
      json_object_object_add(host_object,
			     Utils::jsonLabel(is_client ? SRC_ADDR_LOCAL : DST_ADDR_LOCAL,
					      "is_local", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_boolean(addr->isLocalHost()));
      json_object_object_add(host_object,
			     Utils::jsonLabel(is_client ? SRC_ADDR_BLACKLISTED : DST_ADDR_BLACKLISTED,
					      "is_blacklisted", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_boolean(addr->isBlacklistedAddress()));

      if(get_cli_host()) {
        json_object_object_add(host_object,
			       Utils::jsonLabel(is_client ? SRC_ADDR_SERVICES : DST_ADDR_SERVICES,
						"has_services", jsonbuf, sizeof(jsonbuf)),
			       json_object_new_int(get_cli_host()->getServicesMap()));
        json_object_object_add(host_object,
			       Utils::jsonLabel(is_client ? SRC_NAME : DST_NAME, "domain", jsonbuf,
						sizeof(jsonbuf)),
			       json_object_new_string(get_cli_host()->get_visual_name(buf, sizeof(buf))));
      }
      tmp_ip.reset();
    }

    if(use_nat) {
      json_object *nat = json_object_new_object();
      u_int16_t port = 0;
      if(is_client) {
        tmp_ip.set(getPostNATSrcIp());
        port = getPostNATSrcPort();
      } else {
        tmp_ip.set(getPostNATDstIp());
        port = getPostNATDstPort();
      }

      json_object_object_add(nat, "ip",
                              json_object_new_string(tmp_ip.print(buf, sizeof(buf))));
      json_object_object_add(nat, "port",
                              json_object_new_int(port));
      json_object_object_add(host_object, "nat", nat);
    }

    /* Geolocation info */
    c = host ? host->get_country(buf, sizeof(buf)) : NULL;
    if(c) {
      json_object *geo = json_object_new_object();
      json_object *location = json_object_new_object();

      if(geo) {
        json_object_object_add(geo, "country_name", json_object_new_string(c));

        if(host && location) {
          float latitude, longitude;

          host->get_geocoordinates(&latitude, &longitude);
          json_object_object_add(location, "lon",
                                 json_object_new_double(longitude));
          json_object_object_add(location, "lat",
                                 json_object_new_double(latitude));
          json_object_object_add(geo, "location", location);
        }

        json_object_object_add(host_object, "geo", geo);
      }
    }

    u_int16_t port = 0;
    if(use_nat)
      port = is_client ? getPreNATSrcPort() : getPreNATDstPort();
    else
      port = is_client ? get_cli_port() : get_srv_port();

    /* Type of Service */
    json_object_object_add(host_object,
                           Utils::jsonLabel(is_client ? SRC_TOS : DST_TOS,
                                            "tos", jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int(getTOS(true)));
    json_object_object_add(host_object,
			   Utils::jsonLabel(is_client ? L4_SRC_PORT : L4_DST_PORT, "port", jsonbuf,
					    sizeof(jsonbuf)), json_object_new_int(port));
    json_object_object_add(host_object,
			   Utils::jsonLabel(is_client ? IN_PKTS : OUT_PKTS, "packets", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_int64(is_client ? get_partial_packets_cli2srv()
						 : get_partial_packets_srv2cli()));
    json_object_object_add(host_object,
			   Utils::jsonLabel(is_client ? IN_BYTES : OUT_BYTES, "bytes", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_int64(is_client ? get_partial_bytes_cli2srv()
						 : get_partial_bytes_srv2cli()));
    json_object_object_add(host_object,
			   Utils::jsonLabel(TCP_FLAGS, "packets_retransmissions", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_int64(is_client ? stats.get_cli2srv_tcp_retr()
						 : stats.get_srv2cli_tcp_retr()));
    json_object_object_add(host_object,
			   Utils::jsonLabel(TCP_FLAGS, "packets_out_of_order", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_int64(is_client ? stats.get_cli2srv_tcp_ooo()
						 : stats.get_srv2cli_tcp_ooo()));
    json_object_object_add(host_object,
			   Utils::jsonLabel(TCP_FLAGS, "packets_lost", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int64(is_client ? stats.get_cli2srv_tcp_lost()
						 : stats.get_srv2cli_tcp_lost()));

    if(vlanId > 0)
      json_object_object_add(host_object,
                             Utils::jsonLabel(is_client ? SRC_VLAN : DST_VLAN,
                                              "vlan", jsonbuf, sizeof(jsonbuf)),
                             json_object_new_int(vlanId));

    if(tcp != NULL)
      json_object_object_add(host_object,
			     Utils::jsonLabel(is_client ? CLIENT_NW_LATENCY_MS : SERVER_NW_LATENCY_MS,
					      "latency", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_double(tcp->clientRTT3WH/2.));

    json_object_object_add(my_object, is_client ? "client" : "server", host_object);
  }
}

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

void Flow::formatECSFlow(json_object *my_object) {
  char buf[64];
  time_t t;
  const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
  struct tm *tm_info;

  t = last_seen;
  tm_info = gmtime(&t);

  /*
    strftime in the VS2013 library and earlier are not C99-conformant,
    as they do not accept that format-specifier: MSDN VS2013 strftime page

    https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx
  */
  strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.0Z", tm_info);

  /*
    Dumping flows, using ECS Format
    https://www.elastic.co/guide/en/ecs/current/index.html
  */

  json_object_object_add(my_object, "@timestamp", json_object_new_string(buf));
  json_object_object_add(my_object, "type",
			 json_object_new_string(ntop->getPrefs()->get_es_type()));

  /* Formatting Client */
  formatECSHost(my_object, true, cli_ip, cli_host);

  /* Formatting Server */
  formatECSHost(my_object, false, srv_ip, srv_host);

  formatECSNetwork(my_object, cli_ip);
  formatECSInterface(my_object);
  formatECSObserver(my_object);
  formatECSEvent(my_object);
  formatECSAppProto(my_object);
  formatECSExtraInfo(my_object);

  if(json_info && json_object_object_length(json_info) > 0)
    json_object_object_add(my_object, "json", json_object_get(json_info));
}

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

void Flow::formatSyslogFlow(json_object *my_object) {
  char buf[64], jsonbuf[64];

  if(cli_host && cli_host->getMac() && !cli_host->getMac()->isNull())
    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(IN_SRC_MAC, "IN_SRC_MAC", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(Utils::formatMac(
								   cli_host ? cli_host->get_mac() : NULL, buf, sizeof(buf))));

  if(srv_host && srv_host->getMac() && !srv_host->getMac()->isNull())
    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(OUT_DST_MAC, "OUT_DST_MAC", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(Utils::formatMac(srv_host ? srv_host->get_mac() : NULL, buf, sizeof(buf))));

  if(isTLS() && protos.tls.ja4.client_hash)
    json_object_object_add(my_object,
			   Utils::jsonLabel(JA4C_HASH, "JA4C_HASH", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(protos.tls.ja4.client_hash));

  if(isSSH() && protos.ssh.hassh.client_hash)
    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(HASSHC_HASH, "HASSHC_HASH", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(protos.ssh.hassh.client_hash));

  formatGenericFlow(my_object);
}

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

void Flow::formatGenericFlow(json_object *my_object) {
  char buf[64], jsonbuf[64], *c;
  u_char community_id[200];
  const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();

  if(cli_ip) {
    if(cli_ip->isIPv4()) {
      json_object_object_add(my_object,
			     Utils::jsonLabel(IPV4_SRC_ADDR, "IPV4_SRC_ADDR", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(cli_ip->print(buf, sizeof(buf))));
    } else if(cli_ip->isIPv6()) {
      json_object_object_add(my_object,
			     Utils::jsonLabel(IPV6_SRC_ADDR, "IPV6_SRC_ADDR", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(cli_ip->print(buf, sizeof(buf))));
    }

    json_object_object_add(my_object,
                           Utils::jsonLabel(SRC_ADDR_LOCAL, "SRC_ADDR_LOCAL",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_boolean(cli_ip->isLocalHost()));
    json_object_object_add(my_object,
			   Utils::jsonLabel(SRC_ADDR_BLACKLISTED, "SRC_ADDR_BLACKLISTED", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_boolean(cli_ip->isBlacklistedAddress()));

    if(get_cli_host()) {
#ifdef FULL_SERIALIZATION
    /* Custom information elements not supported (yet) by nProbe */
      json_object_object_add(my_object,
			     Utils::jsonLabel(SRC_ADDR_SERVICES, "SRC_ADDR_SERVICES", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_int(get_cli_host()->getServicesMap()));
#endif
      json_object_object_add(my_object,
			     Utils::jsonLabel(SRC_NAME, "SRC_NAME", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(
						    get_cli_host()->get_visual_name(buf, sizeof(buf))));
    }
  }

  if(srv_ip) {
    if(srv_ip->isIPv4()) {
      json_object_object_add(my_object,
			     Utils::jsonLabel(IPV4_DST_ADDR, "IPV4_DST_ADDR", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(srv_ip->print(buf, sizeof(buf))));
    } else if(srv_ip->isIPv6()) {
      json_object_object_add(my_object,
			     Utils::jsonLabel(IPV6_DST_ADDR, "IPV6_DST_ADDR", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(srv_ip->print(buf, sizeof(buf))));
    }

    json_object_object_add(my_object,
                           Utils::jsonLabel(DST_ADDR_LOCAL, "DST_ADDR_LOCAL",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_boolean(srv_ip->isLocalHost()));
    json_object_object_add(my_object,
			   Utils::jsonLabel(DST_ADDR_BLACKLISTED, "DST_ADDR_BLACKLISTED", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_boolean(srv_ip->isBlacklistedAddress()));

    if(get_srv_host()) {
/* Custom information elements not supported (yet) by nProbe */
#ifdef FULL_SERIALIZATION
      json_object_object_add(my_object,
			     Utils::jsonLabel(DST_ADDR_SERVICES, "DST_ADDR_SERVICES", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_int(get_srv_host()->getServicesMap()));
#endif
      json_object_object_add(my_object,
			     Utils::jsonLabel(SRC_NAME, "DST_NAME", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(
						    get_srv_host()->get_visual_name(buf, sizeof(buf))));
    }
  }

  json_object_object_add(my_object, Utils::jsonLabel(SRC_TOS, "SRC_TOS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(getTOS(true)));
  json_object_object_add(my_object, Utils::jsonLabel(DST_TOS, "DST_TOS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(getTOS(false)));

  json_object_object_add(my_object,
			 Utils::jsonLabel(L4_SRC_PORT, "L4_SRC_PORT", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(get_cli_port()));
  json_object_object_add(my_object,
			 Utils::jsonLabel(L4_DST_PORT, "L4_DST_PORT", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(get_srv_port()));

  json_object_object_add(my_object,
			 Utils::jsonLabel(PROTOCOL, "PROTOCOL", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(protocol));

  if(((get_packets_cli2srv() + get_packets_srv2cli()) > NDPI_MIN_NUM_PACKETS)
      || (ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN)) {
    json_object_object_add(my_object,
			   Utils::jsonLabel(L7_PROTO, "L7_PROTO", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int(ndpiDetectedProtocol.proto.app_protocol));
    json_object_object_add(my_object,
			   Utils::jsonLabel(L7_PROTO_NAME, "L7_PROTO_NAME", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_string(get_detected_protocol_name(buf, sizeof(buf))));
  }

  json_object_object_add(my_object,
			 Utils::jsonLabel(L7_PROTO_RISK, "L7_PROTO_RISK", jsonbuf,
					  sizeof(jsonbuf)),
			 json_object_new_int64((u_int64_t)ndpi_flow_risk_bitmap));

  if(ndpiFlowRiskName)
    json_object_object_add(my_object,
			   Utils::jsonLabel(L7_PROTO_RISK_NAME, "L7_PROTO_RISK_NAME", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(ndpiFlowRiskName));

  if(end_reason)
    json_object_object_add(my_object,
			   Utils::jsonLabel(FLOW_END_REASON, "FLOW_END_REASON", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_string(end_reason));

  if(collection && collection->wifi.wlan_ssid) {
    char mac_buf[20];

    json_object_object_add(my_object,
			   Utils::jsonLabel(WLAN_SSID, "WLAN_SSID", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_string(collection->wifi.wlan_ssid));
    json_object_object_add(my_object,
			   Utils::jsonLabel(WTP_MAC_ADDRESS, "WTP_MAC_ADDRESS", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_string(Utils::formatMac(collection->wifi.wtp_mac_address, mac_buf,
                                                                   sizeof(mac_buf))));
  }

  json_object_object_add(my_object,
			 Utils::jsonLabel(TCP_FLAGS, "TCP_FLAGS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(src2dst_tcp_flags | dst2src_tcp_flags));
  json_object_object_add(my_object,
			 Utils::jsonLabel(TCP_FLAGS, "CLIENT_TCP_FLAGS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(src2dst_tcp_flags));
  json_object_object_add(my_object,
			 Utils::jsonLabel(TCP_FLAGS, "SERVER_TCP_FLAGS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int(dst2src_tcp_flags));

  if(tcp != NULL) {
    json_object_object_add(my_object,
                           Utils::jsonLabel(TCP_FLAGS, "IN_RETRANSMISSIONS",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int64(stats.get_cli2srv_tcp_retr()));
    json_object_object_add(my_object,
                           Utils::jsonLabel(TCP_FLAGS, "OUT_RETRANSMISSIONS",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int64(stats.get_srv2cli_tcp_retr()));
    json_object_object_add(my_object,
                           Utils::jsonLabel(TCP_FLAGS, "IN_OUT_OF_ORDER",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int64(stats.get_cli2srv_tcp_ooo()));
    json_object_object_add(my_object,
                           Utils::jsonLabel(TCP_FLAGS, "OUT_OUT_OF_ORDER",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int64(stats.get_srv2cli_tcp_ooo()));
    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(TCP_FLAGS, "IN_LOST", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int64(stats.get_cli2srv_tcp_lost()));
    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(TCP_FLAGS, "OUT_LOST", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int64(stats.get_srv2cli_tcp_lost()));

    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(APPL_LATENCY_MS, "APPL_LATENCY_MS", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int64(applLatencyMsec));

  }

  json_object_object_add(my_object, Utils::jsonLabel(IN_PKTS, "IN_PKTS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int64(get_partial_packets_cli2srv()));
  json_object_object_add(my_object,
			 Utils::jsonLabel(IN_BYTES, "IN_BYTES", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int64(get_partial_bytes_cli2srv()));

  json_object_object_add(my_object,
			 Utils::jsonLabel(OUT_PKTS, "OUT_PKTS", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int64(get_partial_packets_srv2cli()));
  json_object_object_add(my_object,
			 Utils::jsonLabel(OUT_BYTES, "OUT_BYTES", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_int64(get_partial_bytes_srv2cli()));

  json_object_object_add(my_object,
                         Utils::jsonLabel(FIRST_SWITCHED, "FIRST_SWITCHED",
                                          jsonbuf, sizeof(jsonbuf)),
                         json_object_new_int(get_partial_first_seen()));
  json_object_object_add(my_object,
                         Utils::jsonLabel(LAST_SWITCHED, "LAST_SWITCHED",
                                          jsonbuf, sizeof(jsonbuf)),
                         json_object_new_int(get_partial_last_seen()));

  if(json_info && json_object_object_length(json_info) > 0)
    json_object_object_add(my_object, "json", json_object_get(json_info));

  if(vlanId > 0)
    json_object_object_add(my_object,
			   Utils::jsonLabel(SRC_VLAN, "SRC_VLAN", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int(vlanId));

  if(tcp != NULL) {
    json_object_object_add(my_object,
			   Utils::jsonLabel(CLIENT_NW_LATENCY_MS, "CLIENT_NW_LATENCY_MS", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_double(tcp->clientRTT3WH/2.));
    json_object_object_add(my_object,
			   Utils::jsonLabel(SERVER_NW_LATENCY_MS, "SERVER_NW_LATENCY_MS", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_double(tcp->serverRTT3WH/2.));
  }

  c = cli_host ? cli_host->get_country(buf, sizeof(buf)) : NULL;
  if(c) {
    json_object *location = json_object_new_array();

    json_object_object_add(my_object, "SRC_IP_COUNTRY", json_object_new_string(c));
    if(location && cli_host) {
      float latitude, longitude;

      cli_host->get_geocoordinates(&latitude, &longitude);
      json_object_array_add(location, json_object_new_double(longitude));
      json_object_array_add(location, json_object_new_double(latitude));
      json_object_object_add(my_object, "SRC_IP_LOCATION", location);
    }
  }

  c = srv_host ? srv_host->get_country(buf, sizeof(buf)) : NULL;
  if(c) {
    json_object *location = json_object_new_array();

    json_object_object_add(my_object, "DST_IP_COUNTRY", json_object_new_string(c));
    if(location && srv_host) {
      float latitude, longitude;

      srv_host->get_geocoordinates(&latitude, &longitude);
      json_object_array_add(location, json_object_new_double(longitude));
      json_object_array_add(location, json_object_new_double(latitude));
      json_object_object_add(my_object, "DST_IP_LOCATION", location);
    }
  }

  if(ntop->getPrefs() && ntop->getPrefs()->get_instance_name())
    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(NTOPNG_INSTANCE_NAME, "NTOPNG_INSTANCE_NAME", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_string(ntop->getPrefs()->get_instance_name()));

  if(iface && iface->get_name())
    json_object_object_add(my_object,
                           Utils::jsonLabel(INTERFACE_NAME, "INTERFACE_NAME",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_string(iface->get_name()));

  if(isSMTP()
      /* Discard SMTP connections that become TLS as the SMTP part is not populated */
      && (!isSMTPS())
      ) {
    if(getSMTPMailFrom())
      json_object_object_add(my_object,
			     Utils::jsonLabel(SMTP_MAIL_FROM, "SMTP_MAIL_FROM", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(getSMTPMailFrom()));

    if(getSMTPRcptTo())
      json_object_object_add(my_object,
			     Utils::jsonLabel(SMTP_RCPT_TO, "SMTP_RCPT_TO", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(getSMTPRcptTo()));
  }

  if(isDNS() && protos.dns.last_query)
    json_object_object_add(my_object,
			   Utils::jsonLabel(DNS_QUERY, "DNS_QUERY", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(protos.dns.last_query));

  json_object_object_add(my_object,
			 Utils::jsonLabel(COMMUNITY_ID, "COMMUNITY_ID", jsonbuf, sizeof(jsonbuf)),
			 json_object_new_string((char *)getCommunityId(community_id, sizeof(community_id))));

  json_object_object_add(my_object,
			 Utils::jsonLabel(L7_RISK_SCORE, "L7_RISK_SCORE",
                                          jsonbuf, sizeof(jsonbuf)),
                         json_object_new_int(getScore()));

  if(isHTTP()) {
    if(host_server_name && host_server_name[0] != '\0')
      json_object_object_add(my_object,
			     Utils::jsonLabel(HTTP_HOST, "HTTP_HOST", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(host_server_name));

    if(protos.http.last_url && protos.http.last_url[0] != '\0')
      json_object_object_add(my_object,
			     Utils::jsonLabel(HTTP_URL, "HTTP_URL", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(protos.http.last_url));

    if(protos.http.last_user_agent && protos.http.last_user_agent[0] != '\0')
      json_object_object_add(my_object,
			     Utils::jsonLabel(HTTP_USER_AGENT, "HTTP_USER_AGENT", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_string(protos.http.last_user_agent));

    if(protos.http.last_server && protos.http.last_server[0] != '\0')
      json_object_object_add(my_object,
			     Utils::jsonLabel(HTTP_SITE, "HTTP_SITE", jsonbuf, sizeof(jsonbuf)),
			     json_object_new_string(protos.http.last_server));

    if(protos.http.last_method != NDPI_HTTP_METHOD_UNKNOWN)
      json_object_object_add(my_object,
                             Utils::jsonLabel(HTTP_METHOD, "HTTP_METHOD",
                                              jsonbuf, sizeof(jsonbuf)),
                             json_object_new_string(ndpi_http_method2str(
									 protos.http.last_method)));

    if(protos.http.last_return_code > 0)
      json_object_object_add(my_object,
			     Utils::jsonLabel(HTTP_RET_CODE, "HTTP_RET_CODE", jsonbuf,
					      sizeof(jsonbuf)),
			     json_object_new_int((u_int32_t)protos.http.last_return_code));
  }

  if(protocol == IPPROTO_ICMP) {
    json_object_object_add(my_object,
			   Utils::jsonLabel(ICMP_IPV4_TYPE, "ICMP_IPV4_TYPE", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_int((u_int32_t)protos.icmp.cli2srv.icmp_type));

    json_object_object_add(my_object,
			   Utils::jsonLabel(ICMP_IPV4_CODE, "ICMP_IPV4_CODE", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_int((u_int32_t)protos.icmp.cli2srv.icmp_type));
  }

  if(flow_device.device_ip)
    json_object_object_add(my_object,
			   Utils::jsonLabel(EXPORTER_IPV4_ADDRESS, "EXPORTER_IPV4_ADDRESS",
					    jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(
						  intoaV4(flow_device.device_ip, buf, sizeof(buf))));

  if(bt_hash)
    json_object_object_add(my_object,
                           Utils::jsonLabel(BITTORRENT_HASH, "BITTORRENT_HASH",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_string(bt_hash));

  if(isTLS() && protos.tls.client_requested_server_name)
    json_object_object_add(my_object,
			   Utils::jsonLabel(TLS_SERVER_NAME, "TLS_SERVER_NAME", jsonbuf,
					    sizeof(jsonbuf)),
			   json_object_new_string(protos.tls.client_requested_server_name));

#ifdef HAVE_NEDGE
  if(iface && iface->is_bridge_interface()) {
    json_object_object_add(my_object, "verdict.pass", /* TODO: convert to Utils::jsonLabel(..) */
			   json_object_new_boolean(isPassVerdict() ? (json_bool)1 : (json_bool)0));
    json_object_object_add(my_object, "verdict.reason",
			   json_object_new_int((int) dropVerdictReason));
  }
#else
  if(!passVerdict)
    json_object_object_add(my_object, "verdict.pass", /* TODO: convert to Utils::jsonLabel(..) */
			   json_object_new_boolean((json_bool)0));
#endif

  if(ebpf) ebpf->getJSONObject(my_object);

  if(ntop->getPrefs()->do_dump_extended_json()) {
#ifdef FULL_SERIALIZATION
    const char *info;
    char buf[128];

    json_object_object_add(
			   my_object,
			   Utils::jsonLabel(FLOW_TIME, "FLOW_TIME", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int(last_seen));
#endif

    if(cli_ip) {
      if(cli_ip->isIPv4()) {
        json_object_object_add(my_object,
			       Utils::jsonLabel(IP_PROTOCOL_VERSION, "IP_PROTOCOL_VERSION",
						jsonbuf, sizeof(jsonbuf)),
			       json_object_new_int(4));
      } else if(cli_ip->isIPv6()) {
        json_object_object_add(my_object,
			       Utils::jsonLabel(IP_PROTOCOL_VERSION, "IP_PROTOCOL_VERSION",
						jsonbuf, sizeof(jsonbuf)),
			       json_object_new_int(6));
      }
    }

#ifdef FULL_SERIALIZATION
    json_object_object_add(my_object,
			   Utils::jsonLabel(L7_INFO, "INFO", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(getFlowInfo(false).c_str()));
#endif

#ifdef FULL_SERIALIZATION
#if defined(NTOPNG_PRO) && !defined(HAVE_NEDGE)
    json_object_object_add(my_object,
			   Utils::jsonLabel(PROFILE, "PROFILE", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_string(get_profile_name()));
#endif
#endif

    json_object_object_add(my_object,
                           Utils::jsonLabel(INTERFACE_ID, "INTERFACE_ID",
                                            jsonbuf, sizeof(jsonbuf)),
                           json_object_new_int(iface->get_id()));

    json_object_object_add(
			   my_object, Utils::jsonLabel(STATUS, "STATUS", jsonbuf, sizeof(jsonbuf)),
			   json_object_new_int((u_int8_t)getPredominantAlert().id));
  }
}

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

json_object *Flow::flow2JSON() {
  json_object *my_object;

  if((my_object = json_object_new_object()) == NULL) return (NULL);

  if(ntop->getPrefs()->do_dump_flows_on_es()) {
    formatECSFlow(my_object);
  } else if(ntop->getPrefs()->do_dump_flows_on_syslog()) {
    formatSyslogFlow(my_object);
  } else {
    formatGenericFlow(my_object);
  }

  return (my_object);
}

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

u_char *Flow::getCommunityId(u_char *community_id, u_int community_id_len) {
  if(cli_host && srv_host) {
    IpAddress *c = cli_host->get_ip(), *s = srv_host->get_ip();
    u_int8_t icmp_type = 0, icmp_code = 0;

    if(c->isIPv4()) {
      if(get_protocol() == IPPROTO_ICMP)
        icmp_type = protos.icmp.cli2srv.icmp_type,
	  icmp_code = protos.icmp.cli2srv.icmp_code;

      if(ndpi_flowv4_flow_hash(protocol, ntohl(c->get_ipv4()),
                                ntohl(s->get_ipv4()), get_cli_port(),
                                get_srv_port(), icmp_type, icmp_code,
                                community_id, community_id_len) == 0)
        return (community_id);
    } else {
      if(get_protocol() == IPPROTO_ICMPV6)
        icmp_type = protos.icmp.cli2srv.icmp_type,
	  icmp_code = protos.icmp.cli2srv.icmp_code;

      if(c->isIPv6()) {
        if(ndpi_flowv6_flow_hash(
				  protocol, (struct ndpi_in6_addr *)c->get_ipv6(),
				  (struct ndpi_in6_addr *)s->get_ipv6(), get_cli_port(),
				  get_srv_port(), icmp_type, icmp_code, community_id,
				  community_id_len) == 0)
          return (community_id);
      }
    }
  }

  community_id[0] = '\0';
  return (community_id);
}

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

/* Create a JSON in the alerts format
 * Using the nDPI json serializer instead of jsonc for faster speed (~2.5x) */
void Flow::alert2JSON(FlowAlert *alert, ndpi_serializer *s) {
  char buf[128];
  u_char community_id[200];
  time_t now = time(NULL);

  /*
    If the interface is viewed, the id of the view interface is specified as
    ifid. This ensures flow alerts of any viewed interface end up in the view
    interface, thus giving the user a single point where to look at all the
    troubles.
  */
#ifdef NTOPNG_PRO
  ndpi_serialize_string_int32(s, "ifid",
			      iface->isViewed() ? iface->viewedBy()->get_id() : iface->get_id());
#else
  ndpi_serialize_string_int32(s, "ifid", iface->get_id());
#endif
  ndpi_serialize_string_string(s, "action", "store");
  ndpi_serialize_string_int64(s, "first_seen", get_first_seen());
  ndpi_serialize_string_int32(s, "score", getScore());
  ndpi_serialize_string_boolean(s, "require_attention", !alert->autoAck());

  ndpi_serialize_string_boolean(s, "is_flow_alert", true);
  ndpi_serialize_string_int64(s, "tstamp", now);
  ndpi_serialize_string_int64(s, "alert_id", alert->getAlertType().id);
  ndpi_serialize_string_int64(s, "alert_category", alert->getAlertType().category);
  ndpi_serialize_string_boolean(s, "is_cli_attacker", alert->isCliAttacker());
  ndpi_serialize_string_boolean(s, "is_cli_victim", alert->isCliVictim());
  ndpi_serialize_string_boolean(s, "is_srv_attacker", alert->isSrvAttacker());
  ndpi_serialize_string_boolean(s, "is_srv_victim", alert->isSrvVictim());

  // alert_entity MUST be in sync with alert_consts.lua flow alert entity
  ndpi_serialize_string_int32(s, "entity_id", alert_entity_flow);
  ndpi_serialize_string_string(s, "entity_val", "flow");
  // flows don't have any pool for now
  ndpi_serialize_string_int32(s, "pool_id", NO_HOST_POOL_ID);

  /* See VLANAddressTree.h for details */
  ndpi_serialize_string_int32(s, "vlan_id", get_vlan_id());
  ndpi_serialize_string_int32(s, "observation_point_id",
                              get_observation_point_id());

  ndpi_serialize_string_int32(s, "proto", get_protocol());

  if(hasRisks())
    ndpi_serialize_string_uint64(s, "flow_risk_bitmap", ndpi_flow_risk_bitmap);

  /* All the statuses set */
  char status_buf[64];
  ndpi_serialize_string_string(s, "alerts_map", alerts_map.toHexString(status_buf, sizeof(status_buf)));

  /* nDPI data */
  ndpi_serialize_string_string(s, "proto.ndpi",
			       detection_completed ? get_detected_protocol_name(buf, sizeof(buf))
			       : (char *)CONST_TOO_EARLY);
  ndpi_serialize_string_int32(s, "l7_master_proto",
			      detection_completed ? ndpiDetectedProtocol.proto.master_protocol : -1);
  ndpi_serialize_string_int32(s, "l7_proto",
			      detection_completed ? ndpiDetectedProtocol.proto.app_protocol : -1);
  ndpi_serialize_string_int32(s, "l7_cat", get_protocol_category());

  if(isDNS()) ndpi_serialize_string_string(s, "dns_last_query", getDNSQuery());

  ndpi_serialize_string_int64(s, "cli2srv_bytes", get_bytes_cli2srv());
  ndpi_serialize_string_int64(s, "cli2srv_packets", get_packets_cli2srv());
  ndpi_serialize_string_int64(s, "srv2cli_bytes", get_bytes_srv2cli());
  ndpi_serialize_string_int64(s, "srv2cli_packets", get_packets_srv2cli());

  ndpi_serialize_string_int32(s, "ip_version", get_cli_ip_addr()->getVersion());

  ndpi_serialize_string_string(s, "cli_ip",
                               get_cli_ip_addr()->print(buf, sizeof(buf)));
  ndpi_serialize_string_boolean(s, "cli_blacklisted", isBlacklistedClient());
  ndpi_serialize_string_int32(s, "cli_port", get_cli_port());
  ndpi_serialize_string_int32(s, "cli_location", getCliLocation());

  if(cli_host) {
    cli_host->serialize_geocoordinates(s, "cli_");
    ndpi_serialize_string_string(s, "cli_name",
                                 cli_host->get_visual_name(buf, sizeof(buf)));
    ndpi_serialize_string_string(s, "cli_os",
                                 cli_host->getOSDetail(buf, sizeof(buf)));
    ndpi_serialize_string_int32(s, "cli_asn", cli_host->get_asn());
    ndpi_serialize_string_boolean(s, "cli_localhost", cli_host->isLocalHost());

    ndpi_serialize_string_int32(s, "cli_host_pool_id",
                                cli_host->get_host_pool());
    ndpi_serialize_string_uint32(s, "cli_network",
                                (u_int32_t)cli_host->get_local_network_id());
  }

  ndpi_serialize_string_string(s, "srv_ip",
                               get_srv_ip_addr()->print(buf, sizeof(buf)));
  ndpi_serialize_string_boolean(s, "srv_blacklisted", isBlacklistedServer());
  ndpi_serialize_string_int32(s, "srv_port", get_srv_port());
  ndpi_serialize_string_int32(s, "srv_location", getSrvLocation());

  if(srv_host) {
    srv_host->serialize_geocoordinates(s, "srv_");
    ndpi_serialize_string_string(s, "srv_name",
                                 srv_host->get_visual_name(buf, sizeof(buf)));
    ndpi_serialize_string_string(s, "srv_os",
                                 srv_host->getOSDetail(buf, sizeof(buf)));
    ndpi_serialize_string_int32(s, "srv_asn", srv_host->get_asn());
    ndpi_serialize_string_boolean(s, "srv_localhost", srv_host->isLocalHost());

    ndpi_serialize_string_int32(s, "srv_host_pool_id",
                                srv_host->get_host_pool());
    ndpi_serialize_string_uint32(s, "srv_network",
                                (u_int32_t)srv_host->get_local_network_id());
  }

  ndpi_serialize_string_string(s, "probe_ip", Utils::intoaV4(getFlowDeviceIP(), buf, sizeof(buf)));
  ndpi_serialize_string_int32(s, "input_snmp", getFlowDeviceInIndex());
  ndpi_serialize_string_int32(s, "output_snmp", getFlowDeviceOutIndex());

  ndpi_serialize_string_string(s, "community_id",
			       (char *)getCommunityId(community_id, sizeof(community_id)));

  if(protos.tls.ja4.client_hash)
    ndpi_serialize_string_string(s, "ja4_client_hash", protos.tls.ja4.client_hash);

  if(getErrorCode() != 0)
    ndpi_serialize_string_uint32(s, "l7_error_code", getErrorCode());

  ndpi_serialize_string_string(s, "info", getFlowInfo(false).c_str());

  char *json = alerts_json; /* Copying ref as it may be moved to the shadow meantime */
  ndpi_serialize_string_string(s, "json", json ? json : "");
}

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

#ifdef HAVE_NEDGE

bool Flow::isNetfilterIdleFlow() const {
  /*
    Note that on netfilter interfaces we never observe the
    FIN/RST flags as they have been offloaded to kernel

    Hence on netfilter interfaces flows are purged only for
    inactivity based on lastSeen updates
  */

  if(last_conntrack_update > 0) {
    /*
      - At latest every MIN_CONNTRACK_UPDATE the scan is performed
      - the conntrack scan time that we  assume is less than
      MIN_CONNTRACK_UPDATE
      - in the worst case this method is called when iface->getTimeLastPktRcvd()
      is almost MIN_CONNTRACK_UPDATE past the last scan

      Thuis in total we assume that every 3*MIN_CONNTRACK_UPDATE
      seconds an active flow should have been updated
      by conntrack
    */
    if((u_int32_t)(iface->getTimeLastPktRcvd()) >
        (last_conntrack_update + (3 * MIN_CONNTRACK_UPDATE)))
      return (true);
  }

  /*
    If an conntrack update hasn't been seen for this flow
    we use the standard idleness check

    In case ntopng expires the flow before NetFilter, then
    NetfilterInterface::updateFlowStats() will purge it later.

    This guarantees that ntopng and NetFilter are always in sync.
  */
  return (is_active_entry_now_idle(iface->getFlowMaxIdle()));
}
#endif

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

void Flow::decAllFlowScores() {
  Host *cli_u = getViewSharedClient(), *srv_u = getViewSharedServer();

  for (int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
    ScoreCategory score_category = (ScoreCategory)i;
    u_int16_t cli_score_val = stats.get_cli_score(score_category);
    u_int16_t srv_score_val = stats.get_srv_score(score_category);

#ifdef NTOPNG_PRO
    if(getViewInterfaceFlowStats()) {
      /*
        If this flow belong to a view, the actual score value is the one
        registered in the partializable stats of the view.
      */
      cli_score_val = getViewInterfaceFlowStats()->getPartializableStats()->get_cli_score(score_category);
      srv_score_val = getViewInterfaceFlowStats()->getPartializableStats()->get_srv_score(score_category);
    }
#endif

    if(cli_u && cli_score_val)
      cli_u->decScoreValue(cli_score_val, score_category, true /* as client */);

    if(srv_u && srv_score_val)
      srv_u->decScoreValue(srv_score_val, score_category, false /* as server */);
  }

  /*
    Perform other operations to decrease counters increased by flow user script
    hooks (we're in the same thread)
  */

  if(isFlowAlerted()) {
    bool dec_stats = false;

    iface->decNumAlertedFlows(this, Utils::mapScoreToSeverity(getPredominantAlertScore()));

#ifdef NTOPNG_PRO
    if(!getInterface()->isViewed() /* Always for non-viewed interfaces (increments are
				      always performed and in the same thread) */
       /*
	 For viewed interfaces, do the decrement only if previously
	 incremented.                   A previous increment can fail when the
	 view flows queue                   is full and enqueues fail.
       */
       || (getViewInterfaceFlowStats() && getViewInterfaceFlowStats()->getPartializableStats()->get_is_flow_alerted())) {
      dec_stats = true;
    }
#else
    dec_stats = true;
#endif

    if(dec_stats) {
      if(cli_u) cli_u->decNumAlertedFlows(true /* As client */);
      if(srv_u) srv_u->decNumAlertedFlows(false /* As server */);
    }

#ifdef ALERTED_FLOWS_DEBUG
    iface_alert_dec = true;
#endif
  }
}

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

/*
  This method is executed in the thread which processes packets/flows
  so it must be ultra-fast. Do NOT perform any time-consuming operation here.

  This is called by GenericHash::purgeIdle, in some cases there is a state
  transition after calling this (see purgeIdle), in other cases the transition
  is done in this class (e.g. set_hash_entry_state_flow_notyetdetected)
*/
void Flow::housekeep(time_t t) {
  switch (get_state()) {
  case hash_entry_state_allocated:
    /* This code can be executed multiple times, until there is a call to
     * set_hash_entry_state_flow_notyetdetected */

  case hash_entry_state_flow_notyetdetected:
    /*
      Possibly the time to giveup and end the protocol dissection.
      This happens when a flow with an incomplete TWH stops receiving packets
      for example.

      Giveup is handled differently, depending on whether we are processing
      pcap files or not:
      - When not reading from pcap files, before giving up, we wait at least 5
      seconds since the last flow packet
      - When reading from pcap files, we wait until all the packets in the
      file have been processed before ending the dissection
    */
    if(iface->get_ndpi_struct() && get_ndpi_flow()) {
      if(likely(!getInterface()->read_from_pcap_dump())) {
	/* NOT processing pcap files, at most 5 seconds since the last packet */
	if((t - get_last_seen()) > 5 /* sec */) endProtocolDissection(true);
      } else {
	/* pcap files - wait until all the file has been processed */
	if(getInterface()->read_from_pcap_dump_done())
	  endProtocolDissection(true);
      }
    }

    /*
      If a condition above has determined the flow trasnition to the protocol
      detected state, don't break, continue so to execute detection completed
      checks.
    */
    if(get_state() != hash_entry_state_flow_protocoldetected) break;

  case hash_entry_state_flow_protocoldetected:
    if(!is_swap_requested()) /* The flow will be swapped, hook execution will
				 occur on the swapped flow. */
        iface->execProtocolDetectedChecks(this);
    break;

  case hash_entry_state_active:
    /*
      The hook for periodicUpdate is checked when increasing flow stats inline
      to guarantee timely execution.
      hookPeriodicUpdateCheck(t);
    */
    dumpCheck(t, false /* NOT the last dump before delete */);

    /*
      Swap requested but not yet performed (no more packets seen).
      In case of interfaces processing pcap files, if the processing of the
      pcap file is completed, i.e. read_from_pcap_dump_done() is true, then
      there will be no more packets so the flow swap won't take place. For
      this reason, the callbacks are executed on the original flow that should
      have been swapped but actually it is not.
    */
    if(getInterface()->read_from_pcap_dump_done() && is_swap_requested() &&
	!is_swap_done())
      iface->execProtocolDetectedChecks(this);
    break;

  case hash_entry_state_idle:
    /* This code is executed once, as the flow is removed from the hash table
     * after calling housekeep in this state */

    if(is_swap_requested() &&
	!is_swap_done()) /* Swap requested but never performed (no more
			    packets seen) */
      iface->execProtocolDetectedChecks(this);

    if(!is_swap_requested() /* Swap not requested */
	|| (is_swap_requested() &&
	    !is_swap_done())) /* Or requested but never performed (no more
				 packets seen) */ {
        iface->execFlowEndChecks(this);
    }

    dumpCheck(t, true /* LAST dump before delete */);
    flow_end_stats_update();

#ifdef NTOPNG_PRO
    if(cli_host && srv_host) {
      u_int32_t cli_net_id = cli_host->get_local_network_id(),
	srv_net_id = srv_host->get_local_network_id();

      if(cli_net_id != (u_int32_t)-1 && srv_net_id != (u_int32_t)-1 &&
	  cli_net_id != srv_net_id) {
	NetworkStats *cli_network_stats = iface->getNetworkStats(cli_net_id),
	  *srv_network_stats = iface->getNetworkStats(srv_net_id);

	if(cli_network_stats)
	  cli_network_stats->incTrafficBetweenNets(
						   srv_net_id, get_bytes_cli2srv(), get_bytes_srv2cli());
	if(srv_network_stats)
	  srv_network_stats->incTrafficBetweenNets(
						   cli_net_id, get_bytes_srv2cli(), get_bytes_cli2srv());
#ifdef DEBUG
	ntop->getTrace()->traceEvent(TRACE_NORMAL,
				     "Cli Network ID: %u | Srv Network ID: "
				     "%u | Bytes: %lu | Num Loc Nets: %u",
				     cli_net_id, srv_net_id, get_bytes(),
				     ntop->getNumLocalNetworks());
#endif
      }
    }
#endif

    /*
      Score decrements MUST be performed here as this is the same thread of
      checks execution where scores are increased. NOTE: for view interfaces,
      decrement are performed in ~Flow to avoid races.
    */
#ifdef NTOPNG_PRO
    if(!getInterface()->isViewed()) decAllFlowScores();
#else
    decAllFlowScores();
#endif

    switch (protocol) {
      case IPPROTO_TCP:
      if(cli_host && ((getTcpFlagsCli2Srv() == TH_SYN) || (!non_zero_payload_observed)))
	cli_host->incIncompleteFlows();
      break;

      case IPPROTO_UDP:
      if(cli_host && (get_packets_srv2cli() == 0) /* unidirectional flow */
	  && srv_ip_addr && srv_ip_addr->isNonEmptyUnicastAddress())
	cli_host->incIncompleteFlows();
      break;
    }

    break;

  default:
    break;
  }

  /*
    Check (and possibly enqueue) the flow for processing by a view interface.
    Make sure to enqueue the flow to view interfaces AFTER all housekeeping
    tasks have been performed. This guarantees any change set by these
    operations (e.g., changes in the flow status, flow alerts, etc.) are done
    before the flow is propagated to the view.
  */
  getInterface()->viewEnqueue(t, this);
}

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

bool Flow::get_partial_traffic_stats(PartializableFlowTrafficStats **dst,
                                     PartializableFlowTrafficStats *fts,
                                     bool *first_partial) const {
  if(!fts || !dst) return (false);

  if(!*dst) {
    if(!(*dst = new (std::nothrow) PartializableFlowTrafficStats()))
      return (false);
    *first_partial = true;
  } else {
    *first_partial = false;
  }

  stats.get_partial(*dst, fts);

  return (true);
}

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

/* NOTE: this is only called by the ViewInterface */
#ifdef NTOPNG_PRO
bool Flow::get_partial_traffic_stats_view(PartializableFlowTrafficStats *fts,
                                          bool *first_partial) {
  if(!fts) return (false);

  if(!viewFlowStats) {
    if(!(viewFlowStats = new (std::nothrow) ViewInterfaceFlowStats()))
      return (false);

    *first_partial = true;
  } else
    *first_partial = false;

  stats.get_partial(viewFlowStats->getPartializableStats(), fts);

  return (true);
}
#endif

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

bool Flow::update_partial_traffic_stats_db_dump() {
  bool first_partial;

  if(!get_partial_traffic_stats(&last_db_dump.partial, &last_db_dump.delta,
                                 &first_partial))
    return (false);

  if(first_partial)
    last_db_dump.first_seen = get_first_seen();
  else
    last_db_dump.first_seen = last_db_dump.last_seen;

  last_db_dump.last_seen = get_last_seen();

  return (true);
}

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

void Flow::updatePacketStats(InterarrivalStats *stats,
                             const struct timeval *when, bool update_iat) {
  if(stats) stats->updatePacketStats((struct timeval *)when, update_iat);
}

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

bool Flow::isBlacklistedFlow() const {
  bool res = (isBlacklistedClient() || isBlacklistedServer() ||
              get_protocol_category() == CUSTOM_CATEGORY_MALWARE);

#ifdef BLACKLISTED_FLOWS_DEBUG
  if(res) {
    char buf[512];

    print(buf, sizeof(buf));
    snprintf(&buf[strlen(buf)], sizeof(buf) - strlen(buf),
             "[cli_blacklisted: %u][srv_blacklisted: %u][category: %s][blacklist: %s]",
             isBlacklistedClient(), isBlacklistedServer(),
             get_protocol_category_name(),
	     get_custom_category_file() ? get_custom_category_file() : "");
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s", buf);
  }
#endif

  return res;
};

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

bool Flow::isBlacklistedClient() const {
  if(cli_host)
    return cli_host->isBlacklisted();
  else
    return get_cli_ip_addr()->isBlacklistedAddress();
}

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

bool Flow::isBlacklistedServer() const {
  if(srv_host)
    return srv_host->isBlacklisted();
  else
    return get_srv_ip_addr()->isBlacklistedAddress();
}

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

bool Flow::isTLS() const {
  return (isProto(NDPI_PROTOCOL_TLS) ||
          isProto(NDPI_PROTOCOL_MAIL_IMAPS) ||
          isProto(NDPI_PROTOCOL_MAIL_SMTPS) ||
          isProto(NDPI_PROTOCOL_MAIL_POPS) ||
          isProto(NDPI_PROTOCOL_QUIC));
}

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

void Flow::callFlowUpdate(time_t t) {
  if(get_state() != hash_entry_state_active) return;

  /*
    Flow update is conditional here as it is only performed every 5 minutes when
    the flow is active.
  */
  if(next_call_periodic_update == 0)
    next_call_periodic_update =
      t + FLOW_LUA_CALL_PERIODIC_UPDATE_SECS; /* Set the time of the new
						 periodic call */

  if(trigger_immediate_periodic_update || next_call_periodic_update <= t) {
    iface->execPeriodicUpdateChecks(this);
    next_call_periodic_update = 0; /* Reset */

    if(trigger_immediate_periodic_update)
      trigger_immediate_periodic_update = false; /* Reset if necessary */
  }
}

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

/* Enqueue alert to recipients */
bool Flow::enqueueAlertToRecipients(FlowAlert *alert) {
  bool first_alert = isFlowAlerted();
  bool rv = false;
  u_int32_t buflen;
  AlertFifoItem *notification;
  ndpi_serializer flow_json;
  const char *flow_str;
  const char *instance_name;

  ndpi_init_serializer(&flow_json, ndpi_serialization_format_json);

  /* Prepare the JSON, including a JSON specific of this FlowAlertType */
  alert2JSON(alert, &flow_json);

  if(!first_alert)
    ndpi_serialize_string_boolean(&flow_json, "replace_alert", true);

  flow_str = ndpi_serializer_get_buffer(&flow_json, &buflen);

  notification = new AlertFifoItem();

  if(notification) {
    notification->alert = flow_str;
    notification->score = getPredominantAlertScore();
    notification->alert_severity = Utils::mapScoreToSeverity(notification->score);
    notification->alert_category = alert->getAlertType().category;
    notification->alert_id = alert->getAlertType().id;
    notification->flow.cli_host_pool =
      cli_host ? cli_host->get_host_pool() : 0;
    notification->flow.srv_host_pool =
      srv_host ? srv_host->get_host_pool() : 0;
    notification->alert_entity = alert_entity_flow;

    rv = ntop->recipients_enqueue(notification);

    if(!rv)
      delete(notification);

    if(iface->isSmartRecordingEnabled()
	&& (instance_name = iface->getSmartRecordingInstance())) {
      char key[256], ip_buf[64];
      int expiration = 30*60; /* 30 min */

      snprintf(key, sizeof(key), "n2disk.%s.filter.tuple.%s,%s,%u,%u,%u", instance_name,
	       get_cli_ip_addr()->print(ip_buf, sizeof(ip_buf)),
	       get_srv_ip_addr()->print(ip_buf, sizeof(ip_buf)),
	       cli_port, srv_port, protocol);

      ntop->getRedis()->set(key, "1", expiration);
    }
  }

  if(!rv)
    getInterface()->incNumDroppedAlerts(alert_entity_flow);

  ndpi_term_serializer(&flow_json);

  return rv;
}

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

/* Update server -> client in case no traffic was already observed on the server side */

void Flow::updateServerPortsStats(Host *server, ndpi_protocol *proto, time_t when_seen) {
  /*
    NOTE use the method parameter 'server' instead of
    srv_host as with view interfaces it can be invalid
  */

  if(get_bytes_srv2cli() == 0) return;

  if(server && server->isLocalHost()
      // && (get_bytes_srv2cli() == 0)
      ) {
    switch (protocol) {
    case IPPROTO_TCP:
      if(((src2dst_tcp_flags & TH_SYN) == TH_SYN)
	 /* Ignore connections refused */
	 && ((dst2src_tcp_flags & TH_RST) == 0)
	 ) {
	if(vlanId == 0) /* In case the VLAN is 0 set this port to the network interface */
	  iface->setServerPort(true, ntohs(srv_port), proto);
	else { /* Otherwise set this port to the right VLAN */
	  VLAN *vlan_hash = iface->getVLAN(vlanId, false, false);

	  if(vlan_hash)
	    vlan_hash->setServerPort(true, ntohs(srv_port), proto);
	}

	server->setServerPort(true, ntohs(srv_port), proto, first_seen);
      }
      break;

    case IPPROTO_UDP: {
      u_int16_t c_port = ntohs(cli_port);
      u_int16_t s_port = ntohs(srv_port);

      if(c_port > s_port) /* minimal check, to improve */ {
	if(vlanId == 0) /* In case the VLAN is 0 set this port to the network
			    interface */
	  iface->setServerPort(false, s_port, proto);
	else { /* Otherwise set this port to the right VLAN */
	  VLAN *vlan_hash = iface->getVLAN(vlanId, false, false);

	  if(vlan_hash) vlan_hash->setServerPort(false, s_port, proto);
	}

	server->setServerPort(false, s_port, proto, first_seen);
      }

      break;
    }
    }
  }
}

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

void Flow::updateClientContactedPorts(Host *client, ndpi_protocol *proto) {
  /*
    NOTE use the method parameter 'client' instead of
    cli_host as with view interfaces it can be invalid
  */

  if(client->isLocalHost()) {
    switch (protocol) {
    case IPPROTO_TCP:
      if((src2dst_tcp_flags & TH_SYN) == TH_SYN)
	client->setContactedPort((protocol == IPPROTO_TCP), ntohs(srv_port), proto);
      break;

    case IPPROTO_UDP: {
      u_int16_t c_port = ntohs(cli_port);
      u_int16_t s_port = ntohs(srv_port);

      if(c_port > s_port) /* minimal check, to improve */
	client->setContactedPort(false, s_port, proto);
    } break;
    }
  }
}
/* *************************************** */

void Flow::incStats(bool cli2srv_direction, u_int pkt_len, u_int8_t *payload,
                    u_int payload_len, u_int8_t l4_proto, u_int8_t is_fragment,
                    u_int16_t tcp_flags, const struct timeval *when,
                    u_int16_t fragment_extra_overhead) {
  bool update_iat = true;

  payload_len *= iface->getScalingFactor();
  updateSeen(when->tv_sec);

  if(fragment_extra_overhead) {
    /* Add artificial packet overhead */
    stats.incStats(cli2srv_direction, 1, fragment_extra_overhead,
                   fragment_extra_overhead);
  }

  callFlowUpdate((when->tv_sec));

  /*
    Do not update IAT during initial or final 3WH as we want to compute
    it only on the main traffic flow and not on connection or tear-down
  */
  if((l4_proto == IPPROTO_TCP) && (tcp_flags & (TH_SYN | TH_FIN | TH_RST)))
    update_iat = false;

#ifdef ENABLE_ENTROPHY_CALCULATION
  if((protocol == IPPROTO_ICMP) &&
      (get_packets() < 10) /* Compute only on the first few flow packets */
      && ((protos.icmp.cli2srv.icmp_type == 0) ||
          (protos.icmp.cli2srv.icmp_type == 8)) /* Echo Request or Reply */
      && cli2srv_direction && (payload != NULL) && (payload_len > 0)) {
    /*
      We compute cli->srv entropy to see how much
      packets are different and see if they are
      repetitions or not
    */
    struct ndpi_analyze_struct e;
    float res;

    ndpi_init_data_analysis(&e, 256);
    updateEntropy(&e, payload, payload_len);
    res = ndpi_data_entropy(&e);

    if(protos.icmp.client_to_server.min_entropy == 0)
      protos.icmp.client_to_server.min_entropy =
	protos.icmp.client_to_server.max_entropy = res;
    else {
      if(protos.icmp.client_to_server.min_entropy > res)
        protos.icmp.client_to_server.min_entropy = res;
      else if(protos.icmp.client_to_server.max_entropy < res)
        protos.icmp.client_to_server.max_entropy = res;
    }

    /* See icmp_utils.is_suspicious_entropy() */
    if((protos.icmp.client_to_server.min_entropy < 5) ||
        (protos.icmp.client_to_server.max_entropy > 6) ||
        ((protos.icmp.client_to_server.max_entropy -
          protos.icmp.client_to_server.min_entropy) > 0.3)) {
      ndpi_risk r = ((ndpi_risk)2) << (NDPI_SUSPICIOUS_ENTROPY - 1);

      addRisk(r);
    }

#ifdef DEBUG
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "Entropy %.23f - %.23f",
                                 protos.icmp.client_to_server.min_entropy,
                                 protos.icmp.client_to_server.max_entropy);
#endif

    ndpi_free_data_analysis(&e, 0);
  }
#endif
  
  updatePacketStats(cli2srv_direction ? getCli2SrvIATStats() : getSrv2CliIATStats(), when,
		    update_iat);

  stats.incStats(cli2srv_direction, 1, pkt_len, payload_len);

  if(cli2srv_direction) {
    ip_stats_s2d.pktFrag += is_fragment;
    if(cli_host) cli_host->incSentStats(1, pkt_len);
    if(srv_host) srv_host->incRecvStats(1, pkt_len);
  } else {
    ip_stats_d2s.pktFrag += is_fragment;
    if(cli_host) cli_host->incRecvStats(1, pkt_len);
    if(srv_host) srv_host->incSentStats(1, pkt_len);

    /*
      Need to reset this bit as nDPI might "forget" to do it in case of
      protocol detection with only one packet
    */
    ndpi_flow_risk_bitmap &=
      ~(1UL << NDPI_UNIDIRECTIONAL_TRAFFIC); /* Clear bit */
  }

  if(payload_len > 0) {
#ifdef ENABLE_ENTROPHY_CALCULATION
    if(cli2srv_direction) {
      if(get_bytes_cli2srv() < MAX_ENTROPY_BYTES)
        updateEntropy(initial_bytes_entropy.c2s, payload, payload_len);
    } else {
      if(get_bytes_srv2cli() < MAX_ENTROPY_BYTES)
        updateEntropy(initial_bytes_entropy.s2c, payload, payload_len);
    }
#endif
    
    if(applLatencyMsec == 0) {
      if(cli2srv_direction) {
        memcpy(&c2sFirstGoodputTime, when, sizeof(struct timeval));
      } else {
        if(c2sFirstGoodputTime.tv_sec != 0)
          applLatencyMsec =
	    ((float)(Utils::timeval2usec((struct timeval *)when) -
		     Utils::timeval2usec(&c2sFirstGoodputTime))) /
	    1000;
      }
    }
  }
}

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

bool Flow::addFlowStats(bool new_flow, bool cli2srv_direction, u_int in_pkts,
                        u_int in_bytes, u_int in_goodput_bytes, u_int out_pkts,
                        u_int out_bytes, u_int out_goodput_bytes,
                        u_int in_fragments, u_int out_fragments,
                        time_t first_seen, time_t last_seen) {
  /* Don't update seen if no traffic has been observed */
  if(!(in_bytes || out_bytes || in_pkts || out_pkts))
    return(false);

  updateSeen(last_seen);
  callFlowUpdate(last_seen);

  if(cli2srv_direction) {
    if(in_pkts) stats.incStats(true, in_pkts, in_bytes, in_goodput_bytes);
    if(out_pkts) stats.incStats(false, out_pkts, out_bytes, out_goodput_bytes);
    ip_stats_s2d.pktFrag += in_fragments, ip_stats_d2s.pktFrag += out_fragments;
  } else {
    if(out_pkts) stats.incStats(true, out_pkts, out_bytes, out_goodput_bytes);
    if(in_pkts) stats.incStats(false, in_pkts, in_bytes, in_goodput_bytes);
    ip_stats_s2d.pktFrag += out_fragments, ip_stats_d2s.pktFrag += in_fragments;
  }
#if 0
  ntop->getTrace()->traceEvent(TRACE_NORMAL, "[New flow: %s][first: %u][last: %u][get_last_seen: %u][%u][%u][in_bytes: %u][out_bytes: %u][bytes : %u][thpt: %.2f]",
			       (new_flow ? "YES" : "NO"),
			       first_seen, last_seen,
			       get_last_seen(),
			       last_seen - first_seen,
			       last_seen - get_last_seen(),
			       in_bytes,
			       out_bytes,
			       in_bytes + out_bytes,
			       ((in_bytes + out_bytes) / difftime(last_seen, first_seen)) / 1024 / 1024 * 8);
#endif
  /* New flow, update throughput, otherwise update it in the periodic update */
  //if(new_flow)
  {
    float tdiff_msec = (float)(ndpi_max(1, (last_seen-first_seen))) * 1000.;

    if(cli2srv_direction)
      updateThroughputStats(tdiff_msec, in_pkts, in_bytes, 0, out_pkts, out_bytes, 0);
    else
      updateThroughputStats(tdiff_msec, out_pkts, out_bytes, 0, in_pkts, in_bytes, 0);
  }

  return(true);
}

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

void Flow::updateTcpSeqIssues(const ParsedFlow *pf) {
  stats.incTcpStats(true /* src2dst */, pf->tcp.retr_in_pkts,
                    pf->tcp.ooo_in_pkts, pf->tcp.lost_in_pkts,
                    0 /* keepalive not supported */);
  stats.incTcpStats(false /* dst2src */, pf->tcp.retr_out_pkts,
                    pf->tcp.ooo_out_pkts, pf->tcp.lost_out_pkts,
                    0 /* keepalive not supported */);
}

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

void Flow::updateTcpWindow(u_int16_t window, bool src2dst_direction) {
  /* The update depends on the direction of the flow */
  if(window == 0) {
    if(src2dst_direction)
      src2dst_tcp_zero_window = 1;
    else
      dst2src_tcp_zero_window = 1;
  }
}

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

void Flow::updateICMPFlood(const struct bpf_timeval *when,
                           bool src2dst_direction) {
  if(cli_host)
    cli_host->updateICMPAlertsCounter(when->tv_sec, src2dst_direction);
  if(srv_host)
    srv_host->updateICMPAlertsCounter(when->tv_sec, !src2dst_direction);
}

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

void Flow::updateDNSFlood(const struct bpf_timeval *when,
                          bool src2dst_direction) {
  if(cli_host)
    cli_host->updateDNSAlertsCounter(when->tv_sec, src2dst_direction);
  if(srv_host)
    srv_host->updateDNSAlertsCounter(when->tv_sec, !src2dst_direction);
}

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

void Flow::updateSNMPFlood(const struct bpf_timeval *when,
                           bool src2dst_direction) {
  if(cli_host)
    cli_host->updateSNMPAlertsCounter(when->tv_sec, src2dst_direction);
  if(srv_host)
    srv_host->updateSNMPAlertsCounter(when->tv_sec, !src2dst_direction);
}

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

void Flow::updateTcpFlags(const struct bpf_timeval *when, u_int8_t flags,
                          bool src2dst_direction, bool new_flow) {
  NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;
  bool is_packet_interface = getInterface()->isPacketInterface();
  /* Flags used for the analysis of the 3WH. Original flags are masked for this
     analysis to ignore certain bits such as ECE or CWR which may be present
     during a valid 3WH. See https://github.com/ntop/ntopng/issues/3255 */
  u_int8_t flags_3wh = flags & TCP_3WH_MASK;

#ifdef DEBUG
  char buf[64];

  ntop->getTrace()->traceEvent(TRACE_WARNING, "->> %s", printTCPflags(flags_3wh, buf, sizeof(buf)));
#endif

  iface->incFlagStats(flags, !is_packet_interface);

  if(cli_host) {
    cli_host->incFlagStats(src2dst_direction, flags, !is_packet_interface);
    cli_network_stats = cli_host->getNetworkStats(cli_host->get_local_network_id());
  }

  if(srv_host) {
    srv_host->incFlagStats(!src2dst_direction, flags, !is_packet_interface);
    srv_network_stats = srv_host->getNetworkStats(srv_host->get_local_network_id());
  }

  if(!is_packet_interface) {
    /*
      nProbe

      Main principles
      - With flows we can consider *only* flows with a single flag (e.g. SYN or RST)
        Flows with multiple flags set should be ignored for alerts and flow state as we don't know
	their number but just their presence
      - As flows can be bi-directional, we need to pay attention to direction flags as they might be wrong
     */

    /* Update syn alerts counters. In case of cumulative flags, the AND is used as possibly other flags can be present  */

    /* We have received a SYN flag not observed before for this flow */
    if((flags == TH_SYN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_SYN) != TH_SYN)) {
      if(cli_host)
	cli_host->updateSynAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateSynAlertsCounter(when->tv_sec, !src2dst_direction);

      if(cli_network_stats)
	cli_network_stats->updateSynAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_network_stats)
	srv_network_stats->updateSynAlertsCounter(when->tv_sec, !src2dst_direction);
    }

    /* We have received a RST flag not observed before for this flow */
    if((flags == TH_RST) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_RST) != TH_RST)) {
      iface->getTcpFlowStats()->incReset();

      if(cli_host)
	cli_host->updateRstAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateRstAlertsCounter(when->tv_sec, !src2dst_direction);
    }

    /* We have received a FIN flag not observed before for this flow */
    if((flags == TH_FIN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_FIN) != TH_FIN)) {
      if(cli_host)
	cli_host->updateFinAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateFinAlertsCounter(when->tv_sec, !src2dst_direction);

      iface->getTcpFlowStats()->incFin();
    }

    /* *** */

    if(!twh_over) {
      if(flags & (TH_ACK | TH_PUSH | TH_RST | TH_FIN)) {
	Host *cli_host = getViewSharedClient();
	Host *srv_host = getViewSharedServer();

	twh_over = 1, iface->getTcpFlowStats()->incEstablished();

	if(cli_host || srv_host) setTwhOverForViewInterface();
      }
    }

    if(src2dst_direction)
      src2dst_tcp_flags |= flags;
    else
      dst2src_tcp_flags |= flags;

    /*
      Note
      for nProbe, calculateConnectionState()
      is called in ParserInterface::processFlow
    */
  } else {
    /* Packet Interface */

    /* Update syn alerts counters. In case of cumulative flags, the AND is used as
     * possibly other flags can be present  */
    if(flags_3wh == TH_SYN) {
      if(cli_host)
	cli_host->updateSynAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateSynAlertsCounter(when->tv_sec, !src2dst_direction);

      if(cli_network_stats)
	cli_network_stats->updateSynAlertsCounter(when->tv_sec,
						  src2dst_direction);
      if(srv_network_stats)
	srv_network_stats->updateSynAlertsCounter(when->tv_sec,
						  !src2dst_direction);
    }

    /* Update synack alerts counter. In case of cumulative flags, the AND is used
     * as possibly other flags can be present */
    if(flags_3wh == (TH_SYN | TH_ACK)) {
      if(cli_host)
	cli_host->updateSynAckAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateSynAckAlertsCounter(when->tv_sec, !src2dst_direction);

      if(cli_network_stats)
	cli_network_stats->updateSynAckAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_network_stats)
	srv_network_stats->updateSynAckAlertsCounter(when->tv_sec, !src2dst_direction);
    }

    if((flags & TH_SYN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_SYN) != TH_SYN)) {
      iface->getTcpFlowStats()->incSyn();
    }

    if((flags & TH_RST) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_RST) != TH_RST)) {
      iface->getTcpFlowStats()->incReset();

      if(cli_host)
	cli_host->updateRstAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateRstAlertsCounter(when->tv_sec, !src2dst_direction);
    }

    if((flags & TH_FIN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_FIN) != TH_FIN)) {
      if(cli_host)
	cli_host->updateFinAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateFinAlertsCounter(when->tv_sec, !src2dst_direction);

      iface->getTcpFlowStats()->incFin();
    }

    if((flags & (TH_FIN | TH_ACK))
	&& (((src2dst_tcp_flags | dst2src_tcp_flags) & (TH_FIN | TH_ACK)) != (TH_FIN | TH_ACK))) {
      if(cli_host)
	cli_host->updateFinAckAlertsCounter(when->tv_sec, src2dst_direction);

      if(srv_host)
	srv_host->updateFinAckAlertsCounter(when->tv_sec, !src2dst_direction);

      iface->getTcpFlowStats()->incFin();
    }

    /* *** */
    
    if(src2dst_direction)
      src2dst_tcp_flags |= flags;
    else
      dst2src_tcp_flags |= flags;

    calculateConnectionState(false);

    if(!twh_over) {
      if(flags_3wh == TH_SYN) {
        if(tcp != NULL) {
	  if(tcp->synTime.tv_sec == 0) memcpy(&tcp->synTime, when, sizeof(struct timeval));
	}
	
	if((src2dst_tcp_flags & (TH_SYN | TH_ACK)) == (TH_SYN | TH_ACK)) {
	  /* SYN|ACK arrived before SYN */
	  request_swap();
	}
      } else if(flags_3wh == (TH_SYN | TH_ACK)) {
        if(tcp != NULL) {
	  if((tcp->synAckTime.tv_sec == 0) && (tcp->synTime.tv_sec > 0)) {
	    struct timeval t;
	    
	    memcpy(&tcp->synAckTime, when, sizeof(struct timeval));
	    timeval_diff(&tcp->synTime, (struct timeval *)when, &t, 0);
	    tcp->serverRTT3WH = Utils::timeval2ms(&t);
	    
	    /* Coherence check */
	    if(tcp->serverRTT3WH > 5000 /* 5 sec */ )
	      tcp->serverRTT3WH = 0;
	    else if(srv_host)
	      srv_host->updateNetworkRTT(tcp->serverRTT3WH);
	  }
	}
      } else if((flags_3wh == TH_ACK) ||
                 (flags_3wh == (TH_ACK | TH_PUSH)) /* TCP Fast Open may contain data and PSH
						      in the final TWH ACK */) {
        if(tcp != NULL) {
	  if((tcp->ackTime.tv_sec == 0) && (tcp->synAckTime.tv_sec > 0)) {
	    struct timeval t;

	    memcpy(&tcp->ackTime, when, sizeof(struct timeval));
	    timeval_diff(&tcp->synAckTime, (struct timeval *)when, &t, 0);

	    tcp->clientRTT3WH = Utils::timeval2ms(&t);
#ifdef DEBUG
	    ntop->getTrace()->traceEvent(TRACE_WARNING, "Client RTT: %.1f ms", tcp->clientRTT3WH);
#endif

	    /* Coherence check */
	    if(tcp->clientRTT3WH > 5000 /* 5 sec */)
	      tcp->clientRTT3WH = 0;
	    else if(cli_host)
	      cli_host->updateNetworkRTT(tcp->clientRTT3WH);

#ifdef DEBUG
	    ntop->getTrace()->traceEvent(TRACE_WARNING, "Server RTT: %.1f ms", tcp->serverRTT3WH);
#endif

	    setRTT();
	    iface->getTcpFlowStats()->incEstablished();
	  }
	}
	
        goto not_yet;
      } else {
      not_yet:
	if(twh_over == 0) {
	  twh_over = 1;
	}

	/*
	  Sometimes nDPI detects the protocol at the first packet
	  so we're already on the protocol detected slot. This is
	  is not a good news as we might have protocol detected
	  when 3WH is not yet completed.
	*/
        if(get_state() == hash_entry_state_allocated)
          set_hash_entry_state_flow_notyetdetected();
      }
    }
  }
}

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

void Flow::timeval_diff(struct timeval *begin, const struct timeval *end,
                        struct timeval *result, bool divide_by_two) {
  if(end->tv_sec >= begin->tv_sec) {
    result->tv_sec = end->tv_sec - begin->tv_sec;

    if((end->tv_usec - begin->tv_usec) < 0) {
      result->tv_usec = 1000000 + end->tv_usec - begin->tv_usec;
      if(result->tv_usec > 1000000) begin->tv_usec = 1000000;
      result->tv_sec--;
    } else
      result->tv_usec = end->tv_usec - begin->tv_usec;

    if(divide_by_two) {
      result->tv_usec /= 2;
      if(result->tv_sec % 2) result->tv_usec += 500000;
      result->tv_sec /= 2;
    }
  } else
    result->tv_sec = 0, result->tv_usec = 0;
}

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

std::string Flow::getFlowInfo(bool isLuaRequest) {
  std::string info_field = std::string("");

  if(!isMaskedFlow()) {
    if(iec104) return (iec104->getFlowInfo());
#ifdef NTOPNG_PRO
    if(modbus) return (modbus->getFlowInfo());
#endif

    if(isDNS() && protos.dns.last_query) {
      info_field = std::string(protos.dns.last_query);
    } else if(isHTTP() && protos.http.last_url) {
      info_field = std::string(protos.http.last_url);
    } else if(isTLS() && protos.tls.client_requested_server_name) {
      info_field = std::string(protos.tls.client_requested_server_name);
    } else if(isBittorrent() && bt_hash) {
      info_field = std::string(bt_hash);
    } else if(host_server_name) {
      info_field = std::string(host_server_name);
    } else if(isSSH()) {
      if(protos.ssh.server_signature){
        info_field = std::string(protos.ssh.server_signature);
      } else if(protos.ssh.client_signature) {
        info_field = std::string(protos.ssh.client_signature);
      }
    } else if(isLuaRequest && hasRisk(NDPI_DESKTOP_OR_FILE_SHARING_SESSION)) {
      info_field = std::string("Desktop Sharing");
    } else if(isMining() && protos.mining.currency) {
      info_field = std::string(protos.mining.currency);
    } else if(isSIP()) {
      if(protos.sip.call_id)
	info_field = std::string(protos.sip.call_id);
    } else if(rtp_stream_type != ndpi_multimedia_unknown_flow) {
      if(rtp_stream_type & ndpi_multimedia_audio_flow)
	info_field = std::string("Audio");
      else if(rtp_stream_type & ndpi_multimedia_video_flow)
	info_field = std::string("Video");
      else if(rtp_stream_type & ndpi_multimedia_screen_sharing_flow)
	info_field = std::string("Desktop Sharing");
    } else if((get_protocol() == IPPROTO_ICMP) || (get_protocol() == IPPROTO_ICMPV6)) {
      char str[32];

      if(isBidirectional())
        snprintf(str, sizeof(str), "%u,%u", protos.icmp.srv2cli.icmp_type, protos.icmp.srv2cli.icmp_code);
      else
	snprintf(str, sizeof(str), "%u,%u", protos.icmp.cli2srv.icmp_type, protos.icmp.cli2srv.icmp_code);

      info_field = std::string(str);
    }
  }

  return info_field;
}

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

double Flow::toMs(const struct timeval *t) {
  return (((double)t->tv_sec) * 1000 + ((double)t->tv_usec) / 1000);
}

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

u_int32_t Flow::getNextTcpSeq(u_int8_t tcpFlags, u_int32_t tcpSeqNum,
                              u_int32_t payloadLen) {
  return (tcpSeqNum + ((tcpFlags & TH_SYN) ? 1 : 0) + payloadLen);
}

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

void Flow::setDHCPHostName(const char* name) {
  if((name && name[0]) && (protos.dhcp.name == NULL)) protos.dhcp.name = strdup(name);
}

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

void Flow::setSIPCallId(const char* name) {
  if((name && name[0]) && (protos.sip.call_id == NULL)) protos.sip.call_id = strdup(name);
}

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

void Flow::incTcpBadStats(bool src2dst_direction, Host *cli, Host *srv,
                          NetworkInterface *iface, u_int32_t ooo_pkts,
                          u_int32_t retr_pkts, u_int32_t lost_pkts,
                          u_int32_t keep_alive_pkts) {
#ifdef HAVE_NEDGE
  return;
#endif

  if(!ooo_pkts && !retr_pkts && !lost_pkts && !keep_alive_pkts) return;

  int32_t cli_network_id = -1, srv_network_id = -1;
  u_int32_t cli_asn = (u_int32_t)-1, srv_asn = (u_int32_t)-1;
  AutonomousSystem *cli_as = NULL, *srv_as = NULL;
  NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;
  bool cli_and_srv_in_same_subnet = false, cli_and_srv_in_same_as = false;

  if(iface) {
    if(retr_pkts) iface->incRetransmittedPkts(retr_pkts);
    if(lost_pkts) iface->incLostPkts(lost_pkts);
    if(ooo_pkts) iface->incOOOPkts(ooo_pkts);
    if(keep_alive_pkts) iface->incKeepAlivePkts(keep_alive_pkts);
  }

  if(cli) {
    cli_network_id = cli->get_local_network_id();
    cli_network_stats = cli->getNetworkStats(cli_network_id);
    cli_asn = cli->get_asn();
    cli_as = cli->get_as();

    if(src2dst_direction)
      cli->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
    else
      cli->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
  }

  if(srv) {
    srv_network_id = srv->get_local_network_id();
    srv_network_stats = srv->getNetworkStats(srv_network_id);
    srv_asn = srv->get_asn();
    srv_as = srv->get_as();

    if(src2dst_direction)
      srv->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
    else
      srv->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
  }

  if(cli_network_id >= 0 && (cli_network_id == srv_network_id))
    cli_and_srv_in_same_subnet = true;

  if(cli_network_stats) {
    if(!cli_and_srv_in_same_subnet) {
      if(src2dst_direction)
        cli_network_stats->incEgressTcp(ooo_pkts, retr_pkts, lost_pkts,
                                        keep_alive_pkts);
      else
        cli_network_stats->incIngressTcp(ooo_pkts, retr_pkts, lost_pkts,
                                         keep_alive_pkts);
    } else
      cli_network_stats->incInnerTcp(ooo_pkts, retr_pkts, lost_pkts,
                                     keep_alive_pkts);
  }

  if(srv_network_stats) {
    if(!cli_and_srv_in_same_subnet) {
      if(src2dst_direction)
        srv_network_stats->incIngressTcp(ooo_pkts, retr_pkts, lost_pkts,
                                         keep_alive_pkts);
      else
        srv_network_stats->incEgressTcp(ooo_pkts, retr_pkts, lost_pkts,
                                        keep_alive_pkts);
    }
  }

  if(cli_asn != (u_int32_t)-1 && (cli_asn == srv_asn))
    cli_and_srv_in_same_as = true;

  if(!cli_and_srv_in_same_as) {
    if(cli_as) {
      if(src2dst_direction)
        cli_as->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
      else
        cli_as->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
    }

    if(srv_as) {
      if(src2dst_direction)
        srv_as->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
      else
        srv_as->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
    }
  }
}

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

void Flow::updateTcpSeqNum(const struct bpf_timeval *when, u_int32_t seq_num,
                           u_int32_t ack_seq_num, u_int16_t window,
                           u_int8_t flags, u_int16_t payload_Len,
                           bool src2dst_direction) {
  u_int32_t next_seq_num;
  bool update_last_seqnum = true;
  bool update_next_seqnum = true;
  bool debug = false;
  u_int32_t cnt_keep_alive = 0, cnt_lost = 0, cnt_ooo = 0, cnt_retx = 0;

#ifdef HAVE_NEDGE
  return;
#endif

  if(tcp == NULL) return;
  
  next_seq_num = getNextTcpSeq(flags, seq_num, payload_Len);

  if(debug)
    ntop->getTrace()->traceEvent(TRACE_WARNING,
                                 "[act: %u][next: %u][next - act (in flight): "
                                 "%d][ack: %u][payload len: %u]",
                                 seq_num, next_seq_num, next_seq_num - seq_num,
                                 ack_seq_num, payload_Len);

  if(src2dst_direction) {
    if(debug)
      ntop->getTrace()->traceEvent(TRACE_WARNING,
                                   "[src2dst][seq: %u][last: %u][next: %u]", seq_num,
                                   tcp->tcp_seq_s2d.last, tcp->tcp_seq_s2d.next);

    if(window > 0) tcp->srv2cli_window = window; /* Note the window is reverted */
    if((tcp->tcp_seq_s2d.next > 0) &&
       /* If equal, seq_num is the expected seq_num as determined with prev. segment */
       (tcp->tcp_seq_s2d.next != seq_num) &&
       (tcp->tcp_seq_s2d.next != (seq_num - 1))) {
      if((seq_num == tcp->tcp_seq_s2d.next - 1) &&
         (payload_Len == 0 || payload_Len == 1) &&
         ((flags & (TH_SYN | TH_FIN | TH_RST)) == 0)) {
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING,
                                       "[src2dst] Packet KeepAlive");
        cnt_keep_alive++;
      } else if(seq_num == tcp->tcp_seq_s2d.last ||
                seq_num < tcp->tcp_seq_s2d.last /* older packet retransmitted */) {
        if(tcp->tcp_seq_s2d.next != tcp->tcp_seq_s2d.last) {
          update_next_seqnum = (seq_num == tcp->tcp_seq_s2d.last);
          update_last_seqnum = false;
          cnt_retx++;
          if(debug)
            ntop->getTrace()->traceEvent(TRACE_WARNING,
                                         "[src2dst] Packet retransmission");
        }
      } else if(seq_num > tcp->tcp_seq_s2d.next) {
        cnt_lost++;
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet lost [last: %u][act: %u]",
				       tcp->tcp_seq_s2d.last, seq_num);
      } else {
        cnt_ooo++;
        update_last_seqnum = ((seq_num - 1) > tcp->tcp_seq_s2d.last) ? true : false;
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet OOO [last: %u][act: %u]",
				       tcp->tcp_seq_s2d.last, seq_num);
      }
    }

    if(update_next_seqnum) tcp->tcp_seq_s2d.next = next_seq_num;
    if(update_last_seqnum) tcp->tcp_seq_s2d.last = seq_num;
  } else {
    if(debug)
      ntop->getTrace()->traceEvent(TRACE_WARNING,
                                   "[dst2src][seq: %u][last: %u][next: %u]", seq_num,
                                   tcp->tcp_seq_d2s.last, tcp->tcp_seq_d2s.next);

    if(window > 0) tcp->cli2srv_window = window; /* Note the window is reverted */

    if((tcp->tcp_seq_d2s.next > 0) &&
       (tcp->tcp_seq_d2s.next != seq_num) &&
       (tcp->tcp_seq_d2s.next != (seq_num - 1))) {

      if((seq_num == tcp->tcp_seq_d2s.next - 1) &&
         (payload_Len == 0 || payload_Len == 1) &&
         ((flags & (TH_SYN | TH_FIN | TH_RST)) == 0)) {
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING,
                                       "[dst2src] Packet KeepAlive");
        cnt_keep_alive++;
      } else if(seq_num == tcp->tcp_seq_d2s.last /* last packet retransmitted */ ||
                seq_num < tcp->tcp_seq_d2s.last /* older packet retransmitted */) {
        if(tcp->tcp_seq_d2s.next != tcp->tcp_seq_d2s.last) {
          cnt_retx++;
          update_next_seqnum = (seq_num == tcp->tcp_seq_d2s.last);
          update_last_seqnum = false;
          if(debug)
            ntop->getTrace()->traceEvent(TRACE_WARNING,
                                         "[dst2src] Packet retransmission");
        }
        // bytes
      } else if(seq_num > tcp->tcp_seq_d2s.next) {
        cnt_lost++;
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet lost [last: %u][act: %u]",
				       tcp->tcp_seq_d2s.last, seq_num);
      } else {
        cnt_ooo++;
        update_last_seqnum = ((seq_num - 1) > tcp->tcp_seq_d2s.last) ? true : false;
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING,
                                       "[dst2src] [last: %u][next: %u]",
                                       tcp->tcp_seq_d2s.last, tcp->tcp_seq_d2s.next);
        if(debug)
          ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet OOO [last: %u][act: %u]",
				       tcp->tcp_seq_d2s.last, seq_num);
      }
    }

    if(update_next_seqnum) tcp->tcp_seq_d2s.next = next_seq_num;
    if(update_last_seqnum) tcp->tcp_seq_d2s.last = seq_num;
  }

#if 0
  static int idx = 0;
  ntop->getTrace()->traceEvent(TRACE_WARNING, "%03u %s RETR=%u OOO=%u LOST=%u KPLV=%u", ++idx,
    src2dst_direction ? ">" : "<", cnt_retx, cnt_ooo, cnt_lost, cnt_keep_alive);
#endif

  if (cnt_lost || cnt_ooo || cnt_retx)
    tcp->last_network_issues = when->tv_sec;

  if(cnt_keep_alive || cnt_lost || cnt_ooo || cnt_retx) {
    stats.incTcpStats(src2dst_direction, cnt_retx, cnt_ooo, cnt_lost,
                      cnt_keep_alive);
  }
}

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

u_int32_t Flow::getPid(bool client) {
  if(client && ebpf && ebpf->process_info_set)
    return(ebpf->src_process_info.pid);

  if(!client && ebpf && ebpf->process_info_set)
    return(ebpf->dst_process_info.pid);

  return NO_PID;
};

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

u_int32_t Flow::getFatherPid(bool client) {
  if(client && ebpf && ebpf->process_info_set)
    return(ebpf->src_process_info.father_pid);

  if(!client && ebpf && ebpf->process_info_set)
    return(ebpf->dst_process_info.father_pid);

  return NO_PID;
};

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

u_int32_t Flow::get_uid(bool client) const {
#ifdef WIN32
  return NO_UID;
#else
  if(client && ebpf && ebpf->process_info_set)
    return(ebpf->src_process_info.uid);

  if(!client && ebpf && ebpf->process_info_set)
    return(ebpf->dst_process_info.uid);

  return NO_UID;
#endif
}

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

char *Flow::get_proc_name(bool client) {
  if(client && ebpf && ebpf->process_info_set)
    return(ebpf->src_process_info.process_name);

  if(!client && ebpf && ebpf->process_info_set)
    return(ebpf->dst_process_info.process_name);

  return NULL;
};

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

char *Flow::get_user_name(bool client) {
  if(client && ebpf && ebpf->process_info_set)
    return(ebpf->src_process_info.uid_name);

  if(!client && ebpf && ebpf->process_info_set)
    return(ebpf->dst_process_info.uid_name);

  return NULL;
}

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

bool Flow::match(AddressTree *ptree) {
  if((get_cli_ip_addr() && get_cli_ip_addr()->match(ptree)) ||
      (get_srv_ip_addr() && get_srv_ip_addr()->match(ptree)))
    return (true);
  else
    return (false);
};

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

void Flow::setBittorrentHash(char *hash, u_int len) {
  int i, j, n = 0;
  char bittorrent_hash[41];

  if(len < 20)
    return;

  for (i = 0, j = 0; i < 20; i++) {
    u_char c = hash[i] & 0xFF;

    snprintf(&bittorrent_hash[j], 3, "%02x", (u_int8_t)c);
    j += 2, n += c;
  }

  if(n > 0) bt_hash = strdup(bittorrent_hash);
}

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

void Flow::dissectBittorrent(char *payload, u_int16_t payload_len) {
  /* This dissector is called only for uTP/UDP protocol */

  if(payload_len > 47) {
    char *bt_proto = ndpi_strnstr((const char *)&payload[20],
                                  "BitTorrent protocol", payload_len - 20);

    if(bt_proto) {
      u_int l = strnlen(bt_proto, 27);

      if(l >= 27)
	setBittorrentHash(&bt_proto[27], payload_len-27);
    }
  }
}

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

/*
  Performs DNS query updates. No more than one update per second is performed to
  handle concurrency issues. This is safe in general as it is unlikely to see
  more than one query per second for the same DNS flow.
*/
bool Flow::setDNSQuery(char *query_value, char *rsp_addresses, bool copy_memory) {
  if((query_value != NULL) && isDNS()) {
    time_t last_pkt_rcvd = getInterface()->getTimeLastPktRcvd();

    if(!protos.dns.last_query_shadow /* The first time the swap is done */
        || (protos.dns.last_query_update_time + 1 <
	    last_pkt_rcvd /* Latest swap occurred at least one second ago */)) {
      if(protos.dns.last_query_shadow) free(protos.dns.last_query_shadow);
      protos.dns.last_query_shadow = protos.dns.last_query;
      protos.dns.last_query = copy_memory ? strdup(query_value) : query_value;

      if(protos.dns.last_rsp_shadow) free(protos.dns.last_rsp_shadow);
      protos.dns.last_rsp_shadow = protos.dns.last_rsp;

      if(rsp_addresses && (rsp_addresses[0] != '\0'))
	protos.dns.last_rsp = copy_memory ? strdup(rsp_addresses) : rsp_addresses;
      else
	protos.dns.last_rsp = NULL;

      protos.dns.last_query_update_time = last_pkt_rcvd;

#ifdef DEBUG
      if(protos.dns.last_rsp)
	ntop->getTrace()->traceEvent(TRACE_NORMAL, "**** %s", protos.dns.last_rsp);
#endif

      return(true); /* Swap successful */
    }
  }

  /* Unable to set the DNS query. Too early or not a DNS flow. */
  return(false);
}

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

/*
  @brief Update DNS stats for flows received via ZMQ
*/
void Flow::updateDNS(ParsedFlow *zflow) {
  if(isDNS()) {
    if(zflow->getDNSQuery()) {
      if(setDNSQuery(zflow->getDNSQuery(true), NULL, false /* No need to allocate memory */)) {
        /* Set successful, query will be freed in the destructor */
        setDNSQueryType(zflow->getDNSQueryType());
        setDNSRetCode(zflow->getDNSRetCode());
      }
    }

    stats.incDNSQuery(getLastQueryType());
    stats.incDNSResp(getDNSRetCode());
  }
}

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

/*
  @brief Update TLS stats for flows received via ZMQ
*/
void Flow::updateTLS(ParsedFlow *zflow) {
  if(zflow->getTLSserverName()
      && isTLS()
      && (!protos.tls.client_requested_server_name)) {
    protos.tls.client_requested_server_name = zflow->getTLSserverName(true);
  }
}

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

void Flow::dissectDNS(bool src2dst_direction, char *payload,
                      u_int16_t payload_len) {
  struct ndpi_dns_packet_header dns_header;
  u_int8_t payload_offset = get_protocol() == IPPROTO_UDP ? 0 : 2;

  if(payload_len + payload_offset < sizeof(dns_header)) return;

  memcpy(&dns_header, &payload[payload_offset], sizeof(dns_header));

  if((dns_header.flags & 0x8000) == 0x0000)
    stats.incDNSQuery(getLastQueryType());
  else if((dns_header.flags & 0x8000) == 0x8000)
    stats.incDNSResp(getDNSRetCode());
}

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

void Flow::setICMPTypeCode(u_int16_t icmp_type_code) {
  if(icmp_info == NULL)
    icmp_info = new (std::nothrow) ICMPinfo();

  if(icmp_info != NULL) {
    u_int8_t icmp_code = (u_int8_t)(icmp_type_code & 0x00FF);
    u_int8_t icmp_type = (u_int8_t)((icmp_type_code >> 8) & 0xFF);

    icmp_info->setCode(icmp_code), protos.icmp.cli2srv.icmp_code = icmp_code;
    icmp_info->setType(icmp_type), protos.icmp.cli2srv.icmp_type = icmp_type;
  }
}

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

void Flow::updateHTTP(ParsedFlow *zflow) {
  if(isHTTP()) {
    if(zflow->getHTTPurl())
      setHTTPURL(zflow->getHTTPurl(true));

    if(zflow->getHTTPuserAgent())
      setHTTPUserAgent(zflow->getHTTPuserAgent(true));

    if(zflow->getHTTPsite())
      setServerName(zflow->getHTTPsite(true));

    if(zflow->getHTTPMethod() != NDPI_HTTP_METHOD_UNKNOWN) {
      setHTTPMethod(zflow->getHTTPMethod());
      const char *http_method = getHTTPMethod();

      if(http_method && http_method[0] && http_method[1]) {
        switch (http_method[0]) {
	case 'P':
	  switch (http_method[1]) {
	  case 'O':
	    stats.incHTTPReqPOST();
	    break;
	  case 'U':
	    stats.incHTTPReqPUT();
	    break;
	  default:
	    stats.incHTTPReqOhter();
	    break;
	  }
	  break;
	case 'G':
	  stats.incHTTPReqGET();
	  break;
	case 'H':
	  stats.incHTTPReqHEAD();
	  break;
	default:
	  stats.incHTTPReqOhter();
	  break;
        }
      } else
        stats.incHTTPReqOhter();
    }

    setHTTPRetCode(zflow->getHTTPRetCode());
    u_int16_t ret_code = getHTTPRetCode();
    while (ret_code > 9) ret_code /= 10; /* Take the first digit */
    switch (ret_code) {
    case 1:
      stats.incHTTPResp1xx();
      break;
    case 2:
      stats.incHTTPResp2xx();
      break;
    case 3:
      stats.incHTTPResp3xx();
      break;
    case 4:
      stats.incHTTPResp4xx();
      break;
    case 5:
      stats.incHTTPResp5xx();
      break;
    }
  }
}

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

void Flow::updateSuspiciousDGADomain() {
  if(hasRisk(NDPI_SUSPICIOUS_DGA_DOMAIN) && !suspicious_dga_domain) {
    std::string info_field = getFlowInfo(false);

    if(!info_field.empty()) {
      suspicious_dga_domain = strdup(info_field.c_str());
    }
  }
}

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

void Flow::setHTTPMethod(ndpi_http_method m) {
  if(protos.http.last_method == NDPI_HTTP_METHOD_UNKNOWN)
    protos.http.last_method = m;
}

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

void Flow::setHTTPMethod(const char *method, ssize_t method_len) {
  setHTTPMethod(ndpi_http_str2method(method, method_len));
}

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

void Flow::dissectHTTP(bool src2dst_direction, char *payload,
                       u_int16_t payload_len) {
  ssize_t host_server_name_len = host_server_name && host_server_name[0] != '\0'
    ? strlen(host_server_name)
    : 0;

  if((payload == NULL) || (payload_len == 0)) return;

#if 0 /* Not sure it this is really necessary */
  if(!isThreeWayHandshakeOK())
    ; /* Useless to compute http stats as client and server could be swapped */
  else
#endif

    if(src2dst_direction) {
      char *space;
      dissect_next_http_packet = 1;

      /* use memchr to prevent possibly non-NULL terminated HTTP requests */
      if(payload &&
	  ((space = (char *)memchr(payload, ' ', payload_len - 1)) != NULL)) {
	u_int l = space - payload;
	bool go_deeper = true;

	if(payload_len >= 2) {
	  switch (payload[0]) {
          case 'P':
            switch (payload[1]) {
	    case 'O':
	      stats.incHTTPReqPOST();
	      break;
	    case 'U':
	      stats.incHTTPReqPUT();
	      break;
	    default:
	      stats.incHTTPReqOhter();
	      go_deeper = false;
	      break;
            }
            break;
          case 'G':
            stats.incHTTPReqGET();
            break;
          case 'H':
            stats.incHTTPReqHEAD();
            break;
          default:
            stats.incHTTPReqOhter();
            go_deeper = false;
            break;
	  }
	} else
	  go_deeper = false;

	if(go_deeper) {
	  char *ua;

	  setHTTPMethod(payload, l);

	  payload_len -= (l + 1);
	  payload = &space[1];
	  if((space = (char *)memchr(payload, ' ', payload_len)) != NULL) {
	    l = min_val(space - payload, 512); /* Avoid jumbo URLs */

	    /* Stop at the first non-printable char of the HTTP URL */
	    for (u_int i = 0; i < l; i++) {
	      if(!isprint(payload[i])) {
		l = i;
		break;
	      }
	    }

	    if(!protos.http.last_url &&
		(protos.http.last_url =
		 (char *)malloc(host_server_name_len + l + 1)) != NULL) {
	      protos.http.last_url[0] = '\0';

	      if(host_server_name_len > 0) {
		strncat(protos.http.last_url, host_server_name,
			host_server_name_len);
	      }

	      strncat(protos.http.last_url, payload, l);
	    }
	  }

	  if((ua = ndpi_strnstr(payload, "User-Agent:", payload_len)) != NULL) {
	    char buf[128];
	    u_int i;

	    ua = &ua[11];
	    while (ua[0] == ' ') ua++;

	    for (i = 0;
		 (i < payload_len) && (i < (sizeof(buf) - 1) && (ua[i] != '\r'));
		 i++)
	      buf[i] = ua[i];

	    buf[i] = '\0';

#ifdef DEBUG_UA
	    ntop->getTrace()->traceEvent(TRACE_WARNING, "[UA] %s", buf);
#endif

	    /*
	      https://en.wikipedia.org/wiki/User_agent

	      Most Web browsers use a User-Agent string value as follows:
	      Mozilla/[version] ([system and browser information]) [platform]
	      ([platform details]) [extensions]
	    */

	    if((ua = strchr(buf, '(')) != NULL) {
	      char *end = strchr(buf, ')');

	      if(end) {
		ndpi_os hint;

		end[0] = '\0';
		ua++;

		Utils::getDeviceTypeFromOsDetail(ua, &hint);

		if(hint != ndpi_os_unknown)
		  operating_system = hint;
	      }
	    }
	  }
	}
      }
    } else {
      if(dissect_next_http_packet) {
	char *space;

	// payload[10]=0; ntop->getTrace()->traceEvent(TRACE_WARNING, "[len:
	// %u][%s]", payload_len, payload);
	dissect_next_http_packet = 0;

	if((space = (char *)memchr(payload, ' ', payload_len)) != NULL) {
	  u_int l = space - payload;

	  payload_len -= (l + 1);
	  payload = &space[1];

	  switch (payload[0]) {
          case '1':
            stats.incHTTPResp1xx();
            break;
          case '2':
            stats.incHTTPResp2xx();
            break;
          case '3':
            stats.incHTTPResp3xx();
            break;
          case '4':
            stats.incHTTPResp4xx();
            break;
          case '5':
            stats.incHTTPResp5xx();
            break;
	  }

	  if((space = (char *)memchr(payload, ' ', payload_len)) != NULL) {
	    char tmp[32];
	    l = min_val(space - payload, (int)(sizeof(tmp) - 1));

	    strncpy(tmp, payload, l);
	    tmp[l] = 0;
	    protos.http.last_return_code = atoi(tmp);
	  }
	}
      }
    }
}

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

void Flow::dissectMDNS(u_int8_t *payload, u_int16_t payload_len) {
  u_int16_t answers, i = 0;

  PACK_ON
    struct mdns_rsp_entry {
    u_int16_t rsp_type, rsp_class;
    u_int32_t ttl;
    u_int16_t data_len;
  } PACK_OFF;

  if(((payload[2] & 0x80) != 0x80) || (payload_len < 12))
    return; /* This is a not MDNS response */

  answers = ntohs(*((u_int16_t *)&payload[6])) +
    ntohs(*((u_int16_t *)&payload[8])) +
    ntohs(*((u_int16_t *)&payload[10]));

  payload = &payload[12], payload_len -= 12;

  while ((answers > 0) && (i < payload_len)) {
    char _name[256], *name;
    struct mdns_rsp_entry rsp;
    u_int j;
    u_int16_t rsp_type, data_len;
    DeviceType dtype = device_unknown;
    bool first_char = true;

    memset(_name, 0, sizeof(_name));

    for (j = 0; (i < payload_len) && (j < (sizeof(_name) - 1)); i++) {
      if(payload[i] == 0x0) {
        i++;
        break;
      } else if(payload[i] < 32) {
        if(j > 0) _name[j++] = '.';
      } else if(payload[i] == 0x22) {
        _name[j++] = 'a';
        _name[j++] = 'r';
        _name[j++] = 'p';
        _name[j++] = 'a';
        i++;
        break;
      } else if(payload[i] == 0xC0) {
        u_int8_t offset;
        u_int16_t i_save = i;
        u_int8_t num_loops = 0;
        const u_int8_t max_nested_loops = 8;

      nested_dns_definition:
        offset = payload[i + 1] - 12;
        i = offset;

        if((offset > i) || (i > payload_len) ||
            (num_loops > max_nested_loops)) {
#ifdef DEBUG_DISCOVERY
          ntop->getTrace()->traceEvent(TRACE_WARNING, "Invalid MDNS packet");
#endif
          return; /* Invalid packet */
        } else {
          /* Pointer back */
          while ((i < payload_len) && (payload[i] != 0) &&
                 (j < (sizeof(_name) - 1))) {
            if(payload[i] == 0)
              break;
            else if(payload[i] == 0xC0) {
              num_loops++;
              goto nested_dns_definition;
            } else if(payload[i] < 32) {
              if(j > 0) _name[j++] = '.';
              i++;
            } else
              _name[j++] = payload[i++];
          }

          if(i_save > 0) {
            i = i_save;
            i_save = 0;
          }

          i += 2;
          /*  ntop->getTrace()->traceEvent(TRACE_NORMAL, "===>>> [%d] %s", i,
           * &payload[i-12]); */
          break;
        }
      } else if(!first_char)
        _name[j++] = payload[i];

      first_char = false;
    }

    if((i+sizeof(rsp)) >= payload_len)
      return; /* packet too short */

    memcpy(&rsp, &payload[i], sizeof(rsp));
    data_len = ntohs(rsp.data_len), rsp_type = ntohs(rsp.rsp_type);

#if 1
    name = _name;
#else
    /* Skip lenght for strings >= 32 with head length */
    name = &_name[((data_len <= 32) || (_name[0] >= '0')) ? 0 : 1];
#endif

#ifdef DEBUG_DISCOVERY
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "===>>> [%u][%s][len=%u]",
                                 ntohs(rsp.rsp_type) & 0xFFFF, name, data_len);
#endif

    if(strstr(name, "._device-info._"))
      ;
    else if(strstr(name, "._airplay._") || strstr(name, "._spotify-connect._"))
      dtype = device_multimedia;
    else if(strstr(name, "_ssh._"))
      dtype = device_workstation;
    else if(strstr(name, "._daap._") || strstr(name, "_afpovertcp->_") ||
             strstr(name, "_adisk._") || strstr(name, "_smb._"))
      dtype = device_nas;
    else if(strstr(name, "_hap._"))
      dtype = device_iot;
    else if(strstr(name, "_pdl-datastream._"))
      dtype = device_printer;

    if((dtype != device_unknown) && cli_host && cli_host->getMac()) {
      Mac *m = cli_host->getMac();

      cli_host->setDeviceType(dtype);

      if(m->getDeviceType() == device_unknown)
	m->setDeviceType(dtype);
    }

    switch (rsp_type) {
    case 0x1C: /* AAAA */
    case 0x01: /* AA */
    case 0x10: /* TXT */
      {
        int len = strlen(name);
        char *c;

        if((len > 6) && (strcmp(&name[len - 6], ".local") == 0))
          name[len - 6] = 0;

        c = strstr(name, "._");
        if(c && (c != name) /* Does not begin with... */) c[0] = '\0';
      }

      if(!protos.mdns.name) protos.mdns.name = strdup(name);

      if((rsp_type == 0x10 /* TXT */) && (data_len > 0)) {
	u_int16_t base_off = i + sizeof(rsp);
	char *txt = (char *)&payload[base_off], txt_buf[256];
	u_int16_t off = 0;

	while((off < data_len) && ((off+base_off) < payload_len)) {
	  u_int8_t txt_len = (u_int8_t)txt[off];

	  if(txt_len < data_len) {
	    txt_len = min_val(data_len - off, txt_len);

	    off++;

	    if(txt_len > 0) {
	      char *model = NULL;
	      u_int txt_buf_len = ndpi_min(txt_len, sizeof(txt_buf)-1);

	      if((base_off+off+txt_buf_len) < payload_len)
		break;

	      strncpy(txt_buf, &txt[off], txt_buf_len);
	      txt_buf[txt_buf_len] = '\0';
	      off += txt_len;

#ifdef DEBUG_DISCOVERY
	      ntop->getTrace()->traceEvent(TRACE_NORMAL, "===>>> [TXT][%s]", txt_buf);
#endif

	      if(strncmp(txt_buf, "am=", 3 /* Apple Model */) == 0)
		model = &txt_buf[3];
	      else if(strncmp(txt_buf, "model=", 6) == 0)
		model = &txt_buf[6];
	      else if(strncmp(txt_buf, "md=", 3) == 0)
		model = &txt_buf[3];

	      if(model && cli_host) {
		Mac *mac = cli_host->getMac();

		if(mac) {
		  mac->inlineSetModel(model);
		}
	      }

	      if(strncmp(txt_buf, "nm=", 3) == 0)
		if(!protos.mdns.name_txt)
		  protos.mdns.name_txt = strdup(&txt_buf[3]);

	      if(strncmp(txt_buf, "ssid=", 5) == 0) {
		if(!protos.mdns.ssid) protos.mdns.ssid = strdup(&txt_buf[5]);

		if(cli_host && cli_host->getMac())
		  cli_host->getMac()->inlineSetSSID(&txt_buf[5]);
	      }
	    }
	  } else
	    break;
	}
      }

#ifdef DEBUG_DISCOVERY
      ntop->getTrace()->traceEvent(TRACE_NORMAL, "%u) %u [%s]", answers,
				   rsp_type, name);
#endif
      // return; /* It's enough to decode the first name */
    }

    i += sizeof(rsp) + data_len, answers--;
  }
}

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

void Flow::dissectSSDP(bool src2dst_direction, char *payload,
                       u_int16_t payload_len) {
  char url[512];
  u_int i = 0;

  if(payload_len < 6 /* NOTIFY */) return;

  if(strncmp(payload, "NOTIFY", 6) == 0) {
    payload += 6, payload_len -= 6;

    for (; 0 < payload_len - 9 /* strlen("Location:") */;
         payload++, payload_len--) {
      if(strncasecmp(payload, "Location:", 9)) {
        continue;
      } else {
        payload += 9, payload_len -= 9;

        for (;
             (payload_len > 0) && (payload[0] != '\n') && (payload[0] != '\r');
             payload++, payload_len--) {
          if(*payload == ' ') continue;
          if(i == sizeof(url) - 1) break;
          url[i++] = *payload;
        }

        url[i] = '\0';
        // ntop->getTrace()->traceEvent(TRACE_NORMAL, "[SSDP URL:] %s", url);
        if(!protos.ssdp.location) protos.ssdp.location = strdup(url);
        break;
      }
    }
  }
}

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

void Flow::dissectNetBIOS(u_int8_t *payload, u_int16_t payload_len) {
  char name[64];

  /* Already dissected ? */
  if(protos.netbios.name) return;

  if((payload_len >= 12) &&
      ((payload[2] & 0x80) /* NetBIOS Response */ ||
       ((payload[2] & 0x78) == 0x28 /* NetBIOS Registration */)) &&
      (ndpi_netbios_name_interpret((u_char *)&payload[12], payload_len - 12,
                                   (u_char *)name, sizeof(name)) > 0) &&
      (!strstr(name, "__MSBROWSE__"))) {
    if(name[0] == '*') {
      int limit = min_val(payload_len - 57, (int)sizeof(name) - 1);
      int i = 0;

      while ((i < limit) && (payload[57 + i] != 0x20) &&
             isprint(payload[57 + i])) {
        name[i] = payload[57 + i];
        i++;
      }

      if((i < limit) &&
          (payload[57 + i] != 0x00 /* Not a Workstation/Redirector */))
        name[0] = '\0'; /* ignore */
      else
        name[i] = '\0';
    }
#if 0
    char buf[32];

    ntop->getTrace()->traceEvent(TRACE_NORMAL, "Setting hostname from NetBios [raw=0x%x opcode=0x%x response=0x%x]: ip=%s -> '%s'",
				 payload[2], (payload[2] & 0x78) >> 3, (payload[2] & 0x80) >> 7,
				 (*srcHost)->get_ip()->print(buf, sizeof(buf)), name);
#endif

    if(name[0]) protos.netbios.name = strdup(name);
  }
}



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

#ifdef HAVE_NEDGE

bool Flow::isPassVerdict() {
  if (!passVerdict) return (false);

  if (cli_host && srv_host) {
    if (quota_exceeded) {
      dropVerdictReason = DROP_REASON_QUOTA_EXCEEDED;
      return false;
    }

    if (cli_host->dropAllTraffic()) {
      dropVerdictReason = DROP_REASON_DROP_CLI;
      return false;
    }

    if (srv_host->dropAllTraffic()) {
      dropVerdictReason = DROP_REASON_DROP_SRV;
      return false;
    }

    if (isBlacklistedFlow()) {
      dropVerdictReason = DROP_REASON_BLACKLISTED_FLOW;
      return false;
    }
  }

  return true;
}

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

bool Flow::checkPassVerdict(const struct tm *now) {
  if(!passVerdict) return (false);

  if(!isDetectionCompleted())
    return (true); /* Always pass until detection is completed */

  recheckQuota(now);
  return isPassVerdict();
}

#endif

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

#ifdef HAVE_NEDGE

bool Flow::updateDirectionShapers(bool src2dst_direction,
                                  TrafficShaper **ingress_shaper,
                                  TrafficShaper **egress_shaper) {
  bool verdict = true;

  if(cli_host && srv_host) {
    u_int16_t cli_pool = cli_host->get_host_pool();
    u_int16_t srv_pool = srv_host->get_host_pool();

    if(src2dst_direction) {

      if (cli_pool == NO_HOST_POOL_ID && srv_pool != NO_HOST_POOL_ID)
        *egress_shaper = NULL; /* do not use shaper for Not Assigned if the other host has a pool */
      else
        *egress_shaper  = cli_host->get_egress_shaper(ndpiDetectedProtocol);

      if (srv_pool == NO_HOST_POOL_ID && cli_pool != NO_HOST_POOL_ID)
        *egress_shaper = NULL;
      else
        *ingress_shaper = srv_host->get_ingress_shaper(ndpiDetectedProtocol);

      if(*egress_shaper)  cli2srv_out = (*egress_shaper)->get_shaper_id();
      if(*ingress_shaper) srv2cli_in = (*ingress_shaper)->get_shaper_id();

    } else {

      if (srv_pool == NO_HOST_POOL_ID && cli_pool != NO_HOST_POOL_ID)
        *egress_shaper = NULL;
      else
        *egress_shaper  = srv_host->get_egress_shaper(ndpiDetectedProtocol);


      if (cli_pool == NO_HOST_POOL_ID && srv_pool != NO_HOST_POOL_ID)
        *egress_shaper = NULL;
      else
        *ingress_shaper = cli_host->get_ingress_shaper(ndpiDetectedProtocol);

      if(*egress_shaper)  srv2cli_out = (*egress_shaper)->get_shaper_id();
      if(*ingress_shaper) cli2srv_in = (*ingress_shaper)->get_shaper_id();
    }

    if(((*ingress_shaper) && (*ingress_shaper)->shaping_enabled() && (*ingress_shaper)->get_max_rate_kbit_sec() == 0) ||
        ((*egress_shaper) &&  (*egress_shaper)->shaping_enabled() &&  (*egress_shaper)->get_max_rate_kbit_sec() == 0))
      verdict = false;
  } else
    *ingress_shaper = *egress_shaper = NULL;

  return verdict;
}

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

void Flow::updateFlowShapers(bool first_update) {
  bool cli2srv_verdict, srv2cli_verdict;
  u_int8_t old_verdict = passVerdict;
  bool new_verdict = true;
  u_int16_t old_cli2srv_in  = cli2srv_in,
            old_cli2srv_out = cli2srv_out,
            old_srv2cli_in  = srv2cli_in,
            old_srv2cli_out = srv2cli_out;

  /* Re-compute the verdict */
  cli2srv_verdict = updateDirectionShapers(true,  &flowShaperIds.cli2srv.ingress, &flowShaperIds.cli2srv.egress);
  srv2cli_verdict = updateDirectionShapers(false, &flowShaperIds.srv2cli.ingress, &flowShaperIds.srv2cli.egress);
  if (!cli2srv_verdict) dropVerdictReason = DROP_REASON_CLI2SRV_SHAPER;
  if (!srv2cli_verdict) dropVerdictReason = DROP_REASON_SRV2CLI_SHAPER;
  new_verdict = (cli2srv_verdict && srv2cli_verdict);

  if(ntop->getPrefs()->are_device_protocol_policies_enabled() &&
      cli_host &&
      srv_host &&
      new_verdict) {
    /* NOTE: this must be handled differently to only consider actual peers
     * direction */
    if(cli_host->getDeviceAllowedProtocolStatus(ndpiDetectedProtocol, true /* client */) != device_proto_allowed) {
      dropVerdictReason = DROP_REASON_DEV_NOT_ALLOW_PROTO_CLI;
      new_verdict = false;
    }
    if(srv_host->getDeviceAllowedProtocolStatus(ndpiDetectedProtocol, false /* server */) != device_proto_allowed) {
      dropVerdictReason = DROP_REASON_DEV_NOT_ALLOW_PROTO_SRV;
      new_verdict = false;
    }
  }

  if(!new_verdict) {
    /* Always allow network critical protocols */
    if(Utils::isCriticalNetworkProtocol(ndpiDetectedProtocol.proto.master_protocol) ||
       Utils::isCriticalNetworkProtocol(ndpiDetectedProtocol.proto.app_protocol)) {
      new_verdict = true;
    }
  }

  /* Set the new verdict */
  passVerdict = new_verdict;

  if((!first_update) && (iface->getIfType() == interface_type_NETFILTER) &&
      (((old_verdict != passVerdict)) || (old_cli2srv_in != cli2srv_in) ||
       (old_cli2srv_out != cli2srv_out) || (old_srv2cli_in != srv2cli_in) ||
       (old_srv2cli_out != srv2cli_out)))
    ((NetfilterInterface *)iface)->setPolicyChanged();

#ifdef SHAPER_DEBUG
  {
    char buf[1024];

    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[SHAPERS] %s",
                                 print(buf, sizeof(buf)));
  }
#endif
}

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

void Flow::recheckQuota(const struct tm *now) {
  bool above_quota = false;

  if(cli_host && srv_host) {
    L7PolicySource_t cli_src = policy_source_default;
    L7PolicySource_t srv_src = policy_source_default;

    if((above_quota =
	 cli_host->checkQuota(ndpiDetectedProtocol, &cli_src, now)))
      srv_src = policy_source_default;
    else if((above_quota =
	      srv_host->checkQuota(ndpiDetectedProtocol, &srv_src, now)))
      ;

    /* Use temporary values to guard against partial changes */
    cli_quota_source = cli_src, srv_quota_source = srv_src;
  }

  quota_exceeded = above_quota ? 1 : 0;
}

#endif

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

bool Flow::isTiny() const {
  // if((cli2srv_packets < 3) && (srv2cli_packets == 0))
  if((get_packets() <=
       ntop->getPrefs()->get_max_num_packets_per_tiny_flow()) ||
      (get_bytes() <= ntop->getPrefs()->get_max_num_bytes_per_tiny_flow()))
    return (true);
  else
    return (false);
}

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

#ifdef HAVE_NEDGE
void Flow::setPacketsBytes(time_t now, u_int32_t s2d_pkts, u_int32_t d2s_pkts,
                           u_int64_t s2d_bytes, u_int64_t d2s_bytes) {
  u_int16_t eth_proto = ETHERTYPE_IP;
  u_int32_t s2d_pkts_delta = 0, d2s_pkts_delta = 0;
  u_int64_t s2d_bytes_delta = 0, d2s_bytes_delta = 0;
  bool s2d_increased = false, d2s_increased = false;
  bool s2d_set = s2d_pkts || s2d_bytes; /* Check if s2d direction is set */
  bool d2s_set = d2s_pkts || d2s_bytes; /* Check if d2s direction is set */

  /*
     netfilter (depending on configured timeouts) could expire a flow before
     than ntopng. This heuristics attempt to detect such events.

     Basically, if netfilter is sending counters for a new flow and ntopng
     already has an existing flow matching the same 5-tuple, we sum counters
     rather than overwriting them.

     A complete solution would require the registration of a netfilter check
     and the detection of event NFCT_T_DESTROY.

     Note: for some flows this is called per direction.
  */

  if (s2d_set) {
    s2d_increased = (s2d_pkts >= get_packets_cli2srv())
                 && (s2d_bytes >= get_bytes_cli2srv());

    if (s2d_increased) { /* Compute diff */
      s2d_pkts_delta  = s2d_pkts  - get_packets_cli2srv();
      s2d_bytes_delta = s2d_bytes - get_bytes_cli2srv();
    } else { /* Use abs value */
      s2d_pkts_delta  = s2d_pkts;
      s2d_bytes_delta = s2d_bytes;
    }
  }

  if (d2s_set) {
    d2s_increased = (d2s_pkts >= get_packets_srv2cli())
                 && (d2s_bytes >= get_bytes_srv2cli());

    if (d2s_increased) { /* Compute diff */
      d2s_pkts_delta  = d2s_pkts  - get_packets_srv2cli();
      d2s_bytes_delta = d2s_bytes - get_bytes_srv2cli();
    } else { /* Use abs value */
      d2s_pkts_delta  = d2s_pkts;
      d2s_bytes_delta = d2s_bytes;
    }
  }

#if 0
  if((protocol == IPPROTO_UDP) || (protocol == IPPROTO_TCP)) {
    char buf[256];
    ntop->getTrace()->traceEvent(TRACE_WARNING, "Flow stats update " //"%s "
				 "[curr-pkts %u/%u][updt-pkts %u/%u][delta pkts %u/%u] "
				 "[curr-bytes %u/%u][updt-bytes %u/%u][delta bytes %lu/%lu]",
				 //print(buf, sizeof(buf)),
				 get_packets_cli2srv(), get_packets_srv2cli(),
				 s2d_pkts, d2s_pkts,
				 s2d_pkts_delta, d2s_pkts_delta,
				 get_bytes_cli2srv(), get_bytes_srv2cli(),
				 s2d_bytes, d2s_bytes,
				 s2d_bytes_delta, d2s_bytes_delta);
  }
#endif

  if((s2d_pkts && get_packets_cli2srv() != s2d_pkts) || (d2s_pkts && get_packets_srv2cli() != d2s_pkts)) {
    /* Update last seen only in case of packets changed (i.e. do not update the flow if the are steady) */
    updateSeen();
  }

  if(last_conntrack_update > 0) {
    float tdiff_msec = (now - last_conntrack_update) * 1000;

    updateThroughputStats(tdiff_msec,
			  s2d_pkts_delta,
			  s2d_bytes_delta, 0,
			  d2s_pkts_delta,
			  d2s_bytes_delta, 0);
  }

  /*
    We need to set last_conntrack_update even with 0 packets/bytes
    as this function has been called only within netfilter through
    the conntrack handler, and thus the flow is still alive.
  */
  last_conntrack_update = now;

  if(s2d_set) {
    static_cast<NetfilterInterface *>(iface)->incStatsConntrack(isIngress2EgressDirection(),
							        now, eth_proto, getStatsProtocol(),
							        get_protocol_category(), protocol,
							        s2d_bytes_delta, s2d_pkts_delta);

    if(s2d_increased)
      stats.incStats(true, s2d_pkts_delta, s2d_bytes_delta, 0);
    else
      stats.setStats(true, s2d_pkts, s2d_bytes, 0);
  }

  if(d2s_set) {
    static_cast<NetfilterInterface *>(iface)->incStatsConntrack(!isIngress2EgressDirection(),
							        now, eth_proto, getStatsProtocol(),
							        get_protocol_category(), protocol,
							        d2s_bytes_delta, d2s_pkts_delta);

    if (d2s_increased)
      stats.incStats(false, d2s_pkts_delta, d2s_bytes_delta, 0);
    else
      stats.setStats(false, d2s_pkts, d2s_bytes, 0);
  }

  if((!detection_completed) && (get_packets() > (2*NDPI_MIN_NUM_PACKETS))) {
    /*
       Final resort for a flow that is offloaded
       and that whose counters are updated by conntrack
    */

    endProtocolDissection(true);
  }
}
#endif

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

void Flow::setParsedeBPFInfo(const ParsedeBPF *const _ebpf,
                             bool swap_directions) {
  ParsedeBPF *cur = NULL;
  bool update_ok = true;

  if(!_ebpf) return;

  if(!iface->hasSeenEBPFEvents()) iface->setSeenEBPFEvents();

  if(!ebpf)
    cur = ebpf = new (std::nothrow) ParsedeBPF(*_ebpf, swap_directions);
  else
    update_ok = ebpf->update(_ebpf);

  if(!update_ok) {
    static bool warning_shown = false;
    char *fbuf;
    ssize_t fbuf_len = 512;

    if(!warning_shown && (fbuf = (char *)malloc(fbuf_len))) {
      ntop->getTrace()->traceEvent(TRACE_WARNING, "Identical flow seen across multiple containers? %s",
				   print(fbuf, fbuf_len));

      warning_shown = true;
      free(fbuf);
    }
  }

  if(cur && cur->container_info_set) {
    if(!iface->hasSeenContainers()) iface->setSeenContainers();

    if(!iface->hasSeenPods() &&
        ((cur->src_container_info.data_type == container_info_data_type_k8s &&
          cur->src_container_info.data.k8s.pod) ||
         (cur->dst_container_info.data_type == container_info_data_type_k8s &&
          cur->dst_container_info.data.k8s.pod)))
      iface->setSeenPods();
  }

  updateCliJA4();
  updateHASSH(true /* AS client */);
  updateHASSH(false /* AS server */);
}

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

void Flow::updateCliJA4() {
  if(cli_host && isTLS() && protos.tls.ja4.client_hash) {
    Fingerprint *f = cli_host->getJA4Fingerprint();

    if(f)
      f->update(protos.tls.ja4.client_hash,
		ebpf ? ebpf->src_process_info.process_name : NULL,
		has_malicious_cli_signature);
  }
}

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

void Flow::updateHASSH(bool as_client) {
  if(!isSSH()) return;

  Host *h = as_client ? get_cli_host() : get_srv_host();
  const char *hassh = as_client ? protos.ssh.hassh.client_hash : protos.ssh.hassh.server_hash;
  ProcessInfo *pinfo = NULL;
  Fingerprint *fp;

  if(ebpf)
    pinfo = as_client ? &ebpf->src_process_info : &ebpf->dst_process_info;

  if(h
      && hassh
      && (hassh[0] != '\0')
      && (fp = h->getHASSHFingerprint())) {
    if(fp)
      fp->update(hassh, pinfo ? pinfo->process_name : NULL,
		 false /* malicious */);
  }
}

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

void Flow::fillZMQFlowCategory(ndpi_protocol *res) {
  struct ndpi_detection_module_struct *ndpi_struct = iface->get_ndpi_struct();
  const char *dst_name = NULL;
  const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();

  if(ndpiFlow && cli_ip && srv_ip && cli_ip->isIPv4()) {
    if(ndpi_fill_ip_protocol_category(ndpi_struct, ndpiFlow,
                                       cli_ip->get_ipv4(),
                                       srv_ip->get_ipv4(), res))
      return;
  }

  switch (ndpi_get_lower_proto(*res)) {
  case NDPI_PROTOCOL_DNS:
    dst_name = getDNSQuery();
    break;
  case NDPI_PROTOCOL_HTTP_PROXY:
  case NDPI_PROTOCOL_HTTP:
    dst_name = getFlowServerInfo();
    break;
  case NDPI_PROTOCOL_TLS:
    dst_name = protos.tls.client_requested_server_name;
    break;
  default:
    break;
  }

  if(dst_name) {
    int rc;
    ndpi_protocol_match_result tmp;
    ndpi_protocol_category_t c;

    /* Match for custom protocols (protos.txt) */
    if((rc = ndpi_match_string_subprotocol(ndpi_struct, (char *)dst_name,
                                            strlen(dst_name), &tmp)) != 0) {
      if(rc >= NDPI_MAX_SUPPORTED_PROTOCOLS) {
        /* If the protocol is greater than NDPI_MAX_SUPPORTED_PROTOCOLS, it
           means it is a custom protocol so the application protocol received
           from nprobe can be overridden */
        if(res->proto.master_protocol == NDPI_PROTOCOL_UNKNOWN)
          res->proto.master_protocol = res->proto.app_protocol;

        res->proto.app_protocol = (ndpi_protocol_category_t)rc;
      }
    }

    /* Match for custom categories */
    if(ndpi_match_custom_category(ndpi_struct, (char *)dst_name,
                                   strlen(dst_name), &c) == 0)
      res->category = c;
  }
}

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

void Flow::lua_get_status(lua_State *vm) const {
  lua_push_bool_table_entry(vm, "flow.idle", idle());
  lua_push_uint64_table_entry(vm, "flow.status", getPredominantAlert().id);

  alerts_map.lua(vm, "alerts_map");

  if(isFlowAlerted()) {
    lua_push_bool_table_entry(vm, "flow.alerted", true);
    lua_push_uint64_table_entry(vm, "predominant_alert", getPredominantAlert().id);
    lua_push_uint64_table_entry(vm, "predominant_alert_score", getPredominantAlertScore());
  }
}

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

void Flow::lua_get_protocols(lua_State *vm) const {
  char buf[64];

  lua_push_uint64_table_entry(vm, "proto.l4_id", get_protocol());
  lua_push_str_table_entry(vm, "proto.l4", get_protocol_name());

  if(((get_packets_cli2srv() + get_packets_srv2cli()) >
       NDPI_MIN_NUM_PACKETS) ||
      (ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) ||
      (iface->is_ndpi_enabled() && detection_completed) ||
      iface->isSampledTraffic() || (iface->getIfType() == interface_type_ZMQ) ||
      (iface->getIfType() == interface_type_SYSLOG) ||
      (iface->getIfType() == interface_type_ZC_FLOW)) {
    lua_push_str_table_entry(vm, "proto.ndpi",
                             get_detected_protocol_name(buf, sizeof(buf)));
    lua_push_uint64_table_entry(vm, "proto.ndpi_id",
                                ndpiDetectedProtocol.proto.app_protocol);
    lua_push_uint64_table_entry(vm, "proto.ndpi_informative_proto",
                                (!ndpi_is_subprotocol_informative(ndpiDetectedProtocol.proto.master_protocol)
				 ? ndpiDetectedProtocol.proto.app_protocol
				 : ndpiDetectedProtocol.proto.master_protocol));
    lua_push_uint64_table_entry(vm, "proto.master_ndpi_id",
                                ndpiDetectedProtocol.proto.master_protocol);
  } else {
    lua_push_str_table_entry(vm, "proto.ndpi", (char *)CONST_TOO_EARLY);
    lua_push_int32_table_entry(vm, "proto.ndpi_id", -1);
    lua_push_int32_table_entry(vm, "proto.master_ndpi_id", -1);
  }

  lua_push_str_table_entry(vm, "proto.ndpi_breed", get_protocol_breed_name());
  lua_push_bool_table_entry(vm, "proto.is_encrypted", isEncryptedProto());

  lua_push_uint64_table_entry(vm, "proto.ndpi_cat_id", get_protocol_category());
  lua_push_str_table_entry(vm, "proto.ndpi_cat", get_protocol_category_name());

  if(getAddressFamilyProtocol())
    lua_push_str_table_entry(vm, "proto.ndpi_address_family",
                             getAddressFamilyProtocol());

  if(get_custom_category_file())
    lua_push_str_table_entry(vm, "proto.ndpi_cat_file",
                             get_custom_category_file());
}

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

void Flow::lua_get_bytes(lua_State *vm) const {
  lua_push_uint64_table_entry(vm, "bytes",
                              get_bytes_cli2srv() + get_bytes_srv2cli());
  lua_push_uint64_table_entry(vm, "goodput_bytes",
			      get_goodput_bytes_cli2srv() + get_goodput_bytes_srv2cli());
  lua_push_uint64_table_entry(vm, "bytes.last",
			      get_current_bytes_cli2srv() + get_current_bytes_srv2cli());
  lua_push_uint64_table_entry(vm, "goodput_bytes.last",
                              get_current_goodput_bytes_cli2srv() +
			      get_current_goodput_bytes_srv2cli());
}

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

void Flow::lua_get_throughput(lua_State *vm) const {
  lua_push_float_table_entry(vm, "average_throughput_bps",
			     (get_bytes_cli2srv() + get_bytes_srv2cli()) / get_duration());
  lua_push_float_table_entry(vm, "average_throughput_pps",
			     (get_packets_cli2srv() + get_packets_srv2cli()) / get_duration());

  // overall throughput stats
  lua_push_float_table_entry(vm, "top_throughput_bps", top_bytes_thpt);
  lua_push_float_table_entry(vm, "throughput_bps", get_bytes_thpt());
  lua_push_uint64_table_entry(vm, "throughput_trend_bps", bytes_thpt_trend);
  lua_push_float_table_entry(vm, "top_throughput_pps", top_pkts_thpt);
  lua_push_float_table_entry(vm, "throughput_pps", get_pkts_thpt());
  lua_push_uint64_table_entry(vm, "throughput_trend_pps", pkts_thpt_trend);
}

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

void Flow::lua_get_dir_traffic(lua_State *vm, bool cli2srv) const {
  ndpi_analyze_struct *cur_analyze =
    (ndpi_analyze_struct *)stats.get_analize_struct(cli2srv);
  const IPPacketStats *cur_ip_stats = cli2srv ? &ip_stats_s2d : &ip_stats_d2s;

  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.bytes" : "srv2cli.bytes",
			      cli2srv ? get_bytes_cli2srv() : get_bytes_srv2cli());
  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.goodput_bytes" : "srv2cli.goodput_bytes",
			      cli2srv ? get_goodput_bytes_cli2srv() : get_goodput_bytes_srv2cli());
  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.packets" : "srv2cli.packets",
			      cli2srv ? get_packets_cli2srv() : get_packets_srv2cli());

  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.last" : "srv2cli.last",
			      cli2srv ? get_current_bytes_cli2srv() : get_current_bytes_srv2cli());

  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.pkt_len.min" : "srv2cli.pkt_len.min",
			      ndpi_data_min(cur_analyze));
  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.pkt_len.max" : "srv2cli.pkt_len.max",
			      ndpi_data_max(cur_analyze));
  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.pkt_len.avg" : "srv2cli.pkt_len.avg",
			      ndpi_data_average(cur_analyze));
  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.pkt_len.stddev" : "srv2cli.pkt_len.stddev",
			      ndpi_data_stddev(cur_analyze));

  lua_push_uint64_table_entry(
			      vm, cli2srv ? "cli2srv.fragments" : "srv2cli.fragments",
			      cur_ip_stats->pktFrag);
}

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

void Flow::lua_get_dir_iat(lua_State *vm, bool cli2srv) const {
  InterarrivalStats *s = cli2srv ? getCli2SrvIATStats() : getSrv2CliIATStats();

  if(s) {
    lua_newtable(vm);

    lua_push_uint64_table_entry(vm, "min", s->getMin());
    lua_push_uint64_table_entry(vm, "max", s->getMax());
    lua_push_float_table_entry(vm, "avg", s->getAvg());
    lua_push_float_table_entry(vm, "stddev", s->getStdDev());

    lua_pushstring(vm,
                   cli2srv ? "interarrival.cli2srv" : "interarrival.srv2cli");
    lua_insert(vm, -2);
    lua_settable(vm, -3);
  }
}

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

void Flow::lua_get_packets(lua_State *vm) const {
  lua_push_uint64_table_entry(vm, "packets",
                              get_packets_cli2srv() + get_packets_srv2cli());
  lua_push_uint64_table_entry(vm, "packets.sent", get_packets_cli2srv());
  lua_push_uint64_table_entry(vm, "packets.rcvd", get_packets_srv2cli());
  lua_push_uint64_table_entry(
			      vm, "packets.last",
			      get_current_packets_cli2srv() + get_current_packets_srv2cli());
}

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

void Flow::lua_get_time(lua_State *vm) const {
  lua_push_uint64_table_entry(vm, "seen.first", get_first_seen());
  lua_push_uint64_table_entry(vm, "seen.last", get_last_seen());
  lua_push_uint64_table_entry(vm, "duration", get_duration());
}

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

void Flow::lua_get_ip(lua_State *vm, bool client) const {
  char buf[64];
  Host *h = client ? get_cli_host() : get_srv_host();
  const IpAddress *h_ip = client ? get_cli_ip_addr() : get_srv_ip_addr();
  bool mask_host = true;

  if(h) {
    mask_host = Utils::maskHost(h->isLocalHost());

    lua_push_str_table_entry(
			     vm, client ? "cli.ip" : "srv.ip",
			     h->get_ip()->printMask(buf, sizeof(buf), h->isLocalHost()));
    lua_push_uint64_table_entry(vm, client ? "cli.key" : "srv.key",
                                mask_host ? 0 : h->key());

    if(h->isProtocolServer())
      lua_push_bool_table_entry(vm, client ? "cli.protocol_server" : "srv.protocol_server", true);
  } else if(h_ip) {
    /* Host hasn't been instantiated but we still have the ip address (e.g, in
     * viewed interfaces) */
    lua_push_str_table_entry(vm, client ? "cli.ip" : "srv.ip",
                             h_ip->print(buf, sizeof(buf)));
    lua_push_uint64_table_entry(vm, client ? "cli.key" : "srv.key",
                                h_ip->key());
  }

  if(get_vlan_id())
    lua_push_uint64_table_entry(vm, client ? "cli.vlan" : "srv.vlan",
                                get_vlan_id());

  lua_push_bool_table_entry(
			    vm, client ? "cli.broadmulticast" : "srv.broadmulticast",
			    h_ip->isBroadMulticastAddress());
}

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

void Flow::lua_get_mac(lua_State *vm, bool client) const {
  char buf[24];
  Host *h = client ? get_cli_host() : get_srv_host();

  if(h) {
    Mac *m = h->getMac();

    if(m != NULL) {
      lua_push_str_table_entry(vm, client ? "cli.mac" : "srv.mac",
			       m->print(buf, sizeof(buf)));
      lua_push_bool_table_entry(vm, client ? "cli.serialize_by_mac" : "srv.serialize_by_mac",
				h->serializeByMac());
    }
  }
}

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

void Flow::lua_get_info(lua_State *vm, bool client) const {
  char buf[64];
  Host *h = client ? get_cli_host() : get_srv_host();
  const IpAddress *h_ip = client ? get_cli_ip_addr() : get_srv_ip_addr();
  bool mask_host = true;

  if(h) {
    mask_host = Utils::maskHost(h->isLocalHost());

    if(!mask_host) {
      char cb[64], os[64];
      lua_push_str_table_entry(vm, client ? "cli.host" : "srv.host",
                               h->get_visual_name(buf, sizeof(buf)));
      lua_push_uint64_table_entry(vm,
                                  client ? "cli.source_id" : "srv.source_id",
                                  0 /* was never set by src->getSourceId()*/);

      lua_push_bool_table_entry(vm, client ? "cli.localhost" : "srv.localhost",
                                h->isLocalHost());
      lua_push_bool_table_entry(vm, client ? "cli.systemhost" : "srv.systemhost", h->isSystemHost());
      lua_push_bool_table_entry(vm, client ? "cli.blacklisted" : "srv.blacklisted",
				client ? isBlacklistedClient() : isBlacklistedServer());
      lua_push_bool_table_entry(vm,
				client ? "cli.broadcast_domain_host" : "srv.broadcast_domain_host",
				h->isBroadcastDomainHost());
      lua_push_bool_table_entry(vm, client ? "cli.dhcpHost" : "srv.dhcpHost",
                                h->isDHCPHost());
      lua_push_int32_table_entry(vm,
                                 client ? "cli.network_id" : "srv.network_id",
                                 h->get_local_network_id());
      lua_push_uint64_table_entry(vm, client ? "cli.pool_id" : "srv.pool_id",
                                  h->get_host_pool());
      lua_push_uint64_table_entry(vm, client ? "cli.asn" : "srv.asn",
                                  h->get_asn());
      lua_push_str_table_entry(vm, client ? "cli.country" : "srv.country",
                               h->get_country(cb, sizeof(cb)));
      lua_push_str_table_entry(vm, client ? "cli.os" : "srv.os",
                               h->getOSDetail(os, sizeof(os)));
    }
  }

  lua_get_mac(vm, client);

  if(h_ip) {
    lua_push_bool_table_entry(vm, client ? "cli.private" : "srv.private",
                              h_ip->isPrivateAddress());

    lua_push_bool_table_entry(vm, client ? "cli.localhost" : "srv.localhost",
                              h_ip->isLocalHost());

  }
}

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

/* Get minimal flow information.
 * NOTE: this is intended to be called only from flow user scripts
 * via flow.getInfo(). mask_host/allowed networks are not honored.
 */
void Flow::lua_get_min_info(lua_State *vm) {
  char buf[128];

  lua_newtable(vm);

  lua_push_str_table_entry(vm, "cli.ip",
                           get_cli_ip_addr()->print(buf, sizeof(buf)));
  lua_push_str_table_entry(vm, "srv.ip",
                           get_srv_ip_addr()->print(buf, sizeof(buf)));

  if(get_cli_host() && get_cli_host()->isProtocolServer())
    lua_push_bool_table_entry(vm, "cli.protocol_server", true);
  else if(get_srv_host() && get_srv_host()->isProtocolServer())
    lua_push_bool_table_entry(vm, "srv.protocol_server", true);

  lua_push_int32_table_entry(vm, "cli.port", get_cli_port());
  lua_push_int32_table_entry(vm, "srv.port", get_srv_port());
  lua_push_bool_table_entry(vm, "cli.localhost", getCliLocation() == 1);
  lua_push_bool_table_entry(vm, "srv.localhost", getSrvLocation() == 1);
  lua_push_int32_table_entry(vm, "duration", get_duration());
  lua_push_str_table_entry(vm, "proto.l4", get_protocol_name());
  lua_push_str_table_entry(vm, "proto.ndpi",
                           get_detected_protocol_name(buf, sizeof(buf)));
  lua_push_str_table_entry(
			   vm, "proto.ndpi_app",
			   ndpi_get_proto_name(iface->get_ndpi_struct(),
					       ndpiDetectedProtocol.proto.app_protocol));
  lua_push_str_table_entry(vm, "proto.ndpi_cat", get_protocol_category_name());
  lua_push_uint64_table_entry(vm, "proto.ndpi_cat_id", get_protocol_category());
  lua_push_str_table_entry(vm, "proto.ndpi_breed", get_protocol_breed_name());
  lua_push_bool_table_entry(vm, "proto.is_encrypted", isEncryptedProto());
  lua_push_uint64_table_entry(vm, "cli2srv.bytes", get_bytes_cli2srv());
  lua_push_uint64_table_entry(vm, "srv2cli.bytes", get_bytes_srv2cli());
  lua_push_uint64_table_entry(vm, "cli2srv.packets", get_packets_cli2srv());
  lua_push_uint64_table_entry(vm, "srv2cli.packets", get_packets_srv2cli());
  lua_push_str_table_entry(vm, "info", getFlowInfo(true).c_str());
}

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

/*
 * Get minimal flow information.
 * NOTE: this is intended to be called only from flow user scripts
 */
void Flow::getInfo(ndpi_serializer *serializer) {
  char buf[128];

  ndpi_serialize_string_string(serializer, "cli.ip",
                               get_cli_ip_addr()->print(buf, sizeof(buf)));
  ndpi_serialize_string_string(serializer, "srv.ip",
                               get_srv_ip_addr()->print(buf, sizeof(buf)));

  if(get_cli_host() && get_cli_host()->isProtocolServer())
    ndpi_serialize_string_boolean(serializer, "cli.protocol_server", true);
  else if(get_srv_host() && get_srv_host()->isProtocolServer())
    ndpi_serialize_string_boolean(serializer, "srv.protocol_server", true);

  ndpi_serialize_string_int32(serializer, "cli.port", get_cli_port());
  ndpi_serialize_string_int32(serializer, "srv.port", get_srv_port());
  ndpi_serialize_string_boolean(serializer, "cli.localhost", getCliLocation() == 1);
  ndpi_serialize_string_boolean(serializer, "srv.localhost", getSrvLocation() == 1);
  ndpi_serialize_string_int32(serializer, "duration", get_duration());
  ndpi_serialize_string_string(serializer, "proto.l4", get_protocol_name());
  ndpi_serialize_string_string(serializer, "proto.ndpi",
                               get_detected_protocol_name(buf, sizeof(buf)));
  ndpi_serialize_string_string(
			       serializer, "proto.ndpi_app",
			       ndpi_get_proto_name(iface->get_ndpi_struct(),
						   ndpiDetectedProtocol.proto.app_protocol));
  ndpi_serialize_string_string(serializer, "proto.ndpi_cat",
                               get_protocol_category_name());
  ndpi_serialize_string_uint64(serializer, "proto.ndpi_cat_id",
                               get_protocol_category());
  ndpi_serialize_string_string(serializer, "proto.ndpi_breed",
                               get_protocol_breed_name());
  ndpi_serialize_string_uint64(serializer, "cli2srv.bytes",
                               get_bytes_cli2srv());
  ndpi_serialize_string_uint64(serializer, "srv2cli.bytes",
                               get_bytes_srv2cli());
  ndpi_serialize_string_uint64(serializer, "cli2srv.packets",
                               get_packets_cli2srv());
  ndpi_serialize_string_uint64(serializer, "srv2cli.packets",
                               get_packets_srv2cli());
  ndpi_serialize_string_string(serializer, "info", getFlowInfo(true).c_str());
}

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

u_int32_t Flow::getCliTcpIssues() {
  return (stats.get_cli2srv_tcp_retr() + stats.get_cli2srv_tcp_ooo() +
          stats.get_cli2srv_tcp_lost());
}

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

u_int32_t Flow::getSrvTcpIssues() {
  return (stats.get_srv2cli_tcp_retr() + stats.get_srv2cli_tcp_ooo() +
          stats.get_srv2cli_tcp_lost());
}

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

double Flow::getCliRetrPercentage() {
  if(get_packets_cli2srv() >
      10 /* Do not compute retransmissions with too few packets */)
    return ((double)stats.get_cli2srv_tcp_retr() /
            (double)get_packets_cli2srv());
  else
    return 0;
}

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

double Flow::getSrvRetrPercentage() {
  if(get_packets_srv2cli() >
      10 /* Do not compute retransmissions with too few packets */)
    return ((double)stats.get_srv2cli_tcp_retr() /
            (double)get_packets_srv2cli());
  else
    return 0;
}

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

void Flow::lua_duration_info(lua_State *vm) {
  lua_newtable(vm);

  lua_push_uint64_table_entry(vm, "first_seen", get_first_seen());
  lua_push_uint64_table_entry(vm, "last_seen", get_last_seen());
  lua_push_bool_table_entry(vm, "twh_over", twh_over ? true : false);
}

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

void Flow::lua_snmp_info(lua_State *vm) {
  char str[16];
  u_int32_t device_ip = htonl(flow_device.device_ip);
  inet_ntop(AF_INET, &(device_ip), str, INET_ADDRSTRLEN);
  lua_push_str_table_entry(vm, "device_ip", str);
  lua_push_uint64_table_entry(vm, "in_index", flow_device.in_index);
  lua_push_uint64_table_entry(vm, "out_index", flow_device.out_index);
  lua_push_uint64_table_entry(vm, "observation_point_id",
                              flow_device.observation_point_id);
}

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

void Flow::lua_device_protocol_allowed_info(lua_State *vm) {
  bool cli_allowed, srv_allowed;

  lua_newtable(vm);

  if(!cli_host || !srv_host) return;

  cli_allowed = isCliDeviceAllowedProtocol();
  srv_allowed = isSrvDeviceAllowedProtocol();

  lua_push_int32_table_entry(vm, "cli.devtype",
                             cli_host->getMac()
			     ? cli_host->getMac()->getDeviceType()
			     : device_unknown);
  lua_push_int32_table_entry(vm, "srv.devtype",
                             srv_host->getMac()
			     ? srv_host->getMac()->getDeviceType()
			     : device_unknown);

  lua_push_bool_table_entry(vm, "cli.allowed", cli_allowed);
  if(!cli_allowed)
    lua_push_int32_table_entry(vm, "cli.disallowed_proto",
                               getCliDeviceDisallowedProtocol());

  lua_push_bool_table_entry(vm, "srv.allowed", srv_allowed);
  if(!srv_allowed)
    lua_push_int32_table_entry(vm, "srv.disallowed_proto",
                               getSrvDeviceDisallowedProtocol());
}

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

void Flow::lua_get_unicast_info(lua_State *vm) const {
  const IpAddress *cli_ip = get_cli_ip_addr();
  const IpAddress *srv_ip = get_srv_ip_addr();

  lua_newtable(vm);

  if(cli_ip)
    lua_push_bool_table_entry(vm, "cli.broadmulticast",
                              cli_ip->isBroadMulticastAddress());
  if(srv_ip)
    lua_push_bool_table_entry(vm, "srv.broadmulticast",
                              srv_ip->isBroadMulticastAddress());
}

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

void Flow::lua_get_sip_info(lua_State *vm) const {
  if(isSIP()) {
    lua_push_str_table_entry(vm, "protos.sip_call_id",
			     protos.sip.call_id);
  }
}

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

void Flow::lua_get_tls_info(lua_State *vm) const {
  if(isTLS()) {
    lua_push_int32_table_entry(vm, "protos.tls_version",
                               protos.tls.tls_version);

    if(protos.tls.server_names)
      lua_push_str_table_entry(vm, "protos.tls.server_names",
                               protos.tls.server_names);

    if(protos.tls.client_alpn)
      lua_push_str_table_entry(vm, "protos.tls.client_alpn",
                               protos.tls.client_alpn);

    if(protos.tls.client_tls_supported_versions)
      lua_push_str_table_entry(vm, "protos.tls.client_tls_supported_versions",
                               protos.tls.client_tls_supported_versions);

    if(protos.tls.issuerDN)
      lua_push_str_table_entry(vm, "protos.tls.issuerDN", protos.tls.issuerDN);

    if(protos.tls.subjectDN)
      lua_push_str_table_entry(vm, "protos.tls.subjectDN",
                               protos.tls.subjectDN);

    if(protos.tls.client_requested_server_name)
      lua_push_str_table_entry(vm, "protos.tls.client_requested_server_name",
                               protos.tls.client_requested_server_name);

    if(protos.tls.notBefore && protos.tls.notAfter) {
      lua_push_uint32_table_entry(vm, "protos.tls.notBefore",
                                  protos.tls.notBefore);
      lua_push_uint32_table_entry(vm, "protos.tls.notAfter",
                                  protos.tls.notAfter);
    }

    if(protos.tls.ja4.client_hash) {
      lua_push_str_table_entry(vm, "protos.tls.ja4.client_hash",
                               protos.tls.ja4.client_hash);

      if(has_malicious_cli_signature)
        lua_push_bool_table_entry(vm, "protos.tls.ja4.client_malicious", true);
    }
  }
}

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

void Flow::getTLSInfo(ndpi_serializer *serializer) const {
  if(isTLS()) {
    ndpi_serialize_string_int32(serializer, "tls_version",
                                protos.tls.tls_version);

    if(protos.tls.server_names)
      ndpi_serialize_string_string(serializer, "server_names",
                                   protos.tls.server_names);

    if(protos.tls.client_alpn)
      ndpi_serialize_string_string(serializer, "client_alpn",
                                   protos.tls.client_alpn);

    if(protos.tls.client_tls_supported_versions)
      ndpi_serialize_string_string(serializer, "client_tls_supported_versions",
                                   protos.tls.client_tls_supported_versions);

    if(protos.tls.issuerDN)
      ndpi_serialize_string_string(serializer, "issuerDN", protos.tls.issuerDN);

    if(protos.tls.subjectDN)
      ndpi_serialize_string_string(serializer, "subjectDN",
                                   protos.tls.subjectDN);

    if(protos.tls.client_requested_server_name)
      ndpi_serialize_string_string(serializer, "client_requested_server_name",
                                   protos.tls.client_requested_server_name);

    if(protos.tls.notBefore && protos.tls.notAfter) {
      ndpi_serialize_string_int32(serializer, "notBefore",
                                  protos.tls.notBefore);
      ndpi_serialize_string_int32(serializer, "notAfter", protos.tls.notAfter);
    }

    if(protos.tls.ja4.client_hash) {
      ndpi_serialize_string_string(serializer, "ja4_client_hash",
                                   protos.tls.ja4.client_hash);

      if(has_malicious_cli_signature)
        ndpi_serialize_string_boolean(serializer, "ja4_client_malicious", true);
    }
  }
}

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

void Flow::lua_get_ssh_info(lua_State *vm) const {
  if(isSSH()) {
    if(protos.ssh.client_signature)
      lua_push_str_table_entry(vm, "protos.ssh.client_signature",
                               protos.ssh.client_signature);
    if(protos.ssh.server_signature)
      lua_push_str_table_entry(vm, "protos.ssh.server_signature",
                               protos.ssh.server_signature);

    if(protos.ssh.hassh.client_hash)
      lua_push_str_table_entry(vm, "protos.ssh.hassh.client_hash",
                               protos.ssh.hassh.client_hash);
    if(protos.ssh.hassh.server_hash)
      lua_push_str_table_entry(vm, "protos.ssh.hassh.server_hash",
                               protos.ssh.hassh.server_hash);
  }
}

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

void Flow::getSSHInfo(ndpi_serializer *serializer) const {
  if(isSSH()) {
    if(protos.ssh.client_signature)
      ndpi_serialize_string_string(serializer, "client_signature",
                                   protos.ssh.client_signature);
    if(protos.ssh.server_signature)
      ndpi_serialize_string_string(serializer, "server_signature",
                                   protos.ssh.server_signature);

    if(protos.ssh.hassh.client_hash)
      ndpi_serialize_string_string(serializer, "client_hash_hassh",
                                   protos.ssh.hassh.client_hash);
    if(protos.ssh.hassh.server_hash)
      ndpi_serialize_string_string(serializer, "server_hash_hassh",
                                   protos.ssh.hassh.server_hash);
  }
}

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

void Flow::lua_get_http_info(lua_State *vm) const {
  if(isHTTP()) {
    if(protos.http.last_url) {
      lua_push_str_table_entry(vm, "protos.http.last_method",
                               ndpi_http_method2str(protos.http.last_method));
      lua_push_uint64_table_entry(vm, "protos.http.last_return_code",
                                  protos.http.last_return_code);
      lua_push_str_table_entry(vm, "protos.http.last_url",
                               protos.http.last_url);
      if(protos.http.last_user_agent)
        lua_push_str_table_entry(vm, "protos.http.last_user_agent",
                                 protos.http.last_user_agent);
      if(protos.http.last_server)
        lua_push_str_table_entry(vm, "protos.http.last_server",
                                 protos.http.last_server);
    }

    if(host_server_name)
      lua_push_str_table_entry(vm, "protos.http.server_name", host_server_name);
  }
}

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

void Flow::getHTTPInfo(ndpi_serializer *serializer) const {
  if(isHTTP()) {
    if(protos.http.last_url) {
      ndpi_serialize_string_string(
				   serializer, "last_method",
				   ndpi_http_method2str(protos.http.last_method));
      ndpi_serialize_string_uint64(serializer, "last_return_code",
                                   protos.http.last_return_code);
      ndpi_serialize_string_string(serializer, "last_url",
                                   protos.http.last_url);
      ndpi_serialize_string_string(serializer, "last_user_agent",
                                   protos.http.last_user_agent);
      ndpi_serialize_string_string(serializer, "last_server",
                                   protos.http.last_server);
    }

    if(host_server_name)
      ndpi_serialize_string_string(serializer, "server_name", host_server_name);
  }
}

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

void Flow::lua_get_dns_info(lua_State *vm) const {
  if(isDNS()) {
    if(protos.dns.last_query) {
      lua_push_uint64_table_entry(vm, "protos.dns.last_query_type",
                                  protos.dns.last_query_type);
      lua_push_uint64_table_entry(vm, "protos.dns.last_return_code",
                                  protos.dns.last_return_code);
      lua_push_str_table_entry(vm, "protos.dns.last_query",
                               protos.dns.last_query);

      if(protos.dns.last_rsp)
	lua_push_str_table_entry(vm, "protos.dns.last_rsp", protos.dns.last_rsp);

      if(hasInvalidDNSQueryChars())
        lua_push_bool_table_entry(vm, "protos.dns.invalid_chars_in_query", true);
    }
  }
}

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

void Flow::getDNSInfo(ndpi_serializer *serializer) const {
  if(isDNS()) {
    if(protos.dns.last_query) {
      ndpi_serialize_string_int64(serializer, "last_query_type",
                                  protos.dns.last_query_type);
      ndpi_serialize_string_int64(serializer, "last_return_code",
                                  protos.dns.last_return_code);
      ndpi_serialize_string_string(serializer, "last_query",
                                   protos.dns.last_query);

      if(protos.dns.last_rsp) {
        std::string array = Utils::list2JsonArray(protos.dns.last_rsp);
        ndpi_serialize_string_raw(serializer, "last_rsp_arr",
                                  array.c_str(), array.length());
      }
      if(hasInvalidDNSQueryChars())
        ndpi_serialize_string_boolean(serializer, "invalid_chars_in_query",
                                      true);
    }
  }
}

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

void Flow::getICMPInfo(ndpi_serializer *serializer) const {
  if(isICMP()) {
    ndpi_serialize_string_int32(serializer, "type",
                                isBidirectional()
				? protos.icmp.srv2cli.icmp_type
				: protos.icmp.cli2srv.icmp_type);
    ndpi_serialize_string_int32(serializer, "code",
                                isBidirectional()
				? protos.icmp.srv2cli.icmp_code
				: protos.icmp.cli2srv.icmp_code);
  }
}

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

void Flow::getMDNSInfo(ndpi_serializer *serializer) const {
  if(isMDNS()) {
    ndpi_serialize_string_string(serializer, "answer", protos.mdns.answer);
    ndpi_serialize_string_string(serializer, "name", protos.mdns.name);
    ndpi_serialize_string_string(serializer, "name_txt", protos.mdns.name_txt);
    ndpi_serialize_string_string(serializer, "ssid", protos.mdns.ssid);
  }
}

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

void Flow::getNetBiosInfo(ndpi_serializer *serializer) const {
  if(isNetBIOS()) {
    ndpi_serialize_string_string(serializer, "name", protos.netbios.name);
  }
}

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

void Flow::getSIPInfo(ndpi_serializer *serializer) const {
  if(protos.sip.call_id) {
    ndpi_serialize_string_string(serializer, "call_id", protos.sip.call_id);
  }
}

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

void Flow::lua_get_tcp_info(lua_State *vm) const {
  if(get_protocol() == IPPROTO_TCP) {
    lua_push_bool_table_entry(
			      vm, "tcp.seq_problems",
			      (stats.get_cli2srv_tcp_retr() || stats.get_cli2srv_tcp_ooo() ||
			       stats.get_cli2srv_tcp_lost() || stats.get_cli2srv_tcp_keepalive() ||
			       stats.get_srv2cli_tcp_retr() || stats.get_srv2cli_tcp_ooo() ||
			       stats.get_srv2cli_tcp_lost() || stats.get_srv2cli_tcp_keepalive())
			      ? true
			      : false);


    if(tcp != NULL) {
      lua_push_float_table_entry(vm, "tcp.nw_latency.3wh_client_rtt", tcp->clientRTT3WH);
      lua_push_float_table_entry(vm, "tcp.nw_latency.3wh_server_rtt", tcp->serverRTT3WH);
    }

    lua_push_float_table_entry(vm, "tcp.appl_latency", applLatencyMsec);
    lua_push_float_table_entry(vm, "tcp.max_thpt.cli2srv", getCli2SrvMaxThpt());
    lua_push_float_table_entry(vm, "tcp.max_thpt.srv2cli", getSrv2CliMaxThpt());

    lua_push_uint64_table_entry(vm, "cli2srv.retransmissions",
                                stats.get_cli2srv_tcp_retr());
    lua_push_uint64_table_entry(vm, "cli2srv.out_of_order",
                                stats.get_cli2srv_tcp_ooo());
    lua_push_uint64_table_entry(vm, "cli2srv.lost",
                                stats.get_cli2srv_tcp_lost());
    lua_push_uint64_table_entry(vm, "cli2srv.keep_alive",
                                stats.get_cli2srv_tcp_keepalive());
    lua_push_uint64_table_entry(vm, "srv2cli.retransmissions",
                                stats.get_srv2cli_tcp_retr());
    lua_push_uint64_table_entry(vm, "srv2cli.out_of_order",
                                stats.get_srv2cli_tcp_ooo());
    lua_push_uint64_table_entry(vm, "srv2cli.lost",
                                stats.get_srv2cli_tcp_lost());
    lua_push_uint64_table_entry(vm, "srv2cli.keep_alive",
                                stats.get_srv2cli_tcp_keepalive());

    lua_push_uint64_table_entry(vm, "cli2srv.tcp_flags", src2dst_tcp_flags);
    lua_push_uint64_table_entry(vm, "srv2cli.tcp_flags", dst2src_tcp_flags);

    lua_push_bool_table_entry(vm, "tcp_established", isTCPEstablished());
    lua_push_bool_table_entry(vm, "tcp_connecting", isTCPConnecting());
    lua_push_bool_table_entry(vm, "tcp_closed", isTCPClosed());
    lua_push_bool_table_entry(vm, "tcp_reset", isTCPReset());
  }
}

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

void Flow::lua_get_port(lua_State *vm, bool client) const {
  u_int16_t h_port = client ? get_cli_port() : get_srv_port();

  lua_push_uint64_table_entry(vm, client ? "cli.port" : "srv.port", h_port);
}

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

void Flow::lua_get_geoloc(lua_State *vm, bool client, bool coords,
                          bool country_city) const {
  Host *h = client ? get_cli_host() : get_srv_host();
  float latitude, longitude;
  char buf[32];

  if(h) {
    if(coords) {
      h->get_geocoordinates(&latitude, &longitude);

      lua_push_float_table_entry(vm, client ? "cli.latitude" : "srv.latitude",
                                 latitude);
      lua_push_float_table_entry(vm, client ? "cli.longitude" : "srv.longitude",
                                 longitude);
    }

    if(country_city) {
      lua_push_str_table_entry(vm, client ? "cli.country" : "srv.country",
                               h->get_country(buf, sizeof(buf)));
      lua_push_str_table_entry(vm, client ? "cli.city" : "srv.city",
                               h->get_city(buf, sizeof(buf)));
    }
  }
}

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

bool Flow::hasDissectedTooManyPackets() {
  u_int32_t num_packets;

  if(iface->isSampledTraffic() || (!iface->is_ndpi_enabled()))
    /* Cannot reliably process sampled traffic, giveup the dissection */
    return (true);

#ifdef HAVE_NEDGE
  /* NOTE: in nEdge packet stats are update periodically, so
   * we cannot rely on get_packets() */
  if(ndpiFlow) /* WARNING: can wrap! */
    num_packets = ndpiFlow->num_processed_pkts;
  else
    num_packets = get_packets();
#else
  num_packets = get_packets();
#endif

  return ((num_packets >= NDPI_MIN_NUM_PACKETS) && (!needsExtraDissection()));
}

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

void Flow::setNormalToAlertedCounters() {
  Host *cli_h = get_cli_host(), *srv_h = get_srv_host();

  if(cli_h) {
    u_int32_t local_net_id = cli_h->get_local_network_id();
    NetworkStats *net_stats = cli_h->getNetworkStats(local_net_id);
    AutonomousSystem *cli_as = cli_h ? cli_h->get_as() : NULL;

    if(cli_as) cli_as->incNumAlertedFlows(true /* As client */);
    if(net_stats) net_stats->incNumAlertedFlows(true /* As client */);
    cli_h->incNumAlertedFlows(true /* As client */);
    cli_h->incTotalAlerts();
  }

  if(srv_h) {
    u_int32_t local_net_id = srv_h->get_local_network_id();
    NetworkStats *net_stats = srv_h->getNetworkStats(local_net_id);
    AutonomousSystem *srv_as = srv_h ? srv_h->get_as() : NULL;

    if(srv_as) srv_as->incNumAlertedFlows(true /* As client */);
    if(net_stats) net_stats->incNumAlertedFlows(false /* As server */);
    srv_h->incNumAlertedFlows(false /* As server */);
    srv_h->incTotalAlerts();
  }

  /* Set this into the partializable flow traffic stats as well (necessary for
   * view interfaces) */
  stats.setFlowAlerted();

#ifdef ALERTED_FLOWS_DEBUG
  iface_alert_inc = true;
#endif
}

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

void Flow::setProtocolJSONInfo() {
  ndpi_serializer s;
  char *json = NULL;
  u_int32_t json_len = 0;

  if(ndpi_init_serializer(&s, ndpi_serialization_format_json) == -1)
    return;

  getProtocolJSONInfo(&s);
  getCustomFieldsInfo(&s);
  getJSONRiskInfo(&s);
#ifdef NTOPNG_PRO
  getQoEInfo(&s);
#endif

  json = ndpi_serializer_get_buffer(&s, &json_len);

  if(json_protocol_info) free(json_protocol_info);
  json_protocol_info = strdup(json ? json : "");

  ndpi_term_serializer(&s);
}

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

void Flow::getJSONRiskInfo(ndpi_serializer *serializer) {
  if(serializer && riskInfo) {
    ndpi_serialize_string_raw(serializer, "flow_risk_info", riskInfo, strlen(riskInfo));
  }
}

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

void Flow::getCustomFieldsInfo(ndpi_serializer *serializer) {
  if(get_tlv_info()) {
    ndpi_serialize_start_of_block(serializer, "custom_fields"); /* Custom fields block */
    Utils::tlv2serializer(get_tlv_info(), serializer);
    ndpi_serialize_end_of_block(serializer);
  }
}

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

void Flow::getProtocolJSONInfo(ndpi_serializer *serializer) {
  /* Check JSON info != NULL to not override info */

  if(serializer == NULL) return;

  u_int16_t l7proto = getLowerProtocol();

  ndpi_serialize_start_of_block(serializer, "proto"); /* proto block */

  /* Adding protocol info; switch the lower application protocol */
  switch (l7proto) {
  case NDPI_PROTOCOL_DNS:
    ndpi_serialize_start_of_block(serializer, "dns");
    getDNSInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_HTTP:
  case NDPI_PROTOCOL_HTTP_PROXY:
    ndpi_serialize_start_of_block(serializer, "http");
    getHTTPInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_TLS:
  case NDPI_PROTOCOL_MAIL_IMAPS:
  case NDPI_PROTOCOL_MAIL_SMTPS:
  case NDPI_PROTOCOL_MAIL_POPS:
  case NDPI_PROTOCOL_QUIC:
    ndpi_serialize_start_of_block(serializer, "tls");
    getTLSInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_IP_ICMP:
  case NDPI_PROTOCOL_IP_ICMPV6:
    ndpi_serialize_start_of_block(serializer, "icmp");
    getICMPInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_MDNS:
    ndpi_serialize_start_of_block(serializer, "mdns");
    getMDNSInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_NETBIOS:
    ndpi_serialize_start_of_block(serializer, "netbios");
    getNetBiosInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_SIP:
    ndpi_serialize_start_of_block(serializer, "sip");
    getSIPInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;

  case NDPI_PROTOCOL_SSH:
    ndpi_serialize_start_of_block(serializer, "ssh");
    getSSHInfo(serializer);
    ndpi_serialize_end_of_block(serializer);
    break;
  }

  if(getErrorCode() != 0)
    ndpi_serialize_string_uint32(serializer, "l7_error_code", getErrorCode());

  if(getConfidence() != NDPI_CONFIDENCE_UNKNOWN) {
    switch (getConfidence()) {
    case NDPI_CONFIDENCE_DPI_CACHE:
    case NDPI_CONFIDENCE_DPI:
    case NDPI_CONFIDENCE_NBPF:
      ndpi_serialize_string_uint32(serializer, "confidence",
				   (ndpiConfidence)confidence_dpi);
      break;

    default:
      ndpi_serialize_string_uint32(serializer, "confidence",
				   (ndpiConfidence)confidence_guessed);
      break;
    }
  }

  ndpi_serialize_end_of_block(serializer); /* proto block */

  if(ebpf && ebpf->process_info_set) {
    ndpi_serialize_start_of_block(serializer, "process");

    if(ebpf->src_process_info.process_name)
      ndpi_serialize_string_string(serializer, "src_process_name",
                                   ebpf->src_process_info.process_name);

    if(ebpf->dst_process_info.process_name)
      ndpi_serialize_string_string(serializer, "dst_process_name",
                                   ebpf->dst_process_info.process_name);

    ndpi_serialize_end_of_block(serializer); /* process block */
  }

  if(getTrafficStats()) {
    ndpi_serialize_start_of_block(serializer, "traffic_stats");

    if(getTrafficStats()->get_cli2srv_tcp_retr())
      // cli2srv.retransmissions
      ndpi_serialize_string_uint32(serializer, "cli2srv_retransmissions",
				   getTrafficStats()->get_cli2srv_tcp_retr());
    if(getTrafficStats()->get_cli2srv_tcp_ooo())
      // cli2srv.out_of_order
      ndpi_serialize_string_uint32(serializer, "cli2srv_out_of_order",
				   getTrafficStats()->get_cli2srv_tcp_ooo());
    if(getTrafficStats()->get_cli2srv_tcp_lost())
      // cli2srv.lost
      ndpi_serialize_string_uint32(serializer, "cli2srv_lost",
				   getTrafficStats()->get_cli2srv_tcp_lost());
    if(getTrafficStats()->get_srv2cli_tcp_retr())
      // srv2cli.retransmissions
      ndpi_serialize_string_uint32(serializer, "srv2cli_retransmissions",
				   getTrafficStats()->get_srv2cli_tcp_retr());
    if(getTrafficStats()->get_srv2cli_tcp_ooo())
      // srv2cli.out_of_order
      ndpi_serialize_string_uint32(serializer, "srv2cli_out_of_orders",
				   getTrafficStats()->get_srv2cli_tcp_ooo());
    if(getTrafficStats()->get_srv2cli_tcp_lost())
      // srv2cli.lost
      ndpi_serialize_string_uint32(serializer, "srv2cli_lost",
				   getTrafficStats()->get_srv2cli_tcp_lost());
    ndpi_serialize_end_of_block(serializer); /* traffic_stats block */
  }

  if(protocol == IPPROTO_TCP && applLatencyMsec > 0)
      ndpi_serialize_string_float(serializer, "appl_latency", applLatencyMsec, "%.2f");

  if(triggered_alerts.size() > 0) {
    ndpi_serialize_start_of_block(serializer, "alert_score");

    for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++) {
      FlowAlert *alert = it->second;
      ndpi_serialize_uint32_uint32(serializer, it->first, alert->getCliScore() + alert->getSrvScore());
    }

    ndpi_serialize_end_of_block(serializer);
  }
}

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

void Flow::setAlertInfo(FlowAlert *alert) {
  if(!alert) return;

  alert_info.is_cli_attacker |= alert->isCliAttacker();
  alert_info.is_cli_victim   |= alert->isCliVictim();
  alert_info.is_srv_attacker |= alert->isSrvAttacker();
  alert_info.is_srv_victim   |= alert->isSrvVictim();

  if (!alert->autoAck())
    alert_info.auto_acknowledge = 0;
}

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

void Flow::updateAlertsJSON() {
  ndpi_serializer serializer;
  char *json = NULL;
  u_int32_t json_len = 0;

  if (ndpi_init_serializer(&serializer, ndpi_serialization_format_json) == -1)
    return;

  /* Global info */
  ndpi_serialize_string_uint64(&serializer, "ntopng.key", key());
  ndpi_serialize_string_uint64(&serializer, "hash_entry_id",
                               get_hash_entry_id());

  getJSONRiskInfo(&serializer);

  if (isBlacklistedFlow())
    ndpi_serialize_string_string(&serializer, "blacklist",
      get_custom_category_file() ? get_custom_category_file() : "");

  /* Serialize alerts JSON */

  std::string json_map = "{";
  bool first = true;
  for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++) {
    FlowAlert *a = it->second;
    const char *j = a->getSerializedAlert();
    if (j) {
      if (!first) json_map += ",";
      json_map += "\"" + std::to_string(it->first) + "\": " + j;
      first = false;
    }
  }
  json_map += "}";

  ndpi_serialize_string_raw(&serializer, "alerts", json_map.c_str(), json_map.size());

  json = ndpi_serializer_get_buffer(&serializer, &json_len);

  if(alerts_json) {
    /* Moving to shadow as it may be in use by alert2JSON() or lua() */
    if (alerts_json_shadow) free(alerts_json_shadow);
    alerts_json_shadow = alerts_json;
  }
  alerts_json = strdup(json ? json : "");

  ndpi_term_serializer(&serializer);
}

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

void Flow::setPredominantAlert(FlowAlertType alert_type, u_int16_t score) {
  if(predominant_alert_score) {
    /* Decrease the value previously increased for the previous alert (if not
     * normal) */
    iface->decNumAlertedFlows(
			      this, Utils::mapScoreToSeverity(predominant_alert_score));
  }

  /* Increase the value for the newly set level (if not normal) */
  iface->incNumAlertedFlows(this, Utils::mapScoreToSeverity(score));

  /* Update the current predominant alert and score */
  predominant_alert = alert_type;
  predominant_alert_score = score;
}

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

/*
  This method is called to set score and various other values of the flow

  Return true if the activities are completed successfully, of false otherwise
*/
bool Flow::setAlertsMap(FlowAlert *alert) {
  FlowAlertType alert_type = alert->getAlertType();
  ScoreCategory score_category = Utils::mapAlertToScoreCategory(alert_type.category);
  u_int16_t cli_inc = alert->getCliScore();
  u_int16_t srv_inc = alert->getSrvScore();
  u_int16_t flow_inc = cli_inc + srv_inc;

#ifdef DEBUG_SCORE
  ntop->getTrace()->traceEvent(TRACE_NORMAL,
                               "Set alert score: %u (%u/%u) | MAX VALUE: %u",
                               flow_inc, cli_inc, srv_inc, SCORE_MAX_VALUE);
#endif

  /* Alert type safety checks */
  if(alert_type.id == flow_alert_normal) {
#ifdef DEBUG_SCORE
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "Discarding alert (normal)");
#endif
    delete alert;
    return false;
  }

  /* Check if the same alert has been already triggered and
   * accounted in the score */
  if(alerts_map.isSetBit(alert_type.id)) {
    /* In case of the same alert triggered multiple times, we keep the
     * first one only, and send out a single notificaiton. */
#ifdef DEBUG_SCORE
    ntop->getTrace()->traceEvent(TRACE_NORMAL,
				 "[%s] Discarding alert type %u (already set)",
				 iface->get_name(), alert_type.id);
#endif
    delete alert;
    return false;
  }

  /* Check host filter and if such alert needs to be discarded
   * due to alert exclusions */
  Host *cli_h = get_cli_host(), *srv_h = get_srv_host();

#ifdef NTOPNG_PRO
  ViewInterface *viewedBy = getInterface()->viewedBy();

  if(viewedBy) {
    Mac *srcMac = NULL, *dstMac = NULL;
    Host *cli_host, *srv_host;
    viewedBy->findFlowHosts(getInterfaceIndex(),
			    get_vlan_id(), get_observation_point_id(),
			    getPrivateFlowId(), srcMac,
			    (IpAddress *)get_cli_ip_addr(), &cli_host, dstMac,
			    (IpAddress *)get_srv_ip_addr(), &srv_host);
    if((cli_host && cli_host->isFlowAlertDisabled(alert_type)) ||
	(srv_host && srv_host->isFlowAlertDisabled(alert_type))) {
      delete alert;
      return false;
    }
  } else
#endif
    {
    if((cli_h && cli_h->isFlowAlertDisabled(alert_type)) ||
        (srv_h && srv_h->isFlowAlertDisabled(alert_type))) {
#ifdef DEBUG_SCORE
      ntop->getTrace()->traceEvent(TRACE_NORMAL,
				   "Discarding alert (host filter)");
#endif
      delete alert;
      return false;
    }
  }

  if(!isFlowAlerted())
    /* This is the first time an alert is set on this flow. The flow was normal
     * and now becomes alerted. */
    setNormalToAlertedCounters();

  /* Set alerts bitmap */
  alerts_map.setBit(alert_type.id);
  triggered_alerts[alert_type.id] = alert;
  setAlertInfo(alert);

  /* Update score */
  flow_score += flow_inc;

  stats.incScore(cli_inc, score_category, true /* as client */);
  stats.incScore(srv_inc, score_category, false /* as server */);

  if(!getInterface()->isView()) {
    /* Note: For views, score increments are done periodically */
    if(cli_h)
      cli_h->incScoreValue(cli_inc, score_category, true /* as client */);
    if(srv_h)
      srv_h->incScoreValue(srv_inc, score_category, false /* as server */);
  }

  /* Check if predominant alert should be updated */
  if(!isFlowAlerted() /* Flow is not yet alerted */
     || getPredominantAlertScore() < flow_inc /* The score of the current alerted alert_type is less than the score of this alert_type */) {
#ifdef DEBUG_SCORE
    ntop->getTrace()->traceEvent(
				 TRACE_NORMAL, "[%s] Setting predominant alert (%u) with score %u",
				 iface->get_name(), alert_type.id, flow_inc);
#endif
    setPredominantAlert(alert_type, flow_inc);
#ifdef DEBUG_SCORE
  } else {
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s] Discarding alert (%u) [score %u <= %u]",
				 iface->get_name(), alert_type.id, flow_inc, getPredominantAlertScore());
#endif
  }

  return true;
}

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

/*
  Trigger an alert.
  Called by FlowCheck subclasses to trigger a flow alert. Setting sync
  will causes the alert (FlowAlert) to be immediatly enqueued to recipients.
  Note: alert is released then the flow is released * do NOT free on the caller *
*/
bool Flow::triggerAlert(FlowAlert *alert, bool sync) {
  bool res;

  if (alert == NULL) return false;

  res = setAlertsMap(alert);

  if (res) {
    pending_alerts = true;
    if (sync) flushAlerts();
  }

  return res;
}

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

/*
  Refresh alert stored in flows (rebuild JSON) as the content of some
  of them have been updated (e.g. re-triggered) and the value may be changed
  (e.g. the value that exceeded a threshold has increased further)
*/
void Flow::refreshAlert(FlowAlertTypeEnum alert_type) {
  FlowAlert *alert = getTriggeredAlert(alert_type);
  if (alert) {
    alert->refreshAlert();
    refresh_triggered_alerts = true;
  }
}

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

/*
  Alerts are not flushed immediatly as optimization (unless sync is set in
  triggerAlert). If pending_alerts, they are enqueued to recipients, in a single
  notification for the predominant one.
*/
void Flow::flushAlerts() {
  if (!pending_alerts && !refresh_triggered_alerts) return;

  /* Update JSON */
  updateAlertsJSON();

  if (pending_alerts && !ntop->getPrefs()->dontEmitFlowAlerts()) {
    /* Always enqueue the predominant alert, with json info for all alerts */
    FlowAlert *alert = triggered_alerts[predominant_alert.id];

    /* Enqueue the alert */
    iface->enqueueFlowAlert(alert);
  }

  pending_alerts = refresh_triggered_alerts = false;
}

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

void Flow::setExternalAlert(json_object *a) {
  /* In order to avoid concurrency issues with the getter, at most
   * 1 pending external alert is supported. */
  if(!external_alert.json) {
    json_object *val;

    if(!iface->hasSeenExternalAlerts()) iface->setSeenExternalAlerts();

    if(json_object_object_get_ex(a, "source", &val))
      external_alert.source = strdup(json_object_get_string(val));

    external_alert.json = a;

    /* Manually trigger a periodic update to process the alert */
    trigger_immediate_periodic_update = true;
  }
}

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

void Flow::luaRetrieveExternalAlert(lua_State *vm) {
  const char *json = external_alert.json
    ? json_object_to_json_string(external_alert.json)
    : NULL;

  if(json)
    lua_pushstring(vm, json);
  else
    lua_pushnil(vm);
}

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

#ifdef ENABLE_ENTROPHY_CALCULATION
void Flow::updateEntropy(struct ndpi_analyze_struct *e, u_int8_t *payload,
                         u_int payload_len) {
  if(e != NULL) {
    for (u_int i = 0; i < payload_len; i++) ndpi_data_add_value(e, payload[i]);
  }
}
#endif

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

#ifdef ENABLE_ENTROPHY_CALCULATION
void Flow::lua_entropy(lua_State *vm) {
  if(initial_bytes_entropy.c2s && initial_bytes_entropy.s2c) {
    lua_newtable(vm);

    lua_push_float_table_entry(vm, "client", getEntropy(true));
    lua_push_float_table_entry(vm, "server", getEntropy(false));

    if(protocol == IPPROTO_ICMP) {
      if(protos.icmp.client_to_server.min_entropy != 0) {
        lua_newtable(vm);
        lua_push_float_table_entry(vm, "min",
                                   protos.icmp.client_to_server.min_entropy);
        lua_push_float_table_entry(vm, "max",
                                   protos.icmp.client_to_server.max_entropy);

        lua_pushstring(vm, "icmp");
        lua_insert(vm, -2);
        lua_settable(vm, -3);
      }
    }

    lua_pushstring(vm, "entropy");
    lua_insert(vm, -2);
    lua_settable(vm, -3);
  }
}
#endif

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

void Flow::check_swap()
  /* NOTE: keep in sync with  ZMQParserInterface::preprocessFlow() */{
  if(((protocol == IPPROTO_TCP) && ((src2dst_tcp_flags & TH_SYN) == TH_SYN) /* Ignore in case we have seen a SYN */)
      || (get_cli_port() == 0) || (get_srv_port() == 0))
    return;

  if(!(get_cli_ip_addr()->isNonEmptyUnicastAddress()
       /* Everything that is NOT unicast-to-non-unicast needs to be checked */
       && (!get_srv_ip_addr()->isNonEmptyUnicastAddress()))
      // && get_cli_port() < 1024 /* Relax this constraint and also apply to
      // non-well-known ports such as 8080 */
     ) {

    if(get_cli_port() < get_srv_port()) {
      if(protocol == IPPROTO_UDP) /* && (get_cli_port() > 32768) && (get_srv_port() > 32768) */ {
	/* We disable UDP swap that might be wrong in particular for probing attempts */
	; /* Don't do anything: this might be RTP or similar */
      } else {
	request_swap(); /* This flow will be swapped */
      }
    } else {
      /* The RDP port is > 1024 hence we need this extra logic */
      if((ndpiDetectedProtocol.proto.app_protocol == NDPI_PROTOCOL_RDP) && (get_cli_port() == 3389))
	request_swap(); /* This flow will be swapped */
    }
  }
}

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

void Flow::setJSONRiskInfo(char *r) {
  if(!r) return;

  if(riskInfo) free(riskInfo);
  riskInfo = strdup(r);
}

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

void Flow::setEndReason(char *r) {
  if(!r) return;

  if(end_reason) free(end_reason);
  end_reason = strdup(r);
}

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

char *Flow::getEndReason() { return (end_reason); }

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

void Flow::setWLANInfo(char *_wlan_ssid, u_int8_t *_wtp_mac_address) {
  allocateCollection();

  if(collection) {
    if(_wlan_ssid) {
      if(collection->wifi.wlan_ssid)
	free(collection->wifi.wlan_ssid);
      collection->wifi.wlan_ssid = strdup(_wlan_ssid);
    }

    memcpy(collection->wifi.wtp_mac_address, _wtp_mac_address, 6);
  }
}

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

void Flow::setSMTPMailFrom(char *r) {
  if(!r) return;

  if(protos.smtp.mail_from) free(protos.smtp.mail_from);
  protos.smtp.mail_from = strdup(r);
}

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

char *Flow::getSMTPMailFrom() { return (protos.smtp.mail_from); }

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

void Flow::setSMTPRcptTo(char *r) {
  if(!r) return;

  if(protos.smtp.rcpt_to) free(protos.smtp.rcpt_to);
  protos.smtp.rcpt_to = strdup(r);
}

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

char *Flow::getSMTPRcptTo() { return (protos.smtp.rcpt_to); }

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

void Flow::setFlowRiskName(char *r) {
  if(!r) return;

  if(ndpiFlowRiskName) free(ndpiFlowRiskName);
  ndpiFlowRiskName = strdup(r);
}

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

char *Flow::getFlowRiskName() { return (ndpiFlowRiskName); }

/* *************************************** */
/* The alert will be triggered by src/flow_checks/CustomFlowLuaScript.cpp */
void Flow::triggerCustomFlowAlert(u_int8_t score, char *msg) {
  customFlowAlert.alertTriggered = true, customFlowAlert.score = score;

  if(customFlowAlert.msg) {
    free(customFlowAlert.msg);
    customFlowAlert.msg = NULL;
  }

  if(msg) customFlowAlert.msg = strdup(msg);
}

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

void Flow::swap() {
  IpAddress *i = cli_ip_addr;
  u_int8_t m[6];
  u_int8_t f1 = alert_info.is_cli_attacker;
  u_int8_t f2 = alert_info.is_cli_victim;
#ifdef ENABLE_ENTROPHY_CALCULATION
  struct ndpi_analyze_struct *s = initial_bytes_entropy.c2s;
#endif
  TCPSeqNum ts;
  InterarrivalStats *is = cli2srvPktTime;
  time_t now = time(NULL);
  u_int32_t tmp32;

#if 0
  char buf[128];
  ntop->getTrace()->traceEvent(TRACE_NORMAL, "Swapping %s", print(buf, sizeof(buf)));
#endif

  if(cli_host && srv_host) {
    /* Not a view interface */
    Host *h = cli_host;

    cli_host->decNumFlows(now, true /* as client */, isTCP(), twh_over),
      srv_host->decNumFlows(now, false /* as server */, isTCP(), twh_over);
    cli_host = srv_host, cli_ip_addr = srv_ip_addr;
    srv_host = h, srv_ip_addr = i;
    cli_host->incNumFlows(now, true /* as client */, isTCP()),
      srv_host->incNumFlows(now, false /* as server */, isTCP());
  } else {
    /* This is a view interface */

    if(cli_ip_addr && (cli_host == NULL)
	&& srv_ip_addr && (srv_host == NULL)) {
      IpAddress *c = cli_ip_addr;

      cli_ip_addr = srv_ip_addr;
      srv_ip_addr = c;
    }
  }

  Utils::swap16(&cli_port, &srv_port), Utils::swap32(&srcAS, &dstAS);

  Utils::swap8(&src2dst_tcp_flags, &dst2src_tcp_flags);

#ifdef ENABLE_ENTROPHY_CALCULATION
  initial_bytes_entropy.c2s = initial_bytes_entropy.s2c;
  initial_bytes_entropy.s2c = s;
#endif
  
  memcpy(m, view_cli_mac, 6);
  memcpy(view_cli_mac, view_srv_mac, 6);
  memcpy(view_srv_mac, m, 6);

  stats.swap();

  alert_info.is_cli_attacker = alert_info.is_srv_attacker,
    alert_info.is_cli_victim = alert_info.is_srv_victim;
  alert_info.is_srv_attacker = f1,
    alert_info.is_srv_victim = f2;

  if(tcp != NULL) {
    memcpy(&ts, &tcp->tcp_seq_s2d, sizeof(TCPSeqNum));
    memcpy(&tcp->tcp_seq_d2s, &tcp->tcp_seq_s2d, sizeof(TCPSeqNum));
    memcpy(&tcp->tcp_seq_s2d, &ts, sizeof(TCPSeqNum));
    Utils::swap16(&tcp->cli2srv_window, &tcp->srv2cli_window);
  }

  cli2srvPktTime = srv2cliPktTime;
  srv2cliPktTime = is;

#ifdef HAVE_NEDGE
  TrafficShaper *s1 = flowShaperIds.cli2srv.ingress;
  TrafficShaper *s2 = flowShaperIds.srv2cli.egress;

  flowShaperIds.cli2srv.ingress = flowShaperIds.srv2cli.ingress,
    flowShaperIds.srv2cli.egress = flowShaperIds.cli2srv.egress;
  flowShaperIds.srv2cli.ingress = s1, flowShaperIds.cli2srv.egress = s2;
#endif

  tmp32 = flow_device.in_index;
  flow_device.in_index = flow_device.out_index;
  flow_device.out_index = tmp32;

  if(tcp != NULL) {
    u_int32_t tmp = tcp->rtt.last_cli_ack;

    tcp->rtt.last_cli_ack = tcp->rtt.last_srv_ack;
    tcp->rtt.last_srv_ack = tmp;
  }

  c2sFirstGoodputTime.tv_sec = 0;

  /*
    We do not swap L7 info as if it direction was wrong they were not computed
    Same applies with latency counters
  */

  swap_done = 1, swap_requested = 0;
}

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

void Flow::updateTCPHostServices(Host *cli_h, Host *srv_h) {
  switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
  case NDPI_PROTOCOL_SSH:
  case NDPI_PROTOCOL_TLS:
    if(tcp) {
      if((((src2dst_tcp_flags & TH_SYN) == 0) && ((dst2src_tcp_flags & TH_SYN) != 0))
	 || ((((src2dst_tcp_flags|dst2src_tcp_flags) & TH_SYN) == 0) /* No SYN observed */
	     && (get_cli_port() < get_srv_port()))) {
	request_swap();
      }
    }
    break;

  default:
    break;
  } /* switch */
}

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

void Flow::updateUDPHostServices(bool src2dst_direction) {
  Host *cli_h, *srv_h;

  get_actual_peers(&cli_h, &srv_h);

  switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
  case NDPI_PROTOCOL_DHCP:
    if(getConfidence() == NDPI_CONFIDENCE_DPI) {
      if(cli_port == htons(67)) {
	/* Server -> Client */

	if(cli_host && (!cli_host->isBroadcastHost())) {
	  cli_host->setDhcpServer();
	} else if(cli_ip_addr && !cli_ip_addr->isBroadcastAddress()) {
	  cli_ip_addr->setDhcpServer();
	}
      } else {
	if(srv_host && (!srv_host->isBroadcastHost())) {
	  srv_host->setDhcpServer();
	} else if(srv_ip_addr && !srv_ip_addr->isBroadcastAddress()) {
	  srv_ip_addr->setDhcpServer();
	}

	if(ndpiFlow && cli_host)
	  cli_host->offlineSetDhcpFingerprint(ndpiFlow->protos.dhcp.fingerprint);
      }
    }
    break;

  case NDPI_PROTOCOL_DNS:
    /* Swap check */
    if((!swap_requested)
       && (ndpiFlow != NULL)
       && iface->isPacketInterface()) {
      if(ndpiFlow->protos.dns.is_query) {
	if(src2dst_direction) {
	  ;
	} else {
	  request_swap();
	  // ntop->getTrace()->traceEvent(TRACE_NORMAL, "*** SWAP ***");
	}
      } else {
	/* Response */
	if(src2dst_direction) {
	  request_swap();
	  // ntop->getTrace()->traceEvent(TRACE_NORMAL, "*** SWAP ***");
	} else {
	  ;
	}
      }
    }
    break;

  case NDPI_PROTOCOL_TOR:
  case NDPI_PROTOCOL_TLS:
  case NDPI_PROTOCOL_QUIC:
    if((ndpiDetectedProtocol.proto.app_protocol == NDPI_PROTOCOL_DOH_DOT)
       && cli_h && srv_h && cli_h->isLocalHost())
      cli_h->incDohDoTUses(srv_h);
    break;

  default:
    break;
  } /* switch */
}

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

bool Flow::isDPIDetectedFlow() {
  switch (getConfidence()) {
  case NDPI_CONFIDENCE_DPI_CACHE:
  case NDPI_CONFIDENCE_DPI_PARTIAL_CACHE:
  case NDPI_CONFIDENCE_DPI:
  case NDPI_CONFIDENCE_NBPF:
    return(true);

  default:
    return(false);
  }
}

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

bool Flow::matchFlowIP(IpAddress *ip, u_int16_t vlan_id) {
  if(get_vlan_id() != vlan_id) return(false);

  if((!get_cli_ip_addr()) || (!get_srv_ip_addr()))
    return(false);

  if(get_cli_ip_addr()->equal(ip) || get_srv_ip_addr()->equal(ip))
    return(true);
  else
    return(false);
}


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

bool Flow::matchFlowVLAN(u_int16_t vlan_id) {
  return(get_vlan_id() == vlan_id ? true : false);
}

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

bool Flow::matchFlowDeviceIP(u_int32_t flow_device_ip) {
  return(getFlowDeviceIP() == flow_device_ip ? true : false);
}

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

bool Flow::matchInIfIdx(u_int32_t in_if_idx) {
  return(getFlowDeviceInIndex() == in_if_idx ? true : false);
}

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

bool Flow::matchOutIfIdx(u_int32_t out_if_idx) {
  return(getFlowDeviceOutIndex() == out_if_idx ? true : false);
}

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

void Flow::lua_get_flow_connection_state(lua_State *vm) {
  lua_push_uint64_table_entry(vm, "minor_conn_state", getCurrentConnectionState());
  lua_push_uint64_table_entry(vm, "major_conn_state", getMajorConnState());
}

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

MajorConnectionStates Flow::getMajorConnState() {
  MajorConnectionStates major_conn_state = MAJOR_NO_STATE;

  switch (getCurrentConnectionState()) {
  case S0:
  case REJ:
  case RSTOS0:
  case RSTRH:
  case SH:
  case SHR:
    major_conn_state = ATTEMPTED;
    /* code */
    break;
  case OTH:
  case S1:
    major_conn_state = ESTABLISHED;
    break;
  case SF:
  case S2:
  case S3:
  case RSTO:
  case RSTR:
    major_conn_state = CLOSED;
    break;

  default:
    major_conn_state = MAJOR_NO_STATE;
    break;
  }

  return(major_conn_state);
}

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


bool Flow::isTCPFlagSet(u_int8_t tcp_flags, int flag_to_check) {
  return((tcp_flags & flag_to_check) != 0);
}

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

bool Flow::checkS1ConnState() {
  if(tcp == NULL)
    return(false);
  else
    return(current_c_state == S1 || ((isTCPFlagSet(src2dst_tcp_flags,TCP_3WH_MASK)) &&
				     (isTCPFlagSet(dst2src_tcp_flags,TCP_3WH_MASK))&&                                   /* 3WH OK */
				     !((isTCPFlagSet(src2dst_tcp_flags, TH_FIN)) && (isTCPFlagSet(src2dst_tcp_flags, TH_ACK))) &&  /* NO FIN ACK in src2dst */
				     !(isTCPFlagSet(src2dst_tcp_flags, TH_RST)) && !(isTCPFlagSet(dst2src_tcp_flags, TH_RST))     /* NO RST */
				     ));
}

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

MinorConnectionStates Flow::calculateConnectionState(bool is_cumulative) {
  if(!isTCP() || !tcp)
    return(setCurrentConnectionState(MINOR_NO_STATE));

  /* Check S0 or RSTOS0 or REJ or SH */
  if((isTCPFlagSet(src2dst_tcp_flags, TH_SYN)) &&
      !(isTCPFlagSet(dst2src_tcp_flags, TH_SYN))) {
    if(!(isTCPFlagSet(dst2src_tcp_flags, TH_RST))) {
      if((isTCPFlagSet(src2dst_tcp_flags, TH_RST))) {
        return(setCurrentConnectionState(RSTOS0));
      } else {
        if((isTCPFlagSet(src2dst_tcp_flags, TH_FIN))) {
          return(setCurrentConnectionState(SH));
        } else {
          return(setCurrentConnectionState(S0));
        }
      }
    } else {
      return(setCurrentConnectionState(REJ));
    }
  }

  /* Check RSTRH */
  if((isTCPFlagSet(dst2src_tcp_flags, TH_SYN)) &&
      (isTCPFlagSet(dst2src_tcp_flags, TH_ACK)) &&
      (isTCPFlagSet(dst2src_tcp_flags, TH_RST)) &&
      !(isTCPFlagSet(src2dst_tcp_flags, TH_SYN)))
    return(setCurrentConnectionState(RSTRH));

  /* Check SHR */
  if((isTCPFlagSet(dst2src_tcp_flags, TH_SYN)) &&
      (isTCPFlagSet(dst2src_tcp_flags, TH_ACK)) &&
      (isTCPFlagSet(dst2src_tcp_flags, TH_FIN)) &&
      !(isTCPFlagSet(src2dst_tcp_flags, TH_SYN)))
    return(setCurrentConnectionState(SHR));

  /* Check OTH */
  if(!isTCPFlagSet(src2dst_tcp_flags, TH_SYN) &&
      !isTCPFlagSet(dst2src_tcp_flags, TH_SYN))
    return(setCurrentConnectionState(OTH));

  bool is_s1 = (current_c_state == S1) || is_cumulative;
  bool is_s2 = (current_c_state == S2);
  bool is_s3 = (current_c_state == S3);

  /* Check SF */
  if(((is_s1) || (is_s2) || (is_s3)) &&
      isTCPFlagSet(src2dst_tcp_flags, TH_FIN) &&
      isTCPFlagSet(dst2src_tcp_flags, TH_FIN))
    return(setCurrentConnectionState(SF));

  /* Check S2 */
  if((is_s1) &&
      isTCPFlagSet(src2dst_tcp_flags, TH_FIN) &&
      !isTCPFlagSet(dst2src_tcp_flags, TH_FIN))
    return(setCurrentConnectionState(S2));

  /* Check S3 */
  if((is_s1) &&
      !isTCPFlagSet(src2dst_tcp_flags, TH_FIN) &&
      isTCPFlagSet(dst2src_tcp_flags, TH_FIN))
    return(setCurrentConnectionState(S3));

  /* Check RSTO */
  if((is_s1) && isTCPFlagSet(src2dst_tcp_flags, TH_RST))
    return(setCurrentConnectionState(RSTO));

  /* Check RSTR */
  if((is_s1) && isTCPFlagSet(dst2src_tcp_flags, TH_RST))
    return(setCurrentConnectionState(RSTR));

  /* Check S1 */
  if(checkS1ConnState())
    return(setCurrentConnectionState(S1));

  if(getCurrentConnectionState() != MINOR_NO_STATE)
    return(getCurrentConnectionState());

  return(setCurrentConnectionState(MINOR_NO_STATE));
}

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

void Flow::addPrePostNATIPv4(u_int32_t _src_ip_addr_pre_nat,
                        u_int32_t _dst_ip_addr_pre_nat,
                        u_int32_t _src_ip_addr_post_nat,
                        u_int32_t _dst_ip_addr_post_nat) {

  allocateCollection();

  if(collection) {
    collection->nat.src_ip_addr_pre_nat = _src_ip_addr_pre_nat;
    collection->nat.dst_ip_addr_pre_nat = _dst_ip_addr_pre_nat;
    collection->nat.src_ip_addr_post_nat = _src_ip_addr_post_nat;
    collection->nat.dst_ip_addr_post_nat = _dst_ip_addr_post_nat;
  }
}

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

void Flow::addPrePostNATPort(u_int32_t _src_port_pre_nat,
                          u_int32_t _dst_port_pre_nat,
                          u_int32_t _src_port_post_nat,
                          u_int32_t _dst_port_post_nat) {

  allocateCollection();

  if(collection) {
    collection->nat.src_port_pre_nat = _src_port_pre_nat;
    collection->nat.dst_port_pre_nat = _dst_port_pre_nat;
    collection->nat.src_port_post_nat = _src_port_post_nat;
    collection->nat.dst_port_post_nat = _dst_port_post_nat;
  }
}

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

void Flow::accountBidirectionalTCPProtocolServices() {
  if(isBidirectional()) {
    Host *cli_h, *srv_h;

    get_actual_peers(&cli_h, &srv_h);

    for(int i=0; i<2; i++) {
      switch (i == 0 ? ndpi_get_lower_proto(ndpiDetectedProtocol) : ndpi_get_upper_proto(ndpiDetectedProtocol)) {
      case NDPI_PROTOCOL_MAIL_SMTPS:
      case NDPI_PROTOCOL_MAIL_SMTP:
	if(isThreeWayHandshakeOK()
	   // && (getConfidence() 2== NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isSmtpServer()) {
	      srv_h->setSmtpServer();
	      ntop->trackAssetChange("SMTP", "setSmtpServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    /* View Interface */
	    srv_ip_addr->setSmtpServer();
	    ntop->trackAssetChange("SMTP", "setSmtpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }

	  return; /* Nothing else to do */
	}
	break;

      case NDPI_PROTOCOL_MAIL_IMAPS:
      case NDPI_PROTOCOL_MAIL_IMAP:
	if(isThreeWayHandshakeOK()
	   // && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isImapServer()) {
	      srv_h->setImapServer();
	      ntop->trackAssetChange("IMAP", "setImapServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    /* View Interface */
	    srv_ip_addr->setImapServer();
	    ntop->trackAssetChange("IMAP", "setImapServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }

	  return; /* Nothing else to do */
	}
	break;

      case NDPI_PROTOCOL_MAIL_POPS:
      case NDPI_PROTOCOL_MAIL_POP:
	if(isThreeWayHandshakeOK()
	   // && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isPopServer()) {
	      srv_h->setPopServer();
	      ntop->trackAssetChange("POP", "setPopServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    /* View Interface */
	    srv_ip_addr->setPopServer();
	    ntop->trackAssetChange("POP", "setPopServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }

	  return; /* Nothing else to do */
	}
	break;

      case NDPI_PROTOCOL_TLS:
	/*
	  TLS on port 443 is considered HTTPS

	  TODO: check ALPN to double check
	*/
	if(get_srv_port() != 443) {
	  return;
	}
	/* No break here ! */
      
      case NDPI_PROTOCOL_HTTP:
      case NDPI_PROTOCOL_HTTP_CONNECT:
      case NDPI_PROTOCOL_HTTP_PROXY:
	if(isThreeWayHandshakeOK()
	   // && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isHttpServer()) {
	      srv_h->setHttpServer();
	      ntop->trackAssetChange("HTTP", "setHttpServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    /* View Interface */
	    srv_ip_addr->setHttpServer();
	    ntop->trackAssetChange("HTTP", "setHttpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }

	  return; /* Nothing else to do */
	}
	break;

      case NDPI_PROTOCOL_SSH:
	if(isThreeWayHandshakeOK()
	   // && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isSshServer()) {
	      srv_h->setSshServer();
	      ntop->trackAssetChange("SSH", "setSshServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    /* View Interface */
	    srv_ip_addr->setSshServer();
	    ntop->trackAssetChange("SSH", "setSshServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }

	  return; /* Nothing else to do */
	}
	break;

      case NDPI_PROTOCOL_RDP:
	if(isThreeWayHandshakeOK()
	   // && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isRdpServer()) {
	      srv_h->setRdpServer();
	      ntop->trackAssetChange("RDP", "setRdpServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    /* View Interface */
	    srv_ip_addr->setRdpServer();
	    ntop->trackAssetChange("RDP", "setRdpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }

	  return; /* Nothing else to do */
	}
	break;
      }
    }
  }
}

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

void Flow::accountBidirectionalUDPProtocolServices() {
  if(isBidirectional()) {
    Host *cli_h, *srv_h;

    get_actual_peers(&cli_h, &srv_h);

    switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
    case NDPI_PROTOCOL_NTP:
      if((getConfidence() == NDPI_CONFIDENCE_DPI)
	 || (isTCP() && isTCPEstablished())
	 || isUDP()) {
	//char buf[256];

	if(srv_h) {
	  if(!srv_h->isNtpServer()) {
	    srv_h->setNtpServer();
	    ntop->trackAssetChange("NTP", "setNtpServer-1", NULL, NULL, srv_h, this, NULL);
	  }
	} else if(srv_ip_addr) {
	  if(!srv_ip_addr->isNtpServer()) {
	    srv_ip_addr->setNtpServer();
	    ntop->trackAssetChange("NTP", "setNtpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }
	}
      }
      break;

    case NDPI_PROTOCOL_DNS:
      if((getConfidence() == NDPI_CONFIDENCE_DPI) /* Packet */
	 || (ntohs(srv_port) == 53) /* nProbe case: let's be conservative */
	 ) {
	// char buf[256];

	if(srv_h) {
	  if(!srv_h->isDnsServer()) {
	    srv_h->setDnsServer();
	    ntop->trackAssetChange("DNS", "setDnsServer-3", NULL, NULL, srv_h, this, NULL);
	  }
	} else if(srv_ip_addr) {
	  if(!srv_ip_addr->isDnsServer()) {
	    srv_ip_addr->setDnsServer();
	    ntop->trackAssetChange("DNS", "setDnsServer-4", NULL, srv_ip_addr, NULL, this, NULL);
	  }
	}
      }
      break;

    case NDPI_PROTOCOL_QUIC:
      /*
	QUIC on port 443 is considered HTTPS

	TODO: check ALPN to double check
      */
      if(get_srv_port() == 443) {
	if(isBidirectional()
	   // && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
	   ) {
	  if(srv_h) {
	    if(!srv_h->isHttpServer()) {
	      srv_h->setHttpServer();
	      ntop->trackAssetChange("HTTP", "setHttpServer-1", NULL, NULL, srv_h, this, NULL);
	    }
	  } else if(srv_ip_addr) {
	    srv_ip_addr->setHttpServer();
	    ntop->trackAssetChange("HTTP", "setHttpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
	  }
	}
      }
      break;
    }
  }
}

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

/*
  Account flow traffic in interface traffic if not yet done
*/
void Flow::accountFlowTraffic(bool src2dst_direction) {
  if(!isFlowAccounted()) {
#ifdef DEBUG
    char buf[256];

    ntop->getTrace()->traceEvent(TRACE_WARNING, "Account %s", print(buf, sizeof(buf)));
#endif

    endProtocolDissection(src2dst_direction);

    /*
      Increment interface counters for those flows that have not
      been accounted hence whose traffic has not been accounted
      in the corresponding network interface. See
      NetworkInterface::processPacket()
    */
    iface->incnDPIStats(iface->getTimeLastPktRcvd(),
			getStatsProtocol(), get_protocol_category(),
			get_bytes_cli2srv(), get_bytes_srv2cli(),
      get_packets_cli2srv(), get_packets_srv2cli());
  }
}

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

bool Flow::is_active_entry_now_idle(u_int max_idleness) const {
  bool is_expired = (((u_int)(iface->getTimeLastPktRcvd()) > (creation_time + max_idleness)) ? true : false);

  /*
    This mechanism prevents collected flows with past timestamps
    to be purged immediately, thus guaranteeing that they stay in
    memory at least max_idleness seconds
  */
  if(!is_expired) return(false);

  return(GenericHashEntry::is_active_entry_now_idle(max_idleness));
}

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

/* #define DEBUG_UPDATE  1 */

void Flow::updateServerName(Host *h) {
  if(h && (h->has_name_set() == false)) {
    struct ndpi_address_cache_item *i;
    ndpi_ip_addr_t ip_addr;

    h->get_ip()->fillIP(&ip_addr);

#ifdef DEBUG_UPDATE
    char buf[64];
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "**** Host: %p [%s]", h, srv_ip_addr->print(buf, sizeof(buf)));
#endif

    if((i = ndpi_cache_address_find(iface->get_ndpi_struct(), ip_addr)) != NULL) {
#ifdef DEBUG_UPDATE
      ntop->getTrace()->traceEvent(TRACE_NORMAL, "**** SET %s", i->hostname);
#endif
      h->setServerName(i->hostname);
    }
  }
}

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

const char* Flow::getDomainName() {
  switch (getLowerProtocol()) {
  case NDPI_PROTOCOL_DNS:
    if(protos.dns.last_query)
      return(ndpi_get_host_domain(iface->get_ndpi_struct(), protos.dns.last_query));
    break;

  case NDPI_PROTOCOL_HTTP:
  case NDPI_PROTOCOL_HTTP_PROXY:
  case NDPI_PROTOCOL_TLS:
  case NDPI_PROTOCOL_MAIL_IMAPS:
  case NDPI_PROTOCOL_MAIL_SMTPS:
  case NDPI_PROTOCOL_MAIL_POPS:
  case NDPI_PROTOCOL_QUIC:
    {
      char *s = getFlowServerInfo();

      if(s)
	return(ndpi_get_host_domain(iface->get_ndpi_struct(), s));
    }
    break;

  case NDPI_PROTOCOL_MDNS:
    if(protos.mdns.name)
      return(ndpi_get_host_domain(iface->get_ndpi_struct(), protos.mdns.name));
    break;
  }

  return(NULL);
}

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

void Flow::allocateCollection() {
  if(collection != NULL)
    return;
  else
    collection = (FlowCollectionInfo*)calloc(1, sizeof(FlowCollectionInfo));
}

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

/* Quick check that both sides have (likely) done a 3WH */
bool Flow::isThreeWayHandshakeOK() const {
  u_int16_t mask = TH_SYN | TH_ACK;

  if(((src2dst_tcp_flags & mask) == mask) && ((dst2src_tcp_flags & mask) == mask))
    return(true);
  else
    return(false);
};
