/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 | } |