aboutsummaryrefslogtreecommitdiff
path: root/src/test/test_controller.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/test_controller.c')
-rw-r--r--src/test/test_controller.c624
1 files changed, 569 insertions, 55 deletions
diff --git a/src/test/test_controller.c b/src/test/test_controller.c
index 5b406e159b..a69ec17db8 100644
--- a/src/test/test_controller.c
+++ b/src/test/test_controller.c
@@ -1,12 +1,19 @@
-/* Copyright (c) 2015-2019, The Tor Project, Inc. */
+/* Copyright (c) 2015-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */
-#define CONTROL_PRIVATE
+#define CONTROL_CMD_PRIVATE
+#define CONTROL_GETINFO_PRIVATE
#include "core/or/or.h"
+#include "app/config/config.h"
#include "lib/crypt_ops/crypto_ed25519.h"
#include "feature/client/bridges.h"
#include "feature/control/control.h"
+#include "feature/control/control_cmd.h"
+#include "feature/control/control_getinfo.h"
+#include "feature/control/control_proto.h"
#include "feature/client/entrynodes.h"
+#include "feature/dircache/cached_dir_st.h"
+#include "feature/dircache/dirserv.h"
#include "feature/hs/hs_common.h"
#include "feature/nodelist/networkstatus.h"
#include "feature/rend/rendservice.h"
@@ -15,48 +22,257 @@
#include "test/test.h"
#include "test/test_helpers.h"
#include "lib/net/resolve.h"
+#include "lib/encoding/confline.h"
+#include "lib/encoding/kvline.h"
#include "feature/control/control_connection_st.h"
+#include "feature/control/control_cmd_args_st.h"
#include "feature/dirclient/download_status_st.h"
#include "feature/nodelist/microdesc_st.h"
#include "feature/nodelist/node_st.h"
+typedef struct {
+ const char *input;
+ const char *expected_parse;
+ const char *expected_error;
+} parser_testcase_t;
+
+typedef struct {
+ const control_cmd_syntax_t *syntax;
+ size_t n_testcases;
+ const parser_testcase_t *testcases;
+} parse_test_params_t;
+
+static char *
+control_cmd_dump_args(const control_cmd_args_t *result)
+{
+ buf_t *buf = buf_new();
+ buf_add_string(buf, "{ args=[");
+ if (result->args) {
+ if (smartlist_len(result->args)) {
+ buf_add_string(buf, " ");
+ }
+ SMARTLIST_FOREACH_BEGIN(result->args, const char *, s) {
+ const bool last = (s_sl_idx == smartlist_len(result->args)-1);
+ buf_add_printf(buf, "%s%s ",
+ escaped(s),
+ last ? "" : ",");
+ } SMARTLIST_FOREACH_END(s);
+ }
+ buf_add_string(buf, "]");
+ if (result->cmddata) {
+ buf_add_string(buf, ", obj=");
+ buf_add_string(buf, escaped(result->cmddata));
+ }
+ if (result->kwargs) {
+ buf_add_string(buf, ", { ");
+ const config_line_t *line;
+ for (line = result->kwargs; line; line = line->next) {
+ const bool last = (line->next == NULL);
+ buf_add_printf(buf, "%s=%s%s ", line->key, escaped(line->value),
+ last ? "" : ",");
+ }
+ buf_add_string(buf, "}");
+ }
+ buf_add_string(buf, " }");
+
+ char *encoded = buf_extract(buf, NULL);
+ buf_free(buf);
+ return encoded;
+}
+
+static void
+test_controller_parse_cmd(void *arg)
+{
+ const parse_test_params_t *params = arg;
+ control_cmd_args_t *result = NULL;
+ char *error = NULL;
+ char *encoded = NULL;
+
+ for (size_t i = 0; i < params->n_testcases; ++i) {
+ const parser_testcase_t *t = &params->testcases[i];
+ result = control_cmd_parse_args("EXAMPLE",
+ params->syntax,
+ strlen(t->input),
+ t->input,
+ &error);
+ // A valid test should expect exactly one parse or error.
+ tt_int_op((t->expected_parse == NULL), OP_NE,
+ (t->expected_error == NULL));
+ // We get a result or an error, not both.
+ tt_int_op((result == NULL), OP_EQ, (error != NULL));
+ // We got the one we expected.
+ tt_int_op((result == NULL), OP_EQ, (t->expected_parse == NULL));
+
+ if (result) {
+ encoded = control_cmd_dump_args(result);
+ tt_str_op(encoded, OP_EQ, t->expected_parse);
+ } else {
+ tt_str_op(error, OP_EQ, t->expected_error);
+ }
+
+ tor_free(error);
+ tor_free(encoded);
+ control_cmd_args_free(result);
+ }
+
+ done:
+ tor_free(error);
+ tor_free(encoded);
+ control_cmd_args_free(result);
+}
+
+#ifndef COCCI
+#define OK(inp, out) \
+ { inp "\r\n", out, NULL }
+#define ERR(inp, err) \
+ { inp "\r\n", NULL, err }
+
+#define TESTPARAMS(syntax, array) \
+ { &syntax, \
+ ARRAY_LENGTH(array), \
+ array }
+#endif /* !defined(COCCI) */
+
+static const parser_testcase_t one_to_three_tests[] = {
+ ERR("", "Need at least 1 argument(s)"),
+ ERR(" \t", "Need at least 1 argument(s)"),
+ OK("hello", "{ args=[ \"hello\" ] }"),
+ OK("hello world", "{ args=[ \"hello\", \"world\" ] }"),
+ OK("hello world", "{ args=[ \"hello\", \"world\" ] }"),
+ OK(" hello world", "{ args=[ \"hello\", \"world\" ] }"),
+ OK(" hello world ", "{ args=[ \"hello\", \"world\" ] }"),
+ OK("hello there world", "{ args=[ \"hello\", \"there\", \"world\" ] }"),
+ ERR("why hello there world", "Cannot accept more than 3 argument(s)"),
+ ERR("hello\r\nworld.\r\n.", "Unexpected body"),
+};
+
+static const control_cmd_syntax_t one_to_three_syntax = {
+ .min_args=1, .max_args=3
+};
+
+static const parse_test_params_t parse_one_to_three_params =
+ TESTPARAMS( one_to_three_syntax, one_to_three_tests );
+
+// =
+static const parser_testcase_t no_args_one_obj_tests[] = {
+ ERR("Hi there!\r\n.", "Cannot accept more than 0 argument(s)"),
+ ERR("", "Empty body"),
+ OK("\r\n", "{ args=[], obj=\"\\n\" }"),
+ OK("\r\nHello world\r\n", "{ args=[], obj=\"Hello world\\n\\n\" }"),
+ OK("\r\nHello\r\nworld\r\n", "{ args=[], obj=\"Hello\\nworld\\n\\n\" }"),
+ OK("\r\nHello\r\n..\r\nworld\r\n",
+ "{ args=[], obj=\"Hello\\n.\\nworld\\n\\n\" }"),
+};
+static const control_cmd_syntax_t no_args_one_obj_syntax = {
+ .min_args=0, .max_args=0,
+ .want_cmddata=true,
+};
+static const parse_test_params_t parse_no_args_one_obj_params =
+ TESTPARAMS( no_args_one_obj_syntax, no_args_one_obj_tests );
+
+static const parser_testcase_t no_args_kwargs_tests[] = {
+ OK("", "{ args=[] }"),
+ OK(" ", "{ args=[] }"),
+ OK("hello there=world", "{ args=[], { hello=\"\", there=\"world\" } }"),
+ OK("hello there=world today",
+ "{ args=[], { hello=\"\", there=\"world\", today=\"\" } }"),
+ ERR("=Foo", "Cannot parse keyword argument(s)"),
+};
+static const control_cmd_syntax_t no_args_kwargs_syntax = {
+ .min_args=0, .max_args=0,
+ .accept_keywords=true,
+ .kvline_flags=KV_OMIT_VALS
+};
+static const parse_test_params_t parse_no_args_kwargs_params =
+ TESTPARAMS( no_args_kwargs_syntax, no_args_kwargs_tests );
+
+static const char *one_arg_kwargs_allow_keywords[] = {
+ "Hello", "world", NULL
+};
+static const parser_testcase_t one_arg_kwargs_tests[] = {
+ ERR("", "Need at least 1 argument(s)"),
+ OK("Hi", "{ args=[ \"Hi\" ] }"),
+ ERR("hello there=world", "Unrecognized keyword argument \"there\""),
+ OK("Hi HELLO=foo", "{ args=[ \"Hi\" ], { HELLO=\"foo\" } }"),
+ OK("Hi world=\"bar baz\" hello ",
+ "{ args=[ \"Hi\" ], { world=\"bar baz\", hello=\"\" } }"),
+};
+static const control_cmd_syntax_t one_arg_kwargs_syntax = {
+ .min_args=1, .max_args=1,
+ .accept_keywords=true,
+ .allowed_keywords=one_arg_kwargs_allow_keywords,
+ .kvline_flags=KV_OMIT_VALS|KV_QUOTED,
+};
+static const parse_test_params_t parse_one_arg_kwargs_params =
+ TESTPARAMS( one_arg_kwargs_syntax, one_arg_kwargs_tests );
+
+static char *reply_str = NULL;
+/* Mock for control_write_reply that copies the string for inspection
+ * by tests */
+static void
+mock_control_write_reply(control_connection_t *conn, int code, int c,
+ const char *s)
+{
+ (void)conn;
+ (void)code;
+ (void)c;
+ tor_free(reply_str);
+ reply_str = tor_strdup(s);
+}
+
static void
test_add_onion_helper_keyarg_v3(void *arg)
{
int ret, hs_version;
add_onion_secret_key_t pk;
char *key_new_blob = NULL;
- char *err_msg = NULL;
const char *key_new_alg = NULL;
(void) arg;
+ MOCK(control_write_reply, mock_control_write_reply);
memset(&pk, 0, sizeof(pk));
/* Test explicit ED25519-V3 key generation. */
+ tor_free(reply_str);
ret = add_onion_helper_keyarg("NEW:ED25519-V3", 0, &key_new_alg,
&key_new_blob, &pk, &hs_version,
- &err_msg);
+ NULL);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE);
tt_assert(pk.v3);
tt_str_op(key_new_alg, OP_EQ, "ED25519-V3");
tt_assert(key_new_blob);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
tor_free(pk.v3); pk.v3 = NULL;
tor_free(key_new_blob);
+ /* Test "BEST" key generation (Assumes BEST = ED25519-V3). */
+ tor_free(pk.v3); pk.v3 = NULL;
+ tor_free(key_new_blob);
+ ret = add_onion_helper_keyarg("NEW:BEST", 0, &key_new_alg, &key_new_blob,
+ &pk, &hs_version, NULL);
+ tt_int_op(ret, OP_EQ, 0);
+ tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE);
+ tt_assert(pk.v3);
+ tt_str_op(key_new_alg, OP_EQ, "ED25519-V3");
+ tt_assert(key_new_blob);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
+
/* Test discarding the private key. */
+ tor_free(reply_str);
+ tor_free(pk.v3); pk.v3 = NULL;
+ tor_free(key_new_blob);
ret = add_onion_helper_keyarg("NEW:ED25519-V3", 1, &key_new_alg,
&key_new_blob, &pk, &hs_version,
- &err_msg);
+ NULL);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE);
tt_assert(pk.v3);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
tor_free(pk.v3); pk.v3 = NULL;
tor_free(key_new_blob);
@@ -76,9 +292,10 @@ test_add_onion_helper_keyarg_v3(void *arg)
tor_asprintf(&key_blob, "ED25519-V3:%s", base64_sk);
tt_assert(key_blob);
+ tor_free(reply_str);
ret = add_onion_helper_keyarg(key_blob, 1, &key_new_alg,
&key_new_blob, &pk, &hs_version,
- &err_msg);
+ NULL);
tor_free(key_blob);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(hs_version, OP_EQ, HS_VERSION_THREE);
@@ -86,7 +303,7 @@ test_add_onion_helper_keyarg_v3(void *arg)
tt_mem_op(pk.v3, OP_EQ, hex_sk, 64);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
tor_free(pk.v3); pk.v3 = NULL;
tor_free(key_new_blob);
}
@@ -94,7 +311,8 @@ test_add_onion_helper_keyarg_v3(void *arg)
done:
tor_free(pk.v3);
tor_free(key_new_blob);
- tor_free(err_msg);
+ tor_free(reply_str);
+ UNMOCK(control_write_reply);
}
static void
@@ -105,72 +323,61 @@ test_add_onion_helper_keyarg_v2(void *arg)
crypto_pk_t *pk1 = NULL;
const char *key_new_alg = NULL;
char *key_new_blob = NULL;
- char *err_msg = NULL;
char *encoded = NULL;
char *arg_str = NULL;
(void) arg;
+ MOCK(control_write_reply, mock_control_write_reply);
memset(&pk, 0, sizeof(pk));
/* Test explicit RSA1024 key generation. */
+ tor_free(reply_str);
ret = add_onion_helper_keyarg("NEW:RSA1024", 0, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
+ &pk, &hs_version, NULL);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
tt_assert(pk.v2);
tt_str_op(key_new_alg, OP_EQ, "RSA1024");
tt_assert(key_new_blob);
- tt_ptr_op(err_msg, OP_EQ, NULL);
-
- /* Test "BEST" key generation (Assumes BEST = RSA1024). */
- crypto_pk_free(pk.v2); pk.v2 = NULL;
- tor_free(key_new_blob);
- ret = add_onion_helper_keyarg("NEW:BEST", 0, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
- tt_int_op(ret, OP_EQ, 0);
- tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
- tt_assert(pk.v2);
- tt_str_op(key_new_alg, OP_EQ, "RSA1024");
- tt_assert(key_new_blob);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
/* Test discarding the private key. */
crypto_pk_free(pk.v2); pk.v2 = NULL;
tor_free(key_new_blob);
- ret = add_onion_helper_keyarg("NEW:BEST", 1, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
+ ret = add_onion_helper_keyarg("NEW:RSA1024", 1, &key_new_alg, &key_new_blob,
+ &pk, &hs_version, NULL);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
tt_assert(pk.v2);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
/* Test generating a invalid key type. */
crypto_pk_free(pk.v2); pk.v2 = NULL;
ret = add_onion_helper_keyarg("NEW:RSA512", 0, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
+ &pk, &hs_version, NULL);
tt_int_op(ret, OP_EQ, -1);
tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
tt_assert(!pk.v2);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_assert(err_msg);
+ tt_assert(reply_str);
/* Test loading a RSA1024 key. */
- tor_free(err_msg);
+ tor_free(reply_str);
pk1 = pk_generate(0);
tt_int_op(0, OP_EQ, crypto_pk_base64_encode_private(pk1, &encoded));
tor_asprintf(&arg_str, "RSA1024:%s", encoded);
ret = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
+ &pk, &hs_version, NULL);
tt_int_op(ret, OP_EQ, 0);
tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
tt_assert(pk.v2);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
tt_int_op(crypto_pk_cmp_keys(pk1, pk.v2), OP_EQ, 0);
/* Test loading a invalid key type. */
@@ -179,36 +386,37 @@ test_add_onion_helper_keyarg_v2(void *arg)
crypto_pk_free(pk.v2); pk.v2 = NULL;
tor_asprintf(&arg_str, "RSA512:%s", encoded);
ret = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
+ &pk, &hs_version, NULL);
tt_int_op(ret, OP_EQ, -1);
tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
tt_assert(!pk.v2);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_assert(err_msg);
+ tt_assert(reply_str);
/* Test loading a invalid key. */
tor_free(arg_str);
crypto_pk_free(pk.v2); pk.v2 = NULL;
- tor_free(err_msg);
+ tor_free(reply_str);
encoded[strlen(encoded)/2] = '\0';
tor_asprintf(&arg_str, "RSA1024:%s", encoded);
ret = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob,
- &pk, &hs_version, &err_msg);
+ &pk, &hs_version, NULL);
tt_int_op(ret, OP_EQ, -1);
tt_int_op(hs_version, OP_EQ, HS_VERSION_TWO);
tt_assert(!pk.v2);
tt_ptr_op(key_new_alg, OP_EQ, NULL);
tt_ptr_op(key_new_blob, OP_EQ, NULL);
- tt_assert(err_msg);
+ tt_assert(reply_str);
done:
crypto_pk_free(pk1);
crypto_pk_free(pk.v2);
tor_free(key_new_blob);
- tor_free(err_msg);
+ tor_free(reply_str);
tor_free(encoded);
tor_free(arg_str);
+ UNMOCK(control_write_reply);
}
static void
@@ -362,49 +570,52 @@ static void
test_add_onion_helper_clientauth(void *arg)
{
rend_authorized_client_t *client = NULL;
- char *err_msg = NULL;
int created = 0;
(void)arg;
+ MOCK(control_write_reply, mock_control_write_reply);
/* Test "ClientName" only. */
- client = add_onion_helper_clientauth("alice", &created, &err_msg);
+ tor_free(reply_str);
+ client = add_onion_helper_clientauth("alice", &created, NULL);
tt_assert(client);
tt_assert(created);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
rend_authorized_client_free(client);
/* Test "ClientName:Blob" */
+ tor_free(reply_str);
client = add_onion_helper_clientauth("alice:475hGBHPlq7Mc0cRZitK/B",
- &created, &err_msg);
+ &created, NULL);
tt_assert(client);
tt_assert(!created);
- tt_ptr_op(err_msg, OP_EQ, NULL);
+ tt_ptr_op(reply_str, OP_EQ, NULL);
rend_authorized_client_free(client);
/* Test invalid client names */
+ tor_free(reply_str);
client = add_onion_helper_clientauth("no*asterisks*allowed", &created,
- &err_msg);
+ NULL);
tt_ptr_op(client, OP_EQ, NULL);
- tt_assert(err_msg);
- tor_free(err_msg);
+ tt_assert(reply_str);
/* Test invalid auth cookie */
- client = add_onion_helper_clientauth("alice:12345", &created, &err_msg);
+ tor_free(reply_str);
+ client = add_onion_helper_clientauth("alice:12345", &created, NULL);
tt_ptr_op(client, OP_EQ, NULL);
- tt_assert(err_msg);
- tor_free(err_msg);
+ tt_assert(reply_str);
/* Test invalid syntax */
+ tor_free(reply_str);
client = add_onion_helper_clientauth(":475hGBHPlq7Mc0cRZitK/B", &created,
- &err_msg);
+ NULL);
tt_ptr_op(client, OP_EQ, NULL);
- tt_assert(err_msg);
- tor_free(err_msg);
+ tt_assert(reply_str);
done:
rend_authorized_client_free(client);
- tor_free(err_msg);
+ tor_free(reply_str);
+ UNMOCK(control_write_reply);
}
/* Mocks and data/variables used for GETINFO download status tests */
@@ -1485,6 +1696,138 @@ test_download_status_bridge(void *arg)
return;
}
+/** Mock cached consensus */
+static cached_dir_t *mock_ns_consensus_cache;
+static cached_dir_t *mock_microdesc_consensus_cache;
+
+/** Mock the function that retrieves consensus from cache. These use a
+ * global variable so that they can be cleared from within the test.
+ * The actual code retains the pointer to the consensus data, but
+ * we are doing this here, to prevent memory leaks
+ * from within the tests */
+static cached_dir_t *
+mock_dirserv_get_consensus(const char *flavor_name)
+{
+ if (!strcmp(flavor_name, "ns")) {
+ mock_ns_consensus_cache = tor_malloc_zero(sizeof(cached_dir_t));
+ mock_ns_consensus_cache->dir = tor_strdup("mock_ns_consensus");
+ return mock_ns_consensus_cache;
+ } else {
+ mock_microdesc_consensus_cache = tor_malloc_zero(sizeof(cached_dir_t));
+ mock_microdesc_consensus_cache->dir = tor_strdup(
+ "mock_microdesc_consensus");
+ return mock_microdesc_consensus_cache;
+ }
+}
+
+/** Mock the function that retrieves consensuses
+ * from a files in the directory. */
+static tor_mmap_t *
+mock_tor_mmap_file(const char* filename)
+{
+ tor_mmap_t *res;
+ res = tor_malloc_zero(sizeof(tor_mmap_t));
+ if (strstr(filename, "cached-consensus") != NULL) {
+ res->data = "mock_ns_consensus";
+ } else if (strstr(filename, "cached-microdesc-consensus") != NULL) {
+ res->data = "mock_microdesc_consensus";
+ } else {
+ res->data = ".";
+ }
+ res->size = strlen(res->data);
+ return res;
+}
+
+/** Mock the function that clears file data
+ * loaded into the memory */
+static int
+mock_tor_munmap_file(tor_mmap_t *handle)
+{
+ tor_free(handle);
+ return 0;
+}
+
+static void
+test_getinfo_helper_current_consensus_from_file(void *arg)
+{
+ /* We just need one of these to pass, it doesn't matter what's in it */
+ control_connection_t dummy;
+ /* Get results out */
+ char *answer = NULL;
+ const char *errmsg = NULL;
+
+ (void)arg;
+
+ MOCK(tor_mmap_file, mock_tor_mmap_file);
+ MOCK(tor_munmap_file, mock_tor_munmap_file);
+
+ getinfo_helper_dir(&dummy,
+ "dir/status-vote/current/consensus",
+ &answer,
+ &errmsg);
+ tt_str_op(answer, OP_EQ, "mock_ns_consensus");
+ tt_ptr_op(errmsg, OP_EQ, NULL);
+ tor_free(answer);
+ errmsg = NULL;
+
+ getinfo_helper_dir(&dummy,
+ "dir/status-vote/current/consensus-microdesc",
+ &answer,
+ &errmsg);
+ tt_str_op(answer, OP_EQ, "mock_microdesc_consensus");
+ tt_ptr_op(errmsg, OP_EQ, NULL);
+ errmsg = NULL;
+
+ done:
+ tor_free(answer);
+ UNMOCK(tor_mmap_file);
+ UNMOCK(tor_munmap_file);
+ return;
+}
+
+static void
+test_getinfo_helper_current_consensus_from_cache(void *arg)
+{
+ /* We just need one of these to pass, it doesn't matter what's in it */
+ control_connection_t dummy;
+ /* Get results out */
+ char *answer = NULL;
+ const char *errmsg = NULL;
+
+ (void)arg;
+ or_options_t *options = get_options_mutable();
+ options->FetchUselessDescriptors = 1;
+ MOCK(dirserv_get_consensus, mock_dirserv_get_consensus);
+
+ getinfo_helper_dir(&dummy,
+ "dir/status-vote/current/consensus",
+ &answer,
+ &errmsg);
+ tt_str_op(answer, OP_EQ, "mock_ns_consensus");
+ tt_ptr_op(errmsg, OP_EQ, NULL);
+ tor_free(answer);
+ tor_free(mock_ns_consensus_cache->dir);
+ tor_free(mock_ns_consensus_cache);
+ errmsg = NULL;
+
+ getinfo_helper_dir(&dummy,
+ "dir/status-vote/current/consensus-microdesc",
+ &answer,
+ &errmsg);
+ tt_str_op(answer, OP_EQ, "mock_microdesc_consensus");
+ tt_ptr_op(errmsg, OP_EQ, NULL);
+ tor_free(mock_microdesc_consensus_cache->dir);
+ tor_free(answer);
+ errmsg = NULL;
+
+ done:
+ options->FetchUselessDescriptors = 0;
+ tor_free(answer);
+ tor_free(mock_microdesc_consensus_cache);
+ UNMOCK(dirserv_get_consensus);
+ return;
+}
+
/** Set timeval to a mock date and time. This is necessary
* to make tor_gettimeofday() mockable. */
static void
@@ -1543,7 +1886,7 @@ test_current_time(void *arg)
static size_t n_nodelist_get_list = 0;
static smartlist_t *nodes = NULL;
-static smartlist_t *
+static const smartlist_t *
mock_nodelist_get_list(void)
{
n_nodelist_get_list++;
@@ -1614,7 +1957,172 @@ test_getinfo_md_all(void *arg)
return;
}
+static smartlist_t *reply_strs;
+
+static void
+mock_control_write_reply_list(control_connection_t *conn, int code, int c,
+ const char *s)
+{
+ (void)conn;
+ /* To make matching easier, don't append "\r\n" */
+ smartlist_add_asprintf(reply_strs, "%03d%c%s", code, c, s);
+}
+
+static void
+test_control_reply(void *arg)
+{
+ (void)arg;
+ smartlist_t *lines = smartlist_new();
+
+ MOCK(control_write_reply, mock_control_write_reply);
+
+ tor_free(reply_str);
+ control_reply_clear(lines);
+ control_reply_add_str(lines, 250, "FOO");
+ control_write_reply_lines(NULL, lines);
+ tt_str_op(reply_str, OP_EQ, "FOO");
+
+ tor_free(reply_str);
+ control_reply_clear(lines);
+ control_reply_add_done(lines);
+ control_write_reply_lines(NULL, lines);
+ tt_str_op(reply_str, OP_EQ, "OK");
+
+ tor_free(reply_str);
+ control_reply_clear(lines);
+ UNMOCK(control_write_reply);
+ MOCK(control_write_reply, mock_control_write_reply_list);
+ reply_strs = smartlist_new();
+ control_reply_add_one_kv(lines, 250, 0, "A", "B");
+ control_reply_add_one_kv(lines, 250, 0, "C", "D");
+ control_write_reply_lines(NULL, lines);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 2);
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ, "250-A=B");
+ tt_str_op((char *)smartlist_get(reply_strs, 1), OP_EQ, "250 C=D");
+
+ control_reply_clear(lines);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+ control_reply_add_printf(lines, 250, "PROTOCOLINFO %d", 1);
+ control_reply_add_one_kv(lines, 250, KV_OMIT_VALS|KV_RAW, "AUTH", "");
+ control_reply_append_kv(lines, "METHODS", "COOKIE");
+ control_reply_append_kv(lines, "COOKIEFILE", escaped("/tmp/cookie"));
+ control_reply_add_done(lines);
+ control_write_reply_lines(NULL, lines);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 3);
+ tt_str_op((char *)smartlist_get(reply_strs, 0),
+ OP_EQ, "250-PROTOCOLINFO 1");
+ tt_str_op((char *)smartlist_get(reply_strs, 1),
+ OP_EQ, "250-AUTH METHODS=COOKIE COOKIEFILE=\"/tmp/cookie\"");
+ tt_str_op((char *)smartlist_get(reply_strs, 2),
+ OP_EQ, "250 OK");
+
+ done:
+ UNMOCK(control_write_reply);
+ tor_free(reply_str);
+ control_reply_free(lines);
+ if (reply_strs)
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_free(reply_strs);
+ return;
+}
+
+static void
+test_control_getconf(void *arg)
+{
+ (void)arg;
+ control_connection_t conn;
+ char *args = NULL;
+ int r = -1;
+
+ memset(&conn, 0, sizeof(conn));
+ conn.current_cmd = tor_strdup("GETCONF");
+
+ MOCK(control_write_reply, mock_control_write_reply_list);
+ reply_strs = smartlist_new();
+
+ args = tor_strdup("");
+ r = handle_control_command(&conn, (uint32_t)strlen(args), args);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 1);
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ, "250 OK");
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+ tor_free(args);
+
+ args = tor_strdup("NoSuch");
+ r = handle_control_command(&conn, (uint32_t)strlen(args), args);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 1);
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ,
+ "552 Unrecognized configuration key \"NoSuch\"");
+ tor_free(args);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+
+ args = tor_strdup("NoSuch1 NoSuch2");
+ r = handle_control_command(&conn, (uint32_t)strlen(args), args);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 2);
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ,
+ "552-Unrecognized configuration key \"NoSuch1\"");
+ tt_str_op((char *)smartlist_get(reply_strs, 1), OP_EQ,
+ "552 Unrecognized configuration key \"NoSuch2\"");
+ tor_free(args);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+
+ args = tor_strdup("ControlPort NoSuch");
+ r = handle_control_command(&conn, (uint32_t)strlen(args), args);
+ tt_int_op(r, OP_EQ, 0);
+ /* Valid keys ignored if there are any invalid ones */
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 1);
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ,
+ "552 Unrecognized configuration key \"NoSuch\"");
+ tor_free(args);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+
+ args = tor_strdup("ClientOnly");
+ r = handle_control_command(&conn, (uint32_t)strlen(args), args);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 1);
+ /* According to config.c, this is an exception for the unit tests */
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ, "250 ClientOnly=0");
+ tor_free(args);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+
+ args = tor_strdup("BridgeRelay ClientOnly");
+ r = handle_control_command(&conn, (uint32_t)strlen(args), args);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(reply_strs), OP_EQ, 2);
+ /* Change if config.c changes BridgeRelay default (unlikely) */
+ tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ, "250-BridgeRelay=0");
+ tt_str_op((char *)smartlist_get(reply_strs, 1), OP_EQ, "250 ClientOnly=0");
+ tor_free(args);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_clear(reply_strs);
+
+ done:
+ tor_free(conn.current_cmd);
+ tor_free(args);
+ UNMOCK(control_write_reply);
+ SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+ smartlist_free(reply_strs);
+}
+
+#ifndef COCCI
+#define PARSER_TEST(type) \
+ { "parse/" #type, test_controller_parse_cmd, 0, &passthrough_setup, \
+ (void*)&parse_ ## type ## _params }
+#endif
+
struct testcase_t controller_tests[] = {
+ PARSER_TEST(one_to_three),
+ PARSER_TEST(no_args_one_obj),
+ PARSER_TEST(no_args_kwargs),
+ PARSER_TEST(one_arg_kwargs),
{ "add_onion_helper_keyarg_v2", test_add_onion_helper_keyarg_v2, 0,
NULL, NULL },
{ "add_onion_helper_keyarg_v3", test_add_onion_helper_keyarg_v3, 0,
@@ -1626,11 +2134,17 @@ struct testcase_t controller_tests[] = {
NULL },
{ "download_status_consensus", test_download_status_consensus, 0, NULL,
NULL },
+ {"getinfo_helper_current_consensus_from_cache",
+ test_getinfo_helper_current_consensus_from_cache, 0, NULL, NULL },
+ {"getinfo_helper_current_consensus_from_file",
+ test_getinfo_helper_current_consensus_from_file, 0, NULL, NULL },
{ "download_status_cert", test_download_status_cert, 0, NULL,
NULL },
{ "download_status_desc", test_download_status_desc, 0, NULL, NULL },
{ "download_status_bridge", test_download_status_bridge, 0, NULL, NULL },
{ "current_time", test_current_time, 0, NULL, NULL },
{ "getinfo_md_all", test_getinfo_md_all, 0, NULL, NULL },
+ { "control_reply", test_control_reply, 0, NULL, NULL },
+ { "control_getconf", test_control_getconf, 0, NULL, NULL },
END_OF_TESTCASES
};