TOR_VEGAS: Implement Prop#324 TOR_VEGAS.
[tor.git] / src / test / test_consdiffmgr.c
blob808d6f55b6288357c86d6f62c34d3480e4eafab3
1 /* Copyright (c) 2017-2021, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
4 #define CONSDIFFMGR_PRIVATE
6 #include "core/or/or.h"
7 #include "app/config/config.h"
8 #include "feature/dircache/conscache.h"
9 #include "feature/dircommon/consdiff.h"
10 #include "feature/dircache/consdiffmgr.h"
11 #include "core/mainloop/cpuworker.h"
12 #include "lib/crypt_ops/crypto_rand.h"
13 #include "feature/nodelist/networkstatus.h"
14 #include "feature/dirparse/ns_parse.h"
15 #include "lib/evloop/workqueue.h"
16 #include "lib/compress/compress.h"
17 #include "lib/encoding/confline.h"
19 #include "feature/nodelist/networkstatus_st.h"
21 #include "test/test.h"
22 #include "test/log_test_helpers.h"
24 #define consdiffmgr_add_consensus consdiffmgr_add_consensus_nulterm
26 static char *
27 consensus_diff_apply_(const char *c, const char *d)
29 size_t c_len = strlen(c);
30 size_t d_len = strlen(d);
31 // We use memdup here to ensure that the input is NOT nul-terminated.
32 // This makes it likelier for us to spot bugs.
33 char *c_tmp = tor_memdup(c, c_len);
34 char *d_tmp = tor_memdup(d, d_len);
35 char *result = consensus_diff_apply(c_tmp, c_len, d_tmp, d_len);
36 tor_free(c_tmp);
37 tor_free(d_tmp);
38 return result;
41 // ============================== Setup/teardown the consdiffmgr
42 // These functions get run before/after each test in this module
44 static void *
45 consdiffmgr_test_setup(const struct testcase_t *arg)
47 (void)arg;
48 char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cdm"));
49 tor_free(get_options_mutable()->CacheDirectory);
50 get_options_mutable()->CacheDirectory = ddir_fname; // now owns the pointer.
51 check_private_dir(ddir_fname, CPD_CREATE, NULL);
53 consdiff_cfg_t consdiff_cfg = { 300 };
54 consdiffmgr_configure(&consdiff_cfg);
55 return (void *)1; // must return something non-null.
57 static int
58 consdiffmgr_test_teardown(const struct testcase_t *arg, void *ignore)
60 (void)arg;
61 (void)ignore;
62 consdiffmgr_free_all();
63 return 1;
65 static struct testcase_setup_t setup_diffmgr = {
66 consdiffmgr_test_setup,
67 consdiffmgr_test_teardown
70 // ============================== NS faking functions
71 // These functions are for making quick fake consensus objects and
72 // strings that are just good enough for consdiff and consdiffmgr.
74 static networkstatus_t *
75 fake_ns_new(consensus_flavor_t flav, time_t valid_after)
77 networkstatus_t *ns = tor_malloc_zero(sizeof(networkstatus_t));
78 ns->type = NS_TYPE_CONSENSUS;
79 ns->flavor = flav;
80 ns->valid_after = valid_after;
81 return ns;
84 static char *
85 fake_ns_body_new(consensus_flavor_t flav, time_t valid_after)
87 const char *flavor_string = flav == FLAV_NS ? "" : " microdesc";
88 char valid_after_string[ISO_TIME_LEN+1];
90 format_iso_time(valid_after_string, valid_after);
91 char *random_stuff = crypto_random_hostname(3, 25, "junk ", "");
92 char *random_stuff2 = crypto_random_hostname(3, 10, "", "");
94 char *consensus;
95 tor_asprintf(&consensus,
96 "network-status-version 3%s\n"
97 "vote-status consensus\n"
98 "valid-after %s\n"
99 "r name ccccccccccccccccc etc\nsample\n"
100 "r name eeeeeeeeeeeeeeeee etc\nbar\n"
101 "%s\n"
102 "directory-signature hello-there\n"
103 "directory-signature %s\n",
104 flavor_string,
105 valid_after_string,
106 random_stuff,
107 random_stuff2);
108 tor_free(random_stuff);
109 tor_free(random_stuff2);
110 return consensus;
113 // ============================== Cpuworker mocking code
114 // These mocking functions and types capture the cpuworker calls
115 // so we can inspect them and run them in the main thread.
116 static smartlist_t *fake_cpuworker_queue = NULL;
117 typedef struct fake_work_queue_ent_t {
118 enum workqueue_reply_t (*fn)(void *, void *);
119 void (*reply_fn)(void *);
120 void *arg;
121 } fake_work_queue_ent_t;
122 static struct workqueue_entry_t *
123 mock_cpuworker_queue_work(workqueue_priority_t prio,
124 enum workqueue_reply_t (*fn)(void *, void *),
125 void (*reply_fn)(void *),
126 void *arg)
128 (void) prio;
130 if (! fake_cpuworker_queue)
131 fake_cpuworker_queue = smartlist_new();
133 fake_work_queue_ent_t *ent = tor_malloc_zero(sizeof(*ent));
134 ent->fn = fn;
135 ent->reply_fn = reply_fn;
136 ent->arg = arg;
137 smartlist_add(fake_cpuworker_queue, ent);
138 return (struct workqueue_entry_t *)ent;
140 static int
141 mock_cpuworker_run_work(void)
143 if (! fake_cpuworker_queue)
144 return 0;
145 SMARTLIST_FOREACH(fake_cpuworker_queue, fake_work_queue_ent_t *, ent, {
146 enum workqueue_reply_t r = ent->fn(NULL, ent->arg);
147 if (r != WQ_RPL_REPLY)
148 return -1;
150 return 0;
152 static void
153 mock_cpuworker_handle_replies(void)
155 if (! fake_cpuworker_queue)
156 return;
157 SMARTLIST_FOREACH(fake_cpuworker_queue, fake_work_queue_ent_t *, ent, {
158 ent->reply_fn(ent->arg);
159 tor_free(ent);
161 smartlist_free(fake_cpuworker_queue);
162 fake_cpuworker_queue = NULL;
165 // ============================== Other helpers
167 static consdiff_status_t
168 lookup_diff_from(consensus_cache_entry_t **out,
169 consensus_flavor_t flav,
170 const char *str1)
172 uint8_t digest[DIGEST256_LEN];
173 if (router_get_networkstatus_v3_sha3_as_signed(digest,
174 str1, strlen(str1))<0) {
175 TT_FAIL(("Unable to compute sha3-as-signed"));
176 return CONSDIFF_NOT_FOUND;
178 return consdiffmgr_find_diff_from(out, flav,
179 DIGEST_SHA3_256, digest, sizeof(digest),
180 NO_METHOD);
183 static int
184 lookup_apply_and_verify_diff(consensus_flavor_t flav,
185 const char *str1,
186 const char *str2)
188 consensus_cache_entry_t *ent = NULL;
189 consdiff_status_t status = lookup_diff_from(&ent, flav, str1);
190 if (ent == NULL || status != CONSDIFF_AVAILABLE) {
191 return -1;
194 consensus_cache_entry_incref(ent);
195 size_t size;
196 const char *diff_string = NULL;
197 char *diff_owned = NULL;
198 int r = uncompress_or_set_ptr(&diff_string, &size, &diff_owned, ent);
199 consensus_cache_entry_decref(ent);
200 if (diff_string == NULL || r < 0)
201 return -1;
203 char *applied = consensus_diff_apply(str1, strlen(str1), diff_string, size);
204 tor_free(diff_owned);
205 if (applied == NULL)
206 return -1;
208 int match = !strcmp(applied, str2);
209 tor_free(applied);
210 return match ? 0 : -1;
213 static void
214 cdm_reload(void)
216 consdiffmgr_free_all();
217 cdm_cache_get();
218 consdiffmgr_rescan();
221 // ============================== Beginning of tests
223 #if 0
224 static int got_failure = 0;
225 static void
226 got_assertion_failure(void)
228 ++got_failure;
231 /* XXXX This test won't work, because there is currently no way to actually
232 * XXXX capture a real assertion failure. */
233 static void
234 test_consdiffmgr_init_failure(void *arg)
236 (void)arg;
237 // Capture assertions and bugs.
239 /* As in ...test_setup, but do not create the datadir. The missing directory
240 * will cause a failure. */
241 char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cdm"));
242 tor_free(get_options_mutable()->CacheDirectory);
243 get_options_mutable()->CacheDirectory = ddir_fname; // now owns the pointer.
245 consdiff_cfg_t consdiff_cfg = { 7200, 300 };
247 tor_set_failed_assertion_callback(got_assertion_failure);
248 tor_capture_bugs_(1);
249 consdiffmgr_configure(&consdiff_cfg); // This should fail.
250 tt_int_op(got_failure, OP_EQ, 1);
251 const smartlist_t *bugs = tor_get_captured_bug_log_();
252 tt_int_op(smartlist_len(bugs), OP_EQ, 1);
254 done:
255 tor_end_capture_bugs_();
257 #endif /* 0 */
259 static void
260 test_consdiffmgr_sha3_helper(void *arg)
262 (void) arg;
263 consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier
264 config_line_t *lines = NULL;
265 char *mem_op_hex_tmp = NULL;
266 config_line_prepend(&lines, "good-sha",
267 "F00DF00DF00DF00DF00DF00DF00DF00D"
268 "F00DF00DF00DF00DF00DF00DF00DF00D");
269 config_line_prepend(&lines, "short-sha",
270 "F00DF00DF00DF00DF00DF00DF00DF00D"
271 "F00DF00DF00DF00DF00DF00DF00DF0");
272 config_line_prepend(&lines, "long-sha",
273 "F00DF00DF00DF00DF00DF00DF00DF00D"
274 "F00DF00DF00DF00DF00DF00DF00DF00DF00D");
275 config_line_prepend(&lines, "not-sha",
276 "F00DF00DF00DF00DF00DF00DF00DF00D"
277 "F00DF00DF00DF00DF00DF00DF00DXXXX");
278 consensus_cache_entry_t *ent =
279 consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8);
281 uint8_t buf[DIGEST256_LEN];
282 tt_int_op(-1, OP_EQ, cdm_entry_get_sha3_value(buf, NULL, "good-sha"));
283 tt_int_op(0, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "good-sha"));
284 test_memeq_hex(buf, "F00DF00DF00DF00DF00DF00DF00DF00D"
285 "F00DF00DF00DF00DF00DF00DF00DF00D");
287 tt_int_op(-1, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "missing-sha"));
288 tt_int_op(-2, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "short-sha"));
289 tt_int_op(-2, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "long-sha"));
290 tt_int_op(-2, OP_EQ, cdm_entry_get_sha3_value(buf, ent, "not-sha"));
292 done:
293 consensus_cache_entry_decref(ent);
294 config_free_lines(lines);
295 tor_free(mem_op_hex_tmp);
298 static void
299 test_consdiffmgr_add(void *arg)
301 (void) arg;
302 time_t now = approx_time();
304 const char *body = NULL;
305 char *body_owned = NULL;
307 consensus_cache_entry_t *ent = NULL;
308 networkstatus_t *ns_tmp = fake_ns_new(FLAV_NS, now);
309 const char *dummy = "foo";
310 int r = consdiffmgr_add_consensus(dummy, ns_tmp);
311 tt_int_op(r, OP_EQ, 0);
313 /* If we add it again, it won't work */
314 setup_capture_of_logs(LOG_INFO);
315 dummy = "bar";
316 r = consdiffmgr_add_consensus(dummy, ns_tmp);
317 tt_int_op(r, OP_EQ, -1);
318 expect_single_log_msg_containing("We already have a copy of that "
319 "consensus");
320 mock_clean_saved_logs();
322 /* But it will work fine if the flavor is different */
323 dummy = "baz";
324 ns_tmp->flavor = FLAV_MICRODESC;
325 r = consdiffmgr_add_consensus(dummy, ns_tmp);
326 tt_int_op(r, OP_EQ, 0);
328 /* And it will work fine if the time is different */
329 dummy = "quux";
330 ns_tmp->flavor = FLAV_NS;
331 ns_tmp->valid_after = now - 60;
332 r = consdiffmgr_add_consensus(dummy, ns_tmp);
333 tt_int_op(r, OP_EQ, 0);
335 /* If we add one a long long time ago, it will fail. */
336 dummy = "xyzzy";
337 ns_tmp->valid_after = 86400 * 100; /* A few months into 1970 */
338 r = consdiffmgr_add_consensus(dummy, ns_tmp);
339 tt_int_op(r, OP_EQ, -1);
340 expect_log_msg_containing("it's too old.");
342 /* Try looking up a consensuses. */
343 ent = cdm_cache_lookup_consensus(FLAV_NS, now-60);
344 tt_assert(ent);
345 consensus_cache_entry_incref(ent);
346 size_t s;
347 r = uncompress_or_set_ptr(&body, &s, &body_owned, ent);
348 tt_int_op(r, OP_EQ, 0);
349 tt_int_op(s, OP_EQ, 4);
350 tt_mem_op(body, OP_EQ, "quux", 4);
352 /* Try looking up another entry, but fail */
353 tt_ptr_op(cdm_cache_lookup_consensus(FLAV_MICRODESC, now - 60), OP_EQ, NULL);
354 tt_ptr_op(cdm_cache_lookup_consensus(FLAV_NS, now - 61), OP_EQ, NULL);
356 done:
357 networkstatus_vote_free(ns_tmp);
358 teardown_capture_of_logs();
359 consensus_cache_entry_decref(ent);
360 tor_free(body_owned);
363 static void
364 test_consdiffmgr_make_diffs(void *arg)
366 (void)arg;
367 networkstatus_t *ns = NULL;
368 char *ns_body = NULL, *md_ns_body = NULL, *md_ns_body_2 = NULL;
369 char *applied = NULL, *diff_text = NULL;
370 time_t now = approx_time();
371 int r;
372 consensus_cache_entry_t *diff = NULL;
373 uint8_t md_ns_sha3[DIGEST256_LEN];
374 consdiff_status_t diff_status;
376 MOCK(cpuworker_queue_work, mock_cpuworker_queue_work);
378 // Try rescan with no consensuses: shouldn't crash or queue work.
379 consdiffmgr_rescan();
380 tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue);
382 // Make two consensuses, 1 hour sec ago.
383 ns = fake_ns_new(FLAV_NS, now-3600);
384 ns_body = fake_ns_body_new(FLAV_NS, now-3600);
385 r = consdiffmgr_add_consensus(ns_body, ns);
386 networkstatus_vote_free(ns);
387 tor_free(ns_body);
388 tt_int_op(r, OP_EQ, 0);
390 ns = fake_ns_new(FLAV_MICRODESC, now-3600);
391 md_ns_body = fake_ns_body_new(FLAV_MICRODESC, now-3600);
392 r = consdiffmgr_add_consensus(md_ns_body, ns);
393 router_get_networkstatus_v3_sha3_as_signed(md_ns_sha3, md_ns_body,
394 strlen(md_ns_body));
395 networkstatus_vote_free(ns);
396 tt_int_op(r, OP_EQ, 0);
398 // No diffs will be generated.
399 consdiffmgr_rescan();
400 tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue);
402 // Add a MD consensus from 45 minutes ago. This should cause one diff
403 // worth of work to get queued.
404 ns = fake_ns_new(FLAV_MICRODESC, now-45*60);
405 md_ns_body_2 = fake_ns_body_new(FLAV_MICRODESC, now-45*60);
406 r = consdiffmgr_add_consensus(md_ns_body_2, ns);
407 networkstatus_vote_free(ns);
408 tt_int_op(r, OP_EQ, 0);
410 consdiffmgr_rescan();
411 tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue);
412 tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue));
413 diff_status = consdiffmgr_find_diff_from(&diff, FLAV_MICRODESC,
414 DIGEST_SHA3_256,
415 md_ns_sha3, DIGEST256_LEN,
416 NO_METHOD);
417 tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, diff_status);
419 // Now run that process and get the diff.
420 r = mock_cpuworker_run_work();
421 tt_int_op(r, OP_EQ, 0);
422 mock_cpuworker_handle_replies();
424 // At this point we should be able to get that diff.
425 diff_status = consdiffmgr_find_diff_from(&diff, FLAV_MICRODESC,
426 DIGEST_SHA3_256,
427 md_ns_sha3, DIGEST256_LEN,
428 NO_METHOD);
429 tt_int_op(CONSDIFF_AVAILABLE, OP_EQ, diff_status);
430 tt_assert(diff);
432 /* Make sure applying the diff actually works */
433 const uint8_t *diff_body;
434 size_t diff_size;
435 r = consensus_cache_entry_get_body(diff, &diff_body, &diff_size);
436 tt_int_op(r, OP_EQ, 0);
437 diff_text = tor_memdup_nulterm(diff_body, diff_size);
438 applied = consensus_diff_apply_(md_ns_body, diff_text);
439 tt_assert(applied);
440 tt_str_op(applied, OP_EQ, md_ns_body_2);
442 /* Rescan again: no more work to do. */
443 consdiffmgr_rescan();
444 tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue);
446 done:
447 tor_free(md_ns_body);
448 tor_free(md_ns_body_2);
449 tor_free(diff_text);
450 tor_free(applied);
453 static void
454 test_consdiffmgr_diff_rules(void *arg)
456 (void)arg;
457 #define N 6
458 char *md_body[N], *ns_body[N];
459 networkstatus_t *md_ns[N], *ns_ns[N];
460 int i;
462 MOCK(cpuworker_queue_work, mock_cpuworker_queue_work);
464 /* Create a bunch of consensus things at 15-second intervals. */
465 time_t start = approx_time() - 120;
466 for (i = 0; i < N; ++i) {
467 time_t when = start + i * 15;
468 md_body[i] = fake_ns_body_new(FLAV_MICRODESC, when);
469 ns_body[i] = fake_ns_body_new(FLAV_NS, when);
470 md_ns[i] = fake_ns_new(FLAV_MICRODESC, when);
471 ns_ns[i] = fake_ns_new(FLAV_NS, when);
474 /* For the MD consensuses: add 4 of them, and make sure that
475 * diffs are created to one consensus (the most recent) only. */
476 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[1], md_ns[1]));
477 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[2], md_ns[2]));
478 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[3], md_ns[3]));
479 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[4], md_ns[4]));
480 consdiffmgr_rescan();
481 tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue);
482 tt_int_op(3, OP_EQ, smartlist_len(fake_cpuworker_queue));
483 tt_int_op(0, OP_EQ, mock_cpuworker_run_work());
484 mock_cpuworker_handle_replies();
485 tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue);
487 /* For the NS consensuses: add 3, generate, and add one older one and
488 * make sure that older one is the only one whose diff is generated */
489 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[0], ns_ns[0]));
490 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[1], ns_ns[1]));
491 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[5], ns_ns[5]));
492 consdiffmgr_rescan();
493 tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue);
494 tt_int_op(2, OP_EQ, smartlist_len(fake_cpuworker_queue));
495 tt_int_op(0, OP_EQ, mock_cpuworker_run_work());
496 mock_cpuworker_handle_replies();
498 /* At this point, we should actually have working diffs! */
499 tt_int_op(0, OP_EQ,
500 lookup_apply_and_verify_diff(FLAV_NS, ns_body[0], ns_body[5]));
501 tt_int_op(0, OP_EQ,
502 lookup_apply_and_verify_diff(FLAV_NS, ns_body[1], ns_body[5]));
504 tt_int_op(0, OP_EQ,
505 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[4]));
506 tt_int_op(0, OP_EQ,
507 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[2], md_body[4]));
508 tt_int_op(0, OP_EQ,
509 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[3], md_body[4]));
511 /* Self-to-self diff won't be present */
512 consensus_cache_entry_t *ent;
513 tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ,
514 lookup_diff_from(&ent, FLAV_NS, ns_body[5]));
515 /* No diff from 2 has been added yet */
516 tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ,
517 lookup_diff_from(&ent, FLAV_NS, ns_body[2]));
518 /* No diff arriving at old things. */
519 tt_int_op(-1, OP_EQ,
520 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[2]));
521 /* No backwards diff */
522 tt_int_op(-1, OP_EQ,
523 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[4], md_body[3]));
525 /* Now, an update: add number 2 and make sure it's the only one whose diff
526 * is regenerated. */
527 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(ns_body[2], ns_ns[2]));
528 consdiffmgr_rescan();
529 tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue);
530 tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue));
531 tt_int_op(0, OP_EQ, mock_cpuworker_run_work());
532 mock_cpuworker_handle_replies();
534 tt_int_op(0, OP_EQ,
535 lookup_apply_and_verify_diff(FLAV_NS, ns_body[2], ns_body[5]));
537 /* Finally: reload, and make sure that the information is still indexed */
538 cdm_reload();
540 tt_int_op(0, OP_EQ,
541 lookup_apply_and_verify_diff(FLAV_NS, ns_body[0], ns_body[5]));
542 tt_int_op(0, OP_EQ,
543 lookup_apply_and_verify_diff(FLAV_NS, ns_body[2], ns_body[5]));
544 tt_int_op(0, OP_EQ,
545 lookup_apply_and_verify_diff(FLAV_NS, ns_body[1], ns_body[5]));
547 tt_int_op(0, OP_EQ,
548 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[4]));
549 tt_int_op(0, OP_EQ,
550 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[2], md_body[4]));
551 tt_int_op(0, OP_EQ,
552 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[3], md_body[4]));
554 done:
555 for (i = 0; i < N; ++i) {
556 tor_free(md_body[i]);
557 tor_free(ns_body[i]);
558 networkstatus_vote_free(md_ns[i]);
559 networkstatus_vote_free(ns_ns[i]);
561 UNMOCK(cpuworker_queue_work);
562 #undef N
565 static void
566 test_consdiffmgr_diff_failure(void *arg)
568 (void)arg;
569 MOCK(cpuworker_queue_work, mock_cpuworker_queue_work);
571 /* We're going to make sure that if we have a bogus request where
572 * we can't actually compute a diff, the world must not end. */
573 networkstatus_t *ns1 = NULL;
574 networkstatus_t *ns2 = NULL;
575 int r;
577 ns1 = fake_ns_new(FLAV_NS, approx_time()-100);
578 ns2 = fake_ns_new(FLAV_NS, approx_time()-50);
579 r = consdiffmgr_add_consensus("foo bar baz\n", ns1);
580 tt_int_op(r, OP_EQ, 0);
581 // We refuse to compute a diff to or from a line holding only a single dot.
582 // We can add it here, though.
583 r = consdiffmgr_add_consensus("foo bar baz\n.\n.\n", ns2);
584 tt_int_op(r, OP_EQ, 0);
586 consdiffmgr_rescan();
587 tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue);
588 setup_capture_of_logs(LOG_WARN);
589 tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue));
590 tt_int_op(0, OP_EQ, mock_cpuworker_run_work());
591 expect_single_log_msg_containing("one of the lines to be added is \".\".");
592 mock_clean_saved_logs();
593 mock_cpuworker_handle_replies();
594 expect_single_log_msg_containing("Worker was unable to compute consensus "
595 "diff from ");
597 /* Make sure the diff is not present */
598 consensus_cache_entry_t *ent;
599 tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ,
600 lookup_diff_from(&ent, FLAV_NS, "foo bar baz\n"));
602 done:
603 teardown_capture_of_logs();
604 UNMOCK(cpuworker_queue_work);
605 networkstatus_vote_free(ns1);
606 networkstatus_vote_free(ns2);
609 static void
610 test_consdiffmgr_diff_pending(void *arg)
612 #define N 3
613 (void)arg;
614 char *md_body[N];
615 networkstatus_t *md_ns[N];
616 time_t start = approx_time() - 120;
617 int i;
618 for (i = 0; i < N; ++i) {
619 time_t when = start + i * 30;
620 md_body[i] = fake_ns_body_new(FLAV_MICRODESC, when);
621 md_ns[i] = fake_ns_new(FLAV_MICRODESC, when);
624 MOCK(cpuworker_queue_work, mock_cpuworker_queue_work);
626 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[1], md_ns[1]));
627 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[2], md_ns[2]));
628 /* Make a diff */
629 consdiffmgr_rescan();
630 tt_int_op(1, OP_EQ, smartlist_len(fake_cpuworker_queue));
632 /* Look it up. Is it pending? */
633 consensus_cache_entry_t *ent = NULL;
634 consdiff_status_t diff_status;
635 diff_status = lookup_diff_from(&ent, FLAV_MICRODESC, md_body[1]);
636 tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ, diff_status);
637 tt_ptr_op(ent, OP_EQ, NULL);
639 /* Add another old consensus. only one new diff should launch! */
640 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[0], md_ns[0]));
641 consdiffmgr_rescan();
642 tt_int_op(2, OP_EQ, smartlist_len(fake_cpuworker_queue));
644 tt_int_op(0, OP_EQ, mock_cpuworker_run_work());
645 mock_cpuworker_handle_replies();
647 tt_int_op(0, OP_EQ,
648 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[0], md_body[2]));
649 tt_int_op(0, OP_EQ,
650 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[2]));
652 done:
653 UNMOCK(cpuworker_queue_work);
654 for (i = 0; i < N; ++i) {
655 tor_free(md_body[i]);
656 networkstatus_vote_free(md_ns[i]);
658 #undef N
661 static void
662 test_consdiffmgr_cleanup_old(void *arg)
664 (void)arg;
665 config_line_t *labels = NULL;
666 consensus_cache_entry_t *ent = NULL;
667 consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier
669 /* This item will be will be cleanable because it has a valid-after
670 * time far in the past. */
671 config_line_prepend(&labels, "document-type", "confribble-blarg");
672 config_line_prepend(&labels, "consensus-valid-after",
673 "1980-10-10T10:10:10");
674 ent = consensus_cache_add(cache, labels, (const uint8_t*)"Foo", 3);
675 tt_assert(ent);
676 consensus_cache_entry_decref(ent);
678 setup_capture_of_logs(LOG_DEBUG);
679 tt_int_op(1, OP_EQ, consdiffmgr_cleanup());
680 expect_log_msg_containing("Deleting entry because its consensus-valid-"
681 "after value (1980-10-10T10:10:10) was too old");
683 done:
684 teardown_capture_of_logs();
685 config_free_lines(labels);
688 static void
689 test_consdiffmgr_cleanup_bad_valid_after(void *arg)
691 /* This will seem cleanable, but isn't, because its valid-after time is
692 * malformed. */
694 (void)arg;
695 config_line_t *labels = NULL;
696 consensus_cache_entry_t *ent = NULL;
697 consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier
699 config_line_prepend(&labels, "document-type", "consensus");
700 config_line_prepend(&labels, "consensus-valid-after",
701 "whan that aprille with his shoures soote"); // (~1385?)
702 ent = consensus_cache_add(cache, labels, (const uint8_t*)"Foo", 3);
703 tt_assert(ent);
704 consensus_cache_entry_decref(ent);
706 setup_capture_of_logs(LOG_DEBUG);
707 tt_int_op(0, OP_EQ, consdiffmgr_cleanup());
708 expect_log_msg_containing("Ignoring entry because its consensus-valid-"
709 "after value (\"whan that aprille with his "
710 "shoures soote\") was unparseable");
712 done:
713 teardown_capture_of_logs();
714 config_free_lines(labels);
717 static void
718 test_consdiffmgr_cleanup_no_valid_after(void *arg)
720 (void)arg;
721 config_line_t *labels = NULL;
722 consensus_cache_entry_t *ent = NULL;
723 consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier
725 /* This item will be will be uncleanable because it has no recognized
726 * valid-after. */
727 config_line_prepend(&labels, "document-type", "consensus");
728 config_line_prepend(&labels, "confrooble-voolid-oofter",
729 "2010-10-10T09:08:07");
730 ent = consensus_cache_add(cache, labels, (const uint8_t*)"Foo", 3);
731 tt_assert(ent);
732 consensus_cache_entry_decref(ent);
734 setup_capture_of_logs(LOG_DEBUG);
735 tt_int_op(0, OP_EQ, consdiffmgr_cleanup());
736 expect_log_msg_containing("Ignoring entry because it had no consensus-"
737 "valid-after label");
739 done:
740 teardown_capture_of_logs();
741 config_free_lines(labels);
744 static void
745 test_consdiffmgr_cleanup_old_diffs(void *arg)
747 (void)arg;
748 #define N 4
749 char *md_body[N];
750 networkstatus_t *md_ns[N];
751 int i;
752 consensus_cache_entry_t *hold_ent = NULL, *ent;
754 /* Make sure that the cleanup function removes diffs to the not-most-recent
755 * consensus. */
757 MOCK(cpuworker_queue_work, mock_cpuworker_queue_work);
759 /* Create a bunch of consensus things at 15-second intervals. */
760 time_t start = approx_time() - 120;
761 for (i = 0; i < N; ++i) {
762 time_t when = start + i * 15;
763 md_body[i] = fake_ns_body_new(FLAV_MICRODESC, when);
764 md_ns[i] = fake_ns_new(FLAV_MICRODESC, when);
767 /* add the first 3. */
768 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[0], md_ns[0]));
769 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[1], md_ns[1]));
770 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[2], md_ns[2]));
771 /* Make diffs. */
772 consdiffmgr_rescan();
773 tt_ptr_op(NULL, OP_NE, fake_cpuworker_queue);
774 tt_int_op(2, OP_EQ, smartlist_len(fake_cpuworker_queue));
775 tt_int_op(0, OP_EQ, mock_cpuworker_run_work());
776 mock_cpuworker_handle_replies();
777 tt_ptr_op(NULL, OP_EQ, fake_cpuworker_queue);
779 /* Nothing is deletable now */
780 tt_int_op(0, OP_EQ, consdiffmgr_cleanup());
781 tt_int_op(0, OP_EQ,
782 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[0], md_body[2]));
783 tt_int_op(0, OP_EQ,
784 lookup_apply_and_verify_diff(FLAV_MICRODESC, md_body[1], md_body[2]));
786 tt_int_op(CONSDIFF_AVAILABLE, OP_EQ,
787 lookup_diff_from(&hold_ent, FLAV_MICRODESC, md_body[1]));
788 consensus_cache_entry_incref(hold_ent); // incref, so it is preserved.
790 /* Now add an even-more-recent consensus; this should make all previous
791 * diffs deletable, and make delete */
792 tt_int_op(0, OP_EQ, consdiffmgr_add_consensus(md_body[3], md_ns[3]));
793 tt_int_op(2 * n_diff_compression_methods() +
794 (n_consensus_compression_methods() - 1) , OP_EQ,
795 consdiffmgr_cleanup());
797 tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ,
798 lookup_diff_from(&ent, FLAV_MICRODESC, md_body[0]));
799 /* This one is marked deletable but still in the hashtable */
800 tt_int_op(CONSDIFF_AVAILABLE, OP_EQ,
801 lookup_diff_from(&ent, FLAV_MICRODESC, md_body[1]));
802 tt_int_op(CONSDIFF_NOT_FOUND, OP_EQ,
803 lookup_diff_from(&ent, FLAV_MICRODESC, md_body[2]));
805 /* Everything should be valid at this point */
806 tt_int_op(0, OP_EQ, consdiffmgr_validate());
808 /* And if we recan NOW, we'll purge the hashtable of the entries,
809 * and launch attempts to generate new ones */
810 consdiffmgr_rescan();
811 tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ,
812 lookup_diff_from(&ent, FLAV_MICRODESC, md_body[0]));
813 tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ,
814 lookup_diff_from(&ent, FLAV_MICRODESC, md_body[1]));
815 tt_int_op(CONSDIFF_IN_PROGRESS, OP_EQ,
816 lookup_diff_from(&ent, FLAV_MICRODESC, md_body[2]));
818 /* We're still holding on to this, though, so we can still map it! */
819 const uint8_t *t1 = NULL;
820 size_t s;
821 int r = consensus_cache_entry_get_body(hold_ent, &t1, &s);
822 tt_int_op(r, OP_EQ, 0);
823 tt_assert(t1);
825 done:
826 for (i = 0; i < N; ++i) {
827 tor_free(md_body[i]);
828 networkstatus_vote_free(md_ns[i]);
830 consensus_cache_entry_decref(hold_ent);
831 UNMOCK(cpuworker_queue_work);
832 #undef N
835 static void
836 test_consdiffmgr_validate(void *arg)
838 (void)arg;
839 config_line_t *lines = NULL;
840 consensus_cache_entry_t *ent = NULL;
841 consensus_cache_t *cache = cdm_cache_get(); // violate abstraction barrier
842 smartlist_t *vals = smartlist_new();
844 /* Put these: objects in the cache: one with a good sha3, one with bad sha3,
845 * one with a wrong sha3, and one with no sha3. */
846 config_line_prepend(&lines, "id", "wrong sha3");
847 config_line_prepend(&lines, "sha3-digest",
848 "F00DF00DF00DF00DF00DF00DF00DF00D"
849 "F00DF00DF00DF00DF00DF00DF00DF00D");
850 ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8);
851 consensus_cache_entry_decref(ent);
852 config_free_lines(lines);
853 lines = NULL;
855 config_line_prepend(&lines, "id", "bad sha3");
856 config_line_prepend(&lines, "sha3-digest",
857 "now is the winter of our dicotheque");
858 ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8);
859 consensus_cache_entry_decref(ent);
860 config_free_lines(lines);
861 lines = NULL;
863 config_line_prepend(&lines, "id", "no sha3");
864 ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8);
865 consensus_cache_entry_decref(ent);
866 config_free_lines(lines);
867 lines = NULL;
869 config_line_prepend(&lines, "id", "good sha3");
870 config_line_prepend(&lines, "sha3-digest",
871 "8d8b1998616cd6b4c4055da8d38728dc"
872 "93c758d4131a53c7d81aa6337dee1c05");
873 ent = consensus_cache_add(cache, lines, (const uint8_t *)"Hi there", 8);
874 consensus_cache_entry_decref(ent);
875 config_free_lines(lines);
876 lines = NULL;
878 cdm_reload();
879 cache = cdm_cache_get();
880 tt_int_op(1, OP_EQ, consdiffmgr_validate());
882 consensus_cache_find_all(vals, cache, "id", "good sha3");
883 tt_int_op(smartlist_len(vals), OP_EQ, 1);
884 smartlist_clear(vals);
886 consensus_cache_find_all(vals, cache, "id", "no sha3");
887 tt_int_op(smartlist_len(vals), OP_EQ, 1);
888 smartlist_clear(vals);
890 consensus_cache_find_all(vals, cache, "id", "wrong sha3");
891 tt_int_op(smartlist_len(vals), OP_EQ, 0);
892 consensus_cache_find_all(vals, cache, "id", "bad sha3");
893 tt_int_op(smartlist_len(vals), OP_EQ, 0);
895 done:
896 smartlist_free(vals);
899 #define TEST(name) \
900 { #name, test_consdiffmgr_ ## name , TT_FORK, &setup_diffmgr, NULL }
902 struct testcase_t consdiffmgr_tests[] = {
903 #if 0
904 { "init_failure", test_consdiffmgr_init_failure, TT_FORK, NULL, NULL },
905 #endif
906 TEST(sha3_helper),
907 TEST(add),
908 TEST(make_diffs),
909 TEST(diff_rules),
910 TEST(diff_failure),
911 TEST(diff_pending),
912 TEST(cleanup_old),
913 TEST(cleanup_bad_valid_after),
914 TEST(cleanup_no_valid_after),
915 TEST(cleanup_old_diffs),
916 TEST(validate),
918 // XXXX Test: non-cacheing cases of replyfn().
920 END_OF_TESTCASES