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

/**
 * \file shared_random_client.c
 * \brief This file contains functions that are from the shared random
 *        subsystem but used by many part of tor. The full feature is built
 *        as part of the dirauth module.
 **/

#include "feature/hs_common/shared_random_client.h"

#include "app/config/config.h"
#include "feature/dirauth/authmode.h"
#include "feature/dirauth/voting_schedule.h"
#include "feature/nodelist/microdesc.h"
#include "feature/nodelist/networkstatus.h"
#include "lib/encoding/binascii.h"

#include "feature/nodelist/networkstatus_st.h"

/** Convert a given srv object to a string for the control port. This doesn't
 * fail and the srv object MUST be valid. */
static char *
srv_to_control_string(const sr_srv_t *srv)
{
  char *srv_str;
  char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1];
  tor_assert(srv);

  sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv);
  tor_asprintf(&srv_str, "%s", srv_hash_encoded);
  return srv_str;
}

/**
 * If we have no consensus and we are not an authority, assume that this is
 * the voting interval.  We should never actually use this: only authorities
 * should be trying to figure out the schedule when they don't have a
 * consensus.
 **/
#define DEFAULT_NETWORK_VOTING_INTERVAL (3600)

/* This is an unpleasing workaround for tests.  Our unit tests assume that we
 * are scheduling all of our shared random stuff as if we were a directory
 * authority, but they do not always set V3AuthoritativeDir.
 */
#ifdef TOR_UNIT_TESTS
#define ASSUME_AUTHORITY_SCHEDULING 1
#else
#define ASSUME_AUTHORITY_SCHEDULING 0
#endif

/** Return the voting interval of the tor vote subsystem. */
int
get_voting_interval(void)
{
  int interval;
  networkstatus_t *consensus =
    networkstatus_get_reasonably_live_consensus(time(NULL),
                                                usable_consensus_flavor());

  if (consensus) {
    /* Ideally we have a live consensus and we can just use that. */
    interval = (int)(consensus->fresh_until - consensus->valid_after);
  } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) {
    /* If we don't have a live consensus and we're an authority,
     * we should believe our own view of what the schedule ought to be. */
    interval = dirauth_sched_get_configured_interval();
  } else if ((consensus = networkstatus_get_latest_consensus())) {
    /* If we're a client, then maybe a latest consensus is good enough?
     * It's better than falling back to the non-consensus case. */
    interval = (int)(consensus->fresh_until - consensus->valid_after);
  } else {
    /* We should never be reaching this point, since a client should never
     * call this code unless they have some kind of a consensus. All we can
     * do is hope that this network is using the default voting interval. */
    tor_assert_nonfatal_unreached_once();
    interval = DEFAULT_NETWORK_VOTING_INTERVAL;
  }
  tor_assert(interval > 0);
  return interval;
}

/*
 * Public API
 */

/** Encode the given shared random value and put it in dst. Destination
 * buffer must be at least SR_SRV_VALUE_BASE64_LEN plus the NULL byte. */
void
sr_srv_encode(char *dst, size_t dst_len, const sr_srv_t *srv)
{
  int ret;
  /* Extra byte for the NULL terminated char. */
  char buf[SR_SRV_VALUE_BASE64_LEN + 1];

  tor_assert(dst);
  tor_assert(srv);
  tor_assert(dst_len >= sizeof(buf));

  ret = base64_encode(buf, sizeof(buf), (const char *) srv->value,
                      sizeof(srv->value), 0);
  /* Always expect the full length without the NULL byte. */
  tor_assert(ret == (sizeof(buf) - 1));
  tor_assert(ret <= (int) dst_len);
  strlcpy(dst, buf, dst_len);
}

/** Return the current SRV string representation for the control port. Return a
 * newly allocated string on success containing the value else "" if not found
 * or if we don't have a valid consensus yet. */
char *
sr_get_current_for_control(void)
{
  char *srv_str;
  const networkstatus_t *c = networkstatus_get_latest_consensus();
  if (c && c->sr_info.current_srv) {
    srv_str = srv_to_control_string(c->sr_info.current_srv);
  } else {
    srv_str = tor_strdup("");
  }
  return srv_str;
}

/** Return the previous SRV string representation for the control port. Return
 * a newly allocated string on success containing the value else "" if not
 * found or if we don't have a valid consensus yet. */
char *
sr_get_previous_for_control(void)
{
  char *srv_str;
  const networkstatus_t *c = networkstatus_get_latest_consensus();
  if (c && c->sr_info.previous_srv) {
    srv_str = srv_to_control_string(c->sr_info.previous_srv);
  } else {
    srv_str = tor_strdup("");
  }
  return srv_str;
}

/** Return current shared random value from the latest consensus. Caller can
 * NOT keep a reference to the returned pointer. Return NULL if none. */
const sr_srv_t *
sr_get_current(const networkstatus_t *ns)
{
  const networkstatus_t *consensus;

  /* Use provided ns else get a live one */
  if (ns) {
    consensus = ns;
  } else {
    consensus = networkstatus_get_reasonably_live_consensus(approx_time(),
                                                  usable_consensus_flavor());
  }
  /* Ideally we would never be asked for an SRV without a live consensus. Make
   * sure this assumption is correct. */
  tor_assert_nonfatal(consensus);

  if (consensus) {
    return consensus->sr_info.current_srv;
  }
  return NULL;
}

/** Return previous shared random value from the latest consensus. Caller can
 * NOT keep a reference to the returned pointer. Return NULL if none. */
const sr_srv_t *
sr_get_previous(const networkstatus_t *ns)
{
  const networkstatus_t *consensus;

  /* Use provided ns else get a live one */
  if (ns) {
    consensus = ns;
  } else {
    consensus = networkstatus_get_reasonably_live_consensus(approx_time(),
                                                  usable_consensus_flavor());
  }
  /* Ideally we would never be asked for an SRV without a live consensus. Make
   * sure this assumption is correct. */
  tor_assert_nonfatal(consensus);

  if (consensus) {
    return consensus->sr_info.previous_srv;
  }
  return NULL;
}

/** Parse a list of arguments from a SRV value either from a vote, consensus
 * or from our disk state and return a newly allocated srv object. NULL is
 * returned on error.
 *
 * The arguments' order:
 *    num_reveals, value
 */
sr_srv_t *
sr_parse_srv(const smartlist_t *args)
{
  char *value;
  int ok, ret;
  uint64_t num_reveals;
  sr_srv_t *srv = NULL;

  tor_assert(args);

  if (smartlist_len(args) < 2) {
    goto end;
  }

  /* First argument is the number of reveal values */
  num_reveals = tor_parse_uint64(smartlist_get(args, 0),
                                 10, 0, UINT64_MAX, &ok, NULL);
  if (!ok) {
    goto end;
  }
  /* Second and last argument is the shared random value it self. */
  value = smartlist_get(args, 1);
  if (strlen(value) != SR_SRV_VALUE_BASE64_LEN) {
    goto end;
  }

  srv = tor_malloc_zero(sizeof(*srv));
  srv->num_reveals = num_reveals;
  /* We subtract one byte from the srclen because the function ignores the
   * '=' character in the given buffer. This is broken but it's a documented
   * behavior of the implementation. */
  ret = base64_decode((char *) srv->value, sizeof(srv->value), value,
                      SR_SRV_VALUE_BASE64_LEN - 1);
  if (ret != sizeof(srv->value)) {
    tor_free(srv);
    srv = NULL;
    goto end;
  }
 end:
  return srv;
}

/** Return the start time of the current SR protocol run using the times from
 *  the current consensus. For example, if the latest consensus valid-after is
 *  23/06/2017 23:00:00 and a full SR protocol run is 24 hours, this function
 *  returns 23/06/2017 00:00:00. */
time_t
sr_state_get_start_time_of_current_protocol_run(void)
{
  int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
  int voting_interval = get_voting_interval();
  time_t beginning_of_curr_round;

  /* This function is not used for voting purposes, so if we have a reasonably
   * live consensus, use its valid-after as the beginning of the current
   * round. If we have no consensus but we're an authority, use our own
   * schedule. Otherwise, try using our view of the voting interval to figure
   * out when the current round _should_ be starting. */
  networkstatus_t *ns =
    networkstatus_get_reasonably_live_consensus(approx_time(),
                                                usable_consensus_flavor());
  if (ns) {
    beginning_of_curr_round = ns->valid_after;
  } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) {
    beginning_of_curr_round = dirauth_sched_get_cur_valid_after_time();
  } else {
    /* voting_interval comes from get_voting_interval(), so if we're in
     * this case as a client, we already tried to get the voting interval
     * from the latest_consensus and gave a bug warning if we couldn't.
     *
     * We wouldn't want to look at the latest consensus's valid_after time,
     * since that would be out of date. */
    beginning_of_curr_round = voting_sched_get_start_of_interval_after(
                                             approx_time() - voting_interval,
                                             voting_interval,
                                             0);
  }

  /* Get current SR protocol round */
  int curr_round_slot;
  curr_round_slot = (beginning_of_curr_round / voting_interval) % total_rounds;

  /* Get start time by subtracting the time elapsed from the beginning of the
     protocol run */
  time_t time_elapsed_since_start_of_run = curr_round_slot * voting_interval;

  return beginning_of_curr_round - time_elapsed_since_start_of_run;
}

/** Return the start time of the previous SR protocol run. See
 *  sr_state_get_start_time_of_current_protocol_run() for more details.  */
time_t
sr_state_get_start_time_of_previous_protocol_run(void)
{
  time_t start_time_of_current_run =
    sr_state_get_start_time_of_current_protocol_run();

  /* We get the start time of previous protocol run, by getting the start time
   * of current run and the subtracting a full protocol run from that. */
  return start_time_of_current_run - sr_state_get_protocol_run_duration();
}

/** Return the time (in seconds) it takes to complete a full SR protocol phase
 *  (e.g. the commit phase). */
unsigned int
sr_state_get_phase_duration(void)
{
  return SHARED_RANDOM_N_ROUNDS * get_voting_interval();
}

/** Return the time (in seconds) it takes to complete a full SR protocol run */
unsigned int
sr_state_get_protocol_run_duration(void)
{
  int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
  return total_protocol_rounds * get_voting_interval();
}
