[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [tor/master] prop250: Add memory and disk state in new files



commit b3b4ffce2e23bfb6a2af374cd8bfa5981628a342
Author: David Goulet <dgoulet@xxxxxxxxxxxxxx>
Date:   Tue May 3 10:18:45 2016 -0400

    prop250: Add memory and disk state in new files
    
    This commit introduces two new files with their header.
    
    "shared_random.c" contains basic functions to initialize the state and allow
    commit decoding for the disk state to be able to parse them from disk.
    
    "shared_random_state.c" contains everything that has to do with the state
    for both our memory and disk. Lots of helper functions as well as a
    mechanism to query the state in a synchronized way.
    
    Signed-off-by: David Goulet <dgoulet@xxxxxxxxxxxxxx>
    Signed-off-by: George Kadianakis <desnacked@xxxxxxxxxx>
---
 src/or/dirvote.c             |   90 ++--
 src/or/dirvote.h             |   32 ++
 src/or/include.am            |    4 +
 src/or/shared_random.c       |  286 +++++++++++
 src/or/shared_random.h       |  114 +++++
 src/or/shared_random_state.c | 1086 ++++++++++++++++++++++++++++++++++++++++++
 src/or/shared_random_state.h |  126 +++++
 7 files changed, 1699 insertions(+), 39 deletions(-)

diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index ad0696e..a08f6ec 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -2511,50 +2511,60 @@ dirvote_get_start_of_next_interval(time_t now, int interval, int offset)
   return next;
 }
 
-/** Scheduling information for a voting interval. */
-static struct {
-  /** When do we generate and distribute our vote for this interval? */
-  time_t voting_starts;
-  /** When do we send an HTTP request for any votes that we haven't
-   * been posted yet?*/
-  time_t fetch_missing_votes;
-  /** When do we give up on getting more votes and generate a consensus? */
-  time_t voting_ends;
-  /** When do we send an HTTP request for any signatures we're expecting to
-   * see on the consensus? */
-  time_t fetch_missing_signatures;
-  /** When do we publish the consensus? */
-  time_t interval_starts;
-
-  /* True iff we have generated and distributed our vote. */
-  int have_voted;
-  /* True iff we've requested missing votes. */
-  int have_fetched_missing_votes;
-  /* True iff we have built a consensus and sent the signatures around. */
-  int have_built_consensus;
-  /* True iff we've fetched missing signatures. */
-  int have_fetched_missing_signatures;
-  /* True iff we have published our consensus. */
-  int have_published_consensus;
-} voting_schedule = {0,0,0,0,0,0,0,0,0,0};
+/* Using the time <b>now</b>, return the next voting valid-after time. */
+time_t
+get_next_valid_after_time(time_t now)
+{
+  time_t next_valid_after_time;
+  const or_options_t *options = get_options();
+  voting_schedule_t *new_voting_schedule =
+    get_voting_schedule(options, now, LOG_INFO);
+  tor_assert(new_voting_schedule);
+
+  next_valid_after_time = new_voting_schedule->interval_starts;
+  tor_free(new_voting_schedule);
+
+  return next_valid_after_time;
+}
+
+static voting_schedule_t voting_schedule = {0};
 
 /** Set voting_schedule to hold the timing for the next vote we should be
  * doing. */
 void
 dirvote_recalculate_timing(const or_options_t *options, time_t now)
 {
+  voting_schedule_t *new_voting_schedule;
+
+  if (!authdir_mode_v3(options)) {
+    return;
+  }
+
+  /* get the new voting schedule */
+  new_voting_schedule = get_voting_schedule(options, now, LOG_NOTICE);
+  tor_assert(new_voting_schedule);
+
+  /* Fill in the global static struct now */
+  memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule));
+  tor_free(new_voting_schedule);
+}
+
+/* Populate and return a new voting_schedule_t that can be used to schedule
+ * voting. The object is allocated on the heap and it's the responsibility of
+ * the caller to free it. Can't fail. */
+voting_schedule_t *
+get_voting_schedule(const or_options_t *options, time_t now, int severity)
+{
   int interval, vote_delay, dist_delay;
   time_t start;
   time_t end;
   networkstatus_t *consensus;
+  voting_schedule_t *new_voting_schedule;
 
-  if (!authdir_mode_v3(options))
-    return;
+  new_voting_schedule = tor_malloc_zero(sizeof(voting_schedule_t));
 
   consensus = networkstatus_get_live_consensus(now);
 
-  memset(&voting_schedule, 0, sizeof(voting_schedule));
-
   if (consensus) {
     interval = (int)( consensus->fresh_until - consensus->valid_after );
     vote_delay = consensus->vote_seconds;
@@ -2570,7 +2580,7 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now)
   if (vote_delay + dist_delay > interval/2)
     vote_delay = dist_delay = interval / 4;
 
-  start = voting_schedule.interval_starts =
+  start = new_voting_schedule->interval_starts =
     dirvote_get_start_of_next_interval(now,interval,
                                       options->TestingV3AuthVotingStartOffset);
   end = dirvote_get_start_of_next_interval(start+1, interval,
@@ -2578,18 +2588,20 @@ dirvote_recalculate_timing(const or_options_t *options, time_t now)
 
   tor_assert(end > start);
 
-  voting_schedule.fetch_missing_signatures = start - (dist_delay/2);
-  voting_schedule.voting_ends = start - dist_delay;
-  voting_schedule.fetch_missing_votes = start - dist_delay - (vote_delay/2);
-  voting_schedule.voting_starts = start - dist_delay - vote_delay;
+  new_voting_schedule->fetch_missing_signatures = start - (dist_delay/2);
+  new_voting_schedule->voting_ends = start - dist_delay;
+  new_voting_schedule->fetch_missing_votes = start - dist_delay - (vote_delay/2);
+  new_voting_schedule->voting_starts = start - dist_delay - vote_delay;
 
   {
     char tbuf[ISO_TIME_LEN+1];
-    format_iso_time(tbuf, voting_schedule.interval_starts);
-    log_notice(LD_DIR,"Choosing expected valid-after time as %s: "
-               "consensus_set=%d, interval=%d",
-               tbuf, consensus?1:0, interval);
+    format_iso_time(tbuf, new_voting_schedule->interval_starts);
+    tor_log(severity, LD_DIR,"Choosing expected valid-after time as %s: "
+            "consensus_set=%d, interval=%d",
+            tbuf, consensus?1:0, interval);
   }
+
+  return new_voting_schedule;
 }
 
 /** Entry point: Take whatever voting actions are pending as of <b>now</b>. */
diff --git a/src/or/dirvote.h b/src/or/dirvote.h
index 0b1d284..f2080a5 100644
--- a/src/or/dirvote.h
+++ b/src/or/dirvote.h
@@ -121,12 +121,44 @@ void ns_detached_signatures_free(ns_detached_signatures_t *s);
 authority_cert_t *authority_cert_dup(authority_cert_t *cert);
 
 /* vote scheduling */
+
+/** Scheduling information for a voting interval. */
+typedef struct {
+  /** When do we generate and distribute our vote for this interval? */
+  time_t voting_starts;
+  /** When do we send an HTTP request for any votes that we haven't
+   * been posted yet?*/
+  time_t fetch_missing_votes;
+  /** When do we give up on getting more votes and generate a consensus? */
+  time_t voting_ends;
+  /** When do we send an HTTP request for any signatures we're expecting to
+   * see on the consensus? */
+  time_t fetch_missing_signatures;
+  /** When do we publish the consensus? */
+  time_t interval_starts;
+
+  /* True iff we have generated and distributed our vote. */
+  int have_voted;
+  /* True iff we've requested missing votes. */
+  int have_fetched_missing_votes;
+  /* True iff we have built a consensus and sent the signatures around. */
+  int have_built_consensus;
+  /* True iff we've fetched missing signatures. */
+  int have_fetched_missing_signatures;
+  /* True iff we have published our consensus. */
+  int have_published_consensus;
+} voting_schedule_t;
+
+voting_schedule_t *get_voting_schedule(const or_options_t *options,
+                                       time_t now, int severity);
+
 void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out);
 time_t dirvote_get_start_of_next_interval(time_t now,
                                           int interval,
                                           int offset);
 void dirvote_recalculate_timing(const or_options_t *options, time_t now);
 void dirvote_act(const or_options_t *options, time_t now);
+time_t get_next_valid_after_time(time_t now);
 
 /* invoked on timers and by outside triggers. */
 struct pending_vote_t * dirvote_add_vote(const char *vote_body,
diff --git a/src/or/include.am b/src/or/include.am
index 19f1a7f..744a507 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -62,6 +62,8 @@ LIBTOR_A_SOURCES = \
 	src/or/onion.c					\
 	src/or/onion_fast.c				\
 	src/or/onion_tap.c				\
+	src/or/shared_random.c			\
+	src/or/shared_random_state.c		\
 	src/or/transports.c				\
 	src/or/periodic.c				\
 	src/or/policies.c				\
@@ -173,6 +175,8 @@ ORHEADERS = \
 	src/or/onion_ntor.h				\
 	src/or/onion_tap.h				\
 	src/or/or.h					\
+	src/or/shared_random.h			\
+	src/or/shared_random_state.h		\
 	src/or/transports.h				\
 	src/or/periodic.h				\
 	src/or/policies.h				\
diff --git a/src/or/shared_random.c b/src/or/shared_random.c
new file mode 100644
index 0000000..447ab27
--- /dev/null
+++ b/src/or/shared_random.c
@@ -0,0 +1,286 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file shared_random.c
+ *
+ * \brief Functions and data structure needed to accomplish the shared
+ *        random protocol as defined in proposal #250.
+ **/
+
+#define SHARED_RANDOM_PRIVATE
+
+#include "or.h"
+#include "shared_random.h"
+#include "config.h"
+#include "confparse.h"
+#include "networkstatus.h"
+#include "routerkeys.h"
+#include "router.h"
+#include "routerlist.h"
+#include "shared_random_state.h"
+
+/* Allocate a new commit object and initializing it with <b>identity</b>
+ * that MUST be provided. The digest algorithm is set to the default one
+ * that is supported. The rest is uninitialized. This never returns NULL. */
+static sr_commit_t *
+commit_new(const char *rsa_identity_fpr)
+{
+  sr_commit_t *commit;
+
+  tor_assert(rsa_identity_fpr);
+
+  commit = tor_malloc_zero(sizeof(*commit));
+  commit->alg = SR_DIGEST_ALG;
+  strlcpy(commit->rsa_identity_fpr, rsa_identity_fpr,
+          sizeof(commit->rsa_identity_fpr));
+  return commit;
+}
+
+/* Parse the encoded commit. The format is:
+ *    base64-encode( TIMESTAMP || H(REVEAL) )
+ *
+ * If successfully decoded and parsed, commit is updated and 0 is returned.
+ * On error, return -1. */
+STATIC int
+commit_decode(const char *encoded, sr_commit_t *commit)
+{
+  int decoded_len = 0;
+  size_t offset = 0;
+  /* XXX: Needs two extra bytes for the base64 decode calculation matches
+   * the binary length once decoded. #17868. */
+  char b64_decoded[SR_COMMIT_LEN + 2];
+
+  tor_assert(encoded);
+  tor_assert(commit);
+
+  if (strlen(encoded) > SR_COMMIT_BASE64_LEN) {
+    /* This means that if we base64 decode successfully the reveiced commit,
+     * we'll end up with a bigger decoded commit thus unusable. */
+    goto error;
+  }
+
+  /* Decode our encoded commit. Let's be careful here since _encoded_ is
+   * coming from the network in a dirauth vote so we expect nothing more
+   * than the base64 encoded length of a commit. */
+  decoded_len = base64_decode(b64_decoded, sizeof(b64_decoded),
+                              encoded, strlen(encoded));
+  if (decoded_len < 0) {
+    log_warn(LD_BUG, "SR: Commit from authority %s can't be decoded.",
+             commit->rsa_identity_fpr);
+    goto error;
+  }
+
+  if (decoded_len != SR_COMMIT_LEN) {
+    log_warn(LD_BUG, "SR: Commit from authority %s decoded length doesn't "
+                     "match the expected length (%d vs %d).",
+             commit->rsa_identity_fpr, decoded_len, SR_COMMIT_LEN);
+    goto error;
+  }
+
+  /* First is the timestamp (8 bytes). */
+  commit->commit_ts = (time_t) tor_ntohll(get_uint64(b64_decoded));
+  offset += sizeof(uint64_t);
+  /* Next is hashed reveal. */
+  memcpy(commit->hashed_reveal, b64_decoded + offset,
+         sizeof(commit->hashed_reveal));
+  /* Copy the base64 blob to the commit. Useful for voting. */
+  strlcpy(commit->encoded_commit, encoded, sizeof(commit->encoded_commit));
+
+  return 0;
+
+ error:
+  return -1;
+}
+
+/* Parse the b64 blob at <b>encoded</b> containing reveal information and
+ * store the information in-place in <b>commit</b>. Return 0 on success else
+ * a negative value. */
+STATIC int
+reveal_decode(const char *encoded, sr_commit_t *commit)
+{
+  int decoded_len = 0;
+  /* XXX: Needs two extra bytes for the base64 decode calculation matches
+   * the binary length once decoded. #17868. */
+  char b64_decoded[SR_REVEAL_LEN + 2];
+
+  tor_assert(encoded);
+  tor_assert(commit);
+
+  if (strlen(encoded) > SR_REVEAL_BASE64_LEN) {
+    /* This means that if we base64 decode successfully the received reveal
+     * value, we'll end up with a bigger decoded value thus unusable. */
+    goto error;
+  }
+
+  /* Decode our encoded reveal. Let's be careful here since _encoded_ is
+   * coming from the network in a dirauth vote so we expect nothing more
+   * than the base64 encoded length of our reveal. */
+  decoded_len = base64_decode(b64_decoded, sizeof(b64_decoded),
+                              encoded, strlen(encoded));
+  if (decoded_len < 0) {
+    log_warn(LD_BUG, "SR: Reveal from authority %s can't be decoded.",
+             commit->rsa_identity_fpr);
+    goto error;
+  }
+
+  if (decoded_len != SR_REVEAL_LEN) {
+    log_warn(LD_BUG, "SR: Reveal from authority %s decoded length is "
+                     "doesn't match the expected length (%d vs %d)",
+             commit->rsa_identity_fpr, decoded_len, SR_REVEAL_LEN);
+    goto error;
+  }
+
+  commit->reveal_ts = (time_t) tor_ntohll(get_uint64(b64_decoded));
+  /* Copy the last part, the random value. */
+  memcpy(commit->random_number, b64_decoded + 8,
+         sizeof(commit->random_number));
+  /* Also copy the whole message to use during verification */
+  strlcpy(commit->encoded_reveal, encoded, sizeof(commit->encoded_reveal));
+
+  return 0;
+
+ error:
+  return -1;
+}
+
+/* Cleanup both our global state and disk state. */
+static void
+sr_cleanup(void)
+{
+  sr_state_free();
+}
+
+/* Free a commit object. */
+void
+sr_commit_free(sr_commit_t *commit)
+{
+  if (commit == NULL) {
+    return;
+  }
+  /* Make sure we do not leave OUR random number in memory. */
+  memwipe(commit->random_number, 0, sizeof(commit->random_number));
+  tor_free(commit);
+}
+
+/* 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 num_reveals, ok;
+  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 = (int)tor_parse_long(smartlist_get(args, 0),
+                               10, 0, INT32_MAX, &ok, NULL);
+  if (!ok) {
+    goto end;
+  }
+  srv = tor_malloc_zero(sizeof(*srv));
+  srv->num_reveals = num_reveals;
+
+  /* Second and last argument is the shared random value it self. */
+  value = smartlist_get(args, 1);
+  base16_decode((char *) srv->value, sizeof(srv->value), value,
+                HEX_DIGEST256_LEN);
+ end:
+  return srv;
+}
+
+/* Parse a commit from a vote or from our disk state and return a newly
+ * allocated commit object. NULL is returned on error.
+ *
+ * The commit's data is in <b>args</b> and the order matters very much:
+ *  algname, RSA fingerprint, commit value[, reveal value]
+ */
+sr_commit_t *
+sr_parse_commit(const smartlist_t *args)
+{
+  char *value;
+  digest_algorithm_t alg;
+  const char *rsa_identity_fpr;
+  sr_commit_t *commit = NULL;
+
+  if (smartlist_len(args) < 3) {
+    goto error;
+  }
+
+  /* First argument is the algorithm. */
+  value = smartlist_get(args, 0);
+  alg = crypto_digest_algorithm_parse_name(value);
+  if (alg != SR_DIGEST_ALG) {
+    log_warn(LD_BUG, "SR: Commit algorithm %s is not recognized.",
+             escaped(value));
+    goto error;
+  }
+
+  /* Second argument is the RSA fingerprint of the auth */
+  rsa_identity_fpr = smartlist_get(args, 1);
+  if (base16_decode(digest, DIGEST_LEN, rsa_identity_fpr,
+                    HEX_DIGEST_LEN) < 0) {
+    log_warn(LD_DIR, "SR: RSA fingerprint '%s' not decodable",
+             rsa_identity_fpr);
+    goto error;
+  }
+  /* Let's make sure, for extra safety, that this fingerprint is known to
+   * us. Even though this comes from a vote, doesn't hurt to be
+   * extracareful. */
+  if (trusteddirserver_get_by_v3_auth_digest(digest) == NULL) {
+    log_warn(LD_DIR, "SR: Fingerprint %s is not from a recognized "
+                     "authority. Discarding commit.",
+             rsa_identity_fpr);
+    goto error;
+  }
+
+  /* Allocate commit since we have a valid identity now. */
+  commit = commit_new(rsa_identity_fpr);
+
+  /* Third argument is the commitment value base64-encoded. */
+  value = smartlist_get(args, 2);
+  if (commit_decode(value, commit) < 0) {
+    goto error;
+  }
+
+  /* (Optional) Fourth argument is the revealed value. */
+  if (smartlist_len(args) > 3) {
+    value = smartlist_get(args, 3);
+    if (reveal_decode(value, commit) < 0) {
+      goto error;
+    }
+  }
+
+  return commit;
+
+ error:
+  sr_commit_free(commit);
+  return NULL;
+}
+
+/* Initialize shared random subsystem. This MUST be called early in the boot
+ * process of tor. Return 0 on success else -1 on error. */
+int
+sr_init(int save_to_disk)
+{
+  return sr_state_init(save_to_disk, 1);
+}
+
+/* Save our state to disk and cleanup everything. */
+void
+sr_save_and_cleanup(void)
+{
+  sr_state_save();
+  sr_cleanup();
+}
diff --git a/src/or/shared_random.h b/src/or/shared_random.h
new file mode 100644
index 0000000..447de0c
--- /dev/null
+++ b/src/or/shared_random.h
@@ -0,0 +1,114 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_SHARED_RANDOM_H
+#define TOR_SHARED_RANDOM_H
+
+/*
+ * This file contains ABI/API of the shared random protocol defined in
+ * proposal #250. Every public functions and data structure are namespaced
+ * with "sr_" which stands for shared random.
+ */
+
+#include "or.h"
+
+/* Protocol version */
+#define SR_PROTO_VERSION  1
+/* Default digest algorithm. */
+#define SR_DIGEST_ALG DIGEST_SHA3_256
+/* Invariant token in the SRV calculation. */
+#define SR_SRV_TOKEN "shared-random"
+/* Don't count the NUL terminated byte even though the TOKEN has it. */
+#define SR_SRV_TOKEN_LEN (sizeof(SR_SRV_TOKEN) - 1)
+
+/* Length of the random number (in bytes). */
+#define SR_RANDOM_NUMBER_LEN 32
+/* Size of a decoded commit value in a vote or state. It's a hash and a
+ * timestamp. It adds up to 40 bytes. */
+#define SR_COMMIT_LEN (sizeof(uint64_t) + DIGEST256_LEN)
+/* Size of a decoded reveal value from a vote or state. It's a 64 bit
+ * timestamp and the hashed random number. This adds up to 40 bytes. */
+#define SR_REVEAL_LEN (sizeof(uint64_t) + DIGEST256_LEN)
+/* Size of SRV message length. The construction is has follow:
+ *  "shared-random" | INT_8(reveal_num) | INT_8(version) | PREV_SRV */
+#define SR_SRV_MSG_LEN \
+  (SR_SRV_TOKEN_LEN + 1 + 1 + DIGEST256_LEN)
+
+/* Length of base64 encoded commit NOT including the NULL terminated byte.
+ * Formula is taken from base64_encode_size. */
+#define SR_COMMIT_BASE64_LEN \
+  (((SR_COMMIT_LEN - 1) / 3) * 4 + 4)
+/* Length of base64 encoded reveal NOT including the NULL terminated byte.
+ * Formula is taken from base64_encode_size. This adds up to 56 bytes. */
+#define SR_REVEAL_BASE64_LEN \
+  (((SR_REVEAL_LEN - 1) / 3) * 4 + 4)
+
+/* Protocol phase. */
+typedef enum {
+  /* Commitment phase */
+  SR_PHASE_COMMIT  = 1,
+  /* Reveal phase */
+  SR_PHASE_REVEAL  = 2,
+} sr_phase_t;
+
+/* A shared random value (SRV). */
+typedef struct sr_srv_t {
+  /* The number of reveal values used to derive this SRV. */
+  int num_reveals;
+  /* The actual value. This is the stored result of SHA3-256. */
+  uint8_t value[DIGEST256_LEN];
+} sr_srv_t;
+
+/* A commit (either ours or from another authority). */
+typedef struct sr_commit_t {
+  /* Hashing algorithm used. */
+  digest_algorithm_t alg;
+
+  /* Commit owner info */
+
+  /* The RSA identity fingerprint of the authority. */
+  char rsa_identity_fpr[FINGERPRINT_LEN + 1];
+
+  /* Commitment information */
+
+  /* Timestamp of reveal. Correspond to TIMESTAMP. */
+  time_t reveal_ts;
+  /* H(REVEAL) as found in COMMIT message. */
+  char hashed_reveal[DIGEST256_LEN];
+  /* Base64 encoded COMMIT. We use this to put it in our vote. */
+  char encoded_commit[SR_COMMIT_BASE64_LEN + 1];
+
+  /* Reveal information */
+
+  /* H(RN) which is what we used as the random value for this commit. We
+   * don't use the raw bytes since those are sent on the network thus
+   * avoiding possible information leaks of our PRNG. */
+  uint8_t random_number[SR_RANDOM_NUMBER_LEN];
+  /* Timestamp of commit. Correspond to TIMESTAMP. */
+  time_t commit_ts;
+  /* This is the whole reveal message. We use it during verification */
+  char encoded_reveal[SR_REVEAL_BASE64_LEN + 1];
+} sr_commit_t;
+
+/* API */
+
+/* Public methods: */
+
+int sr_init(int save_to_disk);
+void sr_save_and_cleanup(void);
+void sr_commit_free(sr_commit_t *commit);
+
+/* Private methods (only used by shared_random_state.c): */
+
+sr_commit_t *sr_parse_commit(const smartlist_t *args);
+sr_srv_t *sr_parse_srv(const smartlist_t *args);
+
+#ifdef SHARED_RANDOM_PRIVATE
+
+/* Decode. */
+STATIC int commit_decode(const char *encoded, sr_commit_t *commit);
+STATIC int reveal_decode(const char *encoded, sr_commit_t *commit);
+
+#endif /* SHARED_RANDOM_PRIVATE */
+
+#endif /* TOR_SHARED_RANDOM_H */
diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c
new file mode 100644
index 0000000..9fd3b27
--- /dev/null
+++ b/src/or/shared_random_state.c
@@ -0,0 +1,1086 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file shared_random_state.c
+ *
+ * \brief Functions and data structures for the state of the random protocol
+ *        as defined in proposal #250.
+ **/
+
+#define SHARED_RANDOM_STATE_PRIVATE
+
+#include "or.h"
+#include "shared_random.h"
+#include "config.h"
+#include "confparse.h"
+#include "dirvote.h"
+#include "networkstatus.h"
+#include "router.h"
+#include "shared_random_state.h"
+
+/* Default filename of the shared random state on disk. */
+static const char default_fname[] = "sr-state";
+
+/* Our shared random protocol state. There is only one possible state per
+ * protocol run so this is the global state which is reset at every run once
+ * the shared random value has been computed. */
+static sr_state_t *sr_state = NULL;
+
+/* Representation of our persistent state on disk. The sr_state above
+ * contains the data parsed from this state. When we save to disk, we
+ * translate the sr_state to this sr_disk_state. */
+static sr_disk_state_t *sr_disk_state = NULL;
+
+/* Disk state file keys. */
+static const char dstate_commit_key[] = "Commit";
+static const char dstate_prev_srv_key[] = "SharedRandPreviousValue";
+static const char dstate_cur_srv_key[] = "SharedRandCurrentValue";
+
+/* These next two are duplicates or near-duplicates from config.c */
+#define VAR(name, conftype, member, initvalue)                              \
+  { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \
+    initvalue }
+/* As VAR, but the option name and member name are the same. */
+#define V(member, conftype, initvalue) \
+  VAR(#member, conftype, member, initvalue)
+/* Our persistent state magic number. */
+#define SR_DISK_STATE_MAGIC 0x98AB1254
+/* Each protocol phase has 12 rounds  */
+#define SHARED_RANDOM_N_ROUNDS 12
+/* Number of phase we have in a protocol. */
+#define SHARED_RANDOM_N_PHASES 2
+
+static int
+disk_state_validate_cb(void *old_state, void *state, void *default_state,
+                       int from_setconf, char **msg);
+
+/* Array of variables that are saved to disk as a persistent state. */
+static config_var_t state_vars[] = {
+  V(Version,                    INT, "0"),
+  V(TorVersion,                 STRING, NULL),
+  V(ValidAfter,                 ISOTIME, NULL),
+  V(ValidUntil,                 ISOTIME, NULL),
+
+  V(Commit,                     LINELIST, NULL),
+
+  V(SharedRandValues,           LINELIST_V, NULL),
+  VAR("SharedRandPreviousValue",LINELIST_S, SharedRandValues, NULL),
+  VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL),
+  { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL }
+};
+
+/* "Extra" variable in the state that receives lines we can't parse. This
+ * lets us preserve options from versions of Tor newer than us. */
+static config_var_t state_extra_var = {
+  "__extra", CONFIG_TYPE_LINELIST,
+  STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL
+};
+
+/* Configuration format of sr_disk_state_t. */
+static const config_format_t state_format = {
+  sizeof(sr_disk_state_t),
+  SR_DISK_STATE_MAGIC,
+  STRUCT_OFFSET(sr_disk_state_t, magic_),
+  NULL,
+  state_vars,
+  disk_state_validate_cb,
+  &state_extra_var,
+};
+
+/* Return the voting interval of the tor vote subsystem. */
+static int
+get_voting_interval(void)
+{
+  int interval;
+  networkstatus_t *consensus = networkstatus_get_live_consensus(time(NULL));
+
+  if (consensus) {
+    interval = (int)(consensus->fresh_until - consensus->valid_after);
+  } else {
+    /* Same for both a testing and real network. We voluntarily ignore the
+     * InitialVotingInterval since it complexifies things and it doesn't
+     * affect the SR protocol. */
+    interval = get_options()->V3AuthVotingInterval;
+  }
+  tor_assert(interval > 0);
+  return interval;
+}
+
+/* Given the time <b>now</b>, return the start time of the current round of
+ * the SR protocol. For example, if it's 23:47:08, the current round thus
+ * started at 23:47:00 for a voting interval of 10 seconds. */
+static time_t
+get_start_time_of_current_round(time_t now)
+{
+  const or_options_t *options = get_options();
+  int voting_interval = get_voting_interval();
+  voting_schedule_t *new_voting_schedule =
+    get_voting_schedule(options, now, LOG_INFO);
+  tor_assert(new_voting_schedule);
+
+  /* First, get the start time of the next round */
+  time_t next_start = new_voting_schedule->interval_starts;
+  /* Now roll back next_start by a voting interval to find the start time of
+     the current round. */
+  time_t curr_start = dirvote_get_start_of_next_interval(
+                                     next_start - voting_interval - 1,
+                                     voting_interval,
+                                     options->TestingV3AuthVotingStartOffset);
+
+  tor_free(new_voting_schedule);
+
+  return curr_start;
+}
+
+/* Return the time we should expire the state file created at <b>now</b>.
+ * We expire the state file in the beginning of the next protocol run. */
+STATIC time_t
+get_state_valid_until_time(time_t now)
+{
+  int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+  int current_round, voting_interval, rounds_left;
+  time_t valid_until, beginning_of_current_round;
+
+  voting_interval = get_voting_interval();
+  /* Find the time the current round started. */
+  beginning_of_current_round = get_start_time_of_current_round(now);
+
+  /* Find how many rounds are left till the end of the protocol run */
+  current_round = (now / voting_interval) % total_rounds;
+  rounds_left = total_rounds - current_round;
+
+  /* To find the valid-until time now, take the start time of the current
+   * round and add to it the time it takes for the leftover rounds to
+   * complete. */
+  valid_until = beginning_of_current_round + (rounds_left * voting_interval);
+
+  { /* Logging */
+    char tbuf[ISO_TIME_LEN + 1];
+    format_iso_time(tbuf, valid_until);
+    log_debug(LD_DIR, "SR: Valid until time for state set to %s.", tbuf);
+  }
+
+  return valid_until;
+}
+
+/* Given the consensus 'valid-after' time, return the protocol phase we should
+ * be in. */
+STATIC sr_phase_t
+get_sr_protocol_phase(time_t valid_after)
+{
+  /* Shared random protocol has two phases, commit and reveal. */
+  int total_periods = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES;
+  int current_slot;
+
+  /* Split time into slots of size 'voting_interval'. See which slot we are
+   * currently into, and find which phase it corresponds to. */
+  current_slot = (valid_after / get_voting_interval()) % total_periods;
+
+  if (current_slot < SHARED_RANDOM_N_ROUNDS) {
+    return SR_PHASE_COMMIT;
+  } else {
+    return SR_PHASE_REVEAL;
+  }
+}
+
+/* Add the given <b>commit</b> to <b>state</b>. It MUST be a valid commit
+ * and there shouldn't be a commit from the same authority in the state
+ * already else verification hasn't been done prior. This takes ownership of
+ * the commit once in our state. */
+static void
+commit_add_to_state(sr_commit_t *commit, sr_state_t *state)
+{
+  sr_commit_t *saved_commit;
+
+  tor_assert(commit);
+  tor_assert(state);
+
+  saved_commit = digestmap_set(state->commits, commit->rsa_identity_fpr,
+                               commit);
+  if (saved_commit != NULL) {
+    /* This means we already have that commit in our state so adding twice
+     * the same commit is either a code flow error, a corrupted disk state
+     * or some new unknown issue. */
+    log_warn(LD_DIR, "SR: Commit from %s exists in our state while "
+                     "adding it: '%s'", commit->rsa_identity_fpr,
+                     commit->encoded_commit);
+    sr_commit_free(saved_commit);
+  }
+}
+
+/* Helper: deallocate a commit object. (Used with digestmap_free(), which
+ * requires a function pointer whose argument is void *). */
+static void
+commit_free_(void *p)
+{
+  sr_commit_free(p);
+}
+
+/* Free a state that was allocated with state_new(). */
+static void
+state_free(sr_state_t *state)
+{
+  if (state == NULL) {
+    return;
+  }
+  tor_free(state->fname);
+  digestmap_free(state->commits, commit_free_);
+  tor_free(state->current_srv);
+  tor_free(state->previous_srv);
+  tor_free(state);
+}
+
+/* Allocate an sr_state_t object and returns it. If no <b>fname</b>, the
+ * default file name is used. This function does NOT initialize the state
+ * timestamp, phase or shared random value. NULL is never returned. */
+static sr_state_t *
+state_new(const char *fname, time_t now)
+{
+  sr_state_t *new_state = tor_malloc_zero(sizeof(*new_state));
+  /* If file name is not provided, use default. */
+  if (fname == NULL) {
+    fname = default_fname;
+  }
+  new_state->fname = tor_strdup(fname);
+  new_state->version = SR_PROTO_VERSION;
+  new_state->commits = digestmap_new();
+  new_state->phase = get_sr_protocol_phase(now);
+  new_state->valid_until = get_state_valid_until_time(now);
+  return new_state;
+}
+
+/* Set our global state pointer with the one given. */
+static void
+state_set(sr_state_t *state)
+{
+  tor_assert(state);
+  if (sr_state != NULL) {
+    state_free(sr_state);
+  }
+  sr_state = state;
+}
+
+/* Free an allocated disk state. */
+static void
+disk_state_free(sr_disk_state_t *state)
+{
+  if (state == NULL) {
+    return;
+  }
+  config_free(&state_format, state);
+}
+
+/* Allocate a new disk state, initialize it and return it. */
+static sr_disk_state_t *
+disk_state_new(time_t now)
+{
+  sr_disk_state_t *new_state = tor_malloc_zero(sizeof(*new_state));
+
+  new_state->magic_ = SR_DISK_STATE_MAGIC;
+  new_state->Version = SR_PROTO_VERSION;
+  new_state->TorVersion = tor_strdup(get_version());
+  new_state->ValidUntil = get_state_valid_until_time(now);
+  new_state->ValidAfter = now;
+
+  /* Init config format. */
+  config_init(&state_format, new_state);
+  return new_state;
+}
+
+/* Set our global disk state with the given state. */
+static void
+disk_state_set(sr_disk_state_t *state)
+{
+  tor_assert(state);
+  if (sr_disk_state != NULL) {
+    disk_state_free(sr_disk_state);
+  }
+  sr_disk_state = state;
+}
+
+/* Return -1 if the disk state is invalid (something in there that we can't or
+ * shouldn't use). Return 0 if everything checks out. */
+static int
+disk_state_validate(const sr_disk_state_t *state)
+{
+  time_t now;
+
+  tor_assert(state);
+
+  /* Do we support the protocol version in the state or is it 0 meaning
+   * Version wasn't found in the state file or bad anyway ? */
+  if (state->Version == 0 || state->Version > SR_PROTO_VERSION) {
+    goto invalid;
+  }
+
+  /* If the valid until time is before now, we shouldn't use that state. */
+  now = time(NULL);
+  if (state->ValidUntil < now) {
+    log_info(LD_DIR, "SR: Disk state has expired. Ignoring it.");
+    goto invalid;
+  }
+
+  /* Make sure we don't have a valid after time that is earlier than a valid
+   * until time which would make things not work well. */
+  if (state->ValidAfter >= state->ValidUntil) {
+    log_info(LD_DIR, "SR: Disk state valid after/until times are invalid.");
+    goto invalid;
+  }
+
+  return 0;
+
+ invalid:
+  return -1;
+}
+
+/* Validate the disk state (NOP for now). */
+static int
+disk_state_validate_cb(void *old_state, void *state, void *default_state,
+                       int from_setconf, char **msg)
+{
+  /* We don't use these; only options do. */
+  (void) from_setconf;
+  (void) default_state;
+  (void) old_state;
+
+  /* This is called by config_dump which is just before we are about to
+   * write it to disk. At that point, our global memory state has been
+   * copied to the disk state so it's fair to assume it's trustable. */
+  (void) state;
+  (void) msg;
+  return 0;
+}
+
+/* Parse the Commit line(s) in the disk state and translate them to the
+ * the memory state. Return 0 on success else -1 on error. */
+static int
+disk_state_parse_commits(sr_state_t *state,
+                         const sr_disk_state_t *disk_state)
+{
+  config_line_t *line;
+  smartlist_t *args = NULL;
+
+  tor_assert(state);
+  tor_assert(disk_state);
+
+  for (line = disk_state->Commit; line; line = line->next) {
+    sr_commit_t *commit = NULL;
+
+    /* Extra safety. */
+    if (strcasecmp(line->key, dstate_commit_key) ||
+        line->value == NULL) {
+      /* Ignore any lines that are not commits. */
+      tor_fragile_assert();
+      continue;
+    }
+    args = smartlist_new();
+    smartlist_split_string(args, line->value, " ",
+                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+    if (smartlist_len(args) < 3) {
+      log_warn(LD_BUG, "SR: Too few arguments in Commit Line: %s",
+               escaped(line->value));
+      goto error;
+    }
+    commit = sr_parse_commit(args);
+    if (commit == NULL) {
+      /* Ignore badly formed commit. It could also be a authority
+       * fingerprint that we don't know about so it shouldn't be used. */
+      continue;
+    }
+    /* Add commit to our state pointer. */
+    commit_add_to_state(commit, state);
+
+    SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
+    smartlist_free(args);
+  }
+
+  return 0;
+
+ error:
+  SMARTLIST_FOREACH(args, char *, cp, tor_free(cp));
+  smartlist_free(args);
+  return -1;
+}
+
+/* Parse a share random value line from the disk state and save it to dst
+ * which is an allocated srv object. Return 0 on success else -1. */
+static int
+disk_state_parse_srv(const char *value, sr_srv_t *dst)
+{
+  int ret = -1;
+  smartlist_t *args;
+  sr_srv_t *srv;
+
+  tor_assert(value);
+  tor_assert(dst);
+
+  args = smartlist_new();
+  smartlist_split_string(args, value, " ",
+                         SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+  if (smartlist_len(args) < 2) {
+    log_warn(LD_BUG, "SR: Too few arguments in shared random value. "
+             "Line: %s", escaped(value));
+    goto error;
+  }
+  srv = sr_parse_srv(args);
+  if (srv == NULL) {
+    goto error;
+  }
+  dst->num_reveals = srv->num_reveals;
+  memcpy(dst->value, srv->value, sizeof(dst->value));
+  tor_free(srv);
+  ret = 0;
+
+ error:
+  SMARTLIST_FOREACH(args, char *, s, tor_free(s));
+  smartlist_free(args);
+  return ret;
+}
+
+/* Parse both SharedRandCurrentValue and SharedRandPreviousValue line from
+ * the state. Return 0 on success else -1. */
+static int
+disk_state_parse_sr_values(sr_state_t *state,
+                           const sr_disk_state_t *disk_state)
+{
+  /* Only one value per type (current or previous) is allowed so we keep
+   * track of it with these flag. */
+  unsigned int seen_previous = 0, seen_current = 0;
+  config_line_t *line;
+  sr_srv_t *srv = NULL;
+
+  tor_assert(state);
+  tor_assert(disk_state);
+
+  for (line = disk_state->SharedRandValues; line; line = line->next) {
+    if (line->value == NULL) {
+      continue;
+    }
+    srv = tor_malloc_zero(sizeof(*srv));
+    if (disk_state_parse_srv(line->value, srv) < 0) {
+      log_warn(LD_BUG, "SR: Broken current SRV line in state %s",
+               escaped(line->value));
+      goto bad;
+    }
+    if (!strcasecmp(line->key, dstate_prev_srv_key)) {
+      if (seen_previous) {
+        log_warn(LD_DIR, "SR: Second previous SRV value seen. Bad state");
+        goto bad;
+      }
+      state->previous_srv = srv;
+      seen_previous = 1;
+    } else if (!strcasecmp(line->key, dstate_cur_srv_key)) {
+      if (seen_current) {
+        log_warn(LD_DIR, "SR: Second current SRV value seen. Bad state");
+        goto bad;
+      }
+      state->current_srv = srv;
+      seen_current = 1;
+    } else {
+      /* Unknown key. Ignoring. */
+      tor_free(srv);
+    }
+  }
+
+  return 0;
+ bad:
+  tor_free(srv);
+  return -1;
+}
+
+/* Parse the given disk state and set a newly allocated state. On success,
+ * return that state else NULL. */
+static sr_state_t *
+disk_state_parse(const sr_disk_state_t *new_disk_state)
+{
+  sr_state_t *new_state = state_new(default_fname, time(NULL));
+
+  tor_assert(new_disk_state);
+
+  new_state->version = new_disk_state->Version;
+  new_state->valid_until = new_disk_state->ValidUntil;
+  new_state->valid_after = new_disk_state->ValidAfter;
+
+  /* Set our current phase according to the valid-after time in our disk
+   * state. The disk state we are parsing contains everything for the phase
+   * starting at valid_after so make sure our phase reflects that. */
+  new_state->phase = get_sr_protocol_phase(new_state->valid_after);
+
+  /* Parse the shared random values. */
+  if (disk_state_parse_sr_values(new_state, new_disk_state) < 0) {
+    goto error;
+  }
+  /* Parse the commits. */
+  if (disk_state_parse_commits(new_state, new_disk_state) < 0) {
+    goto error;
+  }
+  /* Great! This new state contains everything we had on disk. */
+  return new_state;
+
+ error:
+  state_free(new_state);
+  return NULL;
+}
+
+/* From a valid commit object and an allocated config line, set the line's
+ * value to the state string representation of a commit. */
+static void
+disk_state_put_commit_line(const sr_commit_t *commit, config_line_t *line)
+{
+  char *reveal_str = NULL;
+
+  tor_assert(commit);
+  tor_assert(line);
+
+  if (!tor_mem_is_zero(commit->encoded_reveal,
+                       sizeof(commit->encoded_reveal))) {
+    /* Add extra whitespace so we can format the line correctly. */
+    tor_asprintf(&reveal_str, " %s", commit->encoded_reveal);
+  }
+  tor_asprintf(&line->value, "%s %s %s%s",
+               crypto_digest_algorithm_get_name(commit->alg),
+               commit->rsa_identity_fpr,
+               commit->encoded_commit,
+               reveal_str != NULL ? reveal_str : "");
+  if (reveal_str != NULL) {
+    memwipe(reveal_str, 0, strlen(reveal_str));
+    tor_free(reveal_str);
+  }
+}
+
+/* From a valid srv object and an allocated config line, set the line's
+ * value to the state string representation of a shared random value. */
+static void
+disk_state_put_srv_line(const sr_srv_t *srv, config_line_t *line)
+{
+  char encoded[HEX_DIGEST256_LEN + 1];
+
+  tor_assert(line);
+
+  /* No SRV value thus don't add the line. This is possible since we might
+   * not have a current or previous SRV value in our state. */
+  if (srv == NULL) {
+    return;
+  }
+  base16_encode(encoded, sizeof(encoded), (const char *) srv->value,
+                sizeof(srv->value));
+  tor_asprintf(&line->value, "%d %s", srv->num_reveals, encoded);
+}
+
+/* Reset disk state that is free allocated memory and zeroed the object. */
+static void
+disk_state_reset(void)
+{
+  config_free_lines(sr_disk_state->Commit);
+  config_free_lines(sr_disk_state->SharedRandValues);
+  config_free_lines(sr_disk_state->ExtraLines);
+  memset(sr_disk_state, 0, sizeof(*sr_disk_state));
+  sr_disk_state->magic_ = SR_DISK_STATE_MAGIC;
+  sr_disk_state->TorVersion = tor_strdup(get_version());
+}
+
+/* Update our disk state based on our global SR state. */
+static void
+disk_state_update(void)
+{
+  config_line_t **next, *line;
+
+  tor_assert(sr_disk_state);
+  tor_assert(sr_state);
+
+  /* Reset current disk state. */
+  disk_state_reset();
+
+  /* First, update elements that we don't need to iterate over a list to
+   * construct something. */
+  sr_disk_state->Version = sr_state->version;
+  sr_disk_state->ValidUntil = sr_state->valid_until;
+  sr_disk_state->ValidAfter = sr_state->valid_after;
+
+  /* Shared random values. */
+  next = &sr_disk_state->SharedRandValues;
+  *next = NULL;
+  if (sr_state->previous_srv != NULL) {
+    *next = line = tor_malloc_zero(sizeof(config_line_t));
+    line->key = tor_strdup(dstate_prev_srv_key);
+    disk_state_put_srv_line(sr_state->previous_srv, line);
+    next = &(line->next);
+  }
+  if (sr_state->current_srv != NULL) {
+    *next = line = tor_malloc_zero(sizeof(*line));
+    line->key = tor_strdup(dstate_cur_srv_key);
+    disk_state_put_srv_line(sr_state->current_srv, line);
+    next = &(line->next);
+  }
+
+  /* Parse the commits and construct config line(s). */
+  next = &sr_disk_state->Commit;
+  DIGESTMAP_FOREACH(sr_state->commits, key, sr_commit_t *, commit) {
+    *next = line = tor_malloc_zero(sizeof(*line));
+    line->key = tor_strdup(dstate_commit_key);
+    disk_state_put_commit_line(commit, line);
+    next = &(line->next);
+  } DIGESTMAP_FOREACH_END;
+}
+
+/* Load state from disk and put it into our disk state. If the state passes
+ * validation, our global state will be updated with it. Return 0 on
+ * success. On error, -EINVAL is returned if the state on disk did contained
+ * something malformed or is unreadable. -ENOENT is returned indicating that
+ * the state file is either empty of non existing. */
+static int
+disk_state_load_from_disk(void)
+{
+  int ret;
+  char *fname;
+
+  fname = get_datadir_fname(default_fname);
+  ret = disk_state_load_from_disk_impl(fname);
+  tor_free(fname);
+
+  return ret;
+}
+
+/* Helper for disk_state_load_from_disk(). */
+STATIC int
+disk_state_load_from_disk_impl(const char *fname)
+{
+  int ret;
+  char *content = NULL;
+  sr_state_t *parsed_state = NULL;
+  sr_disk_state_t *disk_state = NULL;
+
+  /* Read content of file so we can parse it. */
+  if ((content = read_file_to_str(fname, 0, NULL)) == NULL) {
+    log_warn(LD_FS, "SR: Unable to read SR state file %s",
+             escaped(fname));
+    ret = -errno;
+    goto error;
+  }
+
+  {
+    config_line_t *lines = NULL;
+    char *errmsg = NULL;
+
+    /* Every error in this code path will return EINVAL. */
+    ret = -EINVAL;
+    if (config_get_lines(content, &lines, 0) < 0) {
+      config_free_lines(lines);
+      goto error;
+    }
+
+    disk_state = disk_state_new(time(NULL));
+    config_assign(&state_format, disk_state, lines, 0, 0, &errmsg);
+    config_free_lines(lines);
+    if (errmsg) {
+      log_warn(LD_DIR, "SR: Reading state error: %s", errmsg);
+      tor_free(errmsg);
+      goto error;
+    }
+  }
+
+  /* So far so good, we've loaded our state file into our disk state. Let's
+   * validate it and then parse it. */
+  if (disk_state_validate(disk_state) < 0) {
+    ret = -EINVAL;
+    goto error;
+  }
+
+  parsed_state = disk_state_parse(disk_state);
+  if (parsed_state == NULL) {
+    ret = -EINVAL;
+    goto error;
+  }
+  state_set(parsed_state);
+  disk_state_set(disk_state);
+  tor_free(content);
+  log_notice(LD_DIR, "SR: State loaded successfully from file %s", fname);
+  return 0;
+
+ error:
+  disk_state_free(disk_state);
+  tor_free(content);
+  return ret;
+}
+
+/* Save the disk state to disk but before that update it from the current
+ * state so we always have the latest. Return 0 on success else -1. */
+static int
+disk_state_save_to_disk(void)
+{
+  int ret;
+  char *state, *content = NULL, *fname = NULL;
+  char tbuf[ISO_TIME_LEN + 1];
+  time_t now = time(NULL);
+
+  /* If we didn't have the opportunity to setup an internal disk state,
+   * don't bother saving something to disk. */
+  if (sr_disk_state == NULL) {
+    ret = 0;
+    goto done;
+  }
+
+  /* Make sure that our disk state is up to date with our memory state
+   * before saving it to disk. */
+  disk_state_update();
+  state = config_dump(&state_format, NULL, sr_disk_state, 0, 0);
+  format_local_iso_time(tbuf, now);
+  tor_asprintf(&content,
+               "# Tor shared random state file last generated on %s "
+               "local time\n"
+               "# Other times below are in UTC\n"
+               "# Please *do not* edit this file.\n\n%s",
+               tbuf, state);
+  tor_free(state);
+  fname = get_datadir_fname(default_fname);
+  if (write_str_to_file(fname, content, 0) < 0) {
+    log_warn(LD_FS, "SR: Unable to write SR state to file %s", fname);
+    ret = -1;
+    goto done;
+  }
+  ret = 0;
+  log_debug(LD_DIR, "SR: Saved state to file %s", fname);
+
+ done:
+  tor_free(fname);
+  tor_free(content);
+  return ret;
+}
+
+/* Helper function: return a commit using the RSA fingerprint of the
+ * authority or NULL if no such commit is known. */
+static sr_commit_t *
+state_query_get_commit(const char *rsa_fpr)
+{
+  tor_assert(rsa_fpr);
+  return digestmap_get(sr_state->commits, rsa_fpr);
+}
+
+/* Helper function: This handles the GET state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void *
+state_query_get_(sr_state_object_t obj_type, const void *data)
+{
+  void *obj = NULL;
+
+  switch (obj_type) {
+  case SR_STATE_OBJ_COMMIT:
+  {
+    obj = state_query_get_commit(data);
+    break;
+  }
+  case SR_STATE_OBJ_COMMITS:
+    obj = sr_state->commits;
+    break;
+  case SR_STATE_OBJ_CURSRV:
+    obj = sr_state->current_srv;
+    break;
+  case SR_STATE_OBJ_PREVSRV:
+    obj = sr_state->previous_srv;
+    break;
+  case SR_STATE_OBJ_PHASE:
+    obj = &sr_state->phase;
+    break;
+  case SR_STATE_OBJ_VALID_AFTER:
+  default:
+    tor_assert(0);
+  }
+  return obj;
+}
+
+/* Helper function: This handles the PUT state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void
+state_query_put_(sr_state_object_t obj_type, void *data)
+{
+  switch (obj_type) {
+  case SR_STATE_OBJ_COMMIT:
+  {
+    sr_commit_t *commit = data;
+    tor_assert(commit);
+    commit_add_to_state(commit, sr_state);
+    break;
+  }
+  case SR_STATE_OBJ_CURSRV:
+    sr_state->current_srv = (sr_srv_t *) data;
+    break;
+  case SR_STATE_OBJ_PREVSRV:
+    sr_state->previous_srv = (sr_srv_t *) data;
+    break;
+  case SR_STATE_OBJ_VALID_AFTER:
+    sr_state->valid_after = *((time_t *) data);
+    break;
+  /* It's not allowed to change the phase nor the full commitments map from
+   * the state. The phase is decided during a strict process post voting and
+   * the commits should be put individually. */
+  case SR_STATE_OBJ_PHASE:
+  case SR_STATE_OBJ_COMMITS:
+  default:
+    tor_assert(0);
+  }
+}
+
+/* Helper function: This handles the DEL state action using an
+ * <b>obj_type</b> and <b>data</b> needed for the action. */
+static void
+state_query_del_all_(sr_state_object_t obj_type)
+{
+  switch (obj_type) {
+  case SR_STATE_OBJ_COMMIT:
+  {
+    /* We are in a new protocol run so cleanup commitments. */
+    DIGESTMAP_FOREACH_MODIFY(sr_state->commits, key, sr_commit_t *, c) {
+      sr_commit_free(c);
+      MAP_DEL_CURRENT(key);
+    } DIGESTMAP_FOREACH_END;
+    break;
+  }
+  /* The following object are _NOT_ suppose to be removed. */
+  case SR_STATE_OBJ_CURSRV:
+  case SR_STATE_OBJ_PREVSRV:
+  case SR_STATE_OBJ_PHASE:
+  case SR_STATE_OBJ_COMMITS:
+  case SR_STATE_OBJ_VALID_AFTER:
+  default:
+    tor_assert(0);
+  }
+}
+
+/* Query state using an <b>action</b> for an object type <b>obj_type</b>.
+ * The <b>data</b> pointer needs to point to an object that the action needs
+ * to use and if anything is required to be returned, it is stored in
+ * <b>out</b>.
+ *
+ * This mechanism exists so we have one single point where we synchronized
+ * our memory state with our disk state for every actions that changes it.
+ * We then trigger a write on disk immediately.
+ *
+ * This should be the only entry point to our memory state. It's used by all
+ * our state accessors and should be in the future. */
+static void
+state_query(sr_state_action_t action, sr_state_object_t obj_type,
+            void *data, void **out)
+{
+  switch (action) {
+  case SR_STATE_ACTION_GET:
+    *out = state_query_get_(obj_type, data);
+    break;
+  case SR_STATE_ACTION_PUT:
+    state_query_put_(obj_type, data);
+    break;
+  case SR_STATE_ACTION_DEL_ALL:
+    state_query_del_all_(obj_type);
+    break;
+  case SR_STATE_ACTION_SAVE:
+    /* Only trigger a disk state save. */
+    break;
+  default:
+    tor_assert(0);
+  }
+
+  /* If the action actually changes the state, immediately save it to disk.
+   * The following will sync the state -> disk state and then save it. */
+  if (action != SR_STATE_ACTION_GET) {
+    disk_state_save_to_disk();
+  }
+}
+
+/* Set valid after time in the our state. */
+void
+sr_state_set_valid_after(time_t valid_after)
+{
+  state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_VALID_AFTER,
+              (void *) &valid_after, NULL);
+}
+
+/* Return the phase we are currently in according to our state. */
+sr_phase_t
+sr_state_get_phase(void)
+{
+  void *ptr;
+  state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PHASE, NULL, &ptr);
+  return *(sr_phase_t *) ptr;
+}
+
+/* Return the previous SRV value from our state. Value CAN be NULL. */
+sr_srv_t *
+sr_state_get_previous_srv(void)
+{
+  sr_srv_t *srv;
+  state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_PREVSRV, NULL,
+              (void *) &srv);
+  return srv;
+}
+
+/* Set the current SRV value from our state. Value CAN be NULL. The srv
+ * object ownership is transfered to the state object. */
+void
+sr_state_set_previous_srv(const sr_srv_t *srv)
+{
+  state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_PREVSRV, (void *) srv,
+              NULL);
+}
+
+/* Return the current SRV value from our state. Value CAN be NULL. */
+sr_srv_t *
+sr_state_get_current_srv(void)
+{
+  sr_srv_t *srv;
+  state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_CURSRV, NULL,
+              (void *) &srv);
+  return srv;
+}
+
+/* Set the current SRV value from our state. Value CAN be NULL. The srv
+ * object ownership is transfered to the state object. */
+void
+sr_state_set_current_srv(const sr_srv_t *srv)
+{
+  state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_CURSRV, (void *) srv,
+              NULL);
+}
+
+/* Return a pointer to the commits map from our state. CANNOT be NULL. */
+digestmap_t *
+sr_state_get_commits(void)
+{
+  digestmap_t *commits;
+  state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMITS,
+              NULL, (void *) &commits);
+  tor_assert(commits);
+  return commits;
+}
+
+/* Return commit object from the given authority digest <b>identity</b>.
+ * Return NULL if not found. */
+sr_commit_t *
+sr_state_get_commit(const char *rsa_fpr)
+{
+  sr_commit_t *commit;
+
+  tor_assert(rsa_fpr);
+
+  state_query(SR_STATE_ACTION_GET, SR_STATE_OBJ_COMMIT,
+              (void *) rsa_fpr, (void *) &commit);
+  return commit;
+}
+
+/* Add <b>commit</b> to the permanent state. The commit object ownership is
+ * transfered to the state so the caller MUST not free it. */
+void
+sr_state_add_commit(sr_commit_t *commit)
+{
+  tor_assert(commit);
+
+  /* Put the commit to the global state. */
+  state_query(SR_STATE_ACTION_PUT, SR_STATE_OBJ_COMMIT,
+              (void *) commit, NULL);
+
+  log_debug(LD_DIR, "SR: Commit from %s has been added to our state.",
+           commit->rsa_identity_fpr);
+}
+
+/* Remove all commits from our state. */
+void
+sr_state_delete_commits(void)
+{
+  state_query(SR_STATE_ACTION_DEL_ALL, SR_STATE_OBJ_COMMIT, NULL, NULL);
+}
+
+/* Set the fresh SRV flag from our state. This doesn't need to trigger a
+ * disk state synchronization so we directly change the state. */
+void
+sr_state_set_fresh_srv(void)
+{
+  sr_state->is_srv_fresh = 1;
+}
+
+/* Unset the fresh SRV flag from our state. This doesn't need to trigger a
+ * disk state synchronization so we directly change the state. */
+void
+sr_state_unset_fresh_srv(void)
+{
+  sr_state->is_srv_fresh = 0;
+}
+
+/* Return the value of the fresh SRV flag. */
+unsigned int
+sr_state_srv_is_fresh(void)
+{
+  return sr_state->is_srv_fresh;
+}
+
+/* Cleanup and free our disk and memory state. */
+void
+sr_state_free(void)
+{
+  state_free(sr_state);
+  disk_state_free(sr_disk_state);
+  /* Nullify our global state. */
+  sr_state = NULL;
+  sr_disk_state = NULL;
+}
+
+/* Save our current state in memory to disk. */
+void
+sr_state_save(void)
+{
+  /* Query a SAVE action on our current state so it's synced and saved. */
+  state_query(SR_STATE_ACTION_SAVE, 0, NULL, NULL);
+}
+
+/* Initialize the disk and memory state.
+ *
+ * If save_to_disk is set to 1, the state is immediately saved to disk after
+ * creation else it's not thus only kept in memory.
+ * If read_from_disk is set to 1, we try to load the state from the disk and
+ * if not found, a new state is created.
+ *
+ * Return 0 on success else a negative value on error. */
+int
+sr_state_init(int save_to_disk, int read_from_disk)
+{
+  int ret = -ENOENT;
+  time_t now = time(NULL);
+
+  /* We shouldn't have those assigned. */
+  tor_assert(sr_disk_state == NULL);
+  tor_assert(sr_state == NULL);
+
+  /* First, try to load the state from disk. */
+  if (read_from_disk) {
+    ret = disk_state_load_from_disk();
+  }
+
+  if (ret < 0) {
+    switch (-ret) {
+    case EINVAL:
+      /* We have a state on disk but it contains something we couldn't parse
+       * or an invalid entry in the state file. Let's remove it since it's
+       * obviously unusable and replace it by an new fresh state below. */
+    case ENOENT:
+      {
+        /* No state on disk so allocate our states for the first time. */
+        sr_state_t *new_state = state_new(default_fname, now);
+        sr_disk_state_t *new_disk_state = disk_state_new(now);
+        state_set(new_state);
+        /* It's important to set our disk state pointer since the save call
+         * below uses it to synchronized it with our memory state.  */
+        disk_state_set(new_disk_state);
+        /* No entry, let's save our new state to disk. */
+        if (save_to_disk && disk_state_save_to_disk() < 0) {
+          goto error;
+        }
+        break;
+      }
+    default:
+      /* Big problem. Not possible. */
+      tor_assert(0);
+    }
+  }
+  return 0;
+
+ error:
+  return -1;
+}
diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h
new file mode 100644
index 0000000..a9a80ee
--- /dev/null
+++ b/src/or/shared_random_state.h
@@ -0,0 +1,126 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_SHARED_RANDOM_STATE_H
+#define TOR_SHARED_RANDOM_STATE_H
+
+#include "shared_random.h"
+
+/* Action that can be performed on the state for any objects. */
+typedef enum {
+  SR_STATE_ACTION_GET     = 1,
+  SR_STATE_ACTION_PUT     = 2,
+  SR_STATE_ACTION_DEL_ALL = 3,
+  SR_STATE_ACTION_SAVE    = 4,
+} sr_state_action_t;
+
+/* Object in the state that can be queried through the state API. */
+typedef enum {
+  /* Will return a single commit using an authority identity key. */
+  SR_STATE_OBJ_COMMIT,
+  /* Returns the entire list of commits from the state. */
+  SR_STATE_OBJ_COMMITS,
+  /* Return the current SRV object pointer. */
+  SR_STATE_OBJ_CURSRV,
+  /* Return the previous SRV object pointer. */
+  SR_STATE_OBJ_PREVSRV,
+  /* Return the phase. */
+  SR_STATE_OBJ_PHASE,
+  /* Get or Put the valid after time. */
+  SR_STATE_OBJ_VALID_AFTER,
+} sr_state_object_t;
+
+/* State of the protocol. It's also saved on disk in fname. This data
+ * structure MUST be synchronized at all time with the one on disk. */
+typedef struct sr_state_t {
+  /* Filename of the state file on disk. */
+  char *fname;
+  /* Version of the protocol. */
+  uint8_t version;
+  /* The valid-after of the voting period we have prepared the state for. */
+  time_t valid_after;
+  /* Until when is this state valid? */
+  time_t valid_until;
+  /* Protocol phase. */
+  sr_phase_t phase;
+
+  /* Number of runs completed. */
+  uint64_t n_protocol_runs;
+  /* The number of commitment rounds we've performed in this protocol run. */
+  unsigned int n_commit_rounds;
+  /* The number of reveal rounds we've performed in this protocol run. */
+  unsigned int n_reveal_rounds;
+
+  /* A map of all the received commitments for this protocol run. This is
+   * indexed by authority RSA identity digest. */
+  digestmap_t *commits;
+
+  /* Current and previous shared random value. */
+  sr_srv_t *previous_srv;
+  sr_srv_t *current_srv;
+
+  /* Indicate if the state contains an SRV that was _just_ generated. This is
+   * used during voting so that we know whether to use the super majority rule
+   * or not when deciding on keeping it for the consensus. It is _always_ set
+   * to 0 post consensus.
+   *
+   * EDGE CASE: if an authority computes a new SRV then immediately reboots
+   * and, once back up, votes for the current round, it won't know if the
+   * SRV is fresh or not ultimately making it _NOT_ use the super majority
+   * when deciding to put or not the SRV in the consensus. This is for now
+   * an acceptable very rare edge case. */
+  unsigned int is_srv_fresh:1;
+} sr_state_t;
+
+/* Persistent state of the protocol, as saved to disk. */
+typedef struct sr_disk_state_t {
+  uint32_t magic_;
+  /* Version of the protocol. */
+  int Version;
+  /* Version of our running tor. */
+  char *TorVersion;
+  /* Creation time of this state */
+  time_t ValidAfter;
+  /* State valid until? */
+  time_t ValidUntil;
+  /* All commits seen that are valid. */
+  config_line_t *Commit;
+  /* Previous and current shared random value. */
+  config_line_t *SharedRandValues;
+  /* Extra Lines for configuration we might not know. */
+  config_line_t *ExtraLines;
+} sr_disk_state_t;
+
+/* API */
+
+/* Public methods: */
+
+/* Private methods (only used by shared-random.c): */
+
+void sr_state_set_valid_after(time_t valid_after);
+sr_phase_t sr_state_get_phase(void);
+sr_srv_t *sr_state_get_previous_srv(void);
+sr_srv_t *sr_state_get_current_srv(void);
+void sr_state_set_previous_srv(const sr_srv_t *srv);
+void sr_state_set_current_srv(const sr_srv_t *srv);
+void sr_state_clean_srvs(void);
+digestmap_t *sr_state_get_commits(void);
+sr_commit_t *sr_state_get_commit(const char *rsa_fpr);
+void sr_state_add_commit(sr_commit_t *commit);
+void sr_state_delete_commits(void);
+unsigned int sr_state_srv_is_fresh(void);
+void sr_state_set_fresh_srv(void);
+void sr_state_unset_fresh_srv(void);
+int sr_state_init(int save_to_disk, int read_from_disk);
+void sr_state_save(void);
+void sr_state_free(void);
+
+#ifdef SHARED_RANDOM_STATE_PRIVATE
+
+STATIC int disk_state_load_from_disk_impl(const char *fname);
+STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after);
+STATIC time_t get_state_valid_until_time(time_t now);
+
+#endif /* SHARED_RANDOM_STATE_PRIVATE */
+
+#endif /* TOR_SHARED_RANDOM_STATE_H */



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits