/*
 *
 * (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"

#ifndef HAVE_NEDGE

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

ParserInterface::ParserInterface(const char *endpoint,
                                 const char *custom_interface_type)
  : NetworkInterface(endpoint, custom_interface_type) {
  if (trace_new_delete)
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "[new] %s", __FILE__);

  num_companion_interfaces = 0;
  companion_interfaces =
      new (std::nothrow) NetworkInterface *[MAX_NUM_COMPANION_INTERFACES]();

#ifdef NTOPNG_PRO
  flow_interfaces_stats = new (std::nothrow) FlowInterfacesStats();

  if (!flow_interfaces_stats)
    ntop->getTrace()->traceEvent(TRACE_WARNING, "Memory allocation failure");
#endif
}

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

ParserInterface::~ParserInterface() {
  if (companion_interfaces) delete[] companion_interfaces;
}

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

bool ParserInterface::processFlow(ParsedFlow *zflow) {
  bool src2dst_direction, new_flow;
  Flow *flow;
  time_t now;
  bool update_seen = false;
  bpf_timeval now_tv = {0};
  Mac *srcMac = NULL, *dstMac = NULL;
  IpAddress srcIP, dstIP;
  u_int32_t unique_source_id = zflow->unique_source_id;
  u_int32_t in_pkts, in_bytes, out_pkts, out_bytes;

#ifdef NTOPNG_PRO
  if (!flow_interfaces_stats) return false;
#endif

  if (unique_source_id == 0)
    unique_source_id = zflow->nprobe_ip + zflow->exporter_device_ip;

  now = time(NULL);
  now_tv.tv_sec = now;

  // ntop->getTrace()->traceEvent(TRACE_WARNING, "%s()", __FUNCTION__);

  if (unlikely(ntop->getPrefs()->get_num_simulated_ips())) {
    u_int32_t num_sim_ips = ntop->getPrefs()->get_num_simulated_ips();
    u_int32_t base_ip = 167772161; /* 10.0.0.1 */

    zflow->src_ip.set(ntohl(base_ip + rand() % num_sim_ips));
    zflow->dst_ip.set(ntohl(base_ip + rand() % num_sim_ips));
    zflow->vlan_id = 0;
  }

  if (zflow->vlan_id && ntop->getPrefs()->do_ignore_vlans()) zflow->vlan_id = 0;

  if ((zflow->vlan_id == 0) && ntop->getPrefs()->do_simulate_vlans())
    zflow->vlan_id = rand() % SIMULATE_VLANS_MAX_VALUE;

  if (!isSubInterface()) {
    bool processed = false;

    /* Deliver eBPF info to companion queues */
    if (zflow->process_info_set || zflow->container_info_set ||
        zflow->tcp_info_set || zflow->getExternalAlert() ||
        zflow->getAdditionalFieldsJSON()) {
      deliverFlowToCompanions(zflow);
    }

#ifdef NTOPNG_PRO
#ifndef HAVE_NEDGE
    /* Custom disaggregation */
    if (sub_interfaces && (sub_interfaces->getNumSubInterfaces() > 0)) {
      processed = sub_interfaces->processFlow(zflow);
    }
#endif
#endif

    if (!processed && (flowHashingMode != flowhashing_none)) {
      bool observation_point_id_mode = false; /* TODO: handle this mode */

      if (observation_point_id_mode) {
        /*
          Instead of creating sub-interfaces we set the
          observation point id
        */
        u_int16_t virtual_observation_point_id = zflow->vlan_id;

        switch (flowHashingMode) {
          case flowhashing_probe_ip:
            virtual_observation_point_id = zflow->exporter_device_ip & 0xFFF /* 4096 */;
            break;

          case flowhashing_iface_idx:
            /* NOTE: we do not duplicate data but use only the egress interface
             */
            virtual_observation_point_id = zflow->outIndex & 0xFFF /* 4096 */;
            break;

          case flowhashing_ingress_iface_idx:
            virtual_observation_point_id = zflow->inIndex & 0xFFF /* 4096 */;
            break;

          case flowhashing_probe_ip_and_ingress_iface_idx:
            virtual_observation_point_id = ((((u_int64_t)zflow->exporter_device_ip) << 32) +
					    zflow->inIndex) & 0xFFF /* 4096 */;
            break;

          case flowhashing_vrfid:
            virtual_observation_point_id = zflow->vrfId & 0xFFF /* 4096 */;
            break;

          case flowhashing_vlan:
            /* Nothing to do */
            break;

          default:
            break;
        }

        zflow->observationPointId = virtual_observation_point_id;
      } else {
        NetworkInterface *vIface = NULL, *vIfaceEgress = NULL;

        switch (flowHashingMode) {
          case flowhashing_probe_ip:
            vIface = getDynInterface((u_int64_t)zflow->exporter_device_ip, true);
            break;

          case flowhashing_iface_idx:
            if (flowHashingIgnoredInterfaces.find((u_int64_t)zflow->outIndex) ==
                flowHashingIgnoredInterfaces.end())
              vIfaceEgress = getDynInterface((u_int64_t)zflow->outIndex, true);
            /* No break HERE, want to get two interfaces, one for the ingress
               and one for the egress. */

          case flowhashing_ingress_iface_idx:
            if (flowHashingIgnoredInterfaces.find((u_int64_t)zflow->inIndex) ==
                flowHashingIgnoredInterfaces.end())
              vIface = getDynInterface((u_int64_t)zflow->inIndex, true);
            break;

          case flowhashing_probe_ip_and_ingress_iface_idx:
            // ntop->getTrace()->traceEvent(TRACE_NORMAL, "[IP: %u][inIndex:
            // %u]", zflow->exporter_device_ip, zflow->inIndex);
            vIface = getDynInterface((((u_int64_t)zflow->exporter_device_ip) << 32) + zflow->inIndex, true);
            break;

          case flowhashing_vrfid:
            vIface = getDynInterface((u_int64_t)zflow->vrfId, true);
            break;

          case flowhashing_vlan:
            vIface = getDynInterface((u_int64_t)zflow->vlan_id, true);
            break;

          default:
            break;
        }

        if (vIface) {
          ParserInterface *vPIface = dynamic_cast<ParserInterface *>(vIface);

          vPIface->processFlow(zflow);
        }

        if (vIfaceEgress) {
          ParserInterface *vPIface = dynamic_cast<ParserInterface *>(vIfaceEgress);

          vPIface->processFlow(zflow);
        }

        processed = true;
      }

      if (processed && (!showDynamicInterfaceTraffic())) return true;
    }
  }

  if (!ntop->getPrefs()->do_ignore_macs()) {
    u_int8_t zero_mac[6] = { 0 };

    if(memcmp(zflow->src_mac, zero_mac, 6)) {
      srcMac = getMac((u_int8_t *)zflow->src_mac, true /* Create if missing */,
		      true /* Inline call */);
      dstMac = getMac((u_int8_t *)zflow->dst_mac, true /* Create if missing */,
		      true /* Inline call */);
    }
  }

  if((zflow->src_ip.getVersion() == 0)
     || (zflow->dst_ip.getVersion() == 0)) {
    flow = NULL; /* Invalid IPs */
  } else { 
    srcIP.set(&zflow->src_ip), dstIP.set(&zflow->dst_ip);
    
    INTERFACE_PROFILING_SECTION_ENTER("NetworkInterface::processFlow: getFlow", 0);
    
    /* Updating Flow */
    flow = getFlow(UNKNOWN_PKT_IFACE_IDX, srcMac, dstMac, zflow->vlan_id,
		   zflow->observationPointId, zflow->get_private_flow_id(),
		   zflow->exporter_device_ip, zflow->inIndex, zflow->outIndex,
		   NULL /* ICMPinfo */, &srcIP, &dstIP, zflow->src_port,
		   zflow->dst_port, zflow->l4_proto, &src2dst_direction,
		   zflow->first_switched, zflow->last_switched, 0, &new_flow,
		   true, (u_int8_t *)zflow->src_mac, (u_int8_t *)zflow->dst_mac);

    INTERFACE_PROFILING_SECTION_EXIT(0);
  }
  
  if(flow) {
    /* Fix interaface Id (if zero) */

    if(zflow->inIndex != 0) {
      if(src2dst_direction)
	flow->setFlowDeviceInIndex(zflow->inIndex);
      else
	flow->setFlowDeviceOutIndex(zflow->inIndex);
    }

    if(zflow->outIndex != 0) {
      if(src2dst_direction)
	flow->setFlowDeviceOutIndex(zflow->outIndex);
      else
	flow->setFlowDeviceInIndex(zflow->outIndex);
    }
  }

#ifdef NTOPNG_PRO
  if ((unique_source_id != 0)/* && (!isSubInterface())*/) {

#if 0
    ntop->getTrace()->traceEvent(TRACE_NORMAL, "unique_source_id=%u, inIndex=%u, outIndex=%u, exporter_device_ip=%u, nprobe_ip=%u [%u / %u]",
				 unique_source_id,
				 flow ? flow->getFlowDeviceInIndex()  : zflow->inIndex,
				 flow ? flow->getFlowDeviceOutIndex() : zflow->outIndex,
				 zflow->exporter_device_ip, zflow->nprobe_ip,
				 zflow->inIndex, zflow->outIndex);
#endif

    if (!flow_interfaces_stats->checkExporters(unique_source_id,
					       flow ? flow->getFlowDeviceInIndex()  : zflow->inIndex,
					       flow ? flow->getFlowDeviceOutIndex() : zflow->outIndex,
					       zflow->exporter_device_ip, zflow->nprobe_ip)) {
      static bool shown = false;

      if (!shown) {
        ntop->getTrace()->traceEvent(TRACE_NORMAL, "Flow dropped due to license limitations");

        ntop->getTrace()->traceEvent(TRACE_NORMAL,
				     "Exporters: %d/%d max | Exporter Interfaces: %d/%d max",
				     ntop->getNumFlowExporters(), get_max_num_flow_exporters(),
				     ntop->getNumFlowExportersInterfaces(),
				     get_max_num_flow_exporters_interfaces());

        ntop->getTrace()->traceEvent(TRACE_NORMAL,
				     "Discarded [unique_source_id: %u];device_ip: %u][probe_ip: "
				     "%u][iface: %u->%u]",
				     unique_source_id, zflow->exporter_device_ip, zflow->nprobe_ip,
				     zflow->inIndex, zflow->outIndex);

        ntop->getRedis()->set(EXPORTERS_EXCEEDED_LIMITS_KEY, "1");
        shown = true;
      }

      return false;
    }
  }
#endif

  if (flow == NULL) return (false);

  if (zflow->absolute_packet_octet_counters) {
    /* Ajdust bytes and packets counters if the zflow update contains absolute
       values.

       As flows may arrive out of sequence, special care is needed to avoid
       counting absolute values multiple times.

       http://www.cisco.com/c/en/us/td/docs/security/asa/special/netflow/guide/asa_netflow.html#pgfId-1331296
       Different events in the life of a flow may be issued in separate NetFlow
       packets and may arrive out-of-order at the collector. For example, the
       packet containing a flow teardown event may reach the collector before
       the packet containing a flow creation event. As a result, it is important
       that collector applications use the Event Time field to correlate events.
    */

    u_int64_t in_cur_pkts = src2dst_direction ? flow->get_packets_cli2srv()
                                              : flow->get_packets_srv2cli(),
              in_cur_bytes = src2dst_direction ? flow->get_bytes_cli2srv()
                                               : flow->get_bytes_srv2cli();
    u_int64_t out_cur_pkts = src2dst_direction ? flow->get_packets_srv2cli()
                                               : flow->get_packets_cli2srv(),
              out_cur_bytes = src2dst_direction ? flow->get_bytes_srv2cli()
                                                : flow->get_bytes_cli2srv();
    bool out_of_sequence = false;

    if (zflow->in_pkts) {
      if (zflow->in_pkts >= in_cur_pkts)
        zflow->in_pkts -= in_cur_pkts;
      else
        zflow->in_pkts = 0, out_of_sequence = true;

      update_seen = true;
    }

    if (zflow->in_bytes) {
      if (zflow->in_bytes >= in_cur_bytes)
        zflow->in_bytes -= in_cur_bytes;
      else
        zflow->in_bytes = 0, out_of_sequence = true;
    }

    if (zflow->out_pkts) {
      if (zflow->out_pkts >= out_cur_pkts)
        zflow->out_pkts -= out_cur_pkts;
      else
        zflow->out_pkts = 0, out_of_sequence = true;

      update_seen = true;
    }

    if (zflow->out_bytes) {
      if (zflow->out_bytes >= out_cur_bytes)
        zflow->out_bytes -= out_cur_bytes;
      else
        zflow->out_bytes = 0, out_of_sequence = true;
    }

    if (out_of_sequence) {
#ifdef ABSOLUTE_COUNTERS_DEBUG
      char flowbuf[265];
      ntop->getTrace()->traceEvent(
          TRACE_WARNING,
          "A flow received an update with absolute values smaller than the "
          "current values. "
          "[in_bytes: %u][in_cur_bytes: %u][out_bytes: %u][out_cur_bytes: %u]"
          "[in_pkts: %u][in_cur_pkts: %u][out_pkts: %u][out_cur_pkts: %u]\n"
          "%s",
          zflow->in_bytes, in_cur_bytes, zflow->out_bytes, out_cur_bytes,
          zflow->in_pkts, in_cur_pkts, zflow->out_pkts, out_cur_pkts,
          flow->print(flowbuf, sizeof(flowbuf)));
#endif
    }
  }

  if (zflow->tcp.clientNwLatency.tv_sec || zflow->tcp.clientNwLatency.tv_usec) {
    /* As nProbe divides RTT by 2, we need to double it */
    zflow->tcp.clientNwLatency.tv_sec *= 2, zflow->tcp.clientNwLatency.tv_usec *= 2;
    flow->setFlowRTT(&zflow->tcp.clientNwLatency, src2dst_direction);
  }
  
  if (zflow->tcp.serverNwLatency.tv_sec || zflow->tcp.serverNwLatency.tv_usec) {
    /* As nProbe divides RTT by 2, we need to double it */
    zflow->tcp.serverNwLatency.tv_sec *= 2, zflow->tcp.serverNwLatency.tv_usec *= 2;
    flow->setFlowRTT(&zflow->tcp.serverNwLatency, !src2dst_direction);
  }
  
  flow->setEndReason(zflow->getEndReason());
  if (zflow->tcp.in_window)
    flow->setFlowTcpWindow(zflow->tcp.in_window, src2dst_direction);
  if (zflow->tcp.out_window)
    flow->setFlowTcpWindow(zflow->tcp.out_window, !src2dst_direction);

  if (zflow->getFlowVerdict() == 2 /* DROP */) flow->setDropVerdict(DROP_REASON_PROBE_VERDICT);

  flow->setRisk(zflow->getRisk());
  flow->setTOS(zflow->src_tos, true), flow->setTOS(zflow->dst_tos, false);
  flow->setRTT();

  if (zflow->getWLANSSID())
    flow->setWLANInfo(zflow->getWLANSSID(), zflow->getWTPMACAddress());

  if (zflow->getL7JSON())
    flow->setL7JSON(zflow->getL7JSON());

  if (zflow->getSIPCallId()) flow->setSIPCallId(zflow->getSIPCallId());

  if (zflow->isSwapped()) flow->set_swap_done();

  flow->setFlowSource(zflow->getFlowSource());

  if (src2dst_direction && (zflow->tcp.applLatencyMsec != 0))
    flow->setFlowApplLatency(zflow->tcp.applLatencyMsec);

  /* Update process and container info */
  if (zflow->hasParsedeBPF()) {
    bool swap_direction = ((ntohs(zflow->src_port) == flow->get_cli_port()) &&
                           (ntohs(zflow->dst_port) == flow->get_srv_port()))
                              ? false
                              : true;

    flow->setParsedeBPFInfo(zflow, swap_direction);

    /* Now refresh the flow last seen so it will stay active as long as we keep
     * receiving updates */
    flow->updateSeen();
  }

  flow->setFlowDevice(zflow->exporter_device_ip, zflow->observationPointId,
                      src2dst_direction ? zflow->inIndex : zflow->outIndex,
                      src2dst_direction ? zflow->outIndex : zflow->inIndex);

#ifdef MAC_DEBUG
  char bufm1[32], bufm2[32];

  ntop->getTrace()->traceEvent(TRACE_NORMAL, "Processing Flow [src mac: %s][dst mac: %s][src2dst: %i]",
			       Utils::formatMac(srcMac->get_mac(), bufm1, sizeof(bufm1)),
			       Utils::formatMac(dstMac->get_mac(), bufm2, sizeof(bufm2)),
			       (src2dst_direction) ? 1 : 0);
#endif

  in_pkts = zflow->pkt_sampling_rate * zflow->in_pkts,
  in_bytes = zflow->pkt_sampling_rate * zflow->in_bytes,
  out_pkts = zflow->pkt_sampling_rate * zflow->out_pkts,
  out_bytes = zflow->pkt_sampling_rate * zflow->out_bytes;

  /* Update MAC stats
     Note: do not use src2dst_direction to inc the stats as
     in_bytes/in_pkts and out_bytes/out_pkts are already relative to the current
     source mac (srcMac) and destination mac (dstMac)
  */
  if (likely(srcMac != NULL)) {
    srcMac->incSentStats(getTimeLastPktRcvd(), in_pkts, in_bytes);
    srcMac->incRcvdStats(getTimeLastPktRcvd(), out_pkts, out_bytes);

    srcMac->setSourceMac();
  }

  if (likely(dstMac != NULL)) {
    dstMac->incSentStats(getTimeLastPktRcvd(), out_pkts, out_bytes);
    dstMac->incRcvdStats(getTimeLastPktRcvd(), in_pkts, in_bytes);
  }

  if (zflow->l4_proto == IPPROTO_TCP) {
    if (zflow->tcp.client_tcp_flags || zflow->tcp.server_tcp_flags) {
      /* There's a breadown between client and server TCP flags */
      if (zflow->tcp.client_tcp_flags)
        flow->updateTcpFlags(&now_tv, zflow->tcp.client_tcp_flags,
                             src2dst_direction, new_flow);

      if (zflow->tcp.server_tcp_flags)
        flow->updateTcpFlags(&now_tv, zflow->tcp.server_tcp_flags,
                             !src2dst_direction, new_flow);

      if (zflow->tcp.tcp_flags && (zflow->tcp.client_tcp_flags == 0) &&
          (zflow->tcp.server_tcp_flags == 0)) {
        /* TCP flags are cumulative and set only if client/server flags are zero
         */

        flow->updateTcpFlags(&now_tv, zflow->tcp.tcp_flags, src2dst_direction,
                             new_flow);
      }
    }

    if (zflow->tcp.client_tcp_flags && zflow->tcp.server_tcp_flags)
      flow->calculateConnectionState(true);

    flow->updateTcpSeqIssues(zflow);

    flow->incTcpBadStats(true, flow->get_cli_host(), flow->get_srv_host(), this,
                         zflow->tcp.ooo_in_pkts, zflow->tcp.retr_in_pkts,
                         zflow->tcp.lost_in_pkts, 0 /* TODO: add keepalive */);
    flow->incTcpBadStats(false, flow->get_cli_host(), flow->get_srv_host(),
                         this, zflow->tcp.ooo_out_pkts,
                         zflow->tcp.retr_out_pkts, zflow->tcp.lost_out_pkts,
                         0 /* TODO: add keepalive */);
  }

  if(flow->addFlowStats(new_flow, src2dst_direction, in_pkts, in_bytes, 0,
			out_pkts, out_bytes, 0,
			zflow->pkt_sampling_rate * zflow->in_fragments,
			zflow->pkt_sampling_rate * zflow->out_fragments,
			zflow->first_switched, zflow->last_switched) == false) {
    if(update_seen) {
      flow->updateSeen(zflow->last_switched);
      flow->callFlowUpdate(zflow->last_switched);    
    }
  }

  if (zflow->getPreNATSrcIp()) {
    /* Add Pre-Post NAT src/dst IPv4 */
    flow->addPrePostNATIPv4(zflow->getPreNATSrcIp(), zflow->getPreNATDstIp(),
                            zflow->getPostNATSrcIp(), zflow->getPostNATDstIp());

    /* Add Pre-Post NAT src/dst Ports */
    flow->addPrePostNATPort(zflow->getPreNATSrcPort(), zflow->getPreNATDstPort(),
			    zflow->getPostNATSrcPort(), zflow->getPostNATDstPort());
  }

  if (!flow->isDetectionCompleted()) {
    ndpi_protocol p = Flow::ndpiUnknownProtocol;
    ndpi_protocol guessed_protocol = Flow::ndpiUnknownProtocol;

    p.proto.app_protocol = zflow->l7_proto.proto.app_protocol;
    p.proto.master_protocol = zflow->l7_proto.proto.master_protocol;
    p.category = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED;

    /*
      First, there's an attempt to guess the protocol so that custom protocols
      defined in ntopng will still be applied to the protocols detected by
      nprobe.
    */
    if (zflow->src_ip.isIPv4()) {
      guessed_protocol = ndpi_guess_undetected_protocol_v4(
          get_ndpi_struct(), flow->get_ndpi_flow(), flow->get_protocol(),
          ntohl(zflow->src_ip.get_ipv4()), ntohs(zflow->src_port),
          ntohl(zflow->dst_ip.get_ipv4()), ntohs(zflow->dst_port));
    } else {
      /* IPv6: use protcol guess only based on ports/protocol */

      guessed_protocol = ndpi_guess_undetected_protocol_v4(
          get_ndpi_struct(), flow->get_ndpi_flow(), flow->get_protocol(), 0,
          ntohs(zflow->src_port), 0, ntohs(zflow->dst_port));
    }

    guessed_protocol.proto.app_protocol = ndpi_map_ndpi_id_to_user_proto_id(
        get_ndpi_struct(), guessed_protocol.proto.app_protocol);
    guessed_protocol.proto.master_protocol = ndpi_map_ndpi_id_to_user_proto_id(
        get_ndpi_struct(), guessed_protocol.proto.master_protocol);

    flow->setFlowVerdict(zflow->getFlowVerdict());
    flow->setJSONInfo(zflow->getAdditionalFieldsJSON());
    flow->setTLVInfo(zflow->getAdditionalFieldsTLV());

    /* This is now incremented in Flow::hosts_periodic_stats_update
     * by calling iface->incLocalStats
    flow->updateInterfaceLocalStats(src2dst_direction,
                                  zflow->pkt_sampling_rate*(zflow->in_pkts+zflow->out_pkts),
                                  zflow->pkt_sampling_rate*(zflow->in_bytes+zflow->out_bytes));
  */

#ifdef NTOPNG_PRO
    if (zflow->getCustomApp().pen) {
      flow->setCustomApp(zflow->getCustomApp());

      if (custom_app_stats ||
          (custom_app_stats = new (std::nothrow) CustomAppStats(this))) {
        custom_app_stats->incStats(
            zflow->getCustomApp().remapped_app_id,
            zflow->pkt_sampling_rate * (zflow->in_bytes + zflow->out_bytes));
      }
    }
#endif

    /*
      Set detected nDPI protocol *before* updating L7 info
      like "flow->updateDNS" as they check the actual protocol
     */

    if (/* If nprobe acts is in collector-passthrough mode L7_PROTO is not
           present, using the protocol guess on the ntopng side is desirable in
           this case */
        ((zflow->l7_proto.proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) &&
         (zflow->l7_proto.proto.master_protocol == NDPI_PROTOCOL_UNKNOWN)) ||
        /* 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 */
        (guessed_protocol.proto.app_protocol >= NDPI_MAX_SUPPORTED_PROTOCOLS)) {
      p = guessed_protocol;
    }

    if (zflow->hasParsedeBPF()) {
      /* nProbe Agent does not perform nDPI detection*/
      p.proto.master_protocol = guessed_protocol.proto.master_protocol;
      p.proto.app_protocol = guessed_protocol.proto.app_protocol;
    }

    /* Now, depending on the q and on the zflow, there's an additional check
       to possibly override the category, according to the rules specified
       in ntopng */
    flow->fillZMQFlowCategory(&p);

    /* Here everything is setup and it is possible to set the actual protocol to
     * the flow */
    flow->setDetectedProtocol(p, src2dst_direction);

    /*
      Parse flow info into the corresponding element (without overriding
      plugin-generated data:
      - When nProbe has plugins enabled, plugin data is taken
      - When nProbe has no plugins enabled, then nDPI data is taken
    */
    if (zflow->getL7Info() && zflow->getL7Info()[0]) {
      if (flow->isDNS()) {
        if (!zflow->getDNSQuery()) zflow->setDNSQuery(zflow->getL7Info());
      } else if (flow->isHTTP()) {
        if (!zflow->getHTTPsite()) {
          zflow->setHTTPsite(zflow->getL7Info());
          if (flow->get_cli_host())
            flow->get_cli_host()->incrVisitedWebSite(zflow->getHTTPsite());
        }
      } else if (flow->isTLS()) {
        if (!zflow->getTLSserverName()) {
          zflow->setTLSserverName(zflow->getL7Info());
          if (flow->get_cli_host())
            flow->get_cli_host()->incrVisitedWebSite(zflow->getTLSserverName());
        }
      }
    }

    flow->setErrorCode(zflow->getL7ErrorCode());
    flow->setConfidence(zflow->getConfidence());
    flow->setNdpiConfidence(zflow->getConfidence());

    if(flow->get_protocol() == IPPROTO_ICMP)
      flow->setICMPTypeCode(zflow->icmp_type_code);
    
    if (flow->isDNS()) flow->updateDNS(zflow);
    if (flow->isHTTP()) flow->updateHTTP(zflow);
    if (flow->isTLS()) flow->updateTLS(zflow);

    if (flow->isSMTP()) {
      if (zflow->getSMTPMailFrom())
        flow->setSMTPMailFrom(zflow->getSMTPMailFrom());
      if (zflow->getSMTPRcptTo()) flow->setSMTPRcptTo(zflow->getSMTPRcptTo());
    }

    if (flow->isDHCP()) {
      if (zflow->getDHCPClientName()) {
        flow->setDHCPHostName(zflow->getDHCPClientName());
      }
    }

    if (zflow->getRiskName()) flow->setFlowRiskName(zflow->getRiskName());

    if (zflow->getBittorrentHash())
      flow->setBTHash(zflow->getBittorrentHash(true));

    if (zflow->vrfId) flow->setVRFid(zflow->vrfId);
    if (zflow->src_as) flow->setSrcAS(zflow->src_as);
    if (zflow->dst_as) flow->setDstAS(zflow->dst_as);

    if (zflow->prev_adjacent_as)
      flow->setPrevAdjacentAS(zflow->prev_adjacent_as);
    if (zflow->next_adjacent_as)
      flow->setNextAdjacentAS(zflow->next_adjacent_as);

    flow->setQoE(zflow->getQoESrc2Dst(), zflow->getQoEDst2Src());

    if(zflow->getOSHint() != ndpi_os_unknown) {
      if(flow->get_cli_host() != NULL)
	flow->get_cli_host()->setnDPIOS(zflow->getOSHint());
    }    
    
    if (zflow->getJA4cHash()) flow->updateJA4C(zflow->getJA4cHash());

    if (zflow->getRiskInfo()) {
      json_object *o, *obj;
      enum json_tokener_error jerr = json_tokener_success;

      flow->setJSONRiskInfo(zflow->getRiskInfo());

      // ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s]",
      // zflow->flow_risk_info);

      /*
        We use riskInfo to grab some flow attributes
        to enrich the memory flor representation
      */
      if ((o = json_tokener_parse_verbose(zflow->getRiskInfo(), &jerr)) != NULL) {
        /* NOTE: keep in sync with  FlowRisk::ignoreRisk() */
        if (json_object_object_get_ex(o, "6" /* NDPI_TLS_SELFSIGNED_CERTIFICATE */, &obj)) {
          const char *issuerDN = json_object_get_string(obj);
	  
          if (flow->isTLS()) flow->setTLSCertificateIssuerDN((char *)issuerDN);
        } else if (json_object_object_get_ex(o, "16" /* NDPI_SUSPICIOUS_DGA_DOMAIN */, &obj)) {
          const char *dgaDomain = json_object_get_string(obj);
	  
          flow->setDGADomain((char *)dgaDomain);
        }

        json_object_put(o);
      }
    }

    if (zflow->getExternalAlert()) {
      enum json_tokener_error jerr = json_tokener_success;
      json_object *o =
          json_tokener_parse_verbose(zflow->getExternalAlert(), &jerr);

      if (o) flow->setExternalAlert(o);
    }

    flow->updateSuspiciousDGADomain();
  }

#ifdef NTOPNG_PRO
  if (unique_source_id) {
    if (!flow_interfaces_stats)
      flow_interfaces_stats = new (std::nothrow) FlowInterfacesStats();

    if (flow_interfaces_stats) {
      flow_interfaces_stats->incStats(now, unique_source_id, zflow->inIndex,
                                      flow->getStatsProtocol(), out_pkts,
                                      out_bytes, in_pkts, in_bytes);

      /* If the SNMP device is actually an host with an SNMP agent, then traffic
         can enter and leave it from the same interface (think to a management
         interface). For this reason it is important to check the outIndex and
         increase its counters only if it is different from inIndex to avoid
         double counting. */

      if (zflow->outIndex != zflow->inIndex)
        flow_interfaces_stats->incStats(now, unique_source_id, zflow->outIndex,
                                        flow->getStatsProtocol(), in_pkts,
                                        in_bytes, out_pkts, out_bytes);
    }
  }
#endif

  /* Do not put incStats before guessing the flow protocol */
  u_int16_t eth_type = srcIP.isIPv4() ? ETHERTYPE_IP : ETHERTYPE_IPV6;

#if 0
  ntop->getTrace()->traceEvent(TRACE_WARNING, "%s(%d) [in: %u][out: %u]",
			       (zflow->direction == 0 /* RX */) ? "RX" : "TX",
			       zflow->direction,
			       zflow->in_bytes, zflow->out_bytes);
#endif

#ifdef DEBUG
  char a[32], b[32];

  ntop->getTrace()->traceEvent(
      TRACE_WARNING, "Direction: %u [ntop: %s][%s -> %s]", zflow->direction,
      flow->isLocalToRemote() ? "L->R" : "R->L",
      flow->get_cli_ip_addr()->print(a, sizeof(a)),
      flow->get_srv_ip_addr()->print(b, sizeof(b)));
#endif

  if (zflow->direction == UNKNOWN_FLOW_DIRECTION) {
    if (flow->isLocalToRemote())
      zflow->direction = 1 /* TX */;
    else
      zflow->direction = 0 /* RX */;
  }

  if (zflow->direction == 0 /* RX */) {
    if (zflow->in_pkts)
      incStats(true /* Ingress */, now, eth_type, flow->getStatsProtocol(),
               flow->get_protocol_category(), zflow->l4_proto, in_bytes,
               in_pkts);
    if (zflow->out_pkts)
      incStats(false /* Egress */, now, eth_type, flow->getStatsProtocol(),
               flow->get_protocol_category(), zflow->l4_proto, out_bytes,
               out_pkts);
  } else { /* TX */
    if (zflow->out_bytes)
      incStats(true /* Ingress */, now, eth_type, flow->getStatsProtocol(),
               flow->get_protocol_category(), zflow->l4_proto, out_bytes,
               out_pkts);
    if (zflow->in_pkts)
      incStats(false /* Egress */, now, eth_type, flow->getStatsProtocol(),
               flow->get_protocol_category(), zflow->l4_proto, in_bytes,
               in_pkts);
  }

#ifdef NTOPNG_PRO
  /* Check if direct flow dump is enabled */
  if (ntop->getPrefs()->do_dump_flows_direct() &&
      (ntop->getPrefs()->is_flows_dump_enabled()
#ifdef HAVE_ZMQ
#ifndef HAVE_NEDGE
       || ntop->get_export_interface()
#endif
#endif
           )) {
    /* Dump flow */
    flow->dump(zflow->last_switched, true /* last dump before free */);
  }
#endif

  /* purge is actually performed at most one time every FLOW_PURGE_FREQUENCY */
  // purgeIdle(zflow->last_switched);

  return (true);
}

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

#if 0
bool ParserInterface::isProbingFlow(const ParsedFlow *zflow) {
  switch (zflow->l4_proto) {
    case IPPROTO_TCP: {
      /* zflow->tcp.tcp_flags are, according to the specs, the 'Cumulative of
         all the TCP flags seen for this flow'. Hence, for bi-directional flows,
         they are the locigal OR of client and server flags whereas for
         mono-directional flows they are the logical OR of client-to-server
         flags. */

      /* A SYN only seen by the client is very likely a scan. Any established
         TCP connection involves at least an ACK from both parties as this is
         also part of the initial three-way-handshake. */
      if ((zflow->tcp.client_tcp_flags & TCP_SCAN_MASK) == TH_SYN ||
          (zflow->tcp.tcp_flags & TCP_SCAN_MASK) == TH_SYN)
        return true;

      /* A client SYN+RST can be found when a scan finds the destination port
         OPEN. For example, using nmap, a scan which finds destination port 22
         open involves the following 3 packets:
         1. client sends SYN to server port 22
         2. server responds with SYN+ACK as its port 22 is open and it is
         willing to establish the connection
         3. client immediately closes the connection with RST

         See: https://nmap.org/book/synscan.html */
      if ((zflow->tcp.client_tcp_flags & TCP_SCAN_MASK) == (TH_SYN | TH_RST) ||
          (zflow->tcp.tcp_flags & TCP_SCAN_MASK) == (TH_SYN | TH_RST))
        return true;

      /* A server RST+ACK can be found when a scan finds the destination port
         CLOSED. For example, using nmap, a scan which finds destination port 22
         closed involves the following 2 packets:
         1. client sends SYN to server port 22
         2. server responds with SYN+RST because either its port is closed or is
         not willing to establish the connection */
      if ((zflow->tcp.server_tcp_flags & TCP_SCAN_MASK) == (TH_RST | TH_ACK) ||
          (zflow->tcp.tcp_flags & TCP_SCAN_MASK) == (TH_RST | TH_ACK))
        return true;

      /* When only a RST is seen from the server, it means no data has been
         exchanged and the server is not willing to communicate with the client
         which is very likely a scanner */
      if ((zflow->tcp.server_tcp_flags & TCP_SCAN_MASK) == TH_RST ||
          (zflow->tcp.tcp_flags & TCP_SCAN_MASK) == TH_RST)
        return true;
    } break;
    case IPPROTO_UDP: {
      if (zflow->in_pkts + zflow->out_pkts <= 1) return true;
    } break;
    default:
      break;
  }

  return false;
}
#endif

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

void ParserInterface::reloadCompanions() {
  char key[CONST_MAX_LEN_REDIS_KEY];
  int num_companions;
  char **companions = NULL;
  bool found;

  if (!ntop->getRedis()) return;

  snprintf(key, sizeof(key), CONST_IFACE_COMPANIONS_SET, get_id());
  num_companions = ntop->getRedis()->smembers(key, &companions);

  companions_lock.lock(__FILE__, __LINE__);

  if (num_companion_interfaces > 0) {
    /* Check and possibly remove old companions */
    for (int i = 0; i < MAX_NUM_COMPANION_INTERFACES; i++) {
      if (!companion_interfaces[i]) continue;

      found = false;
      for (int j = 0; j < num_companions; j++) {
        if (companion_interfaces[i]->get_id() == atoi(companions[j])) {
          found = true;
          break;
        }
      }

      if (!found) {
        // ntop->getTrace()->traceEvent(TRACE_NORMAL, "Removed companion
        // interface [interface: %s][companion: %s]", get_name(),
        // companion_interfaces[i]->get_name());
        companion_interfaces[i] = NULL;
        num_companion_interfaces--;
      }
    }
  }

  if (num_companions > 0) {
    /* Check and possibly add new companions */
    for (int i = 0; i < num_companions; i++) {
      found = false;
      for (int j = 0; j < MAX_NUM_COMPANION_INTERFACES; j++) {
        if (companion_interfaces[j] &&
            companion_interfaces[j]->get_id() == atoi(companions[i])) {
          found = true;
          break;
        }
      }

      if (!found) {
        if (num_companion_interfaces < MAX_NUM_COMPANION_INTERFACES) {
          for (int j = 0; j < MAX_NUM_COMPANION_INTERFACES; j++) {
            if (!companion_interfaces[j]) {
              companion_interfaces[j] =
                  ntop->getInterfaceById(atoi(companions[i]));

              if (companion_interfaces[j]) {
                num_companion_interfaces++;
                // ntop->getTrace()->traceEvent(TRACE_NORMAL, "Added new
                // companion interface [interface: %s][companion: %s]",
                // 			     get_name(),
                // companion_interfaces[j]->get_name());
              }

              break;
            }
          }
        } else
          ntop->getTrace()->traceEvent(
              TRACE_ERROR,
              "Too many companion interfaces defined [interface: %s]",
              get_name());
      }

      free(companions[i]);
    }
  }

  companions_lock.unlock(__FILE__, __LINE__);

  if (companions) free(companions);

  // ntop->getTrace()->traceEvent(TRACE_NORMAL, "Companion interface reloaded
  // [interface: %s][companion: %s]", 			       get_name(),
  // companion_interface ? companion_interface->get_name() : "NULL");
}

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

void ParserInterface::deliverFlowToCompanions(ParsedFlow *const flow) {
  if (num_companion_interfaces > 0) {
    NetworkInterface *flow_interface =
        flow->ifname ? ntop->getNetworkInterface(flow->ifname) : NULL;

    for (int i = 0; i < MAX_NUM_COMPANION_INTERFACES; i++) {
      NetworkInterface *cur_companion = companion_interfaces[i];

      if (!cur_companion) continue;

      // ntop->getTrace()->traceEvent(TRACE_NORMAL,
      //         "Trying to enqueue flow to companion interface [from: %s][to:
      //         %s]", get_name(), cur_companion->get_name());

      if (cur_companion->isTrafficMirrored() ||
          !cur_companion->isPacketInterface()) {
        cur_companion->enqueueFlowToCompanion(flow,
                                              true /* Skip loopback traffic */);
      } else if (cur_companion == flow_interface) {
        cur_companion->enqueueFlowToCompanion(
            flow, false /* do NOT skip loopback traffic */);
      }
    }
  }
}

#endif
