summaryrefslogtreecommitdiffstats
path: root/src/lib/test-connection.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/test-connection.c')
-rw-r--r--src/lib/test-connection.c744
1 files changed, 744 insertions, 0 deletions
diff --git a/src/lib/test-connection.c b/src/lib/test-connection.c
new file mode 100644
index 0000000..22677c3
--- /dev/null
+++ b/src/lib/test-connection.c
@@ -0,0 +1,744 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "ioloop.h"
+#include "connection.h"
+#include "istream.h"
+#include "ostream.h"
+#include "strnum.h"
+#include "strescape.h"
+
+#include <unistd.h>
+
+static const struct connection_settings client_set =
+{
+ .service_name_in = "TEST-S",
+ .service_name_out = "TEST-C",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+};
+
+static const struct connection_settings server_set =
+{
+ .service_name_in = "TEST-C",
+ .service_name_out = "TEST-S",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = FALSE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+};
+
+static bool received_quit = FALSE;
+static bool was_resumed = FALSE;
+static bool was_idle_killed = FALSE;
+static int received_count = 0;
+
+static void test_connection_run(const struct connection_settings *set_s,
+ const struct connection_settings *set_c,
+ const struct connection_vfuncs *v_s,
+ const struct connection_vfuncs *v_c,
+ unsigned int iter_count)
+{
+ int fds[2];
+
+ struct ioloop *loop = io_loop_create();
+ struct connection_list *clients = connection_list_init(set_c, v_c);
+ struct connection_list *servers = connection_list_init(set_s, v_s);
+ struct connection *conn_c = i_new(struct connection, 1);
+ struct connection *conn_s = i_new(struct connection, 1);
+
+ conn_s->ioloop = loop;
+ conn_c->ioloop = loop;
+
+ for(unsigned int iters = 0; iters < iter_count; iters++) {
+ test_assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0);
+
+ fd_set_nonblock(fds[0], TRUE);
+ fd_set_nonblock(fds[1], TRUE);
+ connection_init_server(servers, conn_s, "client", fds[1], fds[1]);
+ connection_init_client_fd(clients, conn_c, "server", fds[0], fds[0]);
+
+ io_loop_run(loop);
+
+ connection_deinit(conn_c);
+ connection_deinit(conn_s);
+ }
+
+ i_free(conn_c);
+ i_free(conn_s);
+
+ connection_list_deinit(&clients);
+ connection_list_deinit(&servers);
+
+ io_loop_destroy(&loop);
+}
+
+/* BEGIN SIMPLE TEST */
+
+static void test_connection_simple_client_connected(struct connection *conn, bool success)
+{
+ if (conn->list->set.client)
+ o_stream_nsend_str(conn->output, "QUIT\n");
+ test_assert(success);
+};
+
+static int
+test_connection_simple_input_args(struct connection *conn, const char *const *args)
+{
+ if (strcmp(args[0], "QUIT") == 0) {
+ received_quit = TRUE;
+ connection_disconnect(conn);
+ return 0;
+ }
+ i_error("invalid input");
+ return -1;
+}
+
+static void test_connection_simple_destroy(struct connection *conn)
+{
+ io_loop_stop(conn->ioloop);
+ connection_disconnect(conn);
+}
+
+static const struct connection_vfuncs simple_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .destroy = test_connection_simple_destroy,
+};
+
+static void test_connection_simple(void)
+{
+ test_begin("connection simple");
+
+ test_connection_run(&server_set, &client_set, &simple_v, &simple_v, 10);
+
+ test_assert(received_quit);
+ received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN NO INPUT TEST */
+
+static const struct connection_settings no_input_client_set =
+{
+ .service_name_in = "TEST-S",
+ .service_name_out = "TEST-C",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = 0,
+ .output_max_size = SIZE_MAX,
+};
+
+static const struct connection_settings no_input_server_set =
+{
+ .service_name_in = "TEST-C",
+ .service_name_out = "TEST-S",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = FALSE,
+ .input_max_size = 0,
+ .output_max_size = SIZE_MAX,
+};
+
+static void
+test_connection_no_input_input(struct connection *conn)
+{
+ const char *input;
+ struct istream *is = i_stream_create_fd(conn->fd_in, SIZE_MAX);
+ i_stream_set_blocking(is, FALSE);
+ while ((input = i_stream_read_next_line(is)) != NULL) {
+ const char *const *args = t_strsplit_tabescaped(input);
+ if (!conn->handshake_received) {
+ if (connection_handshake_args_default(conn, args) > -1)
+ conn->handshake_received = TRUE;
+ continue;
+ }
+ if (strcmp(args[0], "QUIT") == 0) {
+ received_quit = TRUE;
+ io_loop_stop(conn->ioloop);
+ break;
+ }
+ }
+ i_stream_unref(&is);
+}
+
+static const struct connection_vfuncs no_input_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input = test_connection_no_input_input,
+ .destroy = test_connection_simple_destroy,
+};
+
+static void test_connection_no_input(void)
+{
+ test_begin("connection no input stream");
+
+ test_connection_run(&no_input_server_set, &no_input_client_set,
+ &no_input_v, &no_input_v, 1);
+
+ test_assert(received_quit);
+ received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN HANDSHAKE TEST */
+static void test_connection_custom_handshake_client_connected(struct connection *conn, bool success)
+{
+ if (conn->list->set.client)
+ o_stream_nsend_str(conn->output, "HANDSHAKE\tFRIEND\n");
+ test_assert(success);
+};
+
+static int test_connection_custom_handshake_args(struct connection *conn,
+ const char *const *args)
+{
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(conn, args) < 0)
+ return -1;
+ return 0;
+ }
+ if (!conn->handshake_received) {
+ if (strcmp(args[0], "HANDSHAKE") == 0 &&
+ strcmp(args[1], "FRIEND") == 0) {
+ if (!conn->list->set.client)
+ o_stream_nsend_str(conn->output, "HANDSHAKE\tFRIEND\n");
+ else
+ o_stream_nsend_str(conn->output, "QUIT\n");
+ return 1;
+ }
+ return -1;
+ }
+ return 1;
+}
+
+static const struct connection_vfuncs custom_handshake_v =
+{
+ .client_connected = test_connection_custom_handshake_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .handshake_args = test_connection_custom_handshake_args,
+ .destroy = test_connection_simple_destroy,
+};
+
+static void test_connection_custom_handshake(void)
+{
+ test_begin("connection custom handshake");
+
+ test_connection_run(&server_set, &client_set, &custom_handshake_v,
+ &custom_handshake_v, 10);
+
+ test_assert(received_quit);
+ received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN PING PONG TEST */
+
+static int test_connection_ping_pong_input_args(struct connection *conn, const char *const *args)
+{
+ unsigned int n;
+ test_assert(args[0] != NULL && args[1] != NULL);
+ if (args[0] == NULL || args[1] == NULL)
+ return -1;
+ if (str_to_uint(args[1], &n) < 0)
+ return -1;
+ if (n > 10)
+ o_stream_nsend_str(conn->output, "QUIT\t0\n");
+ else if (strcmp(args[0], "QUIT") == 0)
+ connection_disconnect(conn);
+ else if (strcmp(args[0], "PING") == 0) {
+ received_count++;
+ o_stream_nsend_str(conn->output, t_strdup_printf("PONG\t%u\n", n+1));
+ } else if (strcmp(args[0], "PONG") == 0)
+ o_stream_nsend_str(conn->output, t_strdup_printf("PING\t%u\n", n));
+ else
+ return -1;
+ return 1;
+}
+
+static void test_connection_ping_pong_client_connected(struct connection *conn, bool success)
+{
+ o_stream_nsend_str(conn->output, "PING\t1\n");
+ test_assert(success);
+};
+
+static const struct connection_vfuncs ping_pong_v =
+{
+ .client_connected = test_connection_ping_pong_client_connected,
+ .input_args = test_connection_ping_pong_input_args,
+ .destroy = test_connection_simple_destroy,
+};
+
+static void test_connection_ping_pong(void)
+{
+ test_begin("connection ping pong");
+
+ test_connection_run(&server_set, &client_set, &ping_pong_v,
+ &ping_pong_v, 10);
+
+ test_assert(received_count == 100);
+
+ test_end();
+}
+
+/* BEGIN INPUT FULL TEST */
+
+static const struct connection_settings input_full_client_set =
+{
+ .service_name_in = "TEST-S",
+ .service_name_out = "TEST-C",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = 100,
+ .output_max_size = SIZE_MAX,
+};
+
+static int test_connection_input_full_input_args(struct connection *conn,
+ const char *const *args ATTR_UNUSED)
+{
+ /* send a long line */
+ for (unsigned int i = 0; i < 200; i++)
+ o_stream_nsend(conn->output, "c", 1);
+ return 1;
+}
+
+static void test_connection_input_full_destroy(struct connection *conn)
+{
+ test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_BUFFER_FULL ||
+ conn->list->set.client == FALSE);
+ test_connection_simple_destroy(conn);
+}
+
+static const struct connection_vfuncs input_full_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input_args = test_connection_input_full_input_args,
+ .destroy = test_connection_input_full_destroy,
+};
+
+static void test_connection_input_full(void)
+{
+ test_begin("connection input full");
+
+ test_connection_run(&server_set, &input_full_client_set, &input_full_v,
+ &simple_v, 10);
+ test_end();
+}
+
+/* BEGIN RESUME TEST */
+static struct timeout *to_send_quit = NULL;
+static struct timeout *to_resume = NULL;
+
+static void test_connection_resume_client_connected(struct connection *conn, bool success)
+{
+ test_assert(success);
+ o_stream_nsend_str(conn->output, "BEGIN\n");
+}
+
+static void test_connection_resume_continue(struct connection *conn)
+{
+ timeout_remove(&to_resume);
+ /* ensure QUIT wasn't received early */
+ was_resumed = !received_quit;
+ connection_input_resume(conn);
+}
+
+static void test_connection_resume_send_quit(struct connection *conn)
+{
+ timeout_remove(&to_send_quit);
+ o_stream_nsend_str(conn->output, "QUIT\n");
+}
+
+static int test_connection_resume_input_args(struct connection *conn,
+ const char *const *args)
+{
+ test_assert(args[0] != NULL);
+ if (args[0] == NULL)
+ return -1;
+
+ if (strcmp(args[0], "BEGIN") == 0) {
+ o_stream_nsend_str(conn->output, "HALT\n");
+ to_send_quit = timeout_add_short(10, test_connection_resume_send_quit, conn);
+ } else if (strcmp(args[0], "HALT") == 0) {
+ connection_input_halt(conn);
+ to_resume = timeout_add_short(100, test_connection_resume_continue, conn);
+ } else if (strcmp(args[0], "QUIT") == 0) {
+ received_quit = TRUE;
+ connection_disconnect(conn);
+ }
+
+ return 1;
+}
+
+static const struct connection_vfuncs resume_v =
+{
+ .client_connected = test_connection_resume_client_connected,
+ .input_args = test_connection_resume_input_args,
+ .destroy = test_connection_simple_destroy,
+};
+
+static void test_connection_resume(void)
+{
+ test_begin("connection resume");
+
+ was_resumed = received_quit = FALSE;
+ test_connection_run(&server_set, &client_set, &resume_v, &resume_v, 1);
+
+ test_assert(was_resumed);
+ test_assert(received_quit);
+ was_resumed = received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN RESUME PIPELINED TEST */
+static int test_connection_resume_pipelined_input_args(struct connection *conn,
+ const char *const *args)
+{
+ test_assert(args[0] != NULL);
+ if (args[0] == NULL)
+ return -1;
+
+ if (strcmp(args[0], "BEGIN") == 0) {
+ o_stream_nsend_str(conn->output, "HALT\nQUIT\n");
+ } else if (strcmp(args[0], "HALT") == 0) {
+ connection_input_halt(conn);
+ to_resume = timeout_add_short(100, test_connection_resume_continue, conn);
+ return 0;
+ } else if (strcmp(args[0], "QUIT") == 0) {
+ received_quit = TRUE;
+ connection_disconnect(conn);
+ }
+
+ return 1;
+}
+
+static const struct connection_vfuncs resume_pipelined_v =
+{
+ .client_connected = test_connection_resume_client_connected,
+ .input_args = test_connection_resume_pipelined_input_args,
+ .destroy = test_connection_simple_destroy,
+};
+
+static void test_connection_resume_pipelined(void)
+{
+ test_begin("connection resume pipelined");
+
+ was_resumed = received_quit = FALSE;
+ test_connection_run(&server_set, &client_set,
+ &resume_pipelined_v, &resume_pipelined_v, 1);
+
+ test_assert(was_resumed);
+ test_assert(received_quit);
+ was_resumed = received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN IDLE KILL TEST */
+
+static void
+test_connection_idle_kill_client_connected(struct connection *conn ATTR_UNUSED,
+ bool success)
+{
+ test_assert(success);
+};
+
+static const struct connection_settings idle_kill_server_set =
+{
+ .service_name_in = "TEST-C",
+ .service_name_out = "TEST-S",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = FALSE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .input_idle_timeout_secs = 1,
+};
+
+static void test_connection_idle_kill_timeout(struct connection *conn)
+{
+ was_idle_killed = TRUE;
+ o_stream_nsend_str(conn->output, "QUIT\n");
+}
+
+static const struct connection_vfuncs idle_kill_v =
+{
+ .client_connected = test_connection_idle_kill_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .destroy = test_connection_simple_destroy,
+ .idle_timeout = test_connection_idle_kill_timeout,
+};
+
+static void test_connection_idle_kill(void)
+{
+ test_begin("connection idle kill");
+
+ was_idle_killed = received_quit = FALSE;
+ test_connection_run(&idle_kill_server_set, &client_set, &idle_kill_v,
+ &idle_kill_v, 1);
+
+ test_assert(received_quit);
+ test_assert(was_idle_killed);
+ was_idle_killed = received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN HANDSHAKE FAILED TEST (version) */
+
+static void test_connection_handshake_failed_destroy(struct connection *conn)
+{
+ test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_HANDSHAKE_FAILED);
+ test_connection_simple_destroy(conn);
+}
+
+static const struct connection_vfuncs handshake_failed_version_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .destroy = test_connection_handshake_failed_destroy,
+};
+
+static void test_connection_handshake_failed_version(void)
+{
+ static const struct connection_settings client_sets[] = {
+ {
+ .service_name_in = "TEST-S",
+ .service_name_out = "TEST-S",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ },
+ {
+ .service_name_in = "TEST-C",
+ .service_name_out = "TEST-C",
+ .major_version = 1,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ },
+ {
+ .service_name_in = "TEST-S",
+ .service_name_out = "TEST-C",
+ .major_version = 2,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ }
+ };
+
+ static const struct connection_settings client_set_minor = {
+ .service_name_in = "TEST-S",
+ .service_name_out = "TEST-C",
+ .major_version = 1,
+ .minor_version = 2,
+ .client = TRUE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ };
+
+ test_begin("connection handshake failed (version)");
+
+ test_expect_errors(N_ELEMENTS(client_sets));
+
+ /* this should stay FALSE during the version mismatch sets */
+ received_quit = FALSE;
+ for (size_t i = 0; i < N_ELEMENTS(client_sets); i++) {
+ test_connection_run(&server_set, &client_sets[i], &simple_v,
+ &handshake_failed_version_v, 1);
+ test_assert(!received_quit);
+ }
+
+ received_quit = FALSE;
+ test_connection_run(&server_set, &client_set_minor, &simple_v,
+ &simple_v, 1);
+ test_assert(received_quit);
+ received_quit = FALSE;
+
+ test_end();
+}
+
+/* BEGIN HANDSHAKE FAILED TEST (args) */
+
+static int test_connection_handshake_failed_1_args(struct connection *conn ATTR_UNUSED,
+ const char *const *args ATTR_UNUSED)
+{
+ /* just fail */
+ return -1;
+}
+
+static const struct connection_vfuncs handshake_failed_1_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .handshake_args = test_connection_handshake_failed_1_args,
+ .destroy = test_connection_handshake_failed_destroy,
+};
+
+static void test_connection_handshake_failed_args(void)
+{
+ test_begin("connection handshake failed (handshake_args)");
+
+ test_connection_run(&server_set, &client_set, &simple_v,
+ &handshake_failed_1_v, 10);
+
+ test_end();
+}
+
+/* BEGIN HANDSHAKE FAILED TEST (handshake_line) */
+
+static int test_connection_handshake_failed_2_line(struct connection *conn ATTR_UNUSED,
+ const char *line ATTR_UNUSED)
+{
+ return -1;
+}
+
+static const struct connection_vfuncs handshake_failed_2_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .handshake_line = test_connection_handshake_failed_2_line,
+ .destroy = test_connection_handshake_failed_destroy,
+};
+
+static void test_connection_handshake_failed_line(void)
+{
+ test_begin("connection handshake failed (handshake_line)");
+
+ test_connection_run(&server_set, &client_set, &simple_v,
+ &handshake_failed_2_v, 10);
+
+ test_end();
+}
+
+/* BEGIN HANDSHAKE FAILED TEST (handshake) */
+
+static int test_connection_handshake_failed_3(struct connection *conn ATTR_UNUSED)
+{
+ return -1;
+}
+
+static const struct connection_vfuncs handshake_failed_3_v =
+{
+ .client_connected = test_connection_simple_client_connected,
+ .input_args = test_connection_simple_input_args,
+ .handshake = test_connection_handshake_failed_3,
+ .destroy = test_connection_handshake_failed_destroy,
+};
+
+static void test_connection_handshake_failed_input(void)
+{
+ test_begin("connection handshake failed (handshake)");
+
+ test_connection_run(&server_set, &client_set, &simple_v,
+ &handshake_failed_3_v, 10);
+
+ test_end();
+}
+
+/* BEGIN CONNECTION ERRORED TEST (ensure correct error) */
+
+static void test_connection_errored_client_connected(struct connection *conn,
+ bool success)
+{
+ test_assert(success);
+ o_stream_nsend_str(conn->output, "HELLO\n");
+}
+
+static void test_connection_errored_destroy(struct connection *conn)
+{
+ test_assert(conn->disconnect_reason == CONNECTION_DISCONNECT_DEINIT);
+ test_connection_simple_destroy(conn);
+}
+
+static int test_connection_errored_input_line(struct connection *conn ATTR_UNUSED,
+ const char *line)
+{
+ if (str_begins(line, "VERSION"))
+ return 1;
+ return -1;
+}
+
+static const struct connection_vfuncs test_connection_errored_1_v =
+{
+ .client_connected = test_connection_errored_client_connected,
+ .input_line = test_connection_errored_input_line,
+ .destroy = test_connection_errored_destroy,
+};
+
+static void test_connection_input_error_reason(void)
+{
+ test_begin("connection input error (correct disconnect reason)");
+
+ test_connection_run(&server_set, &client_set, &test_connection_errored_1_v,
+ &test_connection_errored_1_v, 10);
+
+ test_end();
+}
+
+/* END CONNECTION ERRORED TEST */
+
+/* BEGIN NO VERSION TEST */
+
+static const struct connection_settings no_version_client_set =
+{
+ .major_version = 0,
+ .minor_version = 0,
+ .client = TRUE,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .dont_send_version = TRUE,
+};
+
+static const struct connection_settings no_version_server_set =
+{
+ .major_version = 0,
+ .minor_version = 0,
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .dont_send_version = TRUE,
+};
+
+static void test_connection_no_version(void)
+{
+ test_begin("connection no version sent");
+
+ test_connection_run(&no_version_server_set, &no_version_client_set,
+ &simple_v, &simple_v, 10);
+
+ test_end();
+}
+
+/* END NO VERSION TEST */
+
+void test_connection(void)
+{
+ test_connection_simple();
+ test_connection_no_input();
+ test_connection_custom_handshake();
+ test_connection_ping_pong();
+ test_connection_input_full();
+ test_connection_resume();
+ test_connection_resume_pipelined();
+ test_connection_idle_kill();
+ test_connection_handshake_failed_version();
+ test_connection_handshake_failed_args();
+ test_connection_handshake_failed_line();
+ test_connection_handshake_failed_input();
+ test_connection_input_error_reason();
+ test_connection_no_version();
+}