mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2025-02-26 07:31:33 +01:00
The channel_write_cell() and channel_write_var_cell() can't be possibly called nor are used by tor. We only write on the connection outbuf packed cell coming from the scheduler that takes them from the circuit queue. This makes channel_write_packed_cell() the only usable function. It is simplify and now returns a code value. The reason for this is that in the next commit(s), we'll re-queue the cell onto the circuit queue if the write fails. Finally, channel unit tests are being removed with this commit because they do not match the new semantic. They will be re-written in future commits. Signed-off-by: David Goulet <dgoulet@torproject.org>
1012 lines
25 KiB
C
1012 lines
25 KiB
C
/* Copyright (c) 2013-2017, The Tor Project, Inc. */
|
|
/* See LICENSE for licensing information */
|
|
|
|
#define TOR_CHANNEL_INTERNAL_
|
|
#define CHANNEL_PRIVATE_
|
|
#include "or.h"
|
|
#include "channel.h"
|
|
/* For channel_note_destroy_not_pending */
|
|
#include "circuitlist.h"
|
|
#include "circuitmux.h"
|
|
/* For var_cell_free */
|
|
#include "connection_or.h"
|
|
/* For packed_cell stuff */
|
|
#define RELAY_PRIVATE
|
|
#include "relay.h"
|
|
/* For init/free stuff */
|
|
#include "scheduler.h"
|
|
|
|
/* Test suite stuff */
|
|
#include "test.h"
|
|
#include "fakechans.h"
|
|
|
|
static int test_chan_accept_cells = 0;
|
|
static int test_chan_fixed_cells_recved = 0;
|
|
static cell_t * test_chan_last_seen_fixed_cell_ptr = NULL;
|
|
static int test_chan_var_cells_recved = 0;
|
|
static var_cell_t * test_chan_last_seen_var_cell_ptr = NULL;
|
|
static int test_cells_written = 0;
|
|
static int test_doesnt_want_writes_count = 0;
|
|
static int test_dumpstats_calls = 0;
|
|
static int test_has_waiting_cells_count = 0;
|
|
static double test_overhead_estimate = 1.0;
|
|
static int test_releases_count = 0;
|
|
static channel_t *dump_statistics_mock_target = NULL;
|
|
static int dump_statistics_mock_matches = 0;
|
|
|
|
static void chan_test_channel_dump_statistics_mock(
|
|
channel_t *chan, int severity);
|
|
static void chan_test_cell_handler(channel_t *ch,
|
|
cell_t *cell);
|
|
static const char * chan_test_describe_transport(channel_t *ch);
|
|
static void chan_test_dumpstats(channel_t *ch, int severity);
|
|
static void chan_test_var_cell_handler(channel_t *ch,
|
|
var_cell_t *var_cell);
|
|
static void chan_test_close(channel_t *ch);
|
|
static void chan_test_error(channel_t *ch);
|
|
static void chan_test_finish_close(channel_t *ch);
|
|
static const char * chan_test_get_remote_descr(channel_t *ch, int flags);
|
|
static int chan_test_is_canonical(channel_t *ch, int req);
|
|
static size_t chan_test_num_bytes_queued(channel_t *ch);
|
|
static int chan_test_num_cells_writeable(channel_t *ch);
|
|
static int chan_test_write_cell(channel_t *ch, cell_t *cell);
|
|
static int chan_test_write_packed_cell(channel_t *ch,
|
|
packed_cell_t *packed_cell);
|
|
static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell);
|
|
static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch);
|
|
|
|
static void test_channel_dumpstats(void *arg);
|
|
static void test_channel_incoming(void *arg);
|
|
static void test_channel_lifecycle(void *arg);
|
|
|
|
static const char *
|
|
chan_test_describe_transport(channel_t *ch)
|
|
{
|
|
tt_ptr_op(ch, OP_NE, NULL);
|
|
|
|
done:
|
|
return "Fake channel for unit tests";
|
|
}
|
|
|
|
/**
|
|
* Mock for channel_dump_statistics(); if the channel matches the
|
|
* target, bump a counter - otherwise ignore.
|
|
*/
|
|
|
|
static void
|
|
chan_test_channel_dump_statistics_mock(channel_t *chan, int severity)
|
|
{
|
|
tt_ptr_op(chan, OP_NE, NULL);
|
|
|
|
(void)severity;
|
|
|
|
if (chan != NULL && chan == dump_statistics_mock_target) {
|
|
++dump_statistics_mock_matches;
|
|
}
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Handle an incoming fixed-size cell for unit tests
|
|
*/
|
|
|
|
static void
|
|
chan_test_cell_handler(channel_t *ch,
|
|
cell_t *cell)
|
|
{
|
|
tt_assert(ch);
|
|
tt_assert(cell);
|
|
|
|
test_chan_last_seen_fixed_cell_ptr = cell;
|
|
++test_chan_fixed_cells_recved;
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Fake transport-specific stats call
|
|
*/
|
|
|
|
static void
|
|
chan_test_dumpstats(channel_t *ch, int severity)
|
|
{
|
|
tt_ptr_op(ch, OP_NE, NULL);
|
|
|
|
(void)severity;
|
|
|
|
++test_dumpstats_calls;
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Handle an incoming variable-size cell for unit tests
|
|
*/
|
|
|
|
static void
|
|
chan_test_var_cell_handler(channel_t *ch,
|
|
var_cell_t *var_cell)
|
|
{
|
|
tt_assert(ch);
|
|
tt_assert(var_cell);
|
|
|
|
test_chan_last_seen_var_cell_ptr = var_cell;
|
|
++test_chan_var_cells_recved;
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
static void
|
|
chan_test_close(channel_t *ch)
|
|
{
|
|
tt_assert(ch);
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Close a channel through the error path
|
|
*/
|
|
|
|
static void
|
|
chan_test_error(channel_t *ch)
|
|
{
|
|
tt_assert(ch);
|
|
tt_assert(!(ch->state == CHANNEL_STATE_CLOSING ||
|
|
ch->state == CHANNEL_STATE_ERROR ||
|
|
ch->state == CHANNEL_STATE_CLOSED));
|
|
|
|
channel_close_for_error(ch);
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Finish closing a channel from CHANNEL_STATE_CLOSING
|
|
*/
|
|
|
|
static void
|
|
chan_test_finish_close(channel_t *ch)
|
|
{
|
|
tt_assert(ch);
|
|
tt_assert(ch->state == CHANNEL_STATE_CLOSING);
|
|
|
|
channel_closed(ch);
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
static const char *
|
|
chan_test_get_remote_descr(channel_t *ch, int flags)
|
|
{
|
|
tt_assert(ch);
|
|
tt_int_op(flags & ~(GRD_FLAG_ORIGINAL | GRD_FLAG_ADDR_ONLY), OP_EQ, 0);
|
|
|
|
done:
|
|
return "Fake channel for unit tests; no real endpoint";
|
|
}
|
|
|
|
static double
|
|
chan_test_get_overhead_estimate(channel_t *ch)
|
|
{
|
|
tt_assert(ch);
|
|
|
|
done:
|
|
return test_overhead_estimate;
|
|
}
|
|
|
|
static int
|
|
chan_test_is_canonical(channel_t *ch, int req)
|
|
{
|
|
tt_ptr_op(ch, OP_NE, NULL);
|
|
tt_assert(req == 0 || req == 1);
|
|
|
|
done:
|
|
/* Fake channels are always canonical */
|
|
return 1;
|
|
}
|
|
|
|
static size_t
|
|
chan_test_num_bytes_queued(channel_t *ch)
|
|
{
|
|
tt_assert(ch);
|
|
|
|
done:
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
chan_test_num_cells_writeable(channel_t *ch)
|
|
{
|
|
tt_assert(ch);
|
|
|
|
done:
|
|
return 32;
|
|
}
|
|
|
|
static int
|
|
chan_test_write_cell(channel_t *ch, cell_t *cell)
|
|
{
|
|
int rv = 0;
|
|
|
|
tt_assert(ch);
|
|
tt_assert(cell);
|
|
|
|
if (test_chan_accept_cells) {
|
|
/* Free the cell and bump the counter */
|
|
tor_free(cell);
|
|
++test_cells_written;
|
|
rv = 1;
|
|
}
|
|
/* else return 0, we didn't accept it */
|
|
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
chan_test_write_packed_cell(channel_t *ch,
|
|
packed_cell_t *packed_cell)
|
|
{
|
|
int rv = 0;
|
|
|
|
tt_assert(ch);
|
|
tt_assert(packed_cell);
|
|
|
|
if (test_chan_accept_cells) {
|
|
/* Free the cell and bump the counter */
|
|
packed_cell_free(packed_cell);
|
|
++test_cells_written;
|
|
rv = 1;
|
|
}
|
|
/* else return 0, we didn't accept it */
|
|
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell)
|
|
{
|
|
int rv = 0;
|
|
|
|
tt_assert(ch);
|
|
tt_assert(var_cell);
|
|
|
|
if (test_chan_accept_cells) {
|
|
/* Free the cell and bump the counter */
|
|
var_cell_free(var_cell);
|
|
++test_cells_written;
|
|
rv = 1;
|
|
}
|
|
/* else return 0, we didn't accept it */
|
|
|
|
done:
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Fill out c with a new fake cell for test suite use
|
|
*/
|
|
|
|
void
|
|
make_fake_cell(cell_t *c)
|
|
{
|
|
tt_ptr_op(c, OP_NE, NULL);
|
|
|
|
c->circ_id = 1;
|
|
c->command = CELL_RELAY;
|
|
memset(c->payload, 0, CELL_PAYLOAD_SIZE);
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Fill out c with a new fake var_cell for test suite use
|
|
*/
|
|
|
|
void
|
|
make_fake_var_cell(var_cell_t *c)
|
|
{
|
|
tt_ptr_op(c, OP_NE, NULL);
|
|
|
|
c->circ_id = 1;
|
|
c->command = CELL_VERSIONS;
|
|
c->payload_len = CELL_PAYLOAD_SIZE / 2;
|
|
memset(c->payload, 0, c->payload_len);
|
|
|
|
done:
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Set up a new fake channel for the test suite
|
|
*/
|
|
|
|
channel_t *
|
|
new_fake_channel(void)
|
|
{
|
|
channel_t *chan = tor_malloc_zero(sizeof(channel_t));
|
|
channel_init(chan);
|
|
|
|
chan->close = chan_test_close;
|
|
chan->get_overhead_estimate = chan_test_get_overhead_estimate;
|
|
chan->get_remote_descr = chan_test_get_remote_descr;
|
|
chan->num_bytes_queued = chan_test_num_bytes_queued;
|
|
chan->num_cells_writeable = chan_test_num_cells_writeable;
|
|
chan->write_cell = chan_test_write_cell;
|
|
chan->write_packed_cell = chan_test_write_packed_cell;
|
|
chan->write_var_cell = chan_test_write_var_cell;
|
|
chan->state = CHANNEL_STATE_OPEN;
|
|
|
|
return chan;
|
|
}
|
|
|
|
void
|
|
free_fake_channel(channel_t *chan)
|
|
{
|
|
if (! chan)
|
|
return;
|
|
|
|
if (chan->cmux)
|
|
circuitmux_free(chan->cmux);
|
|
|
|
tor_free(chan);
|
|
}
|
|
|
|
/**
|
|
* Counter query for scheduler_channel_has_waiting_cells_mock()
|
|
*/
|
|
|
|
int
|
|
get_mock_scheduler_has_waiting_cells_count(void)
|
|
{
|
|
return test_has_waiting_cells_count;
|
|
}
|
|
|
|
/**
|
|
* Mock for scheduler_channel_has_waiting_cells()
|
|
*/
|
|
|
|
void
|
|
scheduler_channel_has_waiting_cells_mock(channel_t *ch)
|
|
{
|
|
(void)ch;
|
|
|
|
/* Increment counter */
|
|
++test_has_waiting_cells_count;
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
scheduler_channel_doesnt_want_writes_mock(channel_t *ch)
|
|
{
|
|
(void)ch;
|
|
|
|
/* Increment counter */
|
|
++test_doesnt_want_writes_count;
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Counter query for scheduler_release_channel_mock()
|
|
*/
|
|
|
|
int
|
|
get_mock_scheduler_release_channel_count(void)
|
|
{
|
|
return test_releases_count;
|
|
}
|
|
|
|
/**
|
|
* Mock for scheduler_release_channel()
|
|
*/
|
|
|
|
void
|
|
scheduler_release_channel_mock(channel_t *ch)
|
|
{
|
|
(void)ch;
|
|
|
|
/* Increment counter */
|
|
++test_releases_count;
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Test for channel_dumpstats() and limited test for
|
|
* channel_dump_statistics()
|
|
*/
|
|
|
|
static void
|
|
test_channel_dumpstats(void *arg)
|
|
{
|
|
channel_t *ch = NULL;
|
|
packed_cell_t *p_cell = NULL;
|
|
int old_count;
|
|
|
|
(void)arg;
|
|
|
|
/* Mock these for duration of the test */
|
|
MOCK(scheduler_channel_doesnt_want_writes,
|
|
scheduler_channel_doesnt_want_writes_mock);
|
|
MOCK(scheduler_release_channel,
|
|
scheduler_release_channel_mock);
|
|
|
|
/* Set up a new fake channel */
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
ch->cmux = circuitmux_alloc();
|
|
|
|
/* Try to register it */
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Set up mock */
|
|
dump_statistics_mock_target = ch;
|
|
dump_statistics_mock_matches = 0;
|
|
MOCK(channel_dump_statistics,
|
|
chan_test_channel_dump_statistics_mock);
|
|
|
|
/* Call channel_dumpstats() */
|
|
channel_dumpstats(LOG_DEBUG);
|
|
|
|
/* Assert that we hit the mock */
|
|
tt_int_op(dump_statistics_mock_matches, OP_EQ, 1);
|
|
|
|
/* Close the channel */
|
|
channel_mark_for_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED);
|
|
|
|
/* Try again and hit the finished channel */
|
|
channel_dumpstats(LOG_DEBUG);
|
|
tt_int_op(dump_statistics_mock_matches, OP_EQ, 2);
|
|
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
/* Now we should hit nothing */
|
|
channel_dumpstats(LOG_DEBUG);
|
|
tt_int_op(dump_statistics_mock_matches, OP_EQ, 2);
|
|
|
|
/* Unmock */
|
|
UNMOCK(channel_dump_statistics);
|
|
dump_statistics_mock_target = NULL;
|
|
dump_statistics_mock_matches = 0;
|
|
|
|
/* Now make another channel */
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
ch->cmux = circuitmux_alloc();
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
/* Lie about its age so dumpstats gets coverage for rate calculations */
|
|
ch->timestamp_created = time(NULL) - 30;
|
|
tt_assert(ch->timestamp_created > 0);
|
|
tt_assert(time(NULL) > ch->timestamp_created);
|
|
|
|
/* Put cells through it both ways to make the counters non-zero */
|
|
p_cell = packed_cell_new();
|
|
test_chan_accept_cells = 1;
|
|
old_count = test_cells_written;
|
|
channel_write_packed_cell(ch, p_cell);
|
|
tt_int_op(test_cells_written, OP_EQ, old_count + 1);
|
|
tt_assert(ch->n_bytes_xmitted > 0);
|
|
tt_assert(ch->n_cells_xmitted > 0);
|
|
|
|
/* Receive path */
|
|
channel_set_cell_handlers(ch,
|
|
chan_test_cell_handler,
|
|
chan_test_var_cell_handler);
|
|
tt_ptr_op(channel_get_cell_handler(ch), OP_EQ, chan_test_cell_handler);
|
|
tt_ptr_op(channel_get_var_cell_handler(ch), OP_EQ,
|
|
chan_test_var_cell_handler);
|
|
p_cell = packed_cell_new();
|
|
old_count = test_chan_fixed_cells_recved;
|
|
tt_int_op(test_chan_fixed_cells_recved, OP_EQ, old_count + 1);
|
|
tt_assert(ch->n_bytes_recved > 0);
|
|
tt_assert(ch->n_cells_recved > 0);
|
|
|
|
/* Test channel_dump_statistics */
|
|
ch->describe_transport = chan_test_describe_transport;
|
|
ch->dumpstats = chan_test_dumpstats;
|
|
ch->is_canonical = chan_test_is_canonical;
|
|
old_count = test_dumpstats_calls;
|
|
channel_dump_statistics(ch, LOG_DEBUG);
|
|
tt_int_op(test_dumpstats_calls, OP_EQ, old_count + 1);
|
|
|
|
/* Close the channel */
|
|
channel_mark_for_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
done:
|
|
free_fake_channel(ch);
|
|
|
|
UNMOCK(scheduler_channel_doesnt_want_writes);
|
|
UNMOCK(scheduler_release_channel);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
test_channel_incoming(void *arg)
|
|
{
|
|
channel_t *ch = NULL;
|
|
cell_t *cell = NULL;
|
|
var_cell_t *var_cell = NULL;
|
|
int old_count;
|
|
|
|
(void)arg;
|
|
|
|
/* Mock these for duration of the test */
|
|
MOCK(scheduler_channel_doesnt_want_writes,
|
|
scheduler_channel_doesnt_want_writes_mock);
|
|
MOCK(scheduler_release_channel,
|
|
scheduler_release_channel_mock);
|
|
|
|
/* Accept cells to lower layer */
|
|
test_chan_accept_cells = 1;
|
|
/* Use default overhead factor */
|
|
test_overhead_estimate = 1.0;
|
|
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
/* Start it off in OPENING */
|
|
ch->state = CHANNEL_STATE_OPENING;
|
|
/* We'll need a cmux */
|
|
ch->cmux = circuitmux_alloc();
|
|
|
|
/* Install incoming cell handlers */
|
|
channel_set_cell_handlers(ch,
|
|
chan_test_cell_handler,
|
|
chan_test_var_cell_handler);
|
|
/* Test cell handler getters */
|
|
tt_ptr_op(channel_get_cell_handler(ch), OP_EQ, chan_test_cell_handler);
|
|
tt_ptr_op(channel_get_var_cell_handler(ch), OP_EQ,
|
|
chan_test_var_cell_handler);
|
|
|
|
/* Try to register it */
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Open it */
|
|
channel_change_state_open(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN);
|
|
|
|
/* Receive a fixed cell */
|
|
cell = tor_malloc_zero(sizeof(cell_t));
|
|
make_fake_cell(cell);
|
|
old_count = test_chan_fixed_cells_recved;
|
|
tor_free(cell);
|
|
tt_int_op(test_chan_fixed_cells_recved, OP_EQ, old_count + 1);
|
|
|
|
/* Receive a variable-size cell */
|
|
var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE);
|
|
make_fake_var_cell(var_cell);
|
|
old_count = test_chan_var_cells_recved;
|
|
tor_free(cell);
|
|
tt_int_op(test_chan_var_cells_recved, OP_EQ, old_count + 1);
|
|
|
|
/* Close it */
|
|
channel_mark_for_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
done:
|
|
free_fake_channel(ch);
|
|
tor_free(cell);
|
|
tor_free(var_cell);
|
|
|
|
UNMOCK(scheduler_channel_doesnt_want_writes);
|
|
UNMOCK(scheduler_release_channel);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Normal channel lifecycle test:
|
|
*
|
|
* OPENING->OPEN->MAINT->OPEN->CLOSING->CLOSED
|
|
*/
|
|
|
|
static void
|
|
test_channel_lifecycle(void *arg)
|
|
{
|
|
channel_t *ch1 = NULL, *ch2 = NULL;
|
|
packed_cell_t *p_cell = NULL;
|
|
int old_count, init_doesnt_want_writes_count;
|
|
int init_releases_count;
|
|
|
|
(void)arg;
|
|
|
|
/* Mock these for the whole lifecycle test */
|
|
MOCK(scheduler_channel_doesnt_want_writes,
|
|
scheduler_channel_doesnt_want_writes_mock);
|
|
MOCK(scheduler_release_channel,
|
|
scheduler_release_channel_mock);
|
|
|
|
/* Cache some initial counter values */
|
|
init_doesnt_want_writes_count = test_doesnt_want_writes_count;
|
|
init_releases_count = test_releases_count;
|
|
|
|
/* Accept cells to lower layer */
|
|
test_chan_accept_cells = 1;
|
|
/* Use default overhead factor */
|
|
test_overhead_estimate = 1.0;
|
|
|
|
ch1 = new_fake_channel();
|
|
tt_assert(ch1);
|
|
/* Start it off in OPENING */
|
|
ch1->state = CHANNEL_STATE_OPENING;
|
|
/* We'll need a cmux */
|
|
ch1->cmux = circuitmux_alloc();
|
|
|
|
/* Try to register it */
|
|
channel_register(ch1);
|
|
tt_assert(ch1->registered);
|
|
|
|
/* Try to write a cell through (should queue) */
|
|
p_cell = packed_cell_new();
|
|
old_count = test_cells_written;
|
|
channel_write_packed_cell(ch1, p_cell);
|
|
tt_int_op(old_count, OP_EQ, test_cells_written);
|
|
|
|
/* Move it to OPEN and flush */
|
|
channel_change_state_open(ch1);
|
|
|
|
/* Queue should drain */
|
|
tt_int_op(old_count + 1, OP_EQ, test_cells_written);
|
|
|
|
/* Get another one */
|
|
ch2 = new_fake_channel();
|
|
tt_assert(ch2);
|
|
ch2->state = CHANNEL_STATE_OPENING;
|
|
ch2->cmux = circuitmux_alloc();
|
|
|
|
/* Register */
|
|
channel_register(ch2);
|
|
tt_assert(ch2->registered);
|
|
|
|
/* Check counters */
|
|
tt_int_op(test_doesnt_want_writes_count, OP_EQ,
|
|
init_doesnt_want_writes_count);
|
|
tt_int_op(test_releases_count, OP_EQ, init_releases_count);
|
|
|
|
/* Move ch1 to MAINT */
|
|
channel_change_state(ch1, CHANNEL_STATE_MAINT);
|
|
tt_int_op(test_doesnt_want_writes_count, OP_EQ,
|
|
init_doesnt_want_writes_count + 1);
|
|
tt_int_op(test_releases_count, OP_EQ, init_releases_count);
|
|
|
|
/* Move ch2 to OPEN */
|
|
channel_change_state_open(ch2);
|
|
tt_int_op(test_doesnt_want_writes_count, OP_EQ,
|
|
init_doesnt_want_writes_count + 1);
|
|
tt_int_op(test_releases_count, OP_EQ, init_releases_count);
|
|
|
|
/* Move ch1 back to OPEN */
|
|
channel_change_state_open(ch1);
|
|
tt_int_op(test_doesnt_want_writes_count, OP_EQ,
|
|
init_doesnt_want_writes_count + 1);
|
|
tt_int_op(test_releases_count, OP_EQ, init_releases_count);
|
|
|
|
/* Mark ch2 for close */
|
|
channel_mark_for_close(ch2);
|
|
tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
tt_int_op(test_doesnt_want_writes_count, OP_EQ,
|
|
init_doesnt_want_writes_count + 1);
|
|
tt_int_op(test_releases_count, OP_EQ, init_releases_count + 1);
|
|
|
|
/* Shut down channels */
|
|
channel_free_all();
|
|
ch1 = ch2 = NULL;
|
|
tt_int_op(test_doesnt_want_writes_count, OP_EQ,
|
|
init_doesnt_want_writes_count + 1);
|
|
/* channel_free() calls scheduler_release_channel() */
|
|
tt_int_op(test_releases_count, OP_EQ, init_releases_count + 4);
|
|
|
|
done:
|
|
free_fake_channel(ch1);
|
|
free_fake_channel(ch2);
|
|
|
|
UNMOCK(scheduler_channel_doesnt_want_writes);
|
|
UNMOCK(scheduler_release_channel);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Weird channel lifecycle test:
|
|
*
|
|
* OPENING->CLOSING->CLOSED
|
|
* OPENING->OPEN->CLOSING->ERROR
|
|
* OPENING->OPEN->MAINT->CLOSING->CLOSED
|
|
* OPENING->OPEN->MAINT->CLOSING->ERROR
|
|
*/
|
|
|
|
static void
|
|
test_channel_lifecycle_2(void *arg)
|
|
{
|
|
channel_t *ch = NULL;
|
|
|
|
(void)arg;
|
|
|
|
/* Mock these for the whole lifecycle test */
|
|
MOCK(scheduler_channel_doesnt_want_writes,
|
|
scheduler_channel_doesnt_want_writes_mock);
|
|
MOCK(scheduler_release_channel,
|
|
scheduler_release_channel_mock);
|
|
|
|
/* Accept cells to lower layer */
|
|
test_chan_accept_cells = 1;
|
|
/* Use default overhead factor */
|
|
test_overhead_estimate = 1.0;
|
|
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
/* Start it off in OPENING */
|
|
ch->state = CHANNEL_STATE_OPENING;
|
|
/* The full lifecycle test needs a cmux */
|
|
ch->cmux = circuitmux_alloc();
|
|
|
|
/* Try to register it */
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Try to close it */
|
|
channel_mark_for_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
|
|
/* Finish closing it */
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
/* Now try OPENING->OPEN->CLOSING->ERROR */
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
ch->state = CHANNEL_STATE_OPENING;
|
|
ch->cmux = circuitmux_alloc();
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Finish opening it */
|
|
channel_change_state_open(ch);
|
|
|
|
/* Error exit from lower layer */
|
|
chan_test_error(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_ERROR);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
/* OPENING->OPEN->MAINT->CLOSING->CLOSED close from maintenance state */
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
ch->state = CHANNEL_STATE_OPENING;
|
|
ch->cmux = circuitmux_alloc();
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Finish opening it */
|
|
channel_change_state_open(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN);
|
|
|
|
/* Go to maintenance state */
|
|
channel_change_state(ch, CHANNEL_STATE_MAINT);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_MAINT);
|
|
|
|
/* Lower layer close */
|
|
channel_mark_for_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
|
|
/* Finish */
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
/*
|
|
* OPENING->OPEN->MAINT->CLOSING->CLOSED lower-layer close during
|
|
* maintenance state
|
|
*/
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
ch->state = CHANNEL_STATE_OPENING;
|
|
ch->cmux = circuitmux_alloc();
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Finish opening it */
|
|
channel_change_state_open(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN);
|
|
|
|
/* Go to maintenance state */
|
|
channel_change_state(ch, CHANNEL_STATE_MAINT);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_MAINT);
|
|
|
|
/* Lower layer close */
|
|
channel_close_from_lower_layer(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
|
|
/* Finish */
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
/* OPENING->OPEN->MAINT->CLOSING->ERROR */
|
|
ch = new_fake_channel();
|
|
tt_assert(ch);
|
|
ch->state = CHANNEL_STATE_OPENING;
|
|
ch->cmux = circuitmux_alloc();
|
|
channel_register(ch);
|
|
tt_assert(ch->registered);
|
|
|
|
/* Finish opening it */
|
|
channel_change_state_open(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN);
|
|
|
|
/* Go to maintenance state */
|
|
channel_change_state(ch, CHANNEL_STATE_MAINT);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_MAINT);
|
|
|
|
/* Lower layer close */
|
|
chan_test_error(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING);
|
|
|
|
/* Finish */
|
|
chan_test_finish_close(ch);
|
|
tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_ERROR);
|
|
channel_run_cleanup();
|
|
ch = NULL;
|
|
|
|
/* Shut down channels */
|
|
channel_free_all();
|
|
|
|
done:
|
|
tor_free(ch);
|
|
|
|
UNMOCK(scheduler_channel_doesnt_want_writes);
|
|
UNMOCK(scheduler_release_channel);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
test_channel_id_map(void *arg)
|
|
{
|
|
(void)arg;
|
|
#define N_CHAN 6
|
|
char rsa_id[N_CHAN][DIGEST_LEN];
|
|
ed25519_public_key_t *ed_id[N_CHAN];
|
|
channel_t *chan[N_CHAN];
|
|
int i;
|
|
ed25519_public_key_t ed_zero;
|
|
memset(&ed_zero, 0, sizeof(ed_zero));
|
|
|
|
tt_int_op(DIGEST_LEN, OP_EQ, sizeof(rsa_id[0])); // Do I remember C?
|
|
|
|
for (i = 0; i < N_CHAN; ++i) {
|
|
crypto_rand(rsa_id[i], DIGEST_LEN);
|
|
ed_id[i] = tor_malloc_zero(sizeof(*ed_id[i]));
|
|
crypto_rand((char*)ed_id[i]->pubkey, sizeof(ed_id[i]->pubkey));
|
|
}
|
|
|
|
/* For channel 3, have no Ed identity. */
|
|
tor_free(ed_id[3]);
|
|
|
|
/* Channel 2 and 4 have same ROSA identity */
|
|
memcpy(rsa_id[4], rsa_id[2], DIGEST_LEN);
|
|
|
|
/* Channel 2 and 4 and 5 have same RSA identity */
|
|
memcpy(rsa_id[4], rsa_id[2], DIGEST_LEN);
|
|
memcpy(rsa_id[5], rsa_id[2], DIGEST_LEN);
|
|
|
|
/* Channels 2 and 5 have same Ed25519 identity */
|
|
memcpy(ed_id[5], ed_id[2], sizeof(*ed_id[2]));
|
|
|
|
for (i = 0; i < N_CHAN; ++i) {
|
|
chan[i] = new_fake_channel();
|
|
channel_register(chan[i]);
|
|
channel_set_identity_digest(chan[i], rsa_id[i], ed_id[i]);
|
|
}
|
|
|
|
/* Lookup by RSA id only */
|
|
tt_ptr_op(chan[0], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[0], NULL));
|
|
tt_ptr_op(chan[1], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[1], NULL));
|
|
tt_ptr_op(chan[3], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[3], NULL));
|
|
channel_t *ch;
|
|
ch = channel_find_by_remote_identity(rsa_id[2], NULL);
|
|
tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
|
|
ch = channel_next_with_rsa_identity(ch);
|
|
tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
|
|
ch = channel_next_with_rsa_identity(ch);
|
|
tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
|
|
ch = channel_next_with_rsa_identity(ch);
|
|
tt_ptr_op(ch, OP_EQ, NULL);
|
|
|
|
/* As above, but with zero Ed25519 ID (meaning "any ID") */
|
|
tt_ptr_op(chan[0], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[0], &ed_zero));
|
|
tt_ptr_op(chan[1], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[1], &ed_zero));
|
|
tt_ptr_op(chan[3], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[3], &ed_zero));
|
|
ch = channel_find_by_remote_identity(rsa_id[2], &ed_zero);
|
|
tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
|
|
ch = channel_next_with_rsa_identity(ch);
|
|
tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
|
|
ch = channel_next_with_rsa_identity(ch);
|
|
tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
|
|
ch = channel_next_with_rsa_identity(ch);
|
|
tt_ptr_op(ch, OP_EQ, NULL);
|
|
|
|
/* Lookup nonexistent RSA identity */
|
|
tt_ptr_op(NULL, OP_EQ,
|
|
channel_find_by_remote_identity("!!!!!!!!!!!!!!!!!!!!", NULL));
|
|
|
|
/* Look up by full identity pair */
|
|
tt_ptr_op(chan[0], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[0], ed_id[0]));
|
|
tt_ptr_op(chan[1], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[1], ed_id[1]));
|
|
tt_ptr_op(chan[3], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[3], ed_id[3] /*NULL*/));
|
|
tt_ptr_op(chan[4], OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[4], ed_id[4]));
|
|
ch = channel_find_by_remote_identity(rsa_id[2], ed_id[2]);
|
|
tt_assert(ch == chan[2] || ch == chan[5]);
|
|
|
|
/* Look up RSA identity with wrong ed25519 identity */
|
|
tt_ptr_op(NULL, OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[4], ed_id[0]));
|
|
tt_ptr_op(NULL, OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[2], ed_id[1]));
|
|
tt_ptr_op(NULL, OP_EQ,
|
|
channel_find_by_remote_identity(rsa_id[3], ed_id[1]));
|
|
|
|
done:
|
|
for (i = 0; i < N_CHAN; ++i) {
|
|
channel_clear_identity_digest(chan[i]);
|
|
channel_unregister(chan[i]);
|
|
free_fake_channel(chan[i]);
|
|
tor_free(ed_id[i]);
|
|
}
|
|
#undef N_CHAN
|
|
}
|
|
|
|
struct testcase_t channel_tests[] = {
|
|
{ "dumpstats", test_channel_dumpstats, TT_FORK, NULL, NULL },
|
|
{ "incoming", test_channel_incoming, TT_FORK, NULL, NULL },
|
|
{ "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL },
|
|
{ "lifecycle_2", test_channel_lifecycle_2, TT_FORK, NULL, NULL },
|
|
{ "id_map", test_channel_id_map, TT_FORK, NULL, NULL },
|
|
END_OF_TESTCASES
|
|
};
|
|
|