From ff04c1c1446d5e1c49730e5e18c08119c2deb4c7 Mon Sep 17 00:00:00 2001 From: Paarth Madan Date: Fri, 3 Mar 2023 18:49:09 -0500 Subject: [PATCH] Support setting server options dynamically This will be particularly useful for enabling/disabling multi statements on the fly, after a connection has already been initialized. --- inc/trilogy/client.h | 44 ++++++ inc/trilogy/protocol.h | 14 ++ src/client.c | 19 +++ src/protocol.c | 17 +++ test/client/set_option_test.c | 126 ++++++++++++++++++ .../building/set_option_packet_test.c | 39 ++++++ test/runner.c | 2 + 7 files changed, 261 insertions(+) create mode 100644 test/client/set_option_test.c create mode 100644 test/protocol/building/set_option_packet_test.c diff --git a/inc/trilogy/client.h b/inc/trilogy/client.h index 0201ba5a..cbf131e5 100644 --- a/inc/trilogy/client.h +++ b/inc/trilogy/client.h @@ -299,6 +299,50 @@ int trilogy_change_db_send(trilogy_conn_t *conn, const char *name, size_t name_l */ int trilogy_change_db_recv(trilogy_conn_t *conn); +/* trilogy_set_option_send - Send a set option command to the server. This + * will change server capabilities based on the option selected. + * + * This should only be called while the connection is ready for commands. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected + * trilogy_conn_t is undefined. + * option - The server option to send. + * + * Return values: + * TRILOGY_OK - The change database command was successfully sent to the + * server. + * TRILOGY_AGAIN - The socket wasn't ready for writing. The caller should wait + * for writeability using `conn->sock`. Then call + * trilogy_flush_writes. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_set_option_send(trilogy_conn_t *conn, const uint16_t option); + +/* trilogy_set_option_recv - Read the set option command response from the + * server. + * + * This should be called after all data written by trilogy_set_option_send is + * flushed to the network. Calling this at any other time during the connection + * lifecycle is undefined. + * + * conn - A connected trilogy_conn_t pointer. Using a disconnected trilogy_conn_t is + * undefined. + * + * Return values: + * TRILOGY_OK - The set option command was successfully + * sent to the server. + * TRILOGY_AGAIN - The socket wasn't ready for reading. The + * caller should wait for readability using + * `conn->sock`. Then call this function until + * it returns a different value. + * TRILOGY_UNEXPECTED_PACKET - The response packet wasn't what was expected. + * TRILOGY_PROTOCOL_VIOLATION - An error occurred while processing a network + * packet. + * TRILOGY_CLOSED_CONNECTION - The connection is closed. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_set_option_recv(trilogy_conn_t *conn); + /* trilogy_query_send - Send a query command to the server. * * This should only be called while the connection is ready for commands. diff --git a/inc/trilogy/protocol.h b/inc/trilogy/protocol.h index 3e982633..6984ee31 100644 --- a/inc/trilogy/protocol.h +++ b/inc/trilogy/protocol.h @@ -456,6 +456,20 @@ int trilogy_build_auth_switch_response_packet(trilogy_builder_t *builder, const */ int trilogy_build_change_db_packet(trilogy_builder_t *builder, const char *name, size_t name_len); +/* trilogy_build_set_option_packet - Build a set option command packet. This + * command will enable/disable server capabilities for the connection. Options + * must be one of `enum_mysql_set_option`. + * + * builder - A pointer to a pre-initialized trilogy_builder_t. + * option - An integer corresponding to the operation to perform. + * + * Return values: + * TRILOGY_OK - The packet was successfully built and written to the + * builder's internal buffer. + * TRILOGY_SYSERR - A system error occurred, check errno. + */ +int trilogy_build_set_option_packet(trilogy_builder_t *builder, const uint16_t option); + /* trilogy_build_ping_packet - Build a ping command packet. * * builder - A pointer to a pre-initialized trilogy_builder_t. diff --git a/src/client.c b/src/client.c index 9cdb9238..318ebecb 100644 --- a/src/client.c +++ b/src/client.c @@ -466,6 +466,25 @@ int trilogy_change_db_send(trilogy_conn_t *conn, const char *name, size_t name_l int trilogy_change_db_recv(trilogy_conn_t *conn) { return read_generic_response(conn); } +int trilogy_set_option_send(trilogy_conn_t *conn, const uint16_t option) +{ + trilogy_builder_t builder; + int err = begin_command_phase(&builder, conn, 0); + if (err < 0) { + return err; + } + + err = trilogy_build_set_option_packet(&builder, option); + + if (err < 0) { + return err; + } + + return begin_write(conn); +} + +int trilogy_set_option_recv(trilogy_conn_t *conn) { return read_generic_response(conn); } + int trilogy_ping_send(trilogy_conn_t *conn) { trilogy_builder_t builder; diff --git a/src/protocol.c b/src/protocol.c index debce1b0..0ea39c4f 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -10,6 +10,7 @@ #define TRILOGY_CMD_CHANGE_DB 0x02 #define TRILOGY_CMD_QUERY 0x03 #define TRILOGY_CMD_PING 0x0e +#define TRILOGY_CMD_SET_OPTION 0x1a #define SCRAMBLE_LEN 20 @@ -646,6 +647,22 @@ int trilogy_build_quit_packet(trilogy_builder_t *builder) return rc; } +int trilogy_build_set_option_packet(trilogy_builder_t *builder, const uint16_t option) +{ + int rc = TRILOGY_OK; + + CHECKED(trilogy_builder_write_uint8(builder, TRILOGY_CMD_SET_OPTION)); + CHECKED(trilogy_builder_write_uint16(builder, option)); + + trilogy_builder_finalize(builder); + + return TRILOGY_OK; + +fail: + return rc; +} + + int trilogy_build_ssl_request_packet(trilogy_builder_t *builder, TRILOGY_CAPABILITIES_t flags) { static const char zeroes[23] = {0}; diff --git a/test/client/set_option_test.c b/test/client/set_option_test.c new file mode 100644 index 00000000..bb58b4ba --- /dev/null +++ b/test/client/set_option_test.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include + +#include "../test.h" + +#include "trilogy/blocking.h" +#include "trilogy/client.h" +#include "trilogy/error.h" + +#define do_connect(CONN) \ + do { \ + int err = trilogy_init(CONN); \ + ASSERT_OK(err); \ + err = trilogy_connect(CONN, get_connopt()); \ + ASSERT_OK(err); \ + } while (0) + +TEST test_set_option_send() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const uint16_t option = 1; + + int err = trilogy_set_option_send(&conn, option); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_set_option_send_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + close_socket(&conn); + + const uint16_t option = 1; + + int err = trilogy_set_option_send(&conn, option); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +TEST test_set_option_recv() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const uint16_t option = 0; + + int err = trilogy_set_option_send(&conn, option); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + err = trilogy_set_option_recv(&conn); + while (err == TRILOGY_AGAIN) { + err = wait_readable(&conn); + ASSERT_OK(err); + + err = trilogy_set_option_recv(&conn); + } + + printf("%d %s\n", conn.error_code, conn.error_message); + + ASSERT_OK(err); // TODO: Figure out why this assertion fails... + + trilogy_free(&conn); + PASS(); +} + +TEST test_set_option_recv_closed_socket() +{ + trilogy_conn_t conn; + + do_connect(&conn); + + const uint16_t option = 1; + + int err = trilogy_set_option_send(&conn, option); + while (err == TRILOGY_AGAIN) { + err = wait_writable(&conn); + ASSERT_OK(err); + + err = trilogy_flush_writes(&conn); + } + ASSERT_OK(err); + + close_socket(&conn); + + err = trilogy_set_option_recv(&conn); + ASSERT_ERR(TRILOGY_SYSERR, err); + + trilogy_free(&conn); + PASS(); +} + +int client_set_option_test() +{ + RUN_TEST(test_set_option_send); + RUN_TEST(test_set_option_send_closed_socket); + RUN_TEST(test_set_option_recv); + RUN_TEST(test_set_option_recv_closed_socket); + + return 0; +} diff --git a/test/protocol/building/set_option_packet_test.c b/test/protocol/building/set_option_packet_test.c new file mode 100644 index 00000000..565502e8 --- /dev/null +++ b/test/protocol/building/set_option_packet_test.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "../../test.h" + +#include "trilogy/error.h" +#include "trilogy/protocol.h" + +TEST test_build_set_option_packet() +{ + trilogy_builder_t builder; + trilogy_buffer_t buff; + + int err = trilogy_buffer_init(&buff, 1); + ASSERT_OK(err); + + err = trilogy_builder_init(&builder, &buff, 0); + ASSERT_OK(err); + + const uint16_t option = 1; + + err = trilogy_build_set_option_packet(&builder, option); + ASSERT_OK(err); + + static const uint8_t expected[] = {0x03, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x00}; + + ASSERT_MEM_EQ(buff.buff, expected, buff.len); + + trilogy_buffer_free(&buff); + PASS(); +} + +int build_set_option_packet_test() +{ + RUN_TEST(test_build_set_option_packet); + + return 0; +} diff --git a/test/runner.c b/test/runner.c index bdd1a79d..6b45baaf 100644 --- a/test/runner.c +++ b/test/runner.c @@ -31,11 +31,13 @@ const trilogy_sockopt_t *get_connopt(void) { return &connopt; } SUITE(build_change_db_packet_test) \ SUITE(build_ping_packet_test) \ SUITE(build_quit_packet_test) \ + SUITE(build_set_option_packet_test) \ SUITE(build_query_packet_test) \ SUITE(client_connect_test) \ SUITE(client_escape_test) \ SUITE(client_auth_test) \ SUITE(client_change_db_test) \ + SUITE(client_set_option_test) \ SUITE(client_ping_test) #define XX(name) extern int name();