/*
 * tlsdated-unittest.c - tlsdated unit tests
 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "config.h"

#include "src/test_harness.h"
#include "src/tlsdate.h"
#include "src/util.h"

#include <event2/event.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>

FIXTURE (tempdir)
{
  char path[PATH_MAX];
};

FIXTURE_SETUP (tempdir)
{
  char *p;
  strncpy (self->path, "/tmp/tlsdated-unit-XXXXXX", sizeof (self->path));
  p = mkdtemp (self->path);
  ASSERT_NE (NULL, p);
}

FIXTURE_TEARDOWN(tempdir) {
  char buf[256];
  snprintf(buf, sizeof(buf), "%s/load", self->path);
  unlink(buf);
  snprintf(buf, sizeof(buf), "%s/save", self->path);
  unlink(buf);
  ASSERT_EQ(0, rmdir(self->path));
}

int write_time (const char *path, time_t time)
{
  int fd = open (path, O_WRONLY | O_TRUNC | O_CREAT, 0600);
  if (fd == -1)
    return 1;
  if (save_timestamp_to_fd (fd, time))
    return 1;
  if (write (fd, &time, sizeof (time)) != sizeof (time))
    {
      close (fd);
      return 1;
    }
  return close (fd);
}

int read_time (const char *path, time_t* time)
{
  int fd = open (path, O_RDONLY);
  if (fd == -1)
    return 1;
  if (read (fd, time, sizeof (*time)) != sizeof (*time))
    {
      close (fd);
      return 1;
    }
  return close (fd);
}

TEST (sane_time)
{
  ASSERT_EQ (0, is_sane_time (0));
  ASSERT_EQ (0, is_sane_time (INT_MAX));
}

TEST (sane_host_time)
{
  ASSERT_EQ (1, is_sane_time (time (NULL)));
}

TEST_F (tempdir, load_time)
{
  char buf[PATH_MAX];
  time_t tm = 3;
  time_t now = time (NULL);
  snprintf (buf, sizeof (buf), "%s/load", self->path);
  ASSERT_EQ (0, write_time (buf, 0));
  ASSERT_EQ (-1, load_disk_timestamp (buf, &tm));
  ASSERT_EQ (3, tm);
  ASSERT_EQ (0, write_time (buf, INT_MAX));
  ASSERT_EQ (-1, load_disk_timestamp (buf, &tm));
  ASSERT_EQ (3, tm);
  ASSERT_EQ (0, write_time (buf, now));
  ASSERT_EQ (0, truncate (buf, 2));
  ASSERT_EQ (-1, load_disk_timestamp (buf, &tm));
  ASSERT_EQ (3, tm);
  ASSERT_EQ (0, unlink (buf));
  ASSERT_EQ (-1, load_disk_timestamp (buf, &tm));
  ASSERT_EQ (3, tm);
  ASSERT_EQ (0, write_time (buf, now));
  ASSERT_EQ (0, load_disk_timestamp (buf, &tm));
  ASSERT_EQ (now, tm);
}


TEST_F (tempdir, save_time)
{
  char buf[PATH_MAX];
  time_t now = time (NULL);
  time_t tm;
  snprintf (buf, sizeof (buf), "%s/save", self->path);
  ASSERT_EQ (0, write_time (buf, now));
  ASSERT_EQ (0, read_time (buf, &tm));
  EXPECT_EQ (now, tm);
}

FIXTURE (tlsdate)
{
  struct state state;
  struct timeval timeout;
};


FIXTURE_SETUP (tlsdate)
{
  memset (self, 0, sizeof (*self));
  /* TODO(wad) make this use the same function tlsdated uses. */
  self->state.base = event_base_new();
  set_conf_defaults (&self->state.opts);
  ASSERT_NE (NULL, self->state.base);
  event_base_priority_init (self->state.base, MAX_EVENT_PRIORITIES);
  ASSERT_EQ (0, setup_sigchld_event (&self->state, 1));
  self->state.events[E_TLSDATE] = event_new (self->state.base, -1, EV_TIMEOUT,
                                  action_run_tlsdate, &self->state);
  ASSERT_NE (NULL, self->state.events[E_TLSDATE]);
  event_priority_set (self->state.events[E_TLSDATE], PRI_NET);
  /* The timeout and fd will be filled in per-call. */
  ASSERT_EQ (0, setup_tlsdate_status (&self->state));
  self->timeout.tv_sec = 1;
}

FIXTURE_TEARDOWN (tlsdate)
{
  int i;
  for (i = 0; i < E_MAX; ++i)
    {
      struct event *e = self->state.events[i];
      if (e)
        {
          int fd = event_get_fd (e);
          if (fd >= 0 && ! (event_get_events (e) & EV_SIGNAL))
            close (fd);
          event_free (e);
          self->state.events[i] = NULL;
        }
    }
  /* The other half was closed above. */
  close (self->state.tlsdate_monitor_fd);
  if (self->state.tlsdate_pid)
    {
      kill (self->state.tlsdate_pid, SIGKILL);
      waitpid (self->state.tlsdate_pid, NULL, WNOHANG);
    }
  if (self->state.base)
    event_base_free (self->state.base);
}

static int
runner (FIXTURE_DATA (tlsdate) *self, time_t *newtime)
{
  if (newtime)
    *newtime = 0;
  trigger_event (&self->state, E_TLSDATE, 0);
  event_base_loopexit (self->state.base, &self->timeout);
  if (event_base_dispatch (self->state.base))
    return -1;
  if (self->state.last_time)
    {
      if (newtime)
        *newtime = self->state.last_time;
      return 0;
    }
  return 1;
}

TEST_F (tlsdate, runner_multi)
{
  struct source source =
  {
    .next = NULL,
    .host = "host1",
    .port = "port1",
    .proxy = "proxy1"
  };
  char *args[] = { "/nonexistent", NULL, NULL };
  extern char **environ;
  self->state.opts.sources = &source;
  self->state.opts.base_argv = args;
  self->state.opts.subprocess_tries = 2;
  self->state.opts.subprocess_wait_between_tries = 1;
  self->state.opts.max_tries = 3;
  self->state.envp = environ;
  EXPECT_EQ (1, runner (self, NULL));
  args[0] = "/bin/false";
  self->state.tries = 0;
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (1, runner (self, NULL));
  args[0] = "src/test/check-host-1";
  self->state.tries = 0;
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
  args[0] = "src/test/sleep-wrap";
  args[1] = "3";
  self->state.tries = 0;
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
}

TEST (jitter)
{
  int i = 0;
  int r;
  const int kBase = 100;
  const int kJitter = 25;
  int nonequal = 0;
  for (i = 0; i < 1000; i++)
    {
      r = add_jitter (kBase, kJitter);
      EXPECT_GE (r, kBase - kJitter);
      EXPECT_LE (r, kBase + kJitter);
      if (r != kBase)
        nonequal++;
    }
  EXPECT_NE (nonequal, 0);
}

TEST_F (tlsdate, rotate_hosts)
{
  struct source s2 =
  {
    .next = NULL,
    .host = "host2",
    .port = "port2",
    .proxy = "proxy2"
  };
  struct source s1 =
  {
    .next = &s2,
    .host = "host1",
    .port = "port1",
    .proxy = "proxy1"
  };
  char *args[] = { "src/test/check-host-1",  NULL };
  extern char **environ;
  self->state.envp = environ;
  self->state.opts.sources = &s1;
  self->state.opts.base_argv = args;
  self->state.opts.subprocess_tries = 2;
  self->state.opts.subprocess_wait_between_tries = 1;
  self->state.opts.max_tries = 5;
  self->timeout.tv_sec = 2;
  EXPECT_EQ (0, runner (self, NULL));
  self->state.tries = 0;
  args[0] = "src/test/check-host-2";
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
  self->state.tries = 0;
  args[0] = "src/test/check-host-1";
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
  self->state.tries = 0;
  args[0] = "src/test/check-host-2";
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
}

TEST_F (tlsdate, proxy_override)
{
  struct source s1 =
  {
    .next = NULL,
    .host = "host",
    .port = "port",
    .proxy = NULL,
  };
  char *args[] = { "src/test/proxy-override", NULL };
  extern char **environ;
  self->state.envp = environ;
  self->state.opts.sources = &s1;
  self->state.opts.base_argv = args;
  self->state.opts.subprocess_tries = 2;
  self->state.opts.subprocess_wait_between_tries = 1;
  EXPECT_EQ (0, runner (self, NULL));
  EXPECT_EQ (RECENT_COMPILE_DATE + 1, self->state.last_time);
  s1.proxy = "socks5://bad.proxy";
  self->state.tries = 0;
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
  EXPECT_EQ (RECENT_COMPILE_DATE + 3, self->state.last_time);
  self->state.opts.proxy = "socks5://good.proxy";
  self->state.tries = 0;
  self->state.last_sync_type = SYNC_TYPE_NONE;
  EXPECT_EQ (0, runner (self, NULL));
  EXPECT_EQ (RECENT_COMPILE_DATE + 2, self->state.last_time);
}

FIXTURE(mock_platform) {
  struct platform platform;
  struct platform *old_platform;
};

FIXTURE_SETUP(mock_platform) {
  self->old_platform = platform;
  self->platform.rtc_open = NULL;
  self->platform.rtc_write = NULL;
  self->platform.rtc_read = NULL;
  self->platform.rtc_close = NULL;
  platform = &self->platform;
}

FIXTURE_TEARDOWN(mock_platform) {
  platform = self->old_platform;
}

/* TODO: leap_tests, time_setter tests. */

TEST_HARNESS_MAIN