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

[tor-commits] [tor] 17/20: Prop#329 Tests: Add tests for the conflux pool



This is an automated email from the git hooks/post-receive script.

ahf pushed a commit to branch main
in repository tor.

commit 8d4781e730b3e6c543add3e9acf4eff118bffa70
Author: Mike Perry <mikeperry-git@xxxxxxxxxxxxxx>
AuthorDate: Wed Feb 15 19:47:52 2023 +0000

    Prop#329 Tests: Add tests for the conflux pool
---
 src/test/fakecircs.c         |    6 +
 src/test/include.am          |    1 +
 src/test/test.c              |    1 +
 src/test/test.h              |    1 +
 src/test/test_conflux_pool.c | 1338 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1347 insertions(+)

diff --git a/src/test/fakecircs.c b/src/test/fakecircs.c
index caeacd84ef..e52df022d0 100644
--- a/src/test/fakecircs.c
+++ b/src/test/fakecircs.c
@@ -17,6 +17,9 @@
 #include "core/or/circuitbuild.h"
 #include "core/or/circuitlist.h"
 #include "core/or/circuitpadding.h"
+#include "core/or/congestion_control_common.h"
+#include "core/or/conflux_pool.h"
+#include "core/or/conflux.h"
 #include "core/or/crypt_path.h"
 #include "core/or/relay.h"
 #include "core/or/relay_crypto_st.h"
@@ -87,5 +90,8 @@ free_fake_orcirc(or_circuit_t *orcirc)
     circuitmux_detach_circuit(circ->n_chan->cmux, circ);
   }
 
+  conflux_circuit_about_to_free(circ);
+  congestion_control_free(circ->ccontrol);
+
   tor_free_(circ);
 }
diff --git a/src/test/include.am b/src/test/include.am
index 1c2929c3ca..deff450490 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -148,6 +148,7 @@ src_test_test_SOURCES += \
 	src/test/test_compat_libevent.c \
 	src/test/test_config.c \
 	src/test/test_conflux_cell.c \
+	src/test/test_conflux_pool.c \
 	src/test/test_confmgr.c \
 	src/test/test_confparse.c \
 	src/test/test_connection.c \
diff --git a/src/test/test.c b/src/test/test.c
index e1252eda66..170dafe0c6 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -779,6 +779,7 @@ struct testgroup_t testgroups[] = {
   { "config/mgr/", confmgr_tests },
   { "config/parse/", confparse_tests },
   { "conflux/cell/", conflux_cell_tests },
+  { "conflux/pool/", conflux_pool_tests },
   { "connection/", connection_tests },
   { "conscache/", conscache_tests },
   { "consdiff/", consdiff_tests },
diff --git a/src/test/test.h b/src/test/test.h
index fb0ea98f93..d6c06c658f 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -108,6 +108,7 @@ extern struct testcase_t circuituse_tests[];
 extern struct testcase_t compat_libevent_tests[];
 extern struct testcase_t config_tests[];
 extern struct testcase_t conflux_cell_tests[];
+extern struct testcase_t conflux_pool_tests[];
 extern struct testcase_t confmgr_tests[];
 extern struct testcase_t confparse_tests[];
 extern struct testcase_t connection_tests[];
diff --git a/src/test/test_conflux_pool.c b/src/test/test_conflux_pool.c
new file mode 100644
index 0000000000..b8053b7c0a
--- /dev/null
+++ b/src/test/test_conflux_pool.c
@@ -0,0 +1,1338 @@
+#define CHANNEL_OBJECT_PRIVATE
+#define TOR_TIMERS_PRIVATE
+#define TOR_CONFLUX_PRIVATE
+#define CIRCUITLIST_PRIVATE
+#define NETWORKSTATUS_PRIVATE
+#define CRYPT_PATH_PRIVATE
+#define RELAY_PRIVATE
+#define CONNECTION_PRIVATE
+#define TOR_CONGESTION_CONTROL_PRIVATE
+
+#include "core/or/or.h"
+#include "test/test.h"
+#include "test/log_test_helpers.h"
+#include "lib/testsupport/testsupport.h"
+#include "core/or/connection_or.h"
+#include "core/or/channel.h"
+#include "core/or/channeltls.h"
+#include "core/or/crypt_path.h"
+#include <event.h>
+#include "lib/evloop/compat_libevent.h"
+#include "lib/time/compat_time.h"
+#include "lib/defs/time.h"
+#include "core/or/relay.h"
+#include "core/or/circuitlist.h"
+#include "core/or/circuitstats.h"
+#include "core/or/circuitbuild.h"
+#include "core/or/circuituse.h"
+#include "core/or/congestion_control_common.h"
+#include "core/or/congestion_control_st.h"
+#include "core/or/extendinfo.h"
+#include "core/mainloop/netstatus.h"
+#include "core/crypto/relay_crypto.h"
+#include "core/or/protover.h"
+#include "feature/nodelist/nodelist.h"
+#include "app/config/config.h"
+
+#include "feature/nodelist/routerstatus_st.h"
+#include "feature/nodelist/networkstatus_st.h"
+#include "feature/nodelist/node_st.h"
+#include "core/or/cell_st.h"
+#include "core/or/crypt_path_st.h"
+#include "core/or/or_circuit_st.h"
+#include "core/or/origin_circuit_st.h"
+
+#include "core/mainloop/connection.h"
+#include "core/or/connection_edge.h"
+#include "core/or/edge_connection_st.h"
+
+#include "test/fakecircs.h"
+#include "test/rng_test_helpers.h"
+#include "core/or/conflux_pool.h"
+#include "core/or/conflux_util.h"
+#include "core/or/conflux_params.h"
+#include "core/or/conflux.h"
+#include "core/or/conflux_st.h"
+#include "lib/crypt_ops/crypto_rand.h"
+
+/* Start our monotime mocking at 1 second past whatever monotime_init()
+ * thought the actual wall clock time was, for platforms with bad resolution
+ * and weird timevalues during monotime_init() before mocking. */
+#define MONOTIME_MOCK_START   (monotime_absolute_nsec()+\
+                               TOR_NSEC_PER_USEC*TOR_USEC_PER_SEC)
+
+extern smartlist_t *connection_array;
+void circuit_expire_old_circuits_clientside(void);
+
+circid_t get_unique_circ_id_by_chan(channel_t *chan);
+
+channel_t *new_fake_channel(void);
+
+static void simulate_single_hop_extend(origin_circuit_t *client, int exit);
+static void free_fake_origin_circuit(origin_circuit_t *circ);
+static circuit_t * get_exit_circ(circuit_t *client_circ);
+static circuit_t * get_client_circ(circuit_t *exit_circ);
+static void simulate_circuit_build(circuit_t *client_circ);
+
+static int64_t curr_mocked_time;
+
+static channel_t dummy_channel;
+
+static void
+timers_advance_and_run(int64_t msec_update)
+{
+  curr_mocked_time += msec_update*TOR_NSEC_PER_MSEC;
+  monotime_coarse_set_mock_time_nsec(curr_mocked_time);
+  monotime_set_mock_time_nsec(curr_mocked_time);
+}
+
+/* These lists of circuit endpoints send to eachother via
+ * circuit_package_relay_cell_mocked */
+static smartlist_t *client_circs;
+static smartlist_t *exit_circs;
+static smartlist_t *client_streams;
+static smartlist_t *exit_streams;
+
+typedef struct {
+  circuit_t *client;
+  circuit_t *exit;
+} circ_pair_t;
+static smartlist_t *circ_pairs;
+
+static void
+simulate_circuit_built(circuit_t *client, circuit_t *exit)
+{
+  circ_pair_t *pair = tor_malloc_zero(sizeof(circ_pair_t));
+  pair->client = client;
+  pair->exit = exit;
+  smartlist_add(circ_pairs, pair);
+}
+
+static origin_circuit_t *
+circuit_establish_circuit_conflux_mock(const uint8_t *conflux_nonce,
+                                   uint8_t purpose, extend_info_t *exit_ei,
+                                   int flags)
+{
+  (void)exit_ei;
+  (void)flags;
+  origin_circuit_t *circ = origin_circuit_init(purpose, flags);
+  circ->base_.conflux_pending_nonce = tor_memdup(conflux_nonce, DIGEST256_LEN);
+  circ->base_.purpose = CIRCUIT_PURPOSE_CONFLUX_UNLINKED;
+  smartlist_add(client_circs, circ);
+
+  // This also moves the clock forward as if these hops were opened..
+  // Not a problem, unless we want to accurately test buildtimeouts
+  simulate_single_hop_extend(circ, 0);
+  simulate_single_hop_extend(circ, 0);
+  simulate_single_hop_extend(circ, 1);
+  circ->cpath->prev->ccontrol = tor_malloc_zero(sizeof(congestion_control_t));
+  circ->cpath->prev->ccontrol->sendme_arrival_timestamps = smartlist_new();
+  circ->cpath->prev->ccontrol->sendme_pending_timestamps = smartlist_new();
+  circ->cpath->prev->ccontrol->sendme_inc = 31;
+
+  return circ;
+}
+
+static void
+free_fake_origin_circuit(origin_circuit_t *circ)
+{
+  circuit_clear_cpath(circ);
+  tor_free(circ);
+}
+
+void dummy_nop_timer(void);
+
+static int
+circuit_package_relay_cell_mock(cell_t *cell, circuit_t *circ,
+                           cell_direction_t cell_direction,
+                           crypt_path_t *layer_hint, streamid_t on_stream,
+                           const char *filename, int lineno);
+
+static void
+circuitmux_attach_circuit_mock(circuitmux_t *cmux, circuit_t *circ,
+                               cell_direction_t direction);
+
+static void
+circuitmux_attach_circuit_mock(circuitmux_t *cmux, circuit_t *circ,
+                               cell_direction_t direction)
+{
+  (void)cmux;
+  (void)circ;
+  (void)direction;
+
+  return;
+}
+/* For use in the mock_net smartlist queue:
+ * this struct contains a circuit and a cell to
+ * deliver on it. */
+typedef struct {
+  circuit_t *circ;
+  cell_t *cell;
+} cell_delivery_t;
+
+static smartlist_t *mock_cell_delivery = NULL;
+
+static int
+circuit_package_relay_cell_mock(cell_t *cell, circuit_t *circ,
+                           cell_direction_t cell_direction,
+                           crypt_path_t *layer_hint, streamid_t on_stream,
+                           const char *filename, int lineno)
+{
+  (void)cell; (void)on_stream; (void)filename; (void)lineno;
+  (void)cell_direction;
+  circuit_t *dest_circ = NULL;
+
+  // If we have a layer hint, we are sending to the exit. Look
+  // up the exit circ based on our circuit index in the smartlist
+  if (layer_hint) {
+    tor_assert(CIRCUIT_IS_ORIGIN(circ));
+    tt_int_op(cell_direction, OP_EQ, CELL_DIRECTION_OUT);
+
+    // Search the circ pairs list for the pair whose client is this circ,
+    // and set dest_circ to the exit circ in that pair
+    SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) {
+      if (pair->client == circ) {
+        dest_circ = pair->exit;
+        break;
+      }
+    } SMARTLIST_FOREACH_END(pair);
+  } else {
+    tt_int_op(cell_direction, OP_EQ, CELL_DIRECTION_IN);
+
+    // Search the circ pairs list for the pair whose exit is this circ,
+    // and set dest_circ to the client circ in that pair
+    SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) {
+      if (pair->exit == circ) {
+        dest_circ = pair->client;
+        break;
+      }
+    } SMARTLIST_FOREACH_END(pair);
+  }
+
+  cell_delivery_t *delivery = tor_malloc_zero(sizeof(cell_delivery_t));
+  delivery->circ = dest_circ;
+  delivery->cell = tor_memdup(cell, sizeof(cell_t));
+  smartlist_add(mock_cell_delivery, delivery);
+ done:
+  return 0;
+}
+
+/** Pull the next cell from the mock delivery queue and deliver it. */
+static void
+process_mock_cell_delivery(void)
+{
+  relay_header_t rh;
+
+  cell_delivery_t *delivery = smartlist_pop_last(mock_cell_delivery);
+  tor_assert(delivery);
+  cell_t *cell = delivery->cell;
+  circuit_t *dest_circ = delivery->circ;
+  relay_header_unpack(&rh, cell->payload);
+
+  timers_advance_and_run(1);
+
+  switch (cell->payload[0]) {
+  case RELAY_COMMAND_CONFLUX_LINK:
+    tor_assert(!CIRCUIT_IS_ORIGIN(dest_circ));
+    conflux_process_link(dest_circ, cell, rh.length);
+    break;
+  case RELAY_COMMAND_CONFLUX_LINKED:
+    tor_assert(CIRCUIT_IS_ORIGIN(dest_circ));
+    conflux_process_linked(dest_circ,
+                           TO_ORIGIN_CIRCUIT(dest_circ)->cpath->prev,
+                           cell, rh.length);
+    break;
+  case RELAY_COMMAND_CONFLUX_LINKED_ACK:
+    tor_assert(!CIRCUIT_IS_ORIGIN(dest_circ));
+    conflux_process_linked_ack(dest_circ);
+    break;
+  case RELAY_COMMAND_CONFLUX_SWITCH:
+    // We only test the case where the switch is initiated by the client.
+    // It is symmetric, so this should not matter. If we ever want to test
+    // the case where the switch is initiated by the exit, we will need to
+    // get the cpath layer hint for the client.
+    tor_assert(!CIRCUIT_IS_ORIGIN(dest_circ));
+    conflux_process_switch_command(dest_circ, NULL, cell, &rh);
+    break;
+  }
+
+  tor_free(delivery);
+  tor_free(cell);
+  return;
+}
+
+static uint64_t
+mock_monotime_absolute_usec(void)
+{
+  return 100;
+}
+
+static int
+channel_get_addr_if_possible_mock(const channel_t *chan, tor_addr_t *addr_out)
+{
+  (void)chan;
+  tt_int_op(AF_INET,OP_EQ, tor_addr_parse(addr_out, "18.0.0.1"));
+  return 1;
+
+ done:
+  return 0;
+}
+
+static void
+circuit_mark_for_close_mock(circuit_t *circ, int reason,
+                            int line, const char *file)
+{
+  (void)circ;
+  (void)reason;
+  (void)line;
+  (void)file;
+
+  log_info(LD_CIRC, "Marking circuit for close at %s:%d", file, line);
+
+  if (BUG(circ->marked_for_close)) {
+    log_warn(LD_BUG,
+        "Duplicate call to circuit_mark_for_close at %s:%d"
+        " (first at %s:%d)", file, line,
+        circ->marked_for_close_file, circ->marked_for_close);
+    return;
+  }
+
+  circ->marked_for_close = line;
+  circ->marked_for_close_file = file;
+  circ->marked_for_close_reason = reason;
+
+  if (CIRCUIT_IS_CONFLUX(circ)) {
+    conflux_circuit_has_closed(circ);
+  }
+
+  // Mark the other side for close too. No idea if this even improves things;
+  // We might also want to make this go through the cell queue as a destroy
+  if (CIRCUIT_IS_ORIGIN(circ)) {
+    circuit_t *exit_circ = get_exit_circ(circ);
+    if (!exit_circ->marked_for_close)
+      circuit_mark_for_close_mock(get_exit_circ(circ), reason, line, file);
+  } else {
+    circuit_t *client_circ = get_client_circ(circ);
+    if (!client_circ->marked_for_close)
+      circuit_mark_for_close_mock(get_client_circ(circ), reason, line, file);
+  }
+
+  // XXX: Should we do this?
+  //if (circuits_pending_close == NULL)
+  //  circuits_pending_close = smartlist_new();
+  //smartlist_add(circuits_pending_close, circ);
+}
+
+static void
+simulate_single_hop_extend(origin_circuit_t *client, int exit)
+{
+  char whatevs_key[CPATH_KEY_MATERIAL_LEN];
+  char digest[DIGEST_LEN];
+  tor_addr_t addr;
+
+  // Advance time a tiny bit so we can calculate an RTT
+  curr_mocked_time += 10 * TOR_NSEC_PER_MSEC;
+  monotime_coarse_set_mock_time_nsec(curr_mocked_time);
+  monotime_set_mock_time_nsec(curr_mocked_time);
+
+  // Add a hop to cpath
+  crypt_path_t *hop = tor_malloc_zero(sizeof(crypt_path_t));
+  cpath_extend_linked_list(&client->cpath, hop);
+
+  hop->magic = CRYPT_PATH_MAGIC;
+  hop->state = CPATH_STATE_OPEN;
+
+  // add an extend info to indicate if this node supports padding or not.
+  // (set the first byte of the digest for our mocked node_get_by_id)
+  digest[0] = exit;
+
+  hop->extend_info = extend_info_new(
+          exit ? "exit" : "non-exit",
+          digest, NULL, NULL, NULL,
+          &addr, exit, NULL, exit);
+
+  cpath_init_circuit_crypto(hop, whatevs_key, sizeof(whatevs_key), 0, 0);
+
+  hop->package_window = circuit_initial_package_window();
+  hop->deliver_window = CIRCWINDOW_START;
+}
+
+static void
+test_setup(void)
+{
+  int64_t actual_mocked_monotime_start;
+
+  MOCK(circuitmux_attach_circuit, circuitmux_attach_circuit_mock);
+  MOCK(channel_get_addr_if_possible, channel_get_addr_if_possible_mock);
+  MOCK(circuit_establish_circuit_conflux,
+       circuit_establish_circuit_conflux_mock);
+  MOCK(circuit_package_relay_cell,
+       circuit_package_relay_cell_mock);
+  MOCK(circuit_mark_for_close_,
+       circuit_mark_for_close_mock);
+  MOCK(monotime_absolute_usec, mock_monotime_absolute_usec);
+  testing_enable_reproducible_rng();
+
+  monotime_init();
+  monotime_enable_test_mocking();
+  actual_mocked_monotime_start = MONOTIME_MOCK_START;
+  monotime_set_mock_time_nsec(actual_mocked_monotime_start);
+  monotime_coarse_set_mock_time_nsec(actual_mocked_monotime_start);
+  curr_mocked_time = actual_mocked_monotime_start;
+
+  client_circs = smartlist_new();
+  exit_circs = smartlist_new();
+  circ_pairs = smartlist_new();
+  mock_cell_delivery = smartlist_new();
+  dummy_channel.cmux = circuitmux_alloc();
+
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  congestion_control_set_cc_enabled();
+  max_unlinked_leg_retry = UINT32_MAX;
+}
+
+static void
+test_clear_circs(void)
+{
+  SMARTLIST_FOREACH(circ_pairs, circ_pair_t *, circ_pair, {
+    tor_free(circ_pair);
+  });
+  SMARTLIST_FOREACH(client_circs, circuit_t *, client_side, {
+    conflux_circuit_about_to_free(client_side);
+    circuit_free(client_side);
+  });
+  SMARTLIST_FOREACH(exit_circs, or_circuit_t *, relay_side, {
+    free_fake_orcirc(relay_side);
+  });
+
+  smartlist_clear(circ_pairs);
+  smartlist_clear(client_circs);
+  smartlist_clear(exit_circs);
+
+  if (client_streams) {
+    // Free each edge connection
+    SMARTLIST_FOREACH(client_streams, edge_connection_t *, edge_conn, {
+      connection_free_minimal(TO_CONN(edge_conn));
+    });
+    smartlist_free(client_streams);
+  }
+
+  if (exit_streams) {
+    // Free each edge connection
+    SMARTLIST_FOREACH(exit_streams, edge_connection_t *, edge_conn, {
+      connection_free_minimal(TO_CONN(edge_conn));
+    });
+    smartlist_free(exit_streams);
+  }
+
+  tor_assert(smartlist_len(mock_cell_delivery) == 0);
+
+  (void)free_fake_origin_circuit;
+}
+
+static void
+test_teardown(void)
+{
+  conflux_pool_free_all();
+  smartlist_free(client_circs);
+  smartlist_free(exit_circs);
+  smartlist_free(mock_cell_delivery);
+  circuitmux_detach_all_circuits(dummy_channel.cmux, NULL);
+  circuitmux_free(dummy_channel.cmux);
+  testing_disable_reproducible_rng();
+}
+
+/* Test linking a conflux circuit */
+static void
+test_conflux_link(void *arg)
+{
+  (void) arg;
+  test_setup();
+
+  launch_new_set(2);
+
+  // For each circuit in the client_circs list, we need to create an
+  // exit side circuit and simulate two extends
+  SMARTLIST_FOREACH(client_circs, circuit_t *, client_side, {
+    simulate_circuit_build(client_side);
+
+    /* Handle network activity*/
+    while (smartlist_len(mock_cell_delivery) > 0) {
+      process_mock_cell_delivery();
+    }
+  });
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+
+  // Test that the cells have all been delivered
+  tt_int_op(smartlist_len(mock_cell_delivery), OP_EQ, 0);
+
+  // Test that the client side circuits are linked
+  conflux_t *cfx = ((circuit_t*)smartlist_get(client_circs, 0))->conflux;
+  SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) {
+    tt_int_op(client_side->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+    tt_ptr_op(client_side->conflux, OP_EQ, cfx);
+    tt_ptr_op(client_side->conflux_pending_nonce, OP_EQ, NULL);
+  } SMARTLIST_FOREACH_END(client_side);
+
+  // Test circuit teardown
+  SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) {
+    circuit_mark_for_close(client_side, END_CIRC_REASON_FINISHED);
+  } SMARTLIST_FOREACH_END(client_side);
+
+ done:
+  test_clear_circs();
+  test_teardown();
+}
+
+static void
+simulate_circuit_build(circuit_t *client_circ)
+{
+  // Create a relay circuit, and simulate the extend and open
+  circuit_t *relay_side = NULL;
+
+  relay_side = (circuit_t*)new_fake_orcirc(&dummy_channel, &dummy_channel);
+  relay_side->purpose = CIRCUIT_PURPOSE_OR;
+  relay_side->n_chan = NULL; // No next hop
+  relay_side->ccontrol = tor_malloc_zero(sizeof(congestion_control_t));
+  relay_side->ccontrol->sendme_arrival_timestamps = smartlist_new();
+  relay_side->ccontrol->sendme_pending_timestamps = smartlist_new();
+  relay_side->ccontrol->sendme_inc = 31;
+  smartlist_add(exit_circs, relay_side);
+  simulate_circuit_built(client_circ, relay_side);
+  conflux_circuit_has_opened(TO_ORIGIN_CIRCUIT(client_circ));
+}
+
+static circuit_t *
+simulate_close_retry(circuit_t *close, bool manual_launch)
+{
+  // Find the dest pair for the circuit in the circ pair list,
+  // and close it too
+  circuit_t *dest = NULL;
+  uint8_t *nonce = NULL;
+
+  if (manual_launch) {
+    nonce = tor_memdup(close->conflux->nonce, DIGEST256_LEN);
+  }
+
+  SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) {
+    if (pair->client == close) {
+      dest = pair->exit;
+      SMARTLIST_DEL_CURRENT_KEEPORDER(circ_pairs, pair);
+      tor_free(pair);
+    } else if (pair->exit == close) {
+      // This function should not be called on the exit side..
+      tor_assert(0);
+    }
+  } SMARTLIST_FOREACH_END(pair);
+
+  tor_assert(dest);
+  log_info(LD_CIRC, "Simulating close of %p->%p, dest %p->%p",
+           close, close->conflux, dest, dest->conflux);
+
+  // Free all pending cells related to this close in mock_cell_delivery
+  SMARTLIST_FOREACH(mock_cell_delivery, cell_delivery_t *, cd, {
+    if (cd->circ == close || cd->circ == dest) {
+      SMARTLIST_DEL_CURRENT_KEEPORDER(mock_cell_delivery, cd);
+      tor_free(cd->cell);
+      tor_free(cd);
+    }
+  });
+
+  // When a circuit closes, both ends get notification,
+  // and the client will launch a new circuit. We need to find
+  // that circuit at the end of the list, and then simulate
+  // building it, and creating a relay circuit for it.
+  conflux_circuit_has_closed(close);
+  conflux_circuit_has_closed(dest);
+
+  //tor_assert(digest256map_size(get_unlinked_pool(true)) != 0);
+
+  // Find these legs in our circuit lists, and free them
+  tor_assert(CIRCUIT_IS_ORIGIN(close));
+  tor_assert(!CIRCUIT_IS_ORIGIN(dest));
+  SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) {
+    if (client_side == close) {
+      SMARTLIST_DEL_CURRENT_KEEPORDER(client_circs, client_side);
+      conflux_circuit_about_to_free(client_side);
+      circuit_free(client_side);
+    }
+  } SMARTLIST_FOREACH_END(client_side);
+  SMARTLIST_FOREACH_BEGIN(exit_circs, or_circuit_t *, exit_side) {
+    if (exit_side == (or_circuit_t *)dest) {
+      SMARTLIST_DEL_CURRENT_KEEPORDER(exit_circs, exit_side);
+      free_fake_orcirc(exit_side);
+    }
+  } SMARTLIST_FOREACH_END(exit_side);
+
+  if (manual_launch) {
+    // Launch a new leg for this nonce
+    tor_assert(nonce);
+    conflux_launch_leg(nonce);
+    tor_free(nonce);
+  }
+
+  if (smartlist_len(client_circs) == 0) {
+    // No new circuit was launched
+    return NULL;
+  }
+
+  // At this point, a new circuit will have launched on the client
+  // list. Get that circuit from the end of the list and return it
+  circuit_t * circ = smartlist_get(client_circs,
+                        smartlist_len(client_circs) - 1);
+
+  //tor_assert(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_UNLINKED);
+
+  return circ;
+}
+
+static void
+test_retry(void)
+{
+  log_info(LD_CIRC, "==========NEW RUN ===========");
+  launch_new_set(2);
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  circuit_t *client1 = smartlist_get(client_circs, 0);
+  circuit_t *client2 = smartlist_get(client_circs, 1);
+
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  // Dice roll on which leg builds first
+  if (crypto_rand_int(2) == 0) {
+    simulate_circuit_build(client1);
+    simulate_circuit_build(client2);
+  } else {
+    simulate_circuit_build(client2);
+    simulate_circuit_build(client1);
+  }
+
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+    if (crypto_rand_int(2) == 0) {
+      if (crypto_rand_int(2) == 0) {
+        if (client1->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED) {
+          client1 = simulate_close_retry(client1, false);
+          simulate_circuit_build(client1);
+        }
+      } else {
+        if (client2->purpose != CIRCUIT_PURPOSE_CONFLUX_LINKED) {
+          client2 = simulate_close_retry(client2, false);
+          simulate_circuit_build(client2);
+        }
+      }
+    }
+
+    process_mock_cell_delivery();
+  }
+
+  // Test that the cells have all been delivered
+  tt_int_op(smartlist_len(mock_cell_delivery), OP_EQ, 0);
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+  tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+  conflux_t *cfx = ((circuit_t *)smartlist_get(client_circs, 0))->conflux;
+  SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) {
+    tt_int_op(client_side->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+    tt_ptr_op(client_side->conflux, OP_EQ, cfx);
+    tt_ptr_op(client_side->conflux_pending_nonce, OP_EQ, NULL);
+  } SMARTLIST_FOREACH_END(client_side);
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+
+  cfx = ((circuit_t *)smartlist_get(exit_circs, 0))->conflux;
+  SMARTLIST_FOREACH_BEGIN(exit_circs, circuit_t *, exit_side) {
+    tt_ptr_op(exit_side->conflux, OP_EQ, cfx);
+    tt_ptr_op(exit_side->conflux_pending_nonce, OP_EQ, NULL);
+  } SMARTLIST_FOREACH_END(exit_side);
+
+  // Test circuit teardown
+  SMARTLIST_FOREACH_BEGIN(client_circs, circuit_t *, client_side) {
+    tt_int_op(client_side->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+    circuit_mark_for_close(client_side, END_CIRC_REASON_FINISHED);
+  } SMARTLIST_FOREACH_END(client_side);
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0);
+
+  test_clear_circs();
+
+ done:
+   return;
+}
+
+/* Test linking a conflux circuit with build failures */
+static void
+test_conflux_link_retry(void *arg)
+{
+  (void) arg;
+  test_setup();
+
+  for (int i = 0; i < 500; i++) {
+    test_retry();
+  }
+
+  test_teardown();
+}
+
+#if 0
+/* Test closing both circuits in the set before the link handshake completes
+ * on either leg, by closing circuits before process_mock_cell_delivery.
+ *
+ * XXX: This test currently fails because conflux keeps relaunching closed
+ * circuits. We need to set a limit on the number of times we relaunch a
+ * circuit before we can fix this test.
+ */
+static void
+test_conflux_link_fail(void *arg)
+{
+  (void) arg;
+  test_setup();
+
+  launch_new_set(2);
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  circuit_t *client1 = smartlist_get(client_circs, 0);
+  circuit_t *client2 = smartlist_get(client_circs, 1);
+
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  // Close both circuits before the link handshake completes
+  conflux_circuit_has_closed(client1);
+  conflux_circuit_has_closed(client2);
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0);
+ done:
+  test_clear_circs();
+  test_teardown();
+}
+#endif
+
+//  - Relink test:
+//    - More than 2 legs
+//    - Close one linked leg; relink
+//      - Test mismatching sequence numbers for link and data
+//        - This should destroy the whole set
+//    - RTT timeout relinking test
+//    - Three circuits; close 1; retry and link
+static void
+test_conflux_link_relink(void *arg)
+{
+  (void) arg;
+  test_setup();
+
+  launch_new_set(3);
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 3);
+  circuit_t *client1 = smartlist_get(client_circs, 0);
+  circuit_t *client2 = smartlist_get(client_circs, 1);
+  circuit_t *client3 = smartlist_get(client_circs, 2);
+
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  simulate_circuit_build(client1);
+  simulate_circuit_build(client2);
+  simulate_circuit_build(client3);
+
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 3);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 3);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 3);
+
+    process_mock_cell_delivery();
+  }
+
+  // Now test closing and relinking the third leg
+  client3 = simulate_close_retry(client3, true);
+  simulate_circuit_build(client3);
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 3);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 3);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 3);
+
+    process_mock_cell_delivery();
+  }
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+
+  // Now test closing all circuits and verify the conflux object is gone
+  simulate_close_retry(client1, false);
+  simulate_close_retry(client2, false);
+  simulate_close_retry(client3, false);
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0);
+
+ done:
+  test_clear_circs();
+  test_teardown();
+}
+
+#if 0
+static void
+test_conflux_close(void *arg)
+{
+  (void) arg;
+  test_setup();
+
+  launch_new_set(2);
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  circuit_t *client1 = smartlist_get(client_circs, 0);
+  circuit_t *client2 = smartlist_get(client_circs, 1);
+
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  simulate_circuit_build(client1);
+  simulate_circuit_build(client2);
+
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+    process_mock_cell_delivery();
+  }
+
+  // There are actually 3 kinds of close: mark, mark+free,
+  // and purpose change. We need to test these in link_retry, but
+  // here our focus is on after the set is linked.
+
+  // Additionally, we can close on an unlinked leg, or a non-critical linked
+  // leg, or a critical linked leg that causes teardown
+  // And we can close on linked legs when there are unlinked legs, or not.
+
+  // And we can do this at the client, or the exit.
+  // And we can do this with a circuit that has streams, or not.
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 0);
+ done:
+  test_clear_circs();
+  test_teardown();
+}
+#endif
+
+// Test launching a new set and closing the first leg, but
+// with mismatched sequence numbers (missing data)
+//  - Test this teardown with only linked circs, and with some
+//    unlinked circs
+// Test mismatching sequence numbers for link and data:
+// Test missing sent data from client (link cell mismatch):
+// Test missing sent data from relay (linked cell mismatch):
+
+static circuit_t *
+get_exit_circ(circuit_t *client_circ)
+{
+  circuit_t *exit_circ = NULL;
+  SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) {
+    if (pair->client == client_circ) {
+      exit_circ = pair->exit;
+      break;
+    }
+  } SMARTLIST_FOREACH_END(pair);
+  tor_assert(exit_circ);
+  return exit_circ;
+}
+
+static circuit_t *
+get_client_circ(circuit_t *exit_circ)
+{
+  circuit_t *client_circ = NULL;
+  SMARTLIST_FOREACH_BEGIN(circ_pairs, circ_pair_t *, pair) {
+    if (pair->exit == exit_circ) {
+      client_circ = pair->client;
+      break;
+    }
+  } SMARTLIST_FOREACH_END(pair);
+  tor_assert(client_circ);
+  return client_circ;
+}
+
+static edge_connection_t *
+new_client_stream(origin_circuit_t *on_circ)
+{
+  edge_connection_t *stream = edge_connection_new(CONN_TYPE_EXIT, AF_INET);
+
+  stream->stream_id = get_unique_stream_id_by_circ(on_circ);
+  stream->on_circuit = TO_CIRCUIT(on_circ);
+  stream->cpath_layer = on_circ->cpath->prev;
+
+  stream->next_stream = on_circ->p_streams;
+  on_circ->p_streams = stream;
+  conflux_update_p_streams(on_circ, stream);
+
+  smartlist_add(client_streams, stream);
+
+  return stream;
+}
+
+static edge_connection_t *
+new_exit_stream(circuit_t *on_circ, streamid_t stream_id)
+{
+  edge_connection_t *stream = edge_connection_new(CONN_TYPE_EXIT, AF_INET);
+
+  stream->stream_id = stream_id;
+  stream->on_circuit = on_circ;
+
+  stream->next_stream = TO_OR_CIRCUIT(on_circ)->n_streams;
+  conflux_update_n_streams(TO_OR_CIRCUIT(on_circ), stream);
+
+  smartlist_add(exit_streams, stream);
+
+  return stream;
+}
+
+static void
+validate_stream_counts(circuit_t *circ, int expected)
+{
+  int count = 0;
+
+  conflux_validate_stream_lists(circ->conflux);
+
+  if (CIRCUIT_IS_ORIGIN(circ)) {
+    origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
+    tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_CONFLUX_LINKED);
+    /* Iterate over stream list using next_stream pointer, until null */
+    for (edge_connection_t *stream = ocirc->p_streams; stream;
+           stream = stream->next_stream) {
+      count++;
+    }
+  } else {
+    or_circuit_t *orcirc = TO_OR_CIRCUIT(circ);
+    /* Iterate over stream list using next_stream pointer, until null */
+    for (edge_connection_t *stream = orcirc->n_streams; stream;
+           stream = stream->next_stream) {
+      count++;
+    }
+  }
+  tt_int_op(count, OP_EQ, expected);
+
+ done:
+  return;
+}
+
+//  - Streams test
+//    - Attach streams
+//      - Fail one leg, free it, attach new leg, new stream
+//      - Fail both legs
+//    - Shutdown
+//      - With streams attached
+static void
+test_conflux_link_streams(void *arg)
+{
+  (void) arg;
+  test_setup();
+
+  launch_new_set(2);
+
+  client_streams = smartlist_new();
+  exit_streams = smartlist_new();
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  circuit_t *client1 = smartlist_get(client_circs, 0);
+  circuit_t *client2 = smartlist_get(client_circs, 1);
+
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  simulate_circuit_build(client1);
+  simulate_circuit_build(client2);
+
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+    process_mock_cell_delivery();
+  }
+
+  // Attach a stream to the client1 circuit
+  new_client_stream(TO_ORIGIN_CIRCUIT(client1));
+  new_client_stream(TO_ORIGIN_CIRCUIT(client2));
+  new_client_stream(TO_ORIGIN_CIRCUIT(client1));
+  new_exit_stream(get_exit_circ(client2), 1);
+  new_exit_stream(get_exit_circ(client1), 1);
+  new_exit_stream(get_exit_circ(client1), 1);
+
+  // Test that we can close the first leg, and attach a new one
+  // with a new stream
+  client1 = simulate_close_retry(client1, true);
+  simulate_circuit_build(client1);
+
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+    process_mock_cell_delivery();
+  }
+
+  tt_ptr_op(client1->conflux, OP_EQ, client2->conflux);
+
+  new_client_stream(TO_ORIGIN_CIRCUIT(client1));
+  new_exit_stream(get_exit_circ(client2), 1);
+
+  // Use Ensure that there are four streams on each circuit
+  validate_stream_counts(client1, 4);
+  validate_stream_counts(client2, 4);
+  validate_stream_counts(get_exit_circ(client1), 4);
+  validate_stream_counts(get_exit_circ(client2), 4);
+
+  // Test that we can close all streams on either circuit,
+  // in any order
+  circuit_detach_stream(get_exit_circ(client1),
+                        TO_OR_CIRCUIT(get_exit_circ(client1))->n_streams);
+  validate_stream_counts(get_exit_circ(client2), 3);
+  circuit_detach_stream(get_exit_circ(client2),
+               TO_OR_CIRCUIT(get_exit_circ(client2))->n_streams->next_stream);
+  validate_stream_counts(get_exit_circ(client1), 2);
+  circuit_detach_stream(get_exit_circ(client1),
+                        TO_OR_CIRCUIT(get_exit_circ(client1))->n_streams);
+  validate_stream_counts(get_exit_circ(client1), 1);
+  circuit_detach_stream(get_exit_circ(client1),
+                        TO_OR_CIRCUIT(get_exit_circ(client1))->n_streams);
+  validate_stream_counts(get_exit_circ(client1), 0);
+
+  circuit_detach_stream(client1,
+     TO_ORIGIN_CIRCUIT(client1)->p_streams->next_stream->
+                                 next_stream->next_stream);
+  circuit_detach_stream(client2,
+                     TO_ORIGIN_CIRCUIT(client2)->p_streams);
+  circuit_detach_stream(client2,
+   TO_ORIGIN_CIRCUIT(client2)->p_streams->next_stream);
+  circuit_detach_stream(client2,
+   TO_ORIGIN_CIRCUIT(client2)->p_streams);
+  validate_stream_counts(client1, 0);
+  validate_stream_counts(client2, 0);
+
+ done:
+  test_clear_circs();
+  test_teardown();
+}
+
+// Right now this does not involve congestion control.. But it could,
+// if we actually build and send real RELAY_DATA cells (and also handle them
+// and SENDME cells in the mocked cell delivery)
+static void
+send_fake_cell(circuit_t *client_circ)
+{
+  circuit_t *exit_circ = get_exit_circ(client_circ);
+  conflux_leg_t *exit_leg = conflux_get_leg(exit_circ->conflux,
+                                            exit_circ);
+
+  TO_ORIGIN_CIRCUIT(client_circ)->cpath->prev->ccontrol->inflight++;
+  conflux_note_cell_sent(client_circ->conflux, client_circ,
+                          RELAY_COMMAND_DATA);
+
+  exit_leg->last_seq_recv++;
+  exit_circ->conflux->last_seq_delivered++;
+}
+
+static circuit_t *
+send_until_switch(circuit_t *client_circ)
+{
+  conflux_leg_t *client_leg = conflux_get_leg(client_circ->conflux,
+                                              client_circ);
+  circuit_t *exit_circ = get_exit_circ(client_circ);
+  conflux_leg_t *exit_leg = conflux_get_leg(exit_circ->conflux,
+                                            exit_circ);
+  circuit_t *next_circ = client_circ;
+  int i = 0;
+
+  // XXX: This is a hack so the tests pass using cc->sendme_inc
+  // (There is another hack in circuit_ready_to_send() that causes
+  // us to block early below, and return NULL for next_circ)
+  TO_ORIGIN_CIRCUIT(client_circ)->cpath->prev->ccontrol->sendme_inc = 0;
+
+  while (client_circ == next_circ) {
+    next_circ = conflux_decide_circ_for_send(client_circ->conflux, client_circ,
+                                             RELAY_COMMAND_DATA);
+    tor_assert(next_circ);
+    send_fake_cell(next_circ);
+    i++;
+  }
+
+  // XXX: This too:
+  TO_ORIGIN_CIRCUIT(client_circ)->cpath->prev->ccontrol->sendme_inc = 31;
+
+  log_info(LD_CIRC, "Sent %d cells on client circ", i-1);
+  process_mock_cell_delivery();
+
+  circuit_t *new_client =
+    (circuit_t*)conflux_decide_next_circ(client_circ->conflux);
+  tt_ptr_op(new_client, OP_NE, client_circ);
+  conflux_leg_t *new_client_leg = conflux_get_leg(new_client->conflux,
+                                                  new_client);
+  circuit_t *new_exit = get_exit_circ(new_client);
+  conflux_leg_t *new_exit_leg = conflux_get_leg(new_exit->conflux,
+                                                new_exit);
+
+  // Verify sequence numbers make sense
+  tt_int_op(new_client_leg->last_seq_sent, OP_EQ, client_leg->last_seq_sent+1);
+  tt_int_op(new_client_leg->last_seq_recv, OP_EQ, client_leg->last_seq_recv);
+  tt_int_op(exit_leg->last_seq_sent, OP_EQ, new_exit_leg->last_seq_sent);
+  tt_int_op(exit_leg->last_seq_recv+1, OP_EQ, new_exit_leg->last_seq_recv);
+
+  tt_int_op(client_leg->last_seq_sent+1, OP_EQ, new_exit_leg->last_seq_recv);
+  tt_int_op(client_leg->last_seq_recv, OP_EQ, new_exit_leg->last_seq_sent);
+
+ done:
+  return new_client;
+}
+
+/**
+ * This tests switching as well as the UDP optimization that attaches
+ * a third circuit and closes the slowest one. (This optimization is not
+ * implemented in C-Tor but must be supported at exits, for arti).
+ */
+static void
+test_conflux_switch(void *arg)
+{
+   (void) arg;
+  test_setup();
+
+  launch_new_set(2);
+
+  tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+  circuit_t *client1 = smartlist_get(client_circs, 0);
+  circuit_t *client2 = smartlist_get(client_circs, 1);
+  get_circuit_build_times_mutable()->timeout_ms = 1000;
+
+  simulate_circuit_build(client1);
+  simulate_circuit_build(client2);
+
+  circuit_t *exit1 = get_exit_circ(client1);
+  circuit_t *exit2 = get_exit_circ(client2);
+  circuit_t *next_circ = client1;
+
+  while (smartlist_len(mock_cell_delivery) > 0) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+    process_mock_cell_delivery();
+  }
+
+  // Check to make sure everything is linked`up
+  tt_ptr_op(client1->conflux, OP_EQ, client2->conflux);
+  tt_ptr_op(exit1->conflux, OP_EQ, exit2->conflux);
+  tt_ptr_op(client1->conflux, OP_NE, NULL);
+  tt_ptr_op(exit1->conflux, OP_NE, NULL);
+  tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 2);
+  tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 2);
+  tt_int_op(client1->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+  tt_int_op(client2->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+  tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+  tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+  tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+  tt_ptr_op(get_exit_circ(client1), OP_EQ, exit1);
+  tt_ptr_op(get_exit_circ(client2), OP_EQ, exit2);
+
+  // Give circuits approximately equal RTT:
+  conflux_update_rtt(client1->conflux, client1, 100);
+  conflux_update_rtt(client2->conflux, client2, 125);
+
+  client1->conflux->params.alg = CONFLUX_ALG_LOWRTT;
+  get_exit_circ(client1)->conflux->params.alg = CONFLUX_ALG_LOWRTT;
+  TO_ORIGIN_CIRCUIT(client1)->cpath->prev->ccontrol->cwnd = 300;
+  TO_ORIGIN_CIRCUIT(client2)->cpath->prev->ccontrol->cwnd = 300;
+
+  // Keep sending fake cells until we decide to switch four times
+  for (int i = 0; i < 4; i++) {
+    next_circ = send_until_switch(next_circ);
+
+    // XXX: This can't be set to 0 or we will decide we can switch immediately,
+    // because the client1 has a lower RTT
+    TO_ORIGIN_CIRCUIT(client1)->cpath->prev->ccontrol->inflight = 1;
+
+    // Check to make sure everything is linked`up
+    tt_ptr_op(client1->conflux, OP_EQ, client2->conflux);
+    tt_ptr_op(exit1->conflux, OP_EQ, exit2->conflux);
+    tt_ptr_op(client1->conflux, OP_NE, NULL);
+    tt_ptr_op(exit1->conflux, OP_NE, NULL);
+    tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 2);
+    tt_int_op(client1->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+    tt_int_op(client2->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+    tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+    tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+
+    tt_ptr_op(get_exit_circ(client1), OP_EQ, exit1);
+    tt_ptr_op(get_exit_circ(client2), OP_EQ, exit2);
+    tt_ptr_op(next_circ, OP_EQ, client2);
+
+    next_circ = send_until_switch(next_circ);
+
+    // Check to make sure everything is linked`up
+    tt_ptr_op(client1->conflux, OP_EQ, client2->conflux);
+    tt_ptr_op(exit1->conflux, OP_EQ, exit2->conflux);
+    tt_ptr_op(client1->conflux, OP_NE, NULL);
+    tt_ptr_op(exit1->conflux, OP_NE, NULL);
+    tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 2);
+    tt_int_op(client1->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+    tt_int_op(client2->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+    tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+    tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+
+    tt_ptr_op(get_exit_circ(client1), OP_EQ, exit1);
+    tt_ptr_op(get_exit_circ(client2), OP_EQ, exit2);
+    tt_ptr_op(next_circ, OP_EQ, client1);
+
+    TO_ORIGIN_CIRCUIT(client2)->cpath->prev->ccontrol->inflight = 0;
+  }
+
+  // Try the UDP minRTT reconnect optimization a few times
+  for (int i = 0; i < 500; i++) {
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    client1 = smartlist_get(client_circs, 0);
+    client2 = smartlist_get(client_circs, 1);
+    exit1 = get_exit_circ(client1);
+    exit2 = get_exit_circ(client2);
+
+    // Attach a third leg
+    conflux_launch_leg(client1->conflux->nonce);
+
+    // It should be added to the end of the local test list
+    circuit_t *client3 = smartlist_get(client_circs,
+                                       smartlist_len(client_circs)-1);
+    simulate_circuit_build(client3);
+
+    while (smartlist_len(mock_cell_delivery) > 0) {
+      tt_int_op(smartlist_len(client_circs), OP_EQ, 3);
+      tt_int_op(smartlist_len(exit_circs), OP_EQ, 3);
+      tt_int_op(smartlist_len(circ_pairs), OP_EQ, 3);
+
+      process_mock_cell_delivery();
+    }
+
+    circuit_t *exit3 = get_exit_circ(client3);
+
+    // Check to make sure everything is linked`up
+    tt_ptr_op(client3->conflux, OP_EQ, client2->conflux);
+    tt_ptr_op(exit3->conflux, OP_EQ, exit2->conflux);
+    tt_ptr_op(client3->conflux, OP_NE, NULL);
+    tt_ptr_op(exit3->conflux, OP_NE, NULL);
+    tt_int_op(smartlist_len(client1->conflux->legs), OP_EQ, 3);
+    tt_int_op(smartlist_len(exit1->conflux->legs), OP_EQ, 3);
+    tt_int_op(client3->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+    tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+    tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+
+    conflux_update_rtt(client3->conflux, client3,
+                       crypto_rand_int_range(90, 200));
+    TO_ORIGIN_CIRCUIT(client3)->cpath->prev->ccontrol->cwnd = 300;
+
+    circuit_t *circ_close = NULL;
+    uint64_t max_rtt = 0;
+    // Pick the leg with the highest RTT and close it
+    tor_assert(client3);
+    tor_assert(client3->conflux);
+    tor_assert(client3->conflux->legs);
+    CONFLUX_FOR_EACH_LEG_BEGIN(client3->conflux, leg) {
+      if (client3->conflux->curr_leg == leg)
+        continue;
+
+      if (leg->circ_rtts_usec > max_rtt) {
+        max_rtt = leg->circ_rtts_usec;
+        circ_close = leg->circ;
+      }
+    } CONFLUX_FOR_EACH_LEG_END(leg);
+
+    // Let the second leg "send" all data and close it.
+    tor_assert(circ_close);
+    tor_assert(circ_close->conflux);
+    tor_assert(circ_close->conflux->legs);
+    CONFLUX_FOR_EACH_LEG_BEGIN(circ_close->conflux, leg) {
+      TO_ORIGIN_CIRCUIT(leg->circ)->cpath->prev->ccontrol->inflight = 0;
+    } CONFLUX_FOR_EACH_LEG_END(leg);
+
+    // Close without manual launch (code will not relaunch for linked)
+    simulate_close_retry(circ_close, false);
+
+    tt_int_op(smartlist_len(mock_cell_delivery), OP_EQ, 0);
+    tt_int_op(smartlist_len(client_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(exit_circs), OP_EQ, 2);
+    tt_int_op(smartlist_len(circ_pairs), OP_EQ, 2);
+
+    // Send until we switch to the third leg
+    next_circ = send_until_switch(next_circ);
+
+    // Check to make sure everything is linked`up
+    tt_ptr_op(next_circ->conflux, OP_NE, NULL);
+    tt_int_op(smartlist_len(next_circ->conflux->legs), OP_EQ, 2);
+    tt_int_op(next_circ->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+    tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+    tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+
+    CONFLUX_FOR_EACH_LEG_BEGIN(next_circ->conflux, leg) {
+      TO_ORIGIN_CIRCUIT(leg->circ)->cpath->prev->ccontrol->inflight = 0;
+    } CONFLUX_FOR_EACH_LEG_END(leg);
+
+    // send until we switch back to the first leg
+    next_circ = send_until_switch(next_circ);
+
+    // Check to make sure everything is linked`up
+    tt_ptr_op(next_circ->conflux, OP_NE, NULL);
+    tt_int_op(smartlist_len(next_circ->conflux->legs), OP_EQ, 2);
+    tt_int_op(next_circ->purpose, OP_EQ, CIRCUIT_PURPOSE_CONFLUX_LINKED);
+
+    tt_int_op(digest256map_size(get_linked_pool(true)), OP_EQ, 1);
+    tt_int_op(digest256map_size(get_unlinked_pool(true)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_unlinked_pool(false)), OP_EQ, 0);
+    tt_int_op(digest256map_size(get_linked_pool(false)), OP_EQ, 1);
+  }
+
+ done:
+  test_clear_circs();
+  test_teardown();
+  return;
+ }
+
+struct testcase_t conflux_pool_tests[] = {
+  { "link", test_conflux_link, TT_FORK, NULL, NULL },
+  { "link_retry", test_conflux_link_retry, TT_FORK, NULL, NULL },
+  { "link_relink", test_conflux_link_relink, TT_FORK, NULL, NULL },
+  { "link_streams", test_conflux_link_streams, TT_FORK, NULL, NULL },
+  { "switch", test_conflux_switch, TT_FORK, NULL, NULL },
+  // XXX: These two currently fail, because they are not finished:
+  //{ "link_fail", test_conflux_link_fail, TT_FORK, NULL, NULL },
+  //{ "close", test_conflux_close, TT_FORK, NULL, NULL },
+  END_OF_TESTCASES
+};

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits