#include "reader_util.h"
#include "ndpi_api.h"

#include <pcap/pcap.h>

#include <errno.h>
#include <stdint.h>
#include <stdio.h>

struct ndpi_workflow_prefs *prefs = NULL;

int nDPI_LogLevel = 0;
char *_debug_protocols = NULL;
u_int32_t current_ndpi_memory = 0, max_ndpi_memory = 0;
u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0;
u_int8_t enable_flow_stats = 0;
u_int8_t human_readeable_string_len = 5;
u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */;

int bufferToFile(const char * name, const uint8_t *Data, size_t Size) {
  FILE * fd;
  if (remove(name) != 0) {
    if (errno != ENOENT) {
      perror("remove failed");
      return -1;
    }
  }
  fd = fopen(name, "wb");
  if (fd == NULL) {
    perror("open failed");
    return -2;
  }
  if (fwrite (Data, 1, Size, fd) != Size) {
    fclose(fd);
    return -3;
  }
  fclose(fd);
  return 0;
}

int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  pcap_t * pkts;
  const u_char *pkt;
  struct pcap_pkthdr *header;
  int r;
  char errbuf[PCAP_ERRBUF_SIZE];
  NDPI_PROTOCOL_BITMASK all;
  char * pcap_path = tempnam("/tmp", "fuzz-ndpi-reader");

  if (prefs == NULL) {
    prefs = calloc(sizeof(struct ndpi_workflow_prefs), 1);
    if (prefs == NULL) {
      //should not happen
      return 1;
    }
    prefs->decode_tunnels = 1;
    prefs->num_roots = 16;
    prefs->max_ndpi_flows = 1024;
    prefs->quiet_mode = 0;
  }
  bufferToFile(pcap_path, Data, Size);

  pkts = pcap_open_offline(pcap_path, errbuf);
  if (pkts == NULL) {
    remove(pcap_path);
    free(pcap_path);
    return 0;
  }
  if (ndpi_is_datalink_supported(pcap_datalink(pkts)) == 0)
  {
    /* Do not fail if the datalink type is not supported (may happen often during fuzzing). */
    pcap_close(pkts);
    remove(pcap_path);
    free(pcap_path);
    return 0;
  }
  struct ndpi_workflow * workflow = ndpi_workflow_init(prefs, pkts);
  // enable all protocols
  NDPI_BITMASK_SET_ALL(all);
  ndpi_set_protocol_detection_bitmask2(workflow->ndpi_struct, &all);
  memset(workflow->stats.protocol_counter, 0,
	 sizeof(workflow->stats.protocol_counter));
  memset(workflow->stats.protocol_counter_bytes, 0,
	 sizeof(workflow->stats.protocol_counter_bytes));
  memset(workflow->stats.protocol_flows, 0,
	 sizeof(workflow->stats.protocol_flows));
  ndpi_finalize_initialization(workflow->ndpi_struct);

  header = NULL;
  r = pcap_next_ex(pkts, &header, &pkt);
  while (r > 0) {
    if(header->caplen >= 42 /* ARP+ size */) {
      /* allocate an exact size buffer to check overflows */
      uint8_t *packet_checked = malloc(header->caplen);

      if(packet_checked) {
	ndpi_risk flow_risk;
	
	memcpy(packet_checked, pkt, header->caplen);
	ndpi_workflow_process_packet(workflow, header, packet_checked, &flow_risk, NULL);
	free(packet_checked);
      }
    }

    r = pcap_next_ex(pkts, &header, &pkt);
  }
  ndpi_workflow_free(workflow);
  pcap_close(pkts);

  remove(pcap_path);
  free(pcap_path);

  return 0;
}

#ifdef BUILD_MAIN
int main(int argc, char ** argv)
{
  FILE * pcap_file;
  long pcap_file_size;
  uint8_t * pcap_buffer;
  int test_retval;

  if (argc != 2) {
    fprintf(stderr, "usage: %s: [pcap-file]\n",
            (argc > 0 ? argv[0] : "fuzz_ndpi_reader_with_main"));
    return 1;
  }

  pcap_file = fopen(argv[1], "r");
  if (pcap_file == NULL) {
    perror("fopen failed");
    return 1;
  }

  if (fseek(pcap_file, 0, SEEK_END) != 0) {
    perror("fseek(SEEK_END) failed");
    fclose(pcap_file);
    return 1;
  }

  pcap_file_size = ftell(pcap_file);
  if (pcap_file_size < 0) {
    perror("ftell failed");
    fclose(pcap_file);
    return 1;
  }

  if (fseek(pcap_file, 0, SEEK_SET) != 0) {
    perror("fseek(0, SEEK_SET)  failed");
    fclose(pcap_file);
    return 1;
  }

  pcap_buffer = malloc(pcap_file_size);
  if (pcap_buffer == NULL) {
    perror("malloc failed");
    fclose(pcap_file);
    return 1;
  }

  if (fread(pcap_buffer, sizeof(*pcap_buffer), pcap_file_size, pcap_file) != (size_t)pcap_file_size) {
    perror("fread failed");
    fclose(pcap_file);
    free(pcap_buffer);
    return 1;
  }

  test_retval = LLVMFuzzerTestOneInput(pcap_buffer, pcap_file_size);
  fclose(pcap_file);
  free(pcap_buffer);

  return test_retval;
}
#endif