/* Copyright (c) 2012-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */

#define REPLAYCACHE_PRIVATE

#include "orconfig.h"
#include "core/or/or.h"
#include "feature/hs_common/replaycache.h"
#include "test/test.h"

static const char *test_buffer =
  "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed do eiusmod"
  " tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim"
  " veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea"
  " commodo consequat. Duis aute irure dolor in reprehenderit in voluptate"
  " velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint"
  " occaecat cupidatat non proident, sunt in culpa qui officia deserunt"
  " mollit anim id est laborum.";

static const char *test_buffer_2 =
  "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis"
  " praesentium voluptatum deleniti atque corrupti quos dolores et quas"
  " molestias excepturi sint occaecati cupiditate non provident, similique"
  " sunt in culpa qui officia deserunt mollitia animi, id est laborum et"
  " dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio."
  " Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil"
  " impedit quo minus id quod maxime placeat facere possimus, omnis voluptas"
  " assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut"
  " officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates"
  " repudiandae sint et molestiae non recusandae. Itaque earum rerum hic"
  " tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias"
  " consequatur aut perferendis doloribus asperiores repellat.";

static void
test_replaycache_alloc(void *arg)
{
  replaycache_t *r = NULL;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_badalloc(void *arg)
{
  replaycache_t *r = NULL;

  /* Negative horizon should fail */
  (void)arg;
  r = replaycache_new(-600, 300);
  tt_ptr_op(r, OP_EQ, NULL);
  /* Negative interval should get adjusted to zero */
  r = replaycache_new(600, -300);
  tt_ptr_op(r, OP_NE, NULL);
  tt_int_op(r->scrub_interval,OP_EQ, 0);
  replaycache_free(r);
  /* Negative horizon and negative interval should still fail */
  r = replaycache_new(-600, -300);
  tt_ptr_op(r, OP_EQ, NULL);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_free_null(void *arg)
{
  (void)arg;
  replaycache_free_(NULL);
  /* Assert that we're here without horrible death */
  tt_assert(1);

 done:
  return;
}

static void
test_replaycache_miss(void *arg)
{
  replaycache_t *r = NULL;
  int result;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  result =
    replaycache_add_and_test_internal(1200, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

  /* make sure a different buffer misses as well */
  result =
  replaycache_add_and_test_internal(1200, NULL, test_buffer_2,
                                    strlen(test_buffer_2), NULL);
  tt_int_op(result,OP_EQ, 0);

  /* poke the bad-parameter error case too */
  result =
    replaycache_add_and_test_internal(1200, NULL, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_hit(void *arg)
{
  replaycache_t *r = NULL;
  int result;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  result =
    replaycache_add_and_test_internal(1200, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

  result =
    replaycache_add_and_test_internal(1300, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 1);

  /* make sure a different buffer misses then hits as well */

  result =
  replaycache_add_and_test_internal(1200, r, test_buffer_2,
                                    strlen(test_buffer_2), NULL);
  tt_int_op(result,OP_EQ, 0);

  result =
  replaycache_add_and_test_internal(1300, r, test_buffer_2,
                                    strlen(test_buffer_2), NULL);
  tt_int_op(result,OP_EQ, 1);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_age(void *arg)
{
  replaycache_t *r = NULL;
  int result;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  result =
    replaycache_add_and_test_internal(1200, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

  result =
    replaycache_add_and_test_internal(1300, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 1);

  result =
    replaycache_add_and_test_internal(3000, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_elapsed(void *arg)
{
  replaycache_t *r = NULL;
  int result;
  time_t elapsed;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  result =
    replaycache_add_and_test_internal(1200, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

  result =
    replaycache_add_and_test_internal(1300, r, test_buffer,
        strlen(test_buffer), &elapsed);
  tt_int_op(result,OP_EQ, 1);
  tt_int_op(elapsed,OP_EQ, 100);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_noexpire(void *arg)
{
  replaycache_t *r = NULL;
  int result;

  (void)arg;
  r = replaycache_new(0, 0);
  tt_ptr_op(r, OP_NE, NULL);

  result =
    replaycache_add_and_test_internal(1200, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

  result =
    replaycache_add_and_test_internal(1300, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 1);

  result =
    replaycache_add_and_test_internal(3000, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 1);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_scrub(void *arg)
{
  replaycache_t *r = NULL;
  int result;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  /* Set up like in test_replaycache_hit() */
  result =
    replaycache_add_and_test_internal(100, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 0);

  result =
    replaycache_add_and_test_internal(200, r, test_buffer,
        strlen(test_buffer), NULL);
  tt_int_op(result,OP_EQ, 1);

  /*
   * Poke a few replaycache_scrub_if_needed_internal() error cases that
   * can't happen through replaycache_add_and_test_internal()
   */

  /* Null cache */
  replaycache_scrub_if_needed_internal(300, NULL);
  /* Assert we're still here */
  tt_assert(1);

  /* Make sure we hit the aging-out case too */
  replaycache_scrub_if_needed_internal(1500, r);
  /* Assert that we aged it */
  tt_int_op(digest256map_size(r->digests_seen),OP_EQ, 0);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_future(void *arg)
{
  replaycache_t *r = NULL;
  int result;
  time_t elapsed = 0;

  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  /* Set up like in test_replaycache_hit() */
  result =
    replaycache_add_and_test_internal(100, r, test_buffer,
        strlen(test_buffer), &elapsed);
  tt_int_op(result,OP_EQ, 0);
  /* elapsed should still be 0, since it wasn't written */
  tt_int_op(elapsed,OP_EQ, 0);

  result =
    replaycache_add_and_test_internal(200, r, test_buffer,
        strlen(test_buffer), &elapsed);
  tt_int_op(result,OP_EQ, 1);
  /* elapsed should be the time since the last hit */
  tt_int_op(elapsed,OP_EQ, 100);

  /*
   * Now let's turn the clock back to get coverage on the cache entry from the
   * future not-supposed-to-happen case.
   */
  result =
    replaycache_add_and_test_internal(150, r, test_buffer,
        strlen(test_buffer), &elapsed);
  /* We should still get a hit */
  tt_int_op(result,OP_EQ, 1);
  /* ...but it shouldn't let us see a negative elapsed time */
  tt_int_op(elapsed,OP_EQ, 0);

 done:
  if (r) replaycache_free(r);

  return;
}

static void
test_replaycache_realtime(void *arg)
{
  replaycache_t *r = NULL;
  /*
   * Negative so we fail if replaycache_add_test_and_elapsed() doesn't
   * write to elapsed.
   */
  time_t elapsed = -1;
  int result;

  /* Test the realtime as well as *_internal() entry points */
  (void)arg;
  r = replaycache_new(600, 300);
  tt_ptr_op(r, OP_NE, NULL);

  /* This should miss */
  result =
    replaycache_add_and_test(r, test_buffer, strlen(test_buffer));
  tt_int_op(result,OP_EQ, 0);

  /* This should hit */
  result =
    replaycache_add_and_test(r, test_buffer, strlen(test_buffer));
  tt_int_op(result,OP_EQ, 1);

  /* This should hit and return a small elapsed time */
  result =
    replaycache_add_test_and_elapsed(r, test_buffer,
                                     strlen(test_buffer), &elapsed);
  tt_int_op(result,OP_EQ, 1);
  tt_assert(elapsed >= 0);
  tt_assert(elapsed <= 5);

  /* Scrub it to exercise that entry point too */
  replaycache_scrub_if_needed(r);

 done:
  if (r) replaycache_free(r);
  return;
}

#define REPLAYCACHE_LEGACY(name) \
  { #name, test_replaycache_ ## name , 0, NULL, NULL }

struct testcase_t replaycache_tests[] = {
  REPLAYCACHE_LEGACY(alloc),
  REPLAYCACHE_LEGACY(badalloc),
  REPLAYCACHE_LEGACY(free_null),
  REPLAYCACHE_LEGACY(miss),
  REPLAYCACHE_LEGACY(hit),
  REPLAYCACHE_LEGACY(age),
  REPLAYCACHE_LEGACY(elapsed),
  REPLAYCACHE_LEGACY(noexpire),
  REPLAYCACHE_LEGACY(scrub),
  REPLAYCACHE_LEGACY(future),
  REPLAYCACHE_LEGACY(realtime),
  END_OF_TESTCASES
};

