Coverage Report

Created: 2025-10-10 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tor/src/feature/dircache/consdiffmgr.c
Line
Count
Source
1
/* Copyright (c) 2017-2021, The Tor Project, Inc. */
2
/* See LICENSE for licensing information */
3
4
/**
5
 * \file consdiffmgr.c
6
 *
7
 * \brief consensus diff manager functions
8
 *
9
 * This module is run by directory authorities and caches in order
10
 * to remember a number of past consensus documents, and to generate
11
 * and serve the diffs from those documents to the latest consensus.
12
 */
13
14
#define CONSDIFFMGR_PRIVATE
15
16
#include "core/or/or.h"
17
#include "app/config/config.h"
18
#include "feature/dircache/conscache.h"
19
#include "feature/dircommon/consdiff.h"
20
#include "feature/dircache/consdiffmgr.h"
21
#include "core/mainloop/cpuworker.h"
22
#include "feature/nodelist/networkstatus.h"
23
#include "feature/dirparse/ns_parse.h"
24
#include "lib/evloop/compat_libevent.h"
25
#include "lib/evloop/workqueue.h"
26
#include "lib/compress/compress.h"
27
#include "lib/encoding/confline.h"
28
29
#include "feature/nodelist/networkstatus_st.h"
30
#include "feature/nodelist/networkstatus_voter_info_st.h"
31
32
/**
33
 * Labels to apply to items in the conscache object.
34
 *
35
 * @{
36
 */
37
/* One of DOCTYPE_CONSENSUS or DOCTYPE_CONSENSUS_DIFF */
38
0
#define LABEL_DOCTYPE "document-type"
39
/* The valid-after time for a consensus (or for the target consensus of a
40
 * diff), encoded as ISO UTC. */
41
0
#define LABEL_VALID_AFTER "consensus-valid-after"
42
/* The fresh-until time for a consensus (or for the target consensus of a
43
 * diff), encoded as ISO UTC. */
44
0
#define LABEL_FRESH_UNTIL "consensus-fresh-until"
45
/* The valid-until time for a consensus (or for the target consensus of a
46
 * diff), encoded as ISO UTC. */
47
0
#define LABEL_VALID_UNTIL "consensus-valid-until"
48
/* Comma-separated list of hex-encoded identity digests for the voting
49
 * authorities. */
50
0
#define LABEL_SIGNATORIES "consensus-signatories"
51
/* A hex encoded SHA3 digest of the object, as compressed (if any) */
52
0
#define LABEL_SHA3_DIGEST "sha3-digest"
53
/* A hex encoded SHA3 digest of the object before compression. */
54
0
#define LABEL_SHA3_DIGEST_UNCOMPRESSED "sha3-digest-uncompressed"
55
/* A hex encoded SHA3 digest-as-signed of a consensus */
56
0
#define LABEL_SHA3_DIGEST_AS_SIGNED "sha3-digest-as-signed"
57
/* The flavor of the consensus or consensuses diff */
58
0
#define LABEL_FLAVOR "consensus-flavor"
59
/* Diff only: the SHA3 digest-as-signed of the source consensus. */
60
0
#define LABEL_FROM_SHA3_DIGEST "from-sha3-digest"
61
/* Diff only: the SHA3 digest-in-full of the target consensus. */
62
0
#define LABEL_TARGET_SHA3_DIGEST "target-sha3-digest"
63
/* Diff only: the valid-after date of the source consensus. */
64
0
#define LABEL_FROM_VALID_AFTER "from-valid-after"
65
/* What kind of compression was used? */
66
0
#define LABEL_COMPRESSION_TYPE "compression"
67
/** @} */
68
69
0
#define DOCTYPE_CONSENSUS "consensus"
70
0
#define DOCTYPE_CONSENSUS_DIFF "consensus-diff"
71
72
/**
73
 * Underlying directory that stores consensuses and consensus diffs.  Don't
74
 * use this directly: use cdm_cache_get() instead.
75
 */
76
static consensus_cache_t *cons_diff_cache = NULL;
77
/**
78
 * If true, we have learned at least one new consensus since the
79
 * consensus cache was last up-to-date.
80
 */
81
static int cdm_cache_dirty = 0;
82
/**
83
 * If true, we have scanned the cache to update our hashtable of diffs.
84
 */
85
static int cdm_cache_loaded = 0;
86
87
/**
88
 * Possible status values for cdm_diff_t.cdm_diff_status
89
 **/
90
typedef enum cdm_diff_status_t {
91
  CDM_DIFF_PRESENT=1,
92
  CDM_DIFF_IN_PROGRESS=2,
93
  CDM_DIFF_ERROR=3,
94
} cdm_diff_status_t;
95
96
/** Which methods do we use for precompressing diffs? */
97
static const compress_method_t compress_diffs_with[] = {
98
  NO_METHOD,
99
  GZIP_METHOD,
100
#ifdef HAVE_LZMA
101
  LZMA_METHOD,
102
#endif
103
#ifdef HAVE_ZSTD
104
  ZSTD_METHOD,
105
#endif
106
};
107
108
/**
109
 * Event for rescanning the cache.
110
 */
111
static mainloop_event_t *consdiffmgr_rescan_ev = NULL;
112
113
static void consdiffmgr_rescan_cb(mainloop_event_t *ev, void *arg);
114
static void mark_cdm_cache_dirty(void);
115
116
/** How many different methods will we try to use for diff compression? */
117
STATIC unsigned
118
n_diff_compression_methods(void)
119
0
{
120
0
  return ARRAY_LENGTH(compress_diffs_with);
121
0
}
122
123
/** Which methods do we use for precompressing consensuses? */
124
static const compress_method_t compress_consensus_with[] = {
125
  ZLIB_METHOD,
126
#ifdef HAVE_LZMA
127
  LZMA_METHOD,
128
#endif
129
#ifdef HAVE_ZSTD
130
  ZSTD_METHOD,
131
#endif
132
};
133
134
/** How many different methods will we try to use for diff compression? */
135
STATIC unsigned
136
n_consensus_compression_methods(void)
137
0
{
138
0
  return ARRAY_LENGTH(compress_consensus_with);
139
0
}
140
141
/** For which compression method do we retain old consensuses?  There's no
142
 * need to keep all of them, since we won't be serving them.  We'll
143
 * go with ZLIB_METHOD because it's pretty fast and everyone has it.
144
 */
145
0
#define RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD ZLIB_METHOD
146
147
/** Handles pointing to the latest consensus entries as compressed and
148
 * stored. */
149
static consensus_cache_entry_handle_t *
150
                  latest_consensus[N_CONSENSUS_FLAVORS]
151
                                  [ARRAY_LENGTH(compress_consensus_with)];
152
153
/** Hashtable node used to remember the current status of the diff
154
 * from a given sha3 digest to the current consensus.  */
155
typedef struct cdm_diff_t {
156
  HT_ENTRY(cdm_diff_t) node;
157
158
  /** Consensus flavor for this diff (part of ht key) */
159
  consensus_flavor_t flavor;
160
  /** SHA3-256 digest of the consensus that this diff is _from_. (part of the
161
   * ht key) */
162
  uint8_t from_sha3[DIGEST256_LEN];
163
  /** Method by which the diff is compressed. (part of the ht key */
164
  compress_method_t compress_method;
165
166
  /** One of the CDM_DIFF_* values, depending on whether this diff
167
   * is available, in progress, or impossible to compute. */
168
  cdm_diff_status_t cdm_diff_status;
169
  /** SHA3-256 digest of the consensus that this diff is _to. */
170
  uint8_t target_sha3[DIGEST256_LEN];
171
172
  /** Handle to the cache entry for this diff, if any.  We use a handle here
173
   * to avoid thinking too hard about cache entry lifetime issues. */
174
  consensus_cache_entry_handle_t *entry;
175
} cdm_diff_t;
176
177
/** Hashtable mapping flavor and source consensus digest to status. */
178
static HT_HEAD(cdm_diff_ht, cdm_diff_t) cdm_diff_ht = HT_INITIALIZER();
179
180
#ifdef _WIN32
181
   // XXX(ahf): For tor#24857, a contributor suggested that on Windows, the CPU
182
   // begins to spike at 100% once the number of files handled by the consensus
183
   // diff manager becomes larger than 64. To see if the issue goes away, we
184
   // hardcode this value to 64 now while we investigate a better solution.
185
#  define CACHE_MAX_NUM 64
186
#else /* !defined(_WIN32) */
187
#  define CACHE_MAX_NUM 128
188
#endif /* defined(_WIN32) */
189
190
/**
191
 * Configuration for this module
192
 */
193
static consdiff_cfg_t consdiff_cfg = {
194
  // XXXX I'd like to make this number bigger, but it interferes with the
195
  // XXXX seccomp2 syscall filter, which tops out at BPF_MAXINS (4096)
196
  // XXXX rules.
197
  /* .cache_max_num = */ CACHE_MAX_NUM
198
};
199
200
static int consdiffmgr_ensure_space_for_files(int n);
201
static int consensus_queue_compression_work(const char *consensus,
202
                                            size_t consensus_len,
203
                                            const networkstatus_t *as_parsed);
204
static int consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from,
205
                                          consensus_cache_entry_t *diff_to);
206
static void consdiffmgr_set_cache_flags(void);
207
208
/* =====
209
 * Hashtable setup
210
 * ===== */
211
212
/** Helper: hash the key of a cdm_diff_t. */
213
static unsigned
214
cdm_diff_hash(const cdm_diff_t *diff)
215
0
{
216
0
  uint8_t tmp[DIGEST256_LEN + 2];
217
0
  memcpy(tmp, diff->from_sha3, DIGEST256_LEN);
218
0
  tmp[DIGEST256_LEN] = (uint8_t) diff->flavor;
219
0
  tmp[DIGEST256_LEN+1] = (uint8_t) diff->compress_method;
220
0
  return (unsigned) siphash24g(tmp, sizeof(tmp));
221
0
}
222
/** Helper: compare two cdm_diff_t objects for key equality */
223
static int
224
cdm_diff_eq(const cdm_diff_t *diff1, const cdm_diff_t *diff2)
225
0
{
226
0
  return fast_memeq(diff1->from_sha3, diff2->from_sha3, DIGEST256_LEN) &&
227
0
    diff1->flavor == diff2->flavor &&
228
0
    diff1->compress_method == diff2->compress_method;
229
0
}
230
231
0
HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq);
Unexecuted instantiation: consdiffmgr.c:cdm_diff_ht_HT_INIT
Unexecuted instantiation: consdiffmgr.c:cdm_diff_ht_HT_FIND
Unexecuted instantiation: consdiffmgr.c:cdm_diff_ht_HT_FIND_P_
Unexecuted instantiation: consdiffmgr.c:cdm_diff_ht_HT_NEXT
Unexecuted instantiation: consdiffmgr.c:cdm_diff_ht_HT_START
Unexecuted instantiation: consdiffmgr.c:cdm_diff_ht_HT_NEXT_RMV
232
0
HT_GENERATE2(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq,
233
0
             0.6, tor_reallocarray, tor_free_);
234
235
#define cdm_diff_free(diff) \
236
0
  FREE_AND_NULL(cdm_diff_t, cdm_diff_free_, (diff))
237
238
/** Release all storage held in <b>diff</b>. */
239
static void
240
cdm_diff_free_(cdm_diff_t *diff)
241
0
{
242
0
  if (!diff)
243
0
    return;
244
0
  consensus_cache_entry_handle_free(diff->entry);
245
0
  tor_free(diff);
246
0
}
247
248
/** Create and return a new cdm_diff_t with the given values.  Does not
249
 * add it to the hashtable. */
250
static cdm_diff_t *
251
cdm_diff_new(consensus_flavor_t flav,
252
             const uint8_t *from_sha3,
253
             const uint8_t *target_sha3,
254
             compress_method_t method)
255
0
{
256
0
  cdm_diff_t *ent;
257
0
  ent = tor_malloc_zero(sizeof(cdm_diff_t));
258
0
  ent->flavor = flav;
259
0
  memcpy(ent->from_sha3, from_sha3, DIGEST256_LEN);
260
0
  memcpy(ent->target_sha3, target_sha3, DIGEST256_LEN);
261
0
  ent->compress_method = method;
262
0
  return ent;
263
0
}
264
265
/**
266
 * Examine the diff hashtable to see whether we know anything about computing
267
 * a diff of type <b>flav</b> between consensuses with the two provided
268
 * SHA3-256 digests.  If a computation is in progress, or if the computation
269
 * has already been tried and failed, return 1.  Otherwise, note the
270
 * computation as "in progress" so that we don't reattempt it later, and
271
 * return 0.
272
 */
273
static int
274
cdm_diff_ht_check_and_note_pending(consensus_flavor_t flav,
275
                                   const uint8_t *from_sha3,
276
                                   const uint8_t *target_sha3)
277
0
{
278
0
  struct cdm_diff_t search, *ent;
279
0
  unsigned u;
280
0
  int result = 0;
281
0
  for (u = 0; u < n_diff_compression_methods(); ++u) {
282
0
    compress_method_t method = compress_diffs_with[u];
283
0
    memset(&search, 0, sizeof(cdm_diff_t));
284
0
    search.flavor = flav;
285
0
    search.compress_method = method;
286
0
    memcpy(search.from_sha3, from_sha3, DIGEST256_LEN);
287
0
    ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search);
288
0
    if (ent) {
289
0
      tor_assert_nonfatal(ent->cdm_diff_status != CDM_DIFF_PRESENT);
290
0
      result = 1;
291
0
      continue;
292
0
    }
293
0
    ent = cdm_diff_new(flav, from_sha3, target_sha3, method);
294
0
    ent->cdm_diff_status = CDM_DIFF_IN_PROGRESS;
295
0
    HT_INSERT(cdm_diff_ht, &cdm_diff_ht, ent);
296
0
  }
297
0
  return result;
298
0
}
299
300
/**
301
 * Update the status of the diff of type <b>flav</b> between consensuses with
302
 * the two provided SHA3-256 digests, so that its status becomes
303
 * <b>status</b>, and its value becomes the <b>handle</b>.  If <b>handle</b>
304
 * is NULL, then the old handle (if any) is freed, and replaced with NULL.
305
 */
306
static void
307
cdm_diff_ht_set_status(consensus_flavor_t flav,
308
                       const uint8_t *from_sha3,
309
                       const uint8_t *to_sha3,
310
                       compress_method_t method,
311
                       int status,
312
                       consensus_cache_entry_handle_t *handle)
313
0
{
314
0
  if (handle == NULL) {
315
0
    tor_assert_nonfatal(status != CDM_DIFF_PRESENT);
316
0
  }
317
318
0
  struct cdm_diff_t search, *ent;
319
0
  memset(&search, 0, sizeof(cdm_diff_t));
320
0
  search.flavor = flav;
321
0
  search.compress_method = method,
322
0
  memcpy(search.from_sha3, from_sha3, DIGEST256_LEN);
323
0
  ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search);
324
0
  if (!ent) {
325
0
    ent = cdm_diff_new(flav, from_sha3, to_sha3, method);
326
0
    ent->cdm_diff_status = CDM_DIFF_IN_PROGRESS;
327
0
    HT_INSERT(cdm_diff_ht, &cdm_diff_ht, ent);
328
0
  } else if (fast_memneq(ent->target_sha3, to_sha3, DIGEST256_LEN)) {
329
    // This can happen under certain really pathological conditions
330
    // if we decide we don't care about a diff before it is actually
331
    // done computing.
332
0
    return;
333
0
  }
334
335
0
  tor_assert_nonfatal(ent->cdm_diff_status == CDM_DIFF_IN_PROGRESS);
336
337
0
  ent->cdm_diff_status = status;
338
0
  consensus_cache_entry_handle_free(ent->entry);
339
0
  ent->entry = handle;
340
0
}
341
342
/**
343
 * Helper: Remove from the hash table every present (actually computed) diff
344
 * of type <b>flav</b> whose target digest does not match
345
 * <b>unless_target_sha3_matches</b>.
346
 *
347
 * This function is used for the hash table to throw away references to diffs
348
 * that do not lead to the most given consensus of a given flavor.
349
 */
350
static void
351
cdm_diff_ht_purge(consensus_flavor_t flav,
352
                  const uint8_t *unless_target_sha3_matches)
353
0
{
354
0
  cdm_diff_t **diff, **next;
355
0
  for (diff = HT_START(cdm_diff_ht, &cdm_diff_ht); diff; diff = next) {
356
0
    cdm_diff_t *this = *diff;
357
358
0
    if ((*diff)->cdm_diff_status == CDM_DIFF_PRESENT &&
359
0
        flav == (*diff)->flavor) {
360
361
0
      if (BUG((*diff)->entry == NULL) ||
362
0
          consensus_cache_entry_handle_get((*diff)->entry) == NULL) {
363
        /* the underlying entry has gone away; drop this. */
364
0
        next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff);
365
0
        cdm_diff_free(this);
366
0
        continue;
367
0
      }
368
369
0
      if (unless_target_sha3_matches &&
370
0
          fast_memneq(unless_target_sha3_matches, (*diff)->target_sha3,
371
0
                      DIGEST256_LEN)) {
372
        /* target hash doesn't match; drop this. */
373
0
        next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff);
374
0
        cdm_diff_free(this);
375
0
        continue;
376
0
      }
377
0
    }
378
0
    next = HT_NEXT(cdm_diff_ht, &cdm_diff_ht, diff);
379
0
  }
380
0
}
381
382
/**
383
 * Helper: initialize <b>cons_diff_cache</b>.
384
 */
385
static void
386
cdm_cache_init(void)
387
0
{
388
0
  unsigned n_entries = consdiff_cfg.cache_max_num * 2;
389
390
0
  tor_assert(cons_diff_cache == NULL);
391
0
  cons_diff_cache = consensus_cache_open("diff-cache", n_entries);
392
0
  if (cons_diff_cache == NULL) {
393
    // LCOV_EXCL_START
394
0
    log_err(LD_FS, "Error: Couldn't open storage for consensus diffs.");
395
0
    tor_assert_unreached();
396
    // LCOV_EXCL_STOP
397
0
  } else {
398
0
    consdiffmgr_set_cache_flags();
399
0
  }
400
0
  consdiffmgr_rescan_ev =
401
0
    mainloop_event_postloop_new(consdiffmgr_rescan_cb, NULL);
402
0
  mark_cdm_cache_dirty();
403
0
  cdm_cache_loaded = 0;
404
0
}
405
406
/**
407
 * Helper: return the consensus_cache_t * that backs this manager,
408
 * initializing it if needed.
409
 */
410
STATIC consensus_cache_t *
411
cdm_cache_get(void)
412
0
{
413
0
  if (PREDICT_UNLIKELY(cons_diff_cache == NULL)) {
414
0
    cdm_cache_init();
415
0
  }
416
0
  return cons_diff_cache;
417
0
}
418
419
/**
420
 * Helper: given a list of labels, prepend the hex-encoded SHA3 digest
421
 * of the <b>bodylen</b>-byte object at <b>body</b> to those labels,
422
 * with <b>label</b> as its label.
423
 */
424
static void
425
cdm_labels_prepend_sha3(config_line_t **labels,
426
                        const char *label,
427
                        const uint8_t *body,
428
                        size_t bodylen)
429
0
{
430
0
  uint8_t sha3_digest[DIGEST256_LEN];
431
0
  char hexdigest[HEX_DIGEST256_LEN+1];
432
0
  crypto_digest256((char *)sha3_digest,
433
0
                   (const char *)body, bodylen, DIGEST_SHA3_256);
434
0
  base16_encode(hexdigest, sizeof(hexdigest),
435
0
                (const char *)sha3_digest, sizeof(sha3_digest));
436
437
0
  config_line_prepend(labels, label, hexdigest);
438
0
}
439
440
/** Helper: if there is a sha3-256 hex-encoded digest in <b>ent</b> with the
441
 * given label, set <b>digest_out</b> to that value (decoded), and return 0.
442
 *
443
 * Return -1 if there is no such label, and -2 if it is badly formatted. */
444
STATIC int
445
cdm_entry_get_sha3_value(uint8_t *digest_out,
446
                         consensus_cache_entry_t *ent,
447
                         const char *label)
448
0
{
449
0
  if (ent == NULL)
450
0
    return -1;
451
452
0
  const char *hex = consensus_cache_entry_get_value(ent, label);
453
0
  if (hex == NULL)
454
0
    return -1;
455
456
0
  int n = base16_decode((char*)digest_out, DIGEST256_LEN, hex, strlen(hex));
457
0
  if (n != DIGEST256_LEN)
458
0
    return -2;
459
0
  else
460
0
    return 0;
461
0
}
462
463
/**
464
 * Helper: look for a consensus with the given <b>flavor</b> and
465
 * <b>valid_after</b> time in the cache. Return that consensus if it's
466
 * present, or NULL if it's missing.
467
 */
468
STATIC consensus_cache_entry_t *
469
cdm_cache_lookup_consensus(consensus_flavor_t flavor, time_t valid_after)
470
0
{
471
0
  char formatted_time[ISO_TIME_LEN+1];
472
0
  format_iso_time_nospace(formatted_time, valid_after);
473
0
  const char *flavname = networkstatus_get_flavor_name(flavor);
474
475
  /* We'll filter by valid-after time first, since that should
476
   * match the fewest documents. */
477
  /* We could add an extra hashtable here, but since we only do this scan
478
   * when adding a new consensus, it probably doesn't matter much. */
479
0
  smartlist_t *matches = smartlist_new();
480
0
  consensus_cache_find_all(matches, cdm_cache_get(),
481
0
                           LABEL_VALID_AFTER, formatted_time);
482
0
  consensus_cache_filter_list(matches, LABEL_FLAVOR, flavname);
483
0
  consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
484
485
0
  consensus_cache_entry_t *result = NULL;
486
0
  if (smartlist_len(matches)) {
487
0
    result = smartlist_get(matches, 0);
488
0
  }
489
0
  smartlist_free(matches);
490
491
0
  return result;
492
0
}
493
494
/** Return the maximum age (in seconds) of consensuses that we should consider
495
 * storing. The available space in the directory may impose additional limits
496
 * on how much we store. */
497
static int32_t
498
get_max_age_to_cache(void)
499
0
{
500
0
  const int32_t DEFAULT_MAX_AGE_TO_CACHE = 8192;
501
0
  const int32_t MIN_MAX_AGE_TO_CACHE = 0;
502
0
  const int32_t MAX_MAX_AGE_TO_CACHE = 8192;
503
0
  const char MAX_AGE_TO_CACHE_NAME[] = "max-consensus-age-to-cache-for-diff";
504
505
0
  const or_options_t *options = get_options();
506
507
0
  if (options->MaxConsensusAgeForDiffs) {
508
0
    const int v = options->MaxConsensusAgeForDiffs;
509
0
    if (v >= MAX_MAX_AGE_TO_CACHE * 3600)
510
0
      return MAX_MAX_AGE_TO_CACHE;
511
0
    else
512
0
      return v;
513
0
  }
514
515
  /* The parameter is in hours, so we multiply */
516
0
  return 3600 * networkstatus_get_param(NULL,
517
0
                                        MAX_AGE_TO_CACHE_NAME,
518
0
                                        DEFAULT_MAX_AGE_TO_CACHE,
519
0
                                        MIN_MAX_AGE_TO_CACHE,
520
0
                                        MAX_MAX_AGE_TO_CACHE);
521
0
}
522
523
#ifdef TOR_UNIT_TESTS
524
/** As consdiffmgr_add_consensus, but requires a nul-terminated input. For
525
 * testing. */
526
int
527
consdiffmgr_add_consensus_nulterm(const char *consensus,
528
                                  const networkstatus_t *as_parsed)
529
0
{
530
0
  size_t len = strlen(consensus);
531
  /* make a non-nul-terminated copy so that we can have a better chance
532
   * of catching errors. */
533
0
  char *ctmp = tor_memdup(consensus, len);
534
0
  int r = consdiffmgr_add_consensus(ctmp, len, as_parsed);
535
0
  tor_free(ctmp);
536
0
  return r;
537
0
}
538
#endif /* defined(TOR_UNIT_TESTS) */
539
540
/**
541
 * Given a buffer containing a networkstatus consensus, and the results of
542
 * having parsed that consensus, add that consensus to the cache if it is not
543
 * already present and not too old.  Create new consensus diffs from or to
544
 * that consensus as appropriate.
545
 *
546
 * Return 0 on success and -1 on failure.
547
 */
548
int
549
consdiffmgr_add_consensus(const char *consensus,
550
                          size_t consensus_len,
551
                          const networkstatus_t *as_parsed)
552
0
{
553
0
  if (BUG(consensus == NULL) || BUG(as_parsed == NULL))
554
0
    return -1; // LCOV_EXCL_LINE
555
0
  if (BUG(as_parsed->type != NS_TYPE_CONSENSUS))
556
0
    return -1; // LCOV_EXCL_LINE
557
558
0
  const consensus_flavor_t flavor = as_parsed->flavor;
559
0
  const time_t valid_after = as_parsed->valid_after;
560
561
0
  if (valid_after < approx_time() - get_max_age_to_cache()) {
562
0
    log_info(LD_DIRSERV, "We don't care about this consensus document; it's "
563
0
             "too old.");
564
0
    return -1;
565
0
  }
566
567
  /* Do we already have this one? */
568
0
  consensus_cache_entry_t *entry =
569
0
    cdm_cache_lookup_consensus(flavor, valid_after);
570
0
  if (entry) {
571
0
    log_info(LD_DIRSERV, "We already have a copy of that consensus");
572
0
    return -1;
573
0
  }
574
575
  /* We don't have it. Add it to the cache. */
576
0
  return consensus_queue_compression_work(consensus, consensus_len, as_parsed);
577
0
}
578
579
/**
580
 * Helper: used to sort two smartlists of consensus_cache_entry_t by their
581
 * LABEL_VALID_AFTER labels.
582
 */
583
static int
584
compare_by_valid_after_(const void **a, const void **b)
585
0
{
586
0
  const consensus_cache_entry_t *e1 = *a;
587
0
  const consensus_cache_entry_t *e2 = *b;
588
  /* We're in luck here: sorting UTC iso-encoded values lexically will work
589
   * fine (until 9999). */
590
0
  return strcmp_opt(consensus_cache_entry_get_value(e1, LABEL_VALID_AFTER),
591
0
                    consensus_cache_entry_get_value(e2, LABEL_VALID_AFTER));
592
0
}
593
594
/**
595
 * Helper: Sort <b>lst</b> by LABEL_VALID_AFTER and return the most recent
596
 * entry.
597
 */
598
static consensus_cache_entry_t *
599
sort_and_find_most_recent(smartlist_t *lst)
600
0
{
601
0
  smartlist_sort(lst, compare_by_valid_after_);
602
0
  if (smartlist_len(lst)) {
603
0
    return smartlist_get(lst, smartlist_len(lst) - 1);
604
0
  } else {
605
0
    return NULL;
606
0
  }
607
0
}
608
609
/** Return i such that compress_consensus_with[i] == method. Return
610
 * -1 if no such i exists. */
611
static int
612
consensus_compression_method_pos(compress_method_t method)
613
0
{
614
0
  unsigned i;
615
0
  for (i = 0; i < n_consensus_compression_methods(); ++i) {
616
0
    if (compress_consensus_with[i] == method) {
617
0
      return i;
618
0
    }
619
0
  }
620
0
  return -1;
621
0
}
622
623
/**
624
 * If we know a consensus with the flavor <b>flavor</b> compressed with
625
 * <b>method</b>, set *<b>entry_out</b> to that value.  Return values are as
626
 * for consdiffmgr_find_diff_from().
627
 */
628
consdiff_status_t
629
consdiffmgr_find_consensus(struct consensus_cache_entry_t **entry_out,
630
                           consensus_flavor_t flavor,
631
                           compress_method_t method)
632
0
{
633
0
  tor_assert(entry_out);
634
0
  tor_assert((int)flavor < N_CONSENSUS_FLAVORS);
635
636
0
  int pos = consensus_compression_method_pos(method);
637
0
  if (pos < 0) {
638
     // We don't compress consensuses with this method.
639
0
    return CONSDIFF_NOT_FOUND;
640
0
  }
641
0
  consensus_cache_entry_handle_t *handle = latest_consensus[flavor][pos];
642
0
  if (!handle)
643
0
    return CONSDIFF_NOT_FOUND;
644
0
  *entry_out = consensus_cache_entry_handle_get(handle);
645
0
  if (*entry_out)
646
0
    return CONSDIFF_AVAILABLE;
647
0
  else
648
0
    return CONSDIFF_NOT_FOUND;
649
0
}
650
651
/**
652
 * Look up consensus_cache_entry_t for the consensus of type <b>flavor</b>,
653
 * from the source consensus with the specified digest (which must be SHA3).
654
 *
655
 * If the diff is present, store it into *<b>entry_out</b> and return
656
 * CONSDIFF_AVAILABLE. Otherwise return CONSDIFF_NOT_FOUND or
657
 * CONSDIFF_IN_PROGRESS.
658
 */
659
consdiff_status_t
660
consdiffmgr_find_diff_from(consensus_cache_entry_t **entry_out,
661
                           consensus_flavor_t flavor,
662
                           int digest_type,
663
                           const uint8_t *digest,
664
                           size_t digestlen,
665
                           compress_method_t method)
666
0
{
667
0
  if (BUG(digest_type != DIGEST_SHA3_256) ||
668
0
      BUG(digestlen != DIGEST256_LEN)) {
669
0
    return CONSDIFF_NOT_FOUND; // LCOV_EXCL_LINE
670
0
  }
671
672
  // Try to look up the entry in the hashtable.
673
0
  cdm_diff_t search, *ent;
674
0
  memset(&search, 0, sizeof(search));
675
0
  search.flavor = flavor;
676
0
  search.compress_method = method;
677
0
  memcpy(search.from_sha3, digest, DIGEST256_LEN);
678
0
  ent = HT_FIND(cdm_diff_ht, &cdm_diff_ht, &search);
679
680
0
  if (ent == NULL ||
681
0
      ent->cdm_diff_status == CDM_DIFF_ERROR) {
682
0
    return CONSDIFF_NOT_FOUND;
683
0
  } else if (ent->cdm_diff_status == CDM_DIFF_IN_PROGRESS) {
684
0
    return CONSDIFF_IN_PROGRESS;
685
0
  } else if (BUG(ent->cdm_diff_status != CDM_DIFF_PRESENT)) {
686
0
    return CONSDIFF_IN_PROGRESS;
687
0
  }
688
689
0
  if (BUG(ent->entry == NULL)) {
690
0
    return CONSDIFF_NOT_FOUND;
691
0
  }
692
0
  *entry_out = consensus_cache_entry_handle_get(ent->entry);
693
0
  return (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND;
694
695
#if 0
696
  // XXXX Remove this.  I'm keeping it around for now in case we need to
697
  // XXXX debug issues in the hashtable.
698
  char hex[HEX_DIGEST256_LEN+1];
699
  base16_encode(hex, sizeof(hex), (const char *)digest, digestlen);
700
  const char *flavname = networkstatus_get_flavor_name(flavor);
701
702
  smartlist_t *matches = smartlist_new();
703
  consensus_cache_find_all(matches, cdm_cache_get(),
704
                           LABEL_FROM_SHA3_DIGEST, hex);
705
  consensus_cache_filter_list(matches, LABEL_FLAVOR, flavname);
706
  consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
707
708
  *entry_out = sort_and_find_most_recent(matches);
709
  consdiff_status_t result =
710
    (*entry_out) ? CONSDIFF_AVAILABLE : CONSDIFF_NOT_FOUND;
711
  smartlist_free(matches);
712
713
  return result;
714
#endif /* 0 */
715
0
}
716
717
/**
718
 * Perform periodic cleanup tasks on the consensus diff cache.  Return
719
 * the number of objects marked for deletion.
720
 */
721
int
722
consdiffmgr_cleanup(void)
723
0
{
724
0
  smartlist_t *objects = smartlist_new();
725
0
  smartlist_t *consensuses = smartlist_new();
726
0
  smartlist_t *diffs = smartlist_new();
727
0
  int n_to_delete = 0;
728
729
0
  log_debug(LD_DIRSERV, "Looking for consdiffmgr entries to remove");
730
731
  // 1. Delete any consensus or diff or anything whose valid_after is too old.
732
0
  const time_t valid_after_cutoff = approx_time() - get_max_age_to_cache();
733
734
0
  consensus_cache_find_all(objects, cdm_cache_get(),
735
0
                           NULL, NULL);
736
0
  SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) {
737
0
    const char *lv_valid_after =
738
0
      consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER);
739
0
    if (! lv_valid_after) {
740
0
      log_debug(LD_DIRSERV, "Ignoring entry because it had no %s label",
741
0
                LABEL_VALID_AFTER);
742
0
      continue;
743
0
    }
744
0
    time_t valid_after = 0;
745
0
    if (parse_iso_time_nospace(lv_valid_after, &valid_after) < 0) {
746
0
      log_debug(LD_DIRSERV, "Ignoring entry because its %s value (%s) was "
747
0
                "unparseable", LABEL_VALID_AFTER, escaped(lv_valid_after));
748
0
      continue;
749
0
    }
750
0
    if (valid_after < valid_after_cutoff) {
751
0
      log_debug(LD_DIRSERV, "Deleting entry because its %s value (%s) was "
752
0
                "too old", LABEL_VALID_AFTER, lv_valid_after);
753
0
      consensus_cache_entry_mark_for_removal(ent);
754
0
      ++n_to_delete;
755
0
    }
756
0
  } SMARTLIST_FOREACH_END(ent);
757
758
  // 2. Delete all diffs that lead to a consensus whose valid-after is not the
759
  // latest.
760
0
  for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
761
0
    const char *flavname = networkstatus_get_flavor_name(flav);
762
    /* Determine the most recent consensus of this flavor */
763
0
    consensus_cache_find_all(consensuses, cdm_cache_get(),
764
0
                             LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
765
0
    consensus_cache_filter_list(consensuses, LABEL_FLAVOR, flavname);
766
0
    consensus_cache_entry_t *most_recent =
767
0
      sort_and_find_most_recent(consensuses);
768
0
    if (most_recent == NULL)
769
0
      continue;
770
0
    const char *most_recent_sha3 =
771
0
      consensus_cache_entry_get_value(most_recent,
772
0
                                      LABEL_SHA3_DIGEST_UNCOMPRESSED);
773
0
    if (BUG(most_recent_sha3 == NULL))
774
0
      continue; // LCOV_EXCL_LINE
775
776
    /* consider all such-flavored diffs, and look to see if they match. */
777
0
    consensus_cache_find_all(diffs, cdm_cache_get(),
778
0
                             LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
779
0
    consensus_cache_filter_list(diffs, LABEL_FLAVOR, flavname);
780
0
    SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) {
781
0
      const char *this_diff_target_sha3 =
782
0
        consensus_cache_entry_get_value(diff, LABEL_TARGET_SHA3_DIGEST);
783
0
      if (!this_diff_target_sha3)
784
0
        continue;
785
0
      if (strcmp(this_diff_target_sha3, most_recent_sha3)) {
786
0
        consensus_cache_entry_mark_for_removal(diff);
787
0
        ++n_to_delete;
788
0
      }
789
0
    } SMARTLIST_FOREACH_END(diff);
790
0
    smartlist_clear(consensuses);
791
0
    smartlist_clear(diffs);
792
0
  }
793
794
  // 3. Delete all consensuses except the most recent that are compressed with
795
  // an un-preferred method.
796
0
  for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
797
0
    const char *flavname = networkstatus_get_flavor_name(flav);
798
    /* Determine the most recent consensus of this flavor */
799
0
    consensus_cache_find_all(consensuses, cdm_cache_get(),
800
0
                             LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
801
0
    consensus_cache_filter_list(consensuses, LABEL_FLAVOR, flavname);
802
0
    consensus_cache_entry_t *most_recent =
803
0
      sort_and_find_most_recent(consensuses);
804
0
    if (most_recent == NULL)
805
0
      continue;
806
0
    const char *most_recent_sha3_uncompressed =
807
0
      consensus_cache_entry_get_value(most_recent,
808
0
                                      LABEL_SHA3_DIGEST_UNCOMPRESSED);
809
0
    const char *retain_methodname = compression_method_get_name(
810
0
                               RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD);
811
812
0
    if (BUG(most_recent_sha3_uncompressed == NULL))
813
0
      continue;
814
0
    SMARTLIST_FOREACH_BEGIN(consensuses, consensus_cache_entry_t *, ent) {
815
0
      const char *lv_sha3_uncompressed =
816
0
        consensus_cache_entry_get_value(ent, LABEL_SHA3_DIGEST_UNCOMPRESSED);
817
0
      if (BUG(! lv_sha3_uncompressed))
818
0
        continue;
819
0
      if (!strcmp(lv_sha3_uncompressed, most_recent_sha3_uncompressed))
820
0
        continue; // This _is_ the most recent.
821
0
      const char *lv_methodname =
822
0
        consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE);
823
0
      if (! lv_methodname || strcmp(lv_methodname, retain_methodname)) {
824
0
        consensus_cache_entry_mark_for_removal(ent);
825
0
        ++n_to_delete;
826
0
      }
827
0
    } SMARTLIST_FOREACH_END(ent);
828
0
  }
829
830
0
  smartlist_free(objects);
831
0
  smartlist_free(consensuses);
832
0
  smartlist_free(diffs);
833
834
  // Actually remove files, if they're not used.
835
0
  consensus_cache_delete_pending(cdm_cache_get(), 0);
836
0
  return n_to_delete;
837
0
}
838
839
/**
840
 * Initialize the consensus diff manager and its cache, and configure
841
 * its parameters based on the latest torrc and networkstatus parameters.
842
 */
843
void
844
consdiffmgr_configure(const consdiff_cfg_t *cfg)
845
0
{
846
0
  if (cfg)
847
0
    memcpy(&consdiff_cfg, cfg, sizeof(consdiff_cfg));
848
849
0
  (void) cdm_cache_get();
850
0
}
851
852
/**
853
 * Tell the sandbox (if any) configured by <b>cfg</b> to allow the
854
 * operations that the consensus diff manager will need.
855
 */
856
int
857
consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem_t **cfg)
858
0
{
859
0
  return consensus_cache_register_with_sandbox(cdm_cache_get(), cfg);
860
0
}
861
862
/**
863
 * Scan the consensus diff manager's cache for any grossly malformed entries,
864
 * and mark them as deletable.  Return 0 if no problems were found; 1
865
 * if problems were found and fixed.
866
 */
867
int
868
consdiffmgr_validate(void)
869
0
{
870
  /* Right now, we only check for entries that have bad sha3 values */
871
0
  int problems = 0;
872
873
0
  smartlist_t *objects = smartlist_new();
874
0
  consensus_cache_find_all(objects, cdm_cache_get(),
875
0
                           NULL, NULL);
876
0
  SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, obj) {
877
0
    uint8_t sha3_expected[DIGEST256_LEN];
878
0
    uint8_t sha3_received[DIGEST256_LEN];
879
0
    int r = cdm_entry_get_sha3_value(sha3_expected, obj, LABEL_SHA3_DIGEST);
880
0
    if (r == -1) {
881
      /* digest isn't there; that's allowed */
882
0
      continue;
883
0
    } else if (r == -2) {
884
      /* digest is malformed; that's not allowed */
885
0
      problems = 1;
886
0
      consensus_cache_entry_mark_for_removal(obj);
887
0
      continue;
888
0
    }
889
0
    const uint8_t *body;
890
0
    size_t bodylen;
891
0
    consensus_cache_entry_incref(obj);
892
0
    r = consensus_cache_entry_get_body(obj, &body, &bodylen);
893
0
    if (r == 0) {
894
0
      crypto_digest256((char *)sha3_received, (const char *)body, bodylen,
895
0
                       DIGEST_SHA3_256);
896
0
    }
897
0
    consensus_cache_entry_decref(obj);
898
0
    if (r < 0)
899
0
      continue;
900
901
    // Deconfuse coverity about the possibility of sha3_received being
902
    // uninitialized
903
0
    tor_assert(r <= 0);
904
905
0
    if (fast_memneq(sha3_received, sha3_expected, DIGEST256_LEN)) {
906
0
      problems = 1;
907
0
      consensus_cache_entry_mark_for_removal(obj);
908
0
      continue;
909
0
    }
910
911
0
  } SMARTLIST_FOREACH_END(obj);
912
0
  smartlist_free(objects);
913
0
  return problems;
914
0
}
915
916
/**
917
 * Helper: build new diffs of <b>flavor</b> as needed
918
 */
919
static void
920
consdiffmgr_rescan_flavor_(consensus_flavor_t flavor)
921
0
{
922
0
  smartlist_t *matches = NULL;
923
0
  smartlist_t *diffs = NULL;
924
0
  smartlist_t *compute_diffs_from = NULL;
925
0
  strmap_t *have_diff_from = NULL;
926
927
  // look for the most recent consensus, and for all previous in-range
928
  // consensuses.  Do they all have diffs to it?
929
0
  const char *flavname = networkstatus_get_flavor_name(flavor);
930
931
  // 1. find the most recent consensus, and the ones that we might want
932
  //    to diff to it.
933
0
  const char *methodname = compression_method_get_name(
934
0
                             RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD);
935
936
0
  matches = smartlist_new();
937
0
  consensus_cache_find_all(matches, cdm_cache_get(),
938
0
                           LABEL_FLAVOR, flavname);
939
0
  consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
940
0
  consensus_cache_filter_list(matches, LABEL_COMPRESSION_TYPE, methodname);
941
0
  consensus_cache_entry_t *most_recent = sort_and_find_most_recent(matches);
942
0
  if (!most_recent) {
943
0
    log_info(LD_DIRSERV, "No 'most recent' %s consensus found; "
944
0
             "not making diffs", flavname);
945
0
    goto done;
946
0
  }
947
0
  tor_assert(smartlist_len(matches));
948
0
  smartlist_del(matches, smartlist_len(matches) - 1);
949
950
0
  const char *most_recent_valid_after =
951
0
    consensus_cache_entry_get_value(most_recent, LABEL_VALID_AFTER);
952
0
  if (BUG(most_recent_valid_after == NULL))
953
0
    goto done; //LCOV_EXCL_LINE
954
0
  uint8_t most_recent_sha3[DIGEST256_LEN];
955
0
  if (BUG(cdm_entry_get_sha3_value(most_recent_sha3, most_recent,
956
0
                                   LABEL_SHA3_DIGEST_UNCOMPRESSED) < 0))
957
0
    goto done; //LCOV_EXCL_LINE
958
959
  // 2. Find all the relevant diffs _to_ this consensus. These are ones
960
  //    that we don't need to compute.
961
0
  diffs = smartlist_new();
962
0
  consensus_cache_find_all(diffs, cdm_cache_get(),
963
0
                           LABEL_VALID_AFTER, most_recent_valid_after);
964
0
  consensus_cache_filter_list(diffs, LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
965
0
  consensus_cache_filter_list(diffs, LABEL_FLAVOR, flavname);
966
0
  have_diff_from = strmap_new();
967
0
  SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) {
968
0
    const char *va = consensus_cache_entry_get_value(diff,
969
0
                                                     LABEL_FROM_VALID_AFTER);
970
0
    if (BUG(va == NULL))
971
0
      continue; // LCOV_EXCL_LINE
972
0
    strmap_set(have_diff_from, va, diff);
973
0
  } SMARTLIST_FOREACH_END(diff);
974
975
  // 3. See which consensuses in 'matches' don't have diffs yet.
976
0
  smartlist_reverse(matches); // from newest to oldest.
977
0
  compute_diffs_from = smartlist_new();
978
0
  SMARTLIST_FOREACH_BEGIN(matches, consensus_cache_entry_t *, ent) {
979
0
    const char *va = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER);
980
0
    if (BUG(va == NULL))
981
0
      continue; // LCOV_EXCL_LINE
982
0
    if (strmap_get(have_diff_from, va) != NULL)
983
0
      continue; /* we already have this one. */
984
0
    smartlist_add(compute_diffs_from, ent);
985
    /* Since we are not going to serve this as the most recent consensus
986
     * any more, we should stop keeping it mmap'd when it's not in use.
987
     */
988
0
    consensus_cache_entry_mark_for_aggressive_release(ent);
989
0
  } SMARTLIST_FOREACH_END(ent);
990
991
0
  log_info(LD_DIRSERV,
992
0
           "The most recent %s consensus is valid-after %s. We have diffs to "
993
0
           "this consensus for %d/%d older %s consensuses. Generating diffs "
994
0
           "for the other %d.",
995
0
           flavname,
996
0
           most_recent_valid_after,
997
0
           smartlist_len(matches) - smartlist_len(compute_diffs_from),
998
0
           smartlist_len(matches),
999
0
           flavname,
1000
0
           smartlist_len(compute_diffs_from));
1001
1002
  // 4. Update the hashtable; remove entries in this flavor to other
1003
  //    target consensuses.
1004
0
  cdm_diff_ht_purge(flavor, most_recent_sha3);
1005
1006
  // 5. Actually launch the requests.
1007
0
  SMARTLIST_FOREACH_BEGIN(compute_diffs_from, consensus_cache_entry_t *, c) {
1008
0
    if (BUG(c == most_recent))
1009
0
      continue; // LCOV_EXCL_LINE
1010
1011
0
    uint8_t this_sha3[DIGEST256_LEN];
1012
0
    if (cdm_entry_get_sha3_value(this_sha3, c,
1013
0
                                 LABEL_SHA3_DIGEST_AS_SIGNED)<0) {
1014
      // Not actually a bug, since we might be running with a directory
1015
      // with stale files from before the #22143 fixes.
1016
0
      continue;
1017
0
    }
1018
0
    if (cdm_diff_ht_check_and_note_pending(flavor,
1019
0
                                           this_sha3, most_recent_sha3)) {
1020
      // This is already pending, or we encountered an error.
1021
0
      continue;
1022
0
    }
1023
0
    consensus_diff_queue_diff_work(c, most_recent);
1024
0
  } SMARTLIST_FOREACH_END(c);
1025
1026
0
 done:
1027
0
  smartlist_free(matches);
1028
0
  smartlist_free(diffs);
1029
0
  smartlist_free(compute_diffs_from);
1030
0
  strmap_free(have_diff_from, NULL);
1031
0
}
1032
1033
/**
1034
 * Scan the cache for the latest consensuses and add their handles to
1035
 * latest_consensus
1036
 */
1037
static void
1038
consdiffmgr_consensus_load(void)
1039
0
{
1040
0
  smartlist_t *matches = smartlist_new();
1041
0
  for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
1042
0
    const char *flavname = networkstatus_get_flavor_name(flav);
1043
0
    smartlist_clear(matches);
1044
0
    consensus_cache_find_all(matches, cdm_cache_get(),
1045
0
                             LABEL_FLAVOR, flavname);
1046
0
    consensus_cache_filter_list(matches, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
1047
0
    consensus_cache_entry_t *most_recent = sort_and_find_most_recent(matches);
1048
0
    if (! most_recent)
1049
0
      continue; // no consensuses.
1050
0
    const char *most_recent_sha3 =
1051
0
      consensus_cache_entry_get_value(most_recent,
1052
0
                                      LABEL_SHA3_DIGEST_UNCOMPRESSED);
1053
0
    if (BUG(most_recent_sha3 == NULL))
1054
0
      continue; // LCOV_EXCL_LINE
1055
0
    consensus_cache_filter_list(matches, LABEL_SHA3_DIGEST_UNCOMPRESSED,
1056
0
                                most_recent_sha3);
1057
1058
    // Everything that remains matches the most recent consensus of this
1059
    // flavor.
1060
0
    SMARTLIST_FOREACH_BEGIN(matches, consensus_cache_entry_t *, ent) {
1061
0
      const char *lv_compression =
1062
0
        consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE);
1063
0
      compress_method_t method =
1064
0
        compression_method_get_by_name(lv_compression);
1065
0
      int pos = consensus_compression_method_pos(method);
1066
0
      if (pos < 0)
1067
0
        continue;
1068
0
      consensus_cache_entry_handle_free(latest_consensus[flav][pos]);
1069
0
      latest_consensus[flav][pos] = consensus_cache_entry_handle_new(ent);
1070
0
    } SMARTLIST_FOREACH_END(ent);
1071
0
  }
1072
0
  smartlist_free(matches);
1073
0
}
1074
1075
/**
1076
 * Scan the cache for diffs, and add them to the hashtable.
1077
 */
1078
static void
1079
consdiffmgr_diffs_load(void)
1080
0
{
1081
0
  smartlist_t *diffs = smartlist_new();
1082
0
  consensus_cache_find_all(diffs, cdm_cache_get(),
1083
0
                           LABEL_DOCTYPE, DOCTYPE_CONSENSUS_DIFF);
1084
0
  SMARTLIST_FOREACH_BEGIN(diffs, consensus_cache_entry_t *, diff) {
1085
0
    const char *lv_flavor =
1086
0
      consensus_cache_entry_get_value(diff, LABEL_FLAVOR);
1087
0
    if (!lv_flavor)
1088
0
      continue;
1089
0
    int flavor = networkstatus_parse_flavor_name(lv_flavor);
1090
0
    if (flavor < 0)
1091
0
      continue;
1092
0
    const char *lv_compression =
1093
0
      consensus_cache_entry_get_value(diff, LABEL_COMPRESSION_TYPE);
1094
0
    compress_method_t method = NO_METHOD;
1095
0
    if (lv_compression) {
1096
0
      method = compression_method_get_by_name(lv_compression);
1097
0
      if (method == UNKNOWN_METHOD) {
1098
0
        continue;
1099
0
      }
1100
0
    }
1101
1102
0
    uint8_t from_sha3[DIGEST256_LEN];
1103
0
    uint8_t to_sha3[DIGEST256_LEN];
1104
0
    if (cdm_entry_get_sha3_value(from_sha3, diff, LABEL_FROM_SHA3_DIGEST)<0)
1105
0
      continue;
1106
0
    if (cdm_entry_get_sha3_value(to_sha3, diff, LABEL_TARGET_SHA3_DIGEST)<0)
1107
0
      continue;
1108
1109
0
    cdm_diff_ht_set_status(flavor, from_sha3, to_sha3,
1110
0
                           method,
1111
0
                           CDM_DIFF_PRESENT,
1112
0
                           consensus_cache_entry_handle_new(diff));
1113
0
  } SMARTLIST_FOREACH_END(diff);
1114
0
  smartlist_free(diffs);
1115
0
}
1116
1117
/**
1118
 * Build new diffs as needed.
1119
 */
1120
void
1121
consdiffmgr_rescan(void)
1122
0
{
1123
0
  if (cdm_cache_dirty == 0)
1124
0
    return;
1125
1126
  // Clean up here to make room for new diffs, and to ensure that older
1127
  // consensuses do not have any entries.
1128
0
  consdiffmgr_cleanup();
1129
1130
0
  if (cdm_cache_loaded == 0) {
1131
0
    consdiffmgr_diffs_load();
1132
0
    consdiffmgr_consensus_load();
1133
0
    cdm_cache_loaded = 1;
1134
0
  }
1135
1136
0
  for (int flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
1137
0
    consdiffmgr_rescan_flavor_((consensus_flavor_t) flav);
1138
0
  }
1139
1140
0
  cdm_cache_dirty = 0;
1141
0
}
1142
1143
/** Callback wrapper for consdiffmgr_rescan */
1144
static void
1145
consdiffmgr_rescan_cb(mainloop_event_t *ev, void *arg)
1146
0
{
1147
0
  (void)ev;
1148
0
  (void)arg;
1149
0
  consdiffmgr_rescan();
1150
0
}
1151
1152
/** Mark the cache as dirty, and schedule a rescan event. */
1153
static void
1154
mark_cdm_cache_dirty(void)
1155
0
{
1156
0
  cdm_cache_dirty = 1;
1157
0
  tor_assert(consdiffmgr_rescan_ev);
1158
0
  mainloop_event_activate(consdiffmgr_rescan_ev);
1159
0
}
1160
1161
/**
1162
 * Helper: compare two files by their from-valid-after and valid-after labels,
1163
 * trying to sort in ascending order by from-valid-after (when present) and
1164
 * valid-after (when not).  Place everything that has neither label first in
1165
 * the list.
1166
 */
1167
static int
1168
compare_by_staleness_(const void **a, const void **b)
1169
0
{
1170
0
  const consensus_cache_entry_t *e1 = *a;
1171
0
  const consensus_cache_entry_t *e2 = *b;
1172
0
  const char *va1, *fva1, *va2, *fva2;
1173
0
  va1 = consensus_cache_entry_get_value(e1, LABEL_VALID_AFTER);
1174
0
  va2 = consensus_cache_entry_get_value(e2, LABEL_VALID_AFTER);
1175
0
  fva1 = consensus_cache_entry_get_value(e1, LABEL_FROM_VALID_AFTER);
1176
0
  fva2 = consensus_cache_entry_get_value(e2, LABEL_FROM_VALID_AFTER);
1177
1178
0
  if (fva1)
1179
0
    va1 = fva1;
1180
0
  if (fva2)
1181
0
    va2 = fva2;
1182
1183
  /* See note about iso-encoded values in compare_by_valid_after_.  Also note
1184
   * that missing dates will get placed first. */
1185
0
  return strcmp_opt(va1, va2);
1186
0
}
1187
1188
/** If there are not enough unused filenames to store <b>n</b> files, then
1189
 * delete old consensuses until there are.  (We have to keep track of the
1190
 * number of filenames because of the way that the seccomp2 cache works.)
1191
 *
1192
 * Return 0 on success, -1 on failure.
1193
 **/
1194
static int
1195
consdiffmgr_ensure_space_for_files(int n)
1196
0
{
1197
0
  consensus_cache_t *cache = cdm_cache_get();
1198
0
  if (consensus_cache_get_n_filenames_available(cache) >= n) {
1199
    // there are already enough unused filenames.
1200
0
    return 0;
1201
0
  }
1202
  // Try a cheap deletion of stuff that's waiting to get deleted.
1203
0
  consensus_cache_delete_pending(cache, 0);
1204
0
  if (consensus_cache_get_n_filenames_available(cache) >= n) {
1205
    // okay, _that_ made enough filenames available.
1206
0
    return 0;
1207
0
  }
1208
  // Let's get more assertive: clean out unused stuff, and force-remove
1209
  // the files that we can.
1210
0
  consdiffmgr_cleanup();
1211
0
  consensus_cache_delete_pending(cache, 1);
1212
0
  const int n_to_remove = n - consensus_cache_get_n_filenames_available(cache);
1213
0
  if (n_to_remove <= 0) {
1214
    // okay, finally!
1215
0
    return 0;
1216
0
  }
1217
1218
  // At this point, we're going to have to throw out objects that will be
1219
  // missed.  Too bad!
1220
0
  smartlist_t *objects = smartlist_new();
1221
0
  consensus_cache_find_all(objects, cache, NULL, NULL);
1222
0
  smartlist_sort(objects, compare_by_staleness_);
1223
0
  int n_marked = 0;
1224
0
  SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) {
1225
0
    consensus_cache_entry_mark_for_removal(ent);
1226
0
    if (++n_marked >= n_to_remove)
1227
0
      break;
1228
0
  } SMARTLIST_FOREACH_END(ent);
1229
0
  smartlist_free(objects);
1230
1231
0
  consensus_cache_delete_pending(cache, 1);
1232
1233
0
  if (consensus_cache_may_overallocate(cache)) {
1234
    /* If we're allowed to throw extra files into the cache, let's do so
1235
     * rather getting upset.
1236
     */
1237
0
    return 0;
1238
0
  }
1239
1240
0
  if (BUG(n_marked < n_to_remove))
1241
0
    return -1;
1242
0
  else
1243
0
    return 0;
1244
0
}
1245
1246
/**
1247
 * Set consensus cache flags on the objects in this consdiffmgr.
1248
 */
1249
static void
1250
consdiffmgr_set_cache_flags(void)
1251
0
{
1252
  /* Right now, we just mark the consensus objects for aggressive release,
1253
   * so that they get mmapped for as little time as possible. */
1254
0
  smartlist_t *objects = smartlist_new();
1255
0
  consensus_cache_find_all(objects, cdm_cache_get(), LABEL_DOCTYPE,
1256
0
                           DOCTYPE_CONSENSUS);
1257
0
  SMARTLIST_FOREACH_BEGIN(objects, consensus_cache_entry_t *, ent) {
1258
0
    consensus_cache_entry_mark_for_aggressive_release(ent);
1259
0
  } SMARTLIST_FOREACH_END(ent);
1260
0
  smartlist_free(objects);
1261
0
}
1262
1263
/**
1264
 * Called before shutdown: drop all storage held by the consdiffmgr.c module.
1265
 */
1266
void
1267
consdiffmgr_free_all(void)
1268
0
{
1269
0
  cdm_diff_t **diff, **next;
1270
0
  for (diff = HT_START(cdm_diff_ht, &cdm_diff_ht); diff; diff = next) {
1271
0
    cdm_diff_t *this = *diff;
1272
0
    next = HT_NEXT_RMV(cdm_diff_ht, &cdm_diff_ht, diff);
1273
0
    cdm_diff_free(this);
1274
0
  }
1275
0
  int i;
1276
0
  unsigned j;
1277
0
  for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
1278
0
    for (j = 0; j < n_consensus_compression_methods(); ++j) {
1279
0
      consensus_cache_entry_handle_free(latest_consensus[i][j]);
1280
0
    }
1281
0
  }
1282
0
  memset(latest_consensus, 0, sizeof(latest_consensus));
1283
0
  consensus_cache_free(cons_diff_cache);
1284
0
  cons_diff_cache = NULL;
1285
0
  mainloop_event_free(consdiffmgr_rescan_ev);
1286
0
}
1287
1288
/* =====
1289
   Thread workers
1290
   =====*/
1291
1292
typedef struct compressed_result_t {
1293
  config_line_t *labels;
1294
  /**
1295
   * Output: Body of the diff, as compressed.
1296
   */
1297
  uint8_t *body;
1298
  /**
1299
   * Output: length of body_out
1300
   */
1301
  size_t bodylen;
1302
} compressed_result_t;
1303
1304
/**
1305
 * Compress the bytestring <b>input</b> of length <b>len</b> using the
1306
 * <b>n_methods</b> compression methods listed in the array <b>methods</b>.
1307
 *
1308
 * For each successful compression, set the fields in the <b>results_out</b>
1309
 * array in the position corresponding to the compression method. Use
1310
 * <b>labels_in</b> as a basis for the labels of the result.
1311
 *
1312
 * Return 0 if all compression succeeded; -1 if any failed.
1313
 */
1314
static int
1315
compress_multiple(compressed_result_t *results_out, int n_methods,
1316
                  const compress_method_t *methods,
1317
                  const uint8_t *input, size_t len,
1318
                  const config_line_t *labels_in)
1319
0
{
1320
0
  int rv = 0;
1321
0
  int i;
1322
0
  for (i = 0; i < n_methods; ++i) {
1323
0
    compress_method_t method = methods[i];
1324
0
    const char *methodname = compression_method_get_name(method);
1325
0
    char *result;
1326
0
    size_t sz;
1327
0
    if (0 == tor_compress(&result, &sz, (const char*)input, len, method)) {
1328
0
      results_out[i].body = (uint8_t*)result;
1329
0
      results_out[i].bodylen = sz;
1330
0
      results_out[i].labels = config_lines_dup(labels_in);
1331
0
      cdm_labels_prepend_sha3(&results_out[i].labels, LABEL_SHA3_DIGEST,
1332
0
                              results_out[i].body,
1333
0
                              results_out[i].bodylen);
1334
0
      config_line_prepend(&results_out[i].labels,
1335
0
                          LABEL_COMPRESSION_TYPE,
1336
0
                          methodname);
1337
0
    } else {
1338
0
      rv = -1;
1339
0
    }
1340
0
  }
1341
0
  return rv;
1342
0
}
1343
1344
/**
1345
 * Given an array of <b>n</b> compressed_result_t in <b>results</b>,
1346
 * as produced by compress_multiple, store them all into the
1347
 * consdiffmgr, and store handles to them in the <b>handles_out</b>
1348
 * array.
1349
 *
1350
 * Return CDM_DIFF_PRESENT if any was stored, and CDM_DIFF_ERROR if none
1351
 * was stored.
1352
 */
1353
static cdm_diff_status_t
1354
store_multiple(consensus_cache_entry_handle_t **handles_out,
1355
               int n,
1356
               const compress_method_t *methods,
1357
               const compressed_result_t *results,
1358
               const char *description)
1359
0
{
1360
0
  cdm_diff_status_t status = CDM_DIFF_ERROR;
1361
0
  consdiffmgr_ensure_space_for_files(n);
1362
1363
0
  int i;
1364
0
  for (i = 0; i < n; ++i) {
1365
0
    compress_method_t method = methods[i];
1366
0
    uint8_t *body_out = results[i].body;
1367
0
    size_t bodylen_out = results[i].bodylen;
1368
0
    config_line_t *labels = results[i].labels;
1369
0
    const char *methodname = compression_method_get_name(method);
1370
0
    if (body_out && bodylen_out && labels) {
1371
      /* Success! Store the results */
1372
0
      log_info(LD_DIRSERV, "Adding %s, compressed with %s",
1373
0
               description, methodname);
1374
1375
0
      consensus_cache_entry_t *ent =
1376
0
        consensus_cache_add(cdm_cache_get(),
1377
0
                            labels,
1378
0
                            body_out,
1379
0
                            bodylen_out);
1380
0
      if (ent == NULL) {
1381
0
        static ratelim_t cant_store_ratelim = RATELIM_INIT(5*60);
1382
0
        log_fn_ratelim(&cant_store_ratelim, LOG_WARN, LD_FS,
1383
0
                       "Unable to store object %s compressed with %s.",
1384
0
                       description, methodname);
1385
0
        continue;
1386
0
      }
1387
1388
0
      status = CDM_DIFF_PRESENT;
1389
0
      handles_out[i] = consensus_cache_entry_handle_new(ent);
1390
0
      consensus_cache_entry_decref(ent);
1391
0
    }
1392
0
  }
1393
0
  return status;
1394
0
}
1395
1396
/**
1397
 * An object passed to a worker thread that will try to produce a consensus
1398
 * diff.
1399
 */
1400
typedef struct consensus_diff_worker_job_t {
1401
  /**
1402
   * Input: The consensus to compute the diff from.  Holds a reference to the
1403
   * cache entry, which must not be released until the job is passed back to
1404
   * the main thread. The body must be mapped into memory in the main thread.
1405
   */
1406
  consensus_cache_entry_t *diff_from;
1407
  /**
1408
   * Input: The consensus to compute the diff to.  Holds a reference to the
1409
   * cache entry, which must not be released until the job is passed back to
1410
   * the main thread. The body must be mapped into memory in the main thread.
1411
   */
1412
  consensus_cache_entry_t *diff_to;
1413
1414
  /** Output: labels and bodies */
1415
  compressed_result_t out[ARRAY_LENGTH(compress_diffs_with)];
1416
} consensus_diff_worker_job_t;
1417
1418
/** Given a consensus_cache_entry_t, check whether it has a label claiming
1419
 * that it was compressed.  If so, uncompress its contents into *<b>out</b> and
1420
 * set <b>outlen</b> to hold their size, and set *<b>owned_out</b> to a pointer
1421
 * that the caller will need to free.  If not, just set *<b>out</b> and
1422
 * <b>outlen</b> to its extent in memory.  Return 0 on success, -1 on failure.
1423
 **/
1424
STATIC int
1425
uncompress_or_set_ptr(const char **out, size_t *outlen,
1426
                      char **owned_out,
1427
                      consensus_cache_entry_t *ent)
1428
0
{
1429
0
  const uint8_t *body;
1430
0
  size_t bodylen;
1431
1432
0
  *owned_out = NULL;
1433
1434
0
  if (consensus_cache_entry_get_body(ent, &body, &bodylen) < 0)
1435
0
    return -1;
1436
1437
0
  const char *lv_compression =
1438
0
    consensus_cache_entry_get_value(ent, LABEL_COMPRESSION_TYPE);
1439
0
  compress_method_t method = NO_METHOD;
1440
1441
0
  if (lv_compression)
1442
0
    method = compression_method_get_by_name(lv_compression);
1443
1444
0
  int rv;
1445
0
  if (method == NO_METHOD) {
1446
0
    *out = (const char *)body;
1447
0
    *outlen = bodylen;
1448
0
    rv = 0;
1449
0
  } else {
1450
0
    rv = tor_uncompress(owned_out, outlen, (const char *)body, bodylen,
1451
0
                        method, 1, LOG_WARN);
1452
0
    *out = *owned_out;
1453
0
  }
1454
0
  return rv;
1455
0
}
1456
1457
/**
1458
 * Worker function. This function runs inside a worker thread and receives
1459
 * a consensus_diff_worker_job_t as its input.
1460
 */
1461
static workqueue_reply_t
1462
consensus_diff_worker_threadfn(void *state_, void *work_)
1463
0
{
1464
0
  (void)state_;
1465
0
  consensus_diff_worker_job_t *job = work_;
1466
0
  const uint8_t *diff_from, *diff_to;
1467
0
  size_t len_from, len_to;
1468
0
  int r;
1469
  /* We need to have the body already mapped into RAM here.
1470
   */
1471
0
  r = consensus_cache_entry_get_body(job->diff_from, &diff_from, &len_from);
1472
0
  if (BUG(r < 0))
1473
0
    return WQ_RPL_REPLY; // LCOV_EXCL_LINE
1474
0
  r = consensus_cache_entry_get_body(job->diff_to, &diff_to, &len_to);
1475
0
  if (BUG(r < 0))
1476
0
    return WQ_RPL_REPLY; // LCOV_EXCL_LINE
1477
1478
0
  const char *lv_to_valid_after =
1479
0
    consensus_cache_entry_get_value(job->diff_to, LABEL_VALID_AFTER);
1480
0
  const char *lv_to_fresh_until =
1481
0
    consensus_cache_entry_get_value(job->diff_to, LABEL_FRESH_UNTIL);
1482
0
  const char *lv_to_valid_until =
1483
0
    consensus_cache_entry_get_value(job->diff_to, LABEL_VALID_UNTIL);
1484
0
  const char *lv_to_signatories =
1485
0
    consensus_cache_entry_get_value(job->diff_to, LABEL_SIGNATORIES);
1486
0
  const char *lv_from_valid_after =
1487
0
    consensus_cache_entry_get_value(job->diff_from, LABEL_VALID_AFTER);
1488
0
  const char *lv_from_digest =
1489
0
    consensus_cache_entry_get_value(job->diff_from,
1490
0
                                    LABEL_SHA3_DIGEST_AS_SIGNED);
1491
0
  const char *lv_from_flavor =
1492
0
    consensus_cache_entry_get_value(job->diff_from, LABEL_FLAVOR);
1493
0
  const char *lv_to_flavor =
1494
0
    consensus_cache_entry_get_value(job->diff_to, LABEL_FLAVOR);
1495
0
  const char *lv_to_digest =
1496
0
    consensus_cache_entry_get_value(job->diff_to,
1497
0
                                    LABEL_SHA3_DIGEST_UNCOMPRESSED);
1498
1499
0
  if (! lv_from_digest) {
1500
    /* This isn't a bug right now, since it can happen if you're migrating
1501
     * from an older version of master to a newer one.  The older ones didn't
1502
     * annotate their stored consensus objects with sha3-digest-as-signed.
1503
    */
1504
0
    return WQ_RPL_REPLY; // LCOV_EXCL_LINE
1505
0
  }
1506
1507
  /* All these values are mandatory on the input */
1508
0
  if (BUG(!lv_to_valid_after) ||
1509
0
      BUG(!lv_from_valid_after) ||
1510
0
      BUG(!lv_from_flavor) ||
1511
0
      BUG(!lv_to_flavor)) {
1512
0
    return WQ_RPL_REPLY; // LCOV_EXCL_LINE
1513
0
  }
1514
  /* The flavors need to match */
1515
0
  if (BUG(strcmp(lv_from_flavor, lv_to_flavor))) {
1516
0
    return WQ_RPL_REPLY; // LCOV_EXCL_LINE
1517
0
  }
1518
1519
0
  char *consensus_diff;
1520
0
  {
1521
0
    const char *diff_from_nt = NULL, *diff_to_nt = NULL;
1522
0
    char *owned1 = NULL, *owned2 = NULL;
1523
0
    size_t diff_from_nt_len, diff_to_nt_len;
1524
1525
0
    if (uncompress_or_set_ptr(&diff_from_nt, &diff_from_nt_len, &owned1,
1526
0
                              job->diff_from) < 0) {
1527
0
      return WQ_RPL_REPLY;
1528
0
    }
1529
0
    if (uncompress_or_set_ptr(&diff_to_nt, &diff_to_nt_len, &owned2,
1530
0
                              job->diff_to) < 0) {
1531
0
      tor_free(owned1);
1532
0
      return WQ_RPL_REPLY;
1533
0
    }
1534
0
    tor_assert(diff_from_nt);
1535
0
    tor_assert(diff_to_nt);
1536
1537
    // XXXX ugh; this is going to calculate the SHA3 of both its
1538
    // XXXX inputs again, even though we already have that. Maybe it's time
1539
    // XXXX to change the API here?
1540
0
    consensus_diff = consensus_diff_generate(diff_from_nt,
1541
0
                                             diff_from_nt_len,
1542
0
                                             diff_to_nt,
1543
0
                                             diff_to_nt_len);
1544
0
    tor_free(owned1);
1545
0
    tor_free(owned2);
1546
0
  }
1547
0
  if (!consensus_diff) {
1548
    /* Couldn't generate consensus; we'll leave the reply blank. */
1549
0
    return WQ_RPL_REPLY;
1550
0
  }
1551
1552
  /* Compress the results and send the reply */
1553
0
  tor_assert(compress_diffs_with[0] == NO_METHOD);
1554
0
  size_t difflen = strlen(consensus_diff);
1555
0
  job->out[0].body = (uint8_t *) consensus_diff;
1556
0
  job->out[0].bodylen = difflen;
1557
1558
0
  config_line_t *common_labels = NULL;
1559
0
  if (lv_to_valid_until)
1560
0
    config_line_prepend(&common_labels, LABEL_VALID_UNTIL, lv_to_valid_until);
1561
0
  if (lv_to_fresh_until)
1562
0
    config_line_prepend(&common_labels, LABEL_FRESH_UNTIL, lv_to_fresh_until);
1563
0
  if (lv_to_signatories)
1564
0
    config_line_prepend(&common_labels, LABEL_SIGNATORIES, lv_to_signatories);
1565
0
  cdm_labels_prepend_sha3(&common_labels,
1566
0
                          LABEL_SHA3_DIGEST_UNCOMPRESSED,
1567
0
                          job->out[0].body,
1568
0
                          job->out[0].bodylen);
1569
0
  config_line_prepend(&common_labels, LABEL_FROM_VALID_AFTER,
1570
0
                      lv_from_valid_after);
1571
0
  config_line_prepend(&common_labels, LABEL_VALID_AFTER,
1572
0
                      lv_to_valid_after);
1573
0
  config_line_prepend(&common_labels, LABEL_FLAVOR, lv_from_flavor);
1574
0
  config_line_prepend(&common_labels, LABEL_FROM_SHA3_DIGEST,
1575
0
                      lv_from_digest);
1576
0
  config_line_prepend(&common_labels, LABEL_TARGET_SHA3_DIGEST,
1577
0
                      lv_to_digest);
1578
0
  config_line_prepend(&common_labels, LABEL_DOCTYPE,
1579
0
                      DOCTYPE_CONSENSUS_DIFF);
1580
1581
0
  job->out[0].labels = config_lines_dup(common_labels);
1582
0
  cdm_labels_prepend_sha3(&job->out[0].labels,
1583
0
                          LABEL_SHA3_DIGEST,
1584
0
                          job->out[0].body,
1585
0
                          job->out[0].bodylen);
1586
1587
0
  compress_multiple(job->out+1,
1588
0
                    n_diff_compression_methods()-1,
1589
0
                    compress_diffs_with+1,
1590
0
                    (const uint8_t*)consensus_diff, difflen, common_labels);
1591
1592
0
  config_free_lines(common_labels);
1593
0
  return WQ_RPL_REPLY;
1594
0
}
1595
1596
#define consensus_diff_worker_job_free(job)             \
1597
0
  FREE_AND_NULL(consensus_diff_worker_job_t,            \
1598
0
                consensus_diff_worker_job_free_, (job))
1599
1600
/**
1601
 * Helper: release all storage held in <b>job</b>.
1602
 */
1603
static void
1604
consensus_diff_worker_job_free_(consensus_diff_worker_job_t *job)
1605
0
{
1606
0
  if (!job)
1607
0
    return;
1608
0
  unsigned u;
1609
0
  for (u = 0; u < n_diff_compression_methods(); ++u) {
1610
0
    config_free_lines(job->out[u].labels);
1611
0
    tor_free(job->out[u].body);
1612
0
  }
1613
0
  consensus_cache_entry_decref(job->diff_from);
1614
0
  consensus_cache_entry_decref(job->diff_to);
1615
0
  tor_free(job);
1616
0
}
1617
1618
/**
1619
 * Worker function: This function runs in the main thread, and receives
1620
 * a consensus_diff_worker_job_t that the worker thread has already
1621
 * processed.
1622
 */
1623
static void
1624
consensus_diff_worker_replyfn(void *work_)
1625
0
{
1626
0
  tor_assert(in_main_thread());
1627
0
  tor_assert(work_);
1628
1629
0
  consensus_diff_worker_job_t *job = work_;
1630
1631
0
  const char *lv_from_digest =
1632
0
    consensus_cache_entry_get_value(job->diff_from,
1633
0
                                    LABEL_SHA3_DIGEST_AS_SIGNED);
1634
0
  const char *lv_to_digest =
1635
0
    consensus_cache_entry_get_value(job->diff_to,
1636
0
                                    LABEL_SHA3_DIGEST_UNCOMPRESSED);
1637
0
  const char *lv_flavor =
1638
0
    consensus_cache_entry_get_value(job->diff_to, LABEL_FLAVOR);
1639
0
  if (BUG(lv_from_digest == NULL))
1640
0
    lv_from_digest = "???"; // LCOV_EXCL_LINE
1641
0
  if (BUG(lv_to_digest == NULL))
1642
0
    lv_to_digest = "???"; // LCOV_EXCL_LINE
1643
1644
0
  uint8_t from_sha3[DIGEST256_LEN];
1645
0
  uint8_t to_sha3[DIGEST256_LEN];
1646
0
  int flav = -1;
1647
0
  int cache = 1;
1648
0
  if (BUG(cdm_entry_get_sha3_value(from_sha3, job->diff_from,
1649
0
                                   LABEL_SHA3_DIGEST_AS_SIGNED) < 0))
1650
0
    cache = 0;
1651
0
  if (BUG(cdm_entry_get_sha3_value(to_sha3, job->diff_to,
1652
0
                                   LABEL_SHA3_DIGEST_UNCOMPRESSED) < 0))
1653
0
    cache = 0;
1654
0
  if (BUG(lv_flavor == NULL)) {
1655
0
    cache = 0;
1656
0
  } else if ((flav = networkstatus_parse_flavor_name(lv_flavor)) < 0) {
1657
0
    cache = 0;
1658
0
  }
1659
1660
0
  consensus_cache_entry_handle_t *handles[ARRAY_LENGTH(compress_diffs_with)];
1661
0
  memset(handles, 0, sizeof(handles));
1662
1663
0
  char description[128];
1664
0
  tor_snprintf(description, sizeof(description),
1665
0
               "consensus diff from %s to %s",
1666
0
               lv_from_digest, lv_to_digest);
1667
1668
0
  int status = store_multiple(handles,
1669
0
                              n_diff_compression_methods(),
1670
0
                              compress_diffs_with,
1671
0
                              job->out,
1672
0
                              description);
1673
1674
0
  if (status != CDM_DIFF_PRESENT) {
1675
    /* Failure! Nothing to do but complain */
1676
0
    log_warn(LD_DIRSERV,
1677
0
             "Worker was unable to compute consensus diff "
1678
0
             "from %s to %s", lv_from_digest, lv_to_digest);
1679
    /* Cache this error so we don't try to compute this one again. */
1680
0
    status = CDM_DIFF_ERROR;
1681
0
  }
1682
1683
0
  unsigned u;
1684
0
  for (u = 0; u < ARRAY_LENGTH(handles); ++u) {
1685
0
    compress_method_t method = compress_diffs_with[u];
1686
0
    if (cache) {
1687
0
      consensus_cache_entry_handle_t *h = handles[u];
1688
0
      int this_status = status;
1689
0
      if (h == NULL) {
1690
0
        this_status = CDM_DIFF_ERROR;
1691
0
      }
1692
0
      tor_assert_nonfatal(h != NULL || this_status == CDM_DIFF_ERROR);
1693
0
      cdm_diff_ht_set_status(flav, from_sha3, to_sha3, method, this_status, h);
1694
0
    } else {
1695
0
      consensus_cache_entry_handle_free(handles[u]);
1696
0
    }
1697
0
  }
1698
1699
0
  consensus_diff_worker_job_free(job);
1700
0
}
1701
1702
/**
1703
 * Queue the job of computing the diff from <b>diff_from</b> to <b>diff_to</b>
1704
 * in a worker thread.
1705
 */
1706
static int
1707
consensus_diff_queue_diff_work(consensus_cache_entry_t *diff_from,
1708
                               consensus_cache_entry_t *diff_to)
1709
0
{
1710
0
  tor_assert(in_main_thread());
1711
1712
0
  consensus_cache_entry_incref(diff_from);
1713
0
  consensus_cache_entry_incref(diff_to);
1714
1715
0
  consensus_diff_worker_job_t *job = tor_malloc_zero(sizeof(*job));
1716
0
  job->diff_from = diff_from;
1717
0
  job->diff_to = diff_to;
1718
1719
  /* Make sure body is mapped. */
1720
0
  const uint8_t *body;
1721
0
  size_t bodylen;
1722
0
  int r1 = consensus_cache_entry_get_body(diff_from, &body, &bodylen);
1723
0
  int r2 = consensus_cache_entry_get_body(diff_to, &body, &bodylen);
1724
0
  if (r1 < 0 || r2 < 0)
1725
0
    goto err;
1726
1727
0
  workqueue_entry_t *work;
1728
0
  work = cpuworker_queue_work(WQ_PRI_LOW,
1729
0
                              consensus_diff_worker_threadfn,
1730
0
                              consensus_diff_worker_replyfn,
1731
0
                              job);
1732
0
  if (!work)
1733
0
    goto err;
1734
1735
0
  return 0;
1736
0
 err:
1737
0
  consensus_diff_worker_job_free(job); // includes decrefs.
1738
0
  return -1;
1739
0
}
1740
1741
/**
1742
 * Holds requests and replies for consensus_compress_workers.
1743
 */
1744
typedef struct consensus_compress_worker_job_t {
1745
  char *consensus;
1746
  size_t consensus_len;
1747
  consensus_flavor_t flavor;
1748
  config_line_t *labels_in;
1749
  compressed_result_t out[ARRAY_LENGTH(compress_consensus_with)];
1750
} consensus_compress_worker_job_t;
1751
1752
#define consensus_compress_worker_job_free(job) \
1753
0
  FREE_AND_NULL(consensus_compress_worker_job_t, \
1754
0
                consensus_compress_worker_job_free_, (job))
1755
1756
/**
1757
 * Free all resources held in <b>job</b>
1758
 */
1759
static void
1760
consensus_compress_worker_job_free_(consensus_compress_worker_job_t *job)
1761
0
{
1762
0
  if (!job)
1763
0
    return;
1764
0
  tor_free(job->consensus);
1765
0
  config_free_lines(job->labels_in);
1766
0
  unsigned u;
1767
0
  for (u = 0; u < n_consensus_compression_methods(); ++u) {
1768
0
    config_free_lines(job->out[u].labels);
1769
0
    tor_free(job->out[u].body);
1770
0
  }
1771
0
  tor_free(job);
1772
0
}
1773
/**
1774
 * Worker function. This function runs inside a worker thread and receives
1775
 * a consensus_compress_worker_job_t as its input.
1776
 */
1777
static workqueue_reply_t
1778
consensus_compress_worker_threadfn(void *state_, void *work_)
1779
0
{
1780
0
  (void)state_;
1781
0
  consensus_compress_worker_job_t *job = work_;
1782
0
  consensus_flavor_t flavor = job->flavor;
1783
0
  const char *consensus = job->consensus;
1784
0
  size_t bodylen = job->consensus_len;
1785
1786
0
  config_line_t *labels = config_lines_dup(job->labels_in);
1787
0
  const char *flavname = networkstatus_get_flavor_name(flavor);
1788
1789
0
  cdm_labels_prepend_sha3(&labels, LABEL_SHA3_DIGEST_UNCOMPRESSED,
1790
0
                          (const uint8_t *)consensus, bodylen);
1791
0
  {
1792
0
    const char *start, *end;
1793
0
    if (router_get_networkstatus_v3_signed_boundaries(consensus, bodylen,
1794
0
                                                      &start, &end) < 0) {
1795
0
      start = consensus;
1796
0
      end = consensus+bodylen;
1797
0
    }
1798
0
    cdm_labels_prepend_sha3(&labels, LABEL_SHA3_DIGEST_AS_SIGNED,
1799
0
                            (const uint8_t *)start,
1800
0
                            end - start);
1801
0
  }
1802
0
  config_line_prepend(&labels, LABEL_FLAVOR, flavname);
1803
0
  config_line_prepend(&labels, LABEL_DOCTYPE, DOCTYPE_CONSENSUS);
1804
1805
0
  compress_multiple(job->out,
1806
0
                    n_consensus_compression_methods(),
1807
0
                    compress_consensus_with,
1808
0
                    (const uint8_t*)consensus, bodylen, labels);
1809
0
  config_free_lines(labels);
1810
0
  return WQ_RPL_REPLY;
1811
0
}
1812
1813
/**
1814
 * Worker function: This function runs in the main thread, and receives
1815
 * a consensus_diff_compress_job_t that the worker thread has already
1816
 * processed.
1817
 */
1818
static void
1819
consensus_compress_worker_replyfn(void *work_)
1820
0
{
1821
0
  consensus_compress_worker_job_t *job = work_;
1822
1823
0
  consensus_cache_entry_handle_t *handles[
1824
0
                               ARRAY_LENGTH(compress_consensus_with)];
1825
0
  memset(handles, 0, sizeof(handles));
1826
1827
0
  store_multiple(handles,
1828
0
                 n_consensus_compression_methods(),
1829
0
                 compress_consensus_with,
1830
0
                 job->out,
1831
0
                 "consensus");
1832
0
  mark_cdm_cache_dirty();
1833
1834
0
  unsigned u;
1835
0
  consensus_flavor_t f = job->flavor;
1836
0
  tor_assert((int)f < N_CONSENSUS_FLAVORS);
1837
0
  for (u = 0; u < ARRAY_LENGTH(handles); ++u) {
1838
0
    if (handles[u] == NULL)
1839
0
      continue;
1840
0
    consensus_cache_entry_handle_free(latest_consensus[f][u]);
1841
0
    latest_consensus[f][u] = handles[u];
1842
0
  }
1843
1844
0
  consensus_compress_worker_job_free(job);
1845
0
}
1846
1847
/**
1848
 * If true, we compress in worker threads.
1849
 */
1850
static int background_compression = 0;
1851
1852
/**
1853
 * Queue a job to compress <b>consensus</b> and store its compressed
1854
 * text in the cache.
1855
 */
1856
static int
1857
consensus_queue_compression_work(const char *consensus,
1858
                                 size_t consensus_len,
1859
                                 const networkstatus_t *as_parsed)
1860
0
{
1861
0
  tor_assert(consensus);
1862
0
  tor_assert(as_parsed);
1863
1864
0
  consensus_compress_worker_job_t *job = tor_malloc_zero(sizeof(*job));
1865
0
  job->consensus = tor_memdup_nulterm(consensus, consensus_len);
1866
0
  job->consensus_len = strlen(job->consensus);
1867
0
  job->flavor = as_parsed->flavor;
1868
1869
0
  char va_str[ISO_TIME_LEN+1];
1870
0
  char vu_str[ISO_TIME_LEN+1];
1871
0
  char fu_str[ISO_TIME_LEN+1];
1872
0
  format_iso_time_nospace(va_str, as_parsed->valid_after);
1873
0
  format_iso_time_nospace(fu_str, as_parsed->fresh_until);
1874
0
  format_iso_time_nospace(vu_str, as_parsed->valid_until);
1875
0
  config_line_append(&job->labels_in, LABEL_VALID_AFTER, va_str);
1876
0
  config_line_append(&job->labels_in, LABEL_FRESH_UNTIL, fu_str);
1877
0
  config_line_append(&job->labels_in, LABEL_VALID_UNTIL, vu_str);
1878
0
  if (as_parsed->voters) {
1879
0
    smartlist_t *hexvoters = smartlist_new();
1880
0
    SMARTLIST_FOREACH_BEGIN(as_parsed->voters,
1881
0
                            networkstatus_voter_info_t *, vi) {
1882
0
      if (smartlist_len(vi->sigs) == 0)
1883
0
        continue; // didn't sign.
1884
0
      char d[HEX_DIGEST_LEN+1];
1885
0
      base16_encode(d, sizeof(d), vi->identity_digest, DIGEST_LEN);
1886
0
      smartlist_add_strdup(hexvoters, d);
1887
0
    } SMARTLIST_FOREACH_END(vi);
1888
0
    char *signers = smartlist_join_strings(hexvoters, ",", 0, NULL);
1889
0
    config_line_prepend(&job->labels_in, LABEL_SIGNATORIES, signers);
1890
0
    tor_free(signers);
1891
0
    SMARTLIST_FOREACH(hexvoters, char *, cp, tor_free(cp));
1892
0
    smartlist_free(hexvoters);
1893
0
  }
1894
1895
0
  if (background_compression) {
1896
0
    workqueue_entry_t *work;
1897
0
    work = cpuworker_queue_work(WQ_PRI_LOW,
1898
0
                                consensus_compress_worker_threadfn,
1899
0
                                consensus_compress_worker_replyfn,
1900
0
                                job);
1901
0
    if (!work) {
1902
0
      consensus_compress_worker_job_free(job);
1903
0
      return -1;
1904
0
    }
1905
1906
0
    return 0;
1907
0
  } else {
1908
0
    consensus_compress_worker_threadfn(NULL, job);
1909
0
    consensus_compress_worker_replyfn(job);
1910
0
    return 0;
1911
0
  }
1912
0
}
1913
1914
/**
1915
 * Tell the consdiffmgr backend to compress consensuses in worker threads.
1916
 */
1917
void
1918
consdiffmgr_enable_background_compression(void)
1919
0
{
1920
  // This isn't the default behavior because it would break unit tests.
1921
0
  background_compression = 1;
1922
0
}
1923
1924
/** Read the set of voters from the cached object <b>ent</b> into
1925
 * <b>out</b>, as a list of hex-encoded digests. Return 0 on success,
1926
 * -1 if no signatories were recorded. */
1927
int
1928
consensus_cache_entry_get_voter_id_digests(const consensus_cache_entry_t *ent,
1929
                                           smartlist_t *out)
1930
0
{
1931
0
  tor_assert(ent);
1932
0
  tor_assert(out);
1933
0
  const char *s;
1934
0
  s = consensus_cache_entry_get_value(ent, LABEL_SIGNATORIES);
1935
0
  if (s == NULL)
1936
0
    return -1;
1937
0
  smartlist_split_string(out, s, ",", SPLIT_SKIP_SPACE|SPLIT_STRIP_SPACE, 0);
1938
0
  return 0;
1939
0
}
1940
1941
/** Read the fresh-until time of cached object <b>ent</b> into *<b>out</b>
1942
 * and return 0, or return -1 if no such time was recorded. */
1943
int
1944
consensus_cache_entry_get_fresh_until(const consensus_cache_entry_t *ent,
1945
                                      time_t *out)
1946
0
{
1947
0
  tor_assert(ent);
1948
0
  tor_assert(out);
1949
0
  const char *s;
1950
0
  s = consensus_cache_entry_get_value(ent, LABEL_FRESH_UNTIL);
1951
0
  if (s == NULL || parse_iso_time_nospace(s, out) < 0)
1952
0
    return -1;
1953
0
  else
1954
0
    return 0;
1955
0
}
1956
1957
/** Read the valid until timestamp from the cached object <b>ent</b> into
1958
 * *<b>out</b> and return 0, or return -1 if no such time was recorded. */
1959
int
1960
consensus_cache_entry_get_valid_until(const consensus_cache_entry_t *ent,
1961
                                      time_t *out)
1962
0
{
1963
0
  tor_assert(ent);
1964
0
  tor_assert(out);
1965
1966
0
  const char *s;
1967
0
  s = consensus_cache_entry_get_value(ent, LABEL_VALID_UNTIL);
1968
0
  if (s == NULL || parse_iso_time_nospace(s, out) < 0)
1969
0
    return -1;
1970
0
  else
1971
0
    return 0;
1972
0
}
1973
1974
/** Read the valid after timestamp from the cached object <b>ent</b> into
1975
 * *<b>out</b> and return 0, or return -1 if no such time was recorded. */
1976
int
1977
consensus_cache_entry_get_valid_after(const consensus_cache_entry_t *ent,
1978
                                      time_t *out)
1979
0
{
1980
0
  tor_assert(ent);
1981
0
  tor_assert(out);
1982
1983
0
  const char *s;
1984
0
  s = consensus_cache_entry_get_value(ent, LABEL_VALID_AFTER);
1985
1986
0
  if (s == NULL || parse_iso_time_nospace(s, out) < 0)
1987
0
    return -1;
1988
0
  else
1989
0
    return 0;
1990
0
}