/* Copyright (c) 2001 Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2019, The Tor Project, Inc. */
/* See LICENSE for licensing information */

#include "core/or/or.h"

#include "app/config/or_options_st.h"
#include "core/mainloop/netstatus.h"
#include "feature/dirauth/reachability.h"
#include "feature/stats/rephist.h"

#include "feature/dirauth/bridgeauth.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirauth/dirauth_periodic.h"
#include "feature/dirauth/authmode.h"

#include "core/mainloop/periodic.h"

#define DECLARE_EVENT(name, roles, flags)         \
  static periodic_event_item_t name ## _event =   \
    PERIODIC_EVENT(name,                          \
                   PERIODIC_EVENT_ROLE_##roles,   \
                   flags)

#define FL(name) (PERIODIC_EVENT_FLAG_##name)

/**
 * Periodic callback: if we're an authority, check on our authority
 * certificate (the one that authenticates our authority signing key).
 */
static int
check_authority_cert_callback(time_t now, const or_options_t *options)
{
  (void)now;
  (void)options;
  /* 1e. Periodically, if we're a v3 authority, we check whether our cert is
   * close to expiring and warn the admin if it is. */
  v3_authority_check_key_expiry();
#define CHECK_V3_CERTIFICATE_INTERVAL (5*60)
  return CHECK_V3_CERTIFICATE_INTERVAL;
}

DECLARE_EVENT(check_authority_cert, DIRAUTH, 0);

/**
 * Scheduled callback: Run directory-authority voting functionality.
 *
 * The schedule is a bit complicated here, so dirvote_act() manages the
 * schedule itself.
 **/
static int
dirvote_callback(time_t now, const or_options_t *options)
{
  if (!authdir_mode_v3(options)) {
    tor_assert_nonfatal_unreached();
    return 3600;
  }

  time_t next = dirvote_act(options, now);
  if (BUG(next == TIME_MAX)) {
    /* This shouldn't be returned unless we called dirvote_act() without
     * being an authority.  If it happens, maybe our configuration will
     * fix itself in an hour or so? */
    return 3600;
  }
  return safe_timer_diff(now, next);
}

DECLARE_EVENT(dirvote, DIRAUTH, FL(NEED_NET));

/** Reschedule the directory-authority voting event.  Run this whenever the
 * schedule has changed. */
void
reschedule_dirvote(const or_options_t *options)
{
  if (authdir_mode_v3(options)) {
    periodic_event_reschedule(&dirvote_event);
  }
}

/**
 * Periodic callback: if we're an authority, record our measured stability
 * information from rephist in an mtbf file.
 */
static int
save_stability_callback(time_t now, const or_options_t *options)
{
  if (authdir_mode_tests_reachability(options)) {
    if (rep_hist_record_mtbf_data(now, 1)<0) {
      log_warn(LD_GENERAL, "Couldn't store mtbf data.");
    }
  }
#define SAVE_STABILITY_INTERVAL (30*60)
  return SAVE_STABILITY_INTERVAL;
}

DECLARE_EVENT(save_stability, AUTHORITIES, 0);

/**
 * Periodic callback: if we're an authority, make sure we test
 * the routers on the network for reachability.
 */
static int
launch_reachability_tests_callback(time_t now, const or_options_t *options)
{
  if (authdir_mode_tests_reachability(options) &&
      !net_is_disabled()) {
    /* try to determine reachability of the other Tor relays */
    dirserv_test_reachability(now);
  }
  return REACHABILITY_TEST_INTERVAL;
}

DECLARE_EVENT(launch_reachability_tests, AUTHORITIES, FL(NEED_NET));

/**
 * Periodic callback: if we're an authority, discount the stability
 * information (and other rephist information) that's older.
 */
static int
downrate_stability_callback(time_t now, const or_options_t *options)
{
  (void)options;
  /* 1d. Periodically, we discount older stability information so that new
   * stability info counts more, and save the stability information to disk as
   * appropriate. */
  time_t next = rep_hist_downrate_old_runs(now);
  return safe_timer_diff(now, next);
}

DECLARE_EVENT(downrate_stability, AUTHORITIES, 0);

/**
 * Periodic callback: if we're the bridge authority, write a networkstatus
 * file to disk.
 */
static int
write_bridge_ns_callback(time_t now, const or_options_t *options)
{
  if (options->BridgeAuthoritativeDir) {
    bridgeauth_dump_bridge_status_to_file(now);
#define BRIDGE_STATUSFILE_INTERVAL (30*60)
     return BRIDGE_STATUSFILE_INTERVAL;
  }
  return PERIODIC_EVENT_NO_UPDATE;
}

DECLARE_EVENT(write_bridge_ns, BRIDGEAUTH, 0);

void
dirauth_register_periodic_events(void)
{
  periodic_events_register(&downrate_stability_event);
  periodic_events_register(&launch_reachability_tests_event);
  periodic_events_register(&save_stability_event);
  periodic_events_register(&check_authority_cert_event);
  periodic_events_register(&dirvote_event);
  periodic_events_register(&write_bridge_ns_event);
}
