/*

Copyright (C) 2000 - 2003 Christian Kreibich <christian@whoop.org>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <libnd.h>

#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>

#define TEST_TRACE "test.trace"


static gboolean
test_jump(LND_Trace *trace)
{
  pcapnav_result_t jump_result;
  off_t offset, offset_good;
  gboolean result;

  printf("Testing navigation in space.\n");
  jump_result = libnd_tpm_goto_fraction(trace->tpm, 0.5);

  if (jump_result != PCAPNAV_DEFINITELY && jump_result != PCAPNAV_PERHAPS)
    return FALSE;

  offset_good = 189603;
  offset = pcapnav_get_offset(trace->tpm->current->pcn);
  result = (offset_good == offset);

  printf("Jump should result in offset %lu, result: %lu, correct: %s\n\n",
	 (long unsigned) offset_good, (long unsigned) offset, result ? "yes" : "no");

  return result;
}


static gboolean
test_jump_time(LND_Trace *trace)
{
  struct bpf_timeval ts;
  pcapnav_result_t jump_result;
  off_t offset, offset_good;
  gboolean result;

  printf("Testing navigation in time.\n");

  ts = trace->tpm->base->start_ts;
  ts.tv_sec += 34*60 + 5*60*60; /* This should roughly get us to 13:oo */

  jump_result = libnd_tpm_goto_ts(trace->tpm, &ts);

  if (jump_result != PCAPNAV_DEFINITELY && jump_result != PCAPNAV_PERHAPS)
    {
      printf("Jump failed\n");
      return FALSE;
    }

  offset_good = 47843;
  offset = pcapnav_get_offset(trace->tpm->current->pcn);
  result = (offset_good = offset);

  printf("Jump should result in offset %lu, result: %lu, correct: %s\n\n",
	 (long unsigned) offset_good, (long unsigned) offset, result ? "yes" : "no");

  return result;
}


static gboolean
test_it_loaded_packets(LND_Trace *trace)
{
  LND_PacketIterator pit;
  struct bpf_timeval ts, ts_correct;
  gboolean result;

  printf("Iteration of loaded packets:\n");

  libnd_tpm_load_packets(trace->tpm);

  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_PART_R); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      LND_Packet *packet = libnd_pit_get(&pit);
      ts = packet->ph.ts;
    }

  ts_correct.tv_sec  = 1038584605;
  ts_correct.tv_usec = 365030;

  result = (ts.tv_sec == ts_correct.tv_sec &&
	    ts.tv_usec == ts_correct.tv_usec);
  
  printf("Last loaded packet's timestamp is %lu.%lu, should be %lu.%lu, correct: %s\n\n",
	 (long unsigned)ts.tv_sec,	
	 (long unsigned)ts.tv_usec,
	 (long unsigned)ts_correct.tv_sec,
	 (long unsigned)ts_correct.tv_usec,
	 result ? "yes" : "no");
  
  return result;
}


static gboolean
test_it_blocks(LND_Trace *trace)
{
  pcapnav_result_t jump_result;
  off_t offset;

  printf("Testing block iteration.\n");
  jump_result = libnd_tpm_goto_fraction(trace->tpm, 0.5);
  
  if (jump_result != PCAPNAV_DEFINITELY && jump_result != PCAPNAV_PERHAPS)
    return FALSE;

  printf("Jumped to fraction 0.5\n");
  offset = pcapnav_get_offset(trace->tpm->current->pcn);
  libnd_tpm_load_packets(trace->tpm);

  if (offset != 189603)
    { 
      printf("Offset should be 189603, is %lu -- aborting.\n", (long unsigned) offset);
      return FALSE;
    }

  printf("Jumping to next block\n");
  libnd_tpm_load_next_part(trace->tpm);
  offset = pcapnav_get_offset(trace->tpm->current->pcn);

  if (offset != 354046)
    { 
      printf("Offset should be 354046, is %lu -- aborting.\n", (long unsigned) offset);
      return FALSE;
    }

  printf("Jumping to prev block\n");
  libnd_tpm_load_prev_part(trace->tpm);
  offset = pcapnav_get_offset(trace->tpm->current->pcn);

  if (offset != 258960)
    { 
      printf("Offset should be 258960, is %lu -- aborting.\n", (long unsigned) offset);
      return FALSE;
    }

  return TRUE;
}


static gboolean
test_it_edit_areas_1(void)
{
  LND_Trace *trace;
  LND_TraceArea area_14;
  LND_PacketIterator pit;
  LND_Protocol *proto_ip;
  struct ip *iphdr;
  int num_total = 0, num_ip = 0;
  gboolean result;
  
  if (! (trace = libnd_trace_new(TEST_TRACE)))
    return FALSE;

  /*                   0    1    2    3    4    5
   * 1. modification:  |    |1111|1111|1111|    |
   *
   * result:           |    |1111|1111|1111|    |
   */

  printf("\nTesting editing one trace area.\n");

  if (! (proto_ip = libnd_proto_registry_find(LND_PROTO_LAYER_NET, ETHERTYPE_IP)))
    {
      printf("IP protocol not found.\n");
      return FALSE;
    }

  libnd_trace_area_init_space(&area_14, 0.2, 0.8);
  libnd_trace_set_area(trace, &area_14);
  
  /* Write ones into the IP identifiers: */
  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      num_total++;
      iphdr = (struct ip*) libnd_packet_get_data(libnd_pit_get(&pit), proto_ip, 0);

      if (!iphdr)
	continue;
      
      num_ip++;
      iphdr->ip_src.s_addr = htonl(1);
    }
  
  /* Save result: */
  libnd_trace_save_as(trace, "test-ea1.trace");
  libnd_trace_free(trace);

  result = (num_ip == 423);
  printf("IP packets seen: %i, correct: %s\n",
	 num_ip, result ? "yes" : "no");
  if (!result)
    return result;

  result = (num_total == 423);
  printf("Total packets seen: %i, correct: %s\n",
	 num_total, result ? "yes" : "no");
  if (!result)
    return result;


  return TRUE;
}


static gboolean
test_it_edit_areas_2(void)
{
  LND_Trace *trace;
  LND_TraceArea area_13, area_24;
  LND_PacketIterator pit;
  LND_Protocol *proto_ip;
  struct ip *iphdr;
  int num_total1 = 0, num_ip1 = 0;
  int num_total2 = 0, num_ip2 = 0;
  gboolean result;
  
  if (! (trace = libnd_trace_new(TEST_TRACE)))
    return FALSE;

  /*                   0    1    2    3    4    5
   * 1. modification:  |    |1111|1111|    |    |
   * 2. modification:  |    |    |2222|2222|    |
   *
   * result:           |    |1111|2222|2222|    |
   */

  printf("\nTesting editing two trace areas.\n");

  if (! (proto_ip = libnd_proto_registry_find(LND_PROTO_LAYER_NET, ETHERTYPE_IP)))
    {
      printf("IP protocol not found.\n");
      return FALSE;
    }

  libnd_trace_area_init_space(&area_13, 0.2, 0.6);
  libnd_trace_area_init_space(&area_24, 0.4, 0.8);
  
  libnd_trace_set_area(trace, &area_13);
  
  /* Write ones into the IP identifiers: */
  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      num_total1++;
      iphdr = (struct ip*) libnd_packet_get_data(libnd_pit_get(&pit), proto_ip, 0);

      if (!iphdr)
	continue;
      
      num_ip1++;
      iphdr->ip_src.s_addr = htonl(1);
    }

  libnd_trace_set_area(trace, &area_24);
  
  /* Write ones into the IP identifiers: */
  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      num_total2++;
      iphdr = (struct ip*) libnd_packet_get_data(libnd_pit_get(&pit), proto_ip, 0);

      if (!iphdr)
	continue;
      
      num_ip2++;
      iphdr->ip_src.s_addr = htonl(2);
    }
  
  /* Save result: */
  libnd_trace_save_as(trace, "test-ea2.trace");
  libnd_trace_free(trace);

  result = (num_ip1 == 316);
  printf("IP packets: %i, correct: %s\n",
	 num_ip1, result ? "yes" : "no");
  if (!result)
    return result;

  result = (num_total1 == 316);
  printf("Total packets: %i, correct: %s\n",
	 num_total1, result ? "yes" : "no");
  if (!result)
    return result;


  result = (num_ip2 == 186);
  printf("IP packets: %i, correct: %s\n",
	 num_ip2, result ? "yes" : "no");
  if (!result)
    return result;

  result = (num_total2 == 186);
  printf("Total packets: %i, correct: %s\n",
	 num_total2, result ? "yes" : "no");
  if (!result)
    return result;


  return TRUE;
}


static gboolean
test_it_edit_areas_3(void)
{
  LND_Trace *trace;
  LND_TraceArea area_13, area_24, area_23;
  LND_PacketIterator pit;
  LND_Protocol *proto_ip;
  struct ip *iphdr;
  int num_total1 = 0, num_ip1 = 0;
  int num_total2 = 0, num_ip2 = 0;
  int num_total3 = 0, num_ip3 = 0;
  gboolean result;
  
  if (! (trace = libnd_trace_new(TEST_TRACE)))
    return FALSE;

  /*                   0    1    2    3    4    5
   * 1. modification:  |    |1111|1111|    |    |
   * 2. modification:  |    |    |2222|2222|    |
   * 2. modification:  |    |    |3333|    |    |
   *
   * result:           |    |1111|2222|3333|    |
   */
  printf("\nTesting editing three trace areas.\n");

  if (! (proto_ip = libnd_proto_registry_find(LND_PROTO_LAYER_NET, ETHERTYPE_IP)))
    {
      printf("IP protocol not found.\n");
      return FALSE;
    }

  libnd_trace_area_init_space(&area_13, 0.2, 0.6);
  libnd_trace_area_init_space(&area_24, 0.4, 0.8);
  libnd_trace_area_init_space(&area_23, 0.4, 0.6);
  
  libnd_trace_set_area(trace, &area_13);
  
  /* Write ones into the IP identifiers: */
  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      num_total1++;
      iphdr = (struct ip*) libnd_packet_get_data(libnd_pit_get(&pit), proto_ip, 0);

      if (!iphdr)
	continue;
      
      num_ip1++;
      iphdr->ip_src.s_addr = htonl(1);
    }

  libnd_trace_set_area(trace, &area_24);
  
  /* Write ones into the IP identifiers: */
  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      num_total2++;
      iphdr = (struct ip*) libnd_packet_get_data(libnd_pit_get(&pit), proto_ip, 0);

      if (!iphdr)
	continue;
      
      num_ip2++;
      iphdr->ip_src.s_addr = htonl(2);
    }

  libnd_trace_set_area(trace, &area_23);
  
  /* Write ones into the IP identifiers: */
  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      num_total3++;
      iphdr = (struct ip*) libnd_packet_get_data(libnd_pit_get(&pit), proto_ip, 0);

      if (!iphdr)
	continue;
      
      num_ip3++;
      iphdr->ip_src.s_addr = htonl(3);
    }
  
  /* Save result: */
  libnd_trace_save_as(trace, "test-ea3.trace");
  libnd_trace_free(trace);

  result = (num_ip1 == 316);
  printf("IP packets: %i, correct: %s\n",
	 num_ip1, result ? "yes" : "no");
  if (!result)
    return result;

  result = (num_total1 == 316);
  printf("Total packets: %i, correct: %s\n",
	 num_total1, result ? "yes" : "no");
  if (!result)
    return result;


  result = (num_ip2 == 186);
  printf("IP packets: %i, correct: %s\n",
	 num_ip2, result ? "yes" : "no");
  if (!result)
    return result;

  result = (num_total2 == 186);
  printf("Total packets: %i, correct: %s\n",
	 num_total2, result ? "yes" : "no");
  if (!result)
    return result;


  result = (num_ip3 == 79);
  printf("IP packets: %i, correct: %s\n",
	 num_ip3, result ? "yes" : "no");
  if (!result)
    return result;

  result = (num_total3 == 79);
  printf("Total packets: %i, correct: %s\n",
	 num_total3, result ? "yes" : "no");
  if (!result)
    return result;
  

  return TRUE;
}


static gboolean
test_filters_1_filter(LND_Filter *filter,
		      LND_Packet *packet,
		      LND_Protocol *proto_udp)
{
  struct udphdr *udphdr;

  udphdr = (struct udphdr *) libnd_packet_get_data(packet, proto_udp, 0);

  if (!udphdr)
    return FALSE;

  if (ntohs(udphdr->uh_sport) == 53 ||
      ntohs(udphdr->uh_dport) == 53)
    return TRUE;

  return FALSE;
  filter = NULL;
}


static gboolean
test_filters_1(void)
{
  LND_Trace *trace;
  LND_PacketIterator pit;
  LND_Filter *filter;
  LND_Protocol *proto_udp;
  gboolean result;
  int num_total = 0;
  
  if (! (trace = libnd_trace_new(TEST_TRACE)))
    return FALSE;

  printf("\nTesting read-only filtering.\n");

  if (! (proto_udp = libnd_proto_registry_find(LND_PROTO_LAYER_TRANS, IPPROTO_UDP)))
    {
      printf("UDP protocol not found.\n");
      return FALSE;
    }
  
  filter = libnd_filter_new("DNS Filter", NULL,
			    (LND_FilterFunc) test_filters_1_filter,
			    NULL, NULL, proto_udp);
  libnd_trace_add_filter(trace, filter);
  
  libnd_trace_set_area(trace, NULL);

  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_R); libnd_pit_get(&pit); libnd_pit_next(&pit))
    num_total++;

  libnd_filter_free(filter);
  libnd_trace_free(trace);

  result = (num_total == 516);
  printf("Total DNS packets: %i, correct: %s\n",
	 num_total, result ? "yes" : "no");

  return result;
}


static gboolean
test_filters_2(void)
{
  LND_Trace *trace;
  LND_PacketIterator pit;
  LND_Filter *filter;
  LND_Protocol *proto_udp;
  gboolean result;
  int num_total = 0;
  
  if (! (trace = libnd_trace_new(TEST_TRACE)))
    return FALSE;

  printf("\nTesting read-write filtering: packet drops.\n");

  if (! (proto_udp = libnd_proto_registry_find(LND_PROTO_LAYER_TRANS, IPPROTO_UDP)))
    {
      printf("UDP protocol not found.\n");
      return FALSE;
    }
  
  filter = libnd_filter_new("DNS Filter", NULL,
			    (LND_FilterFunc) test_filters_1_filter,
			    NULL, NULL, proto_udp);
  libnd_trace_add_filter(trace, filter);
  
  libnd_trace_set_area(trace, NULL);

  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      libnd_pit_drop_current(&pit);
      num_total++;
    }

  libnd_trace_save_as(trace, "test-filter2.trace");
  libnd_trace_free(trace);
  libnd_filter_free(filter);

  result = (num_total == 516);
  printf("Total DNS packets: %i, correct: %s\n",
	 num_total, result ? "yes" : "no");

  return result;
}


static gboolean
test_filters_3(void)
{
  LND_Trace *trace;
  LND_PacketIterator pit;
  LND_Filter *filter;
  LND_Protocol *proto_udp;
  gboolean result;
  int num_total = 0;
  
  if (! (trace = libnd_trace_new(TEST_TRACE)))
    return FALSE;

  printf("\nTesting read-write filtering: inserted packets.\n");

  if (! (proto_udp = libnd_proto_registry_find(LND_PROTO_LAYER_TRANS, IPPROTO_UDP)))
    {
      printf("UDP protocol not found.\n");
      return FALSE;
    }
  
  filter = libnd_filter_new("DNS Filter", NULL,
			    (LND_FilterFunc) test_filters_1_filter,
			    NULL, NULL, proto_udp);
  libnd_trace_add_filter(trace, filter);
  
  libnd_trace_set_area(trace, NULL);

  for (libnd_pit_init_mode(&pit, trace, LND_PACKET_IT_AREA_RW); libnd_pit_get(&pit); libnd_pit_next(&pit))
    {
      libnd_tp_write_packet(trace->tpm->current, libnd_pit_get(&pit));
      num_total++;
    }

  libnd_trace_save_as(trace, "test-filter3.trace");
  libnd_trace_free(trace);
  libnd_filter_free(filter);

  result = (num_total == 516);
  printf("Total DNS packets: %i, correct: %s\n",
	 num_total, result ? "yes" : "no");

  return result;
}


int
main(int argc, char **argv)
{
  LND_Trace *trace;
  int i;
  gboolean res_init = FALSE, res_load = FALSE, res_jump = FALSE, res_jump_time = FALSE;
  gboolean res_it_loaded = FALSE, res_it_blocks = FALSE, res_it_areas_1 = FALSE, res_it_areas_2 = FALSE, res_it_areas_3 = FALSE;
  gboolean res_filters_1 = FALSE, res_filters_2 = FALSE, res_filters_3 = FALSE;

  printf("Libnetdude Testsuite\n");
  printf("========================================================================\n");

  do {

    printf("Crossing fingers.\n\n");
    printf("Initializing libnetdude.\n\n");
    if (! libnd_init())
      {
	printf("Initialization failed: %s\n", libnd_pcap_errbuf);
	break;
      }
    res_init = TRUE;

    /* Let's only load 100 packets into memory at any one time: */
    libnd_prefs_set_int_item(LND_DOM_NETDUDE, "num_mem_packets", 100);

    /* Also, make sure we use timestamps in tcpdump output, to get
     * more specific tcpdump output.
     */
    libnd_prefs_set_int_item(LND_DOM_NETDUDE, "tcpdump_print_timestamp", 1);

    libnd_prefs_apply();

    /* Interpret command line args */
    for (i = 1; i < argc; i++)
      {
	if (! strcmp(argv[i], "-d"))
	  {
	    libnd_runtime_options.debug = TRUE;
	    continue;
	  }
	
	if (! strcmp(argv[i], "--debug-limit"))
	  {
	    if (argc < i+1)
	      continue;

	    libnd_runtime_options.debug = TRUE;
	    libnd_runtime_options.calldepth_limit = atoi(argv[i+1]);
	    continue;
	  }
      }
    
    printf("Opening trace.\n\n");
    if (! (trace = libnd_trace_new(TEST_TRACE)))
      break;
    res_load = TRUE;

    if (test_jump_time(trace))
      res_jump_time = TRUE;

    if (test_jump(trace))
      res_jump = TRUE;

    if (test_it_loaded_packets(trace))
      res_it_loaded = TRUE;

    if (test_it_blocks(trace))
      res_it_blocks = TRUE;

    libnd_trace_free(trace);

    if (test_it_edit_areas_1())
      res_it_areas_1 = TRUE;    
    
    if (test_it_edit_areas_2())
      res_it_areas_2 = TRUE;    
    
    if (test_it_edit_areas_3())
      res_it_areas_3 = TRUE;    

    if (test_filters_1())
      res_filters_1 = TRUE;
    
    if (test_filters_2())
      res_filters_2 = TRUE;

    if (test_filters_3())
      res_filters_3 = TRUE;

  } while (0);

  printf("\n\nTest results:\n");
  printf("========================================================================\n");
  printf("[ok] crossing fingers\n");
  printf("[%s] libnetdude initialization\n", res_init ? "ok" : "--");
  printf("[%s] opening trace\n", res_load ? "ok" : "--");
  printf("[%s] jump to timestamp\n", res_jump_time ? "ok" : "--");
  printf("[%s] jump to fraction\n", res_jump ? "ok" : "--");
  printf("[%s] loaded packet iteration\n", res_it_loaded ? "ok" : "--");
  printf("[%s] jumping blockwise\n", res_it_blocks ? "ok" : "--");
  printf("[%s] editing one overlapping area\n", res_it_areas_1 ? "ok" : "--");
  printf("[%s] editing two overlapping areas\n", res_it_areas_2 ? "ok" : "--");
  printf("[%s] editing three overlapping areas\n", res_it_areas_3 ? "ok" : "--");
  printf("[%s] read-only filtering\n", res_filters_1 ? "ok" : "--");
  printf("[%s] read-write filtering (shrinking trace)\n", res_filters_2 ? "ok" : "--");
  printf("[%s] read-write filtering (growing trace)\n", res_filters_2 ? "ok" : "--");

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1