From e2edd4070ab5fe5e8f506cfc7fa6296eb923e919 Mon Sep 17 00:00:00 2001 From: Timerix Date: Tue, 23 Dec 2025 00:27:02 +0500 Subject: [PATCH] implemented ClientCLI DB queries --- .vscode/launch.json | 62 ++++++++++++---- src/cli/ClientCLI/ClientCLI.c | 16 ++--- src/cli/ClientCLI/db/SavedServer.c | 86 +++++++++++++++++++++-- src/cli/ClientCLI/db/client_db.c | 71 +++++++++++++++++-- src/cli/ClientCLI/db/client_db.h | 12 +++- src/cli/ClientCLI/db/client_db_internal.h | 13 +++- src/server/db/Channel.c | 14 ++-- src/server/db/User.c | 6 +- src/server/db/server_db.h | 3 +- src/server/db/server_db_internal.h | 16 ++--- src/server/responses/send_error.c | 4 +- tcp-chat-server.toml.default | 4 +- 12 files changed, 245 insertions(+), 62 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d8957a8..92b24fb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,29 +2,65 @@ "version": "0.2.0", "configurations": [ { - "name": "gdb_debug", + "name": "(gdb) Client | Build and debug", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/bin/tcp-chat", "windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" }, - // "args": [ "-l" ], "preLaunchTask": "build_exec_dbg", + "stopAtEntry": false, "cwd": "${workspaceFolder}/bin", "externalConsole": false, "internalConsoleOptions": "neverOpen", "MIMode": "gdb", - "miDebuggerPath": "gdb", - "setupCommands": [ - { - "text": "-enable-pretty-printing", - "ignoreFailures": true - }, - { - "text": "-gdb-set disassembly-flavor intel", - "ignoreFailures": true - } - ] + "miDebuggerPath": "gdb" + }, + { + "name": "(gdb) Client | Just debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/tcp-chat", + "windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" }, + + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb" + }, + + { + "name": "(gdb) Server | Build and debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/tcp-chat", + "windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" }, + "args": [ "-l" ], + "preLaunchTask": "build_exec_dbg", + + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb" + }, + { + "name": "(gdb) Server | Just debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/tcp-chat", + "windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" }, + "args": [ "-l" ], + + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb" } ] } \ No newline at end of file diff --git a/src/cli/ClientCLI/ClientCLI.c b/src/cli/ClientCLI/ClientCLI.c index 8101f83..ce79395 100644 --- a/src/cli/ClientCLI/ClientCLI.c +++ b/src/cli/ClientCLI/ClientCLI.c @@ -248,13 +248,13 @@ static Result(void) ClientCLI_joinNewServer(ClientCLI* self){ try_void(Client_getServerDescription(self->client, &server_description)); Defer(if(!success) str_destroy(server_description)); - SavedServer server = { - .address = address, - .pk_base64 = server_pk, - .name = server_name, - .description = server_description - }; - try_void(SavedServer_save(self->queries, &server)); + SavedServer server = SavedServer_construct( + address, + server_pk, + server_name, + server_description + ); + try_void(SavedServer_createOrUpdate(self->queries, &server)); List_SavedServer_pushMany(&self->saved_servers, &server, 1); try_void(ClientCLI_showSavedServer(self, &server)); @@ -324,7 +324,7 @@ static Result(void) ClientCLI_selectServerFromCache(ClientCLI* self){ } if(server_info_changed){ - try_void(SavedServer_save(self->queries, selected_server)); + try_void(SavedServer_createOrUpdate(self->queries, selected_server)); } try_void(ClientCLI_showSavedServer(self, selected_server)); diff --git a/src/cli/ClientCLI/db/SavedServer.c b/src/cli/ClientCLI/db/SavedServer.c index 85f9a51..e80662a 100644 --- a/src/cli/ClientCLI/db/SavedServer.c +++ b/src/cli/ClientCLI/db/SavedServer.c @@ -9,16 +9,90 @@ void SavedServer_destroy(SavedServer* self){ str_destroy(self->description); } -Result(void) SavedServer_save(ClientQueries* q, SavedServer* server){ - (void)q; - (void)server; +Result(bool) SavedServer_exists(ClientQueries* q, str address){ Deferral(4); - Return RESULT_VOID; + + tsqlite_statement* st = q->servers.exists; + Defer(tsqlite_statement_reset(st)); + try_void(tsqlite_statement_bind_str(st, "$address", address, NULL)); + + try(bool has_result, i, tsqlite_statement_step(st)); + + Return RESULT_VALUE(i, has_result); +} + +Result(bool) SavedServer_comparePublicKey(ClientQueries* q, str address, str pk_base64){ + Deferral(4); + + tsqlite_statement* st = q->servers.compare_pk; + Defer(tsqlite_statement_reset(st)); + try_void(tsqlite_statement_bind_str(st, "$address", address, NULL)); + try_void(tsqlite_statement_bind_str(st, "$pk_base64", pk_base64, NULL)); + + try(bool has_result, i, tsqlite_statement_step(st)); + + Return RESULT_VALUE(i, has_result); +} + +Result(void) SavedServer_createOrUpdate(ClientQueries* q, SavedServer* server){ + Deferral(4); + try_assert(server->address.len >= HOSTADDR_SIZE_MIN && server->address.len <= HOSTADDR_SIZE_MAX); + try_assert(server->pk_base64.len > 0 && server->pk_base64.len <= PUBLIC_KEY_BASE64_SIZE_MAX); + try_assert(server->name.len >= SERVER_NAME_SIZE_MIN && server->name.len <= SERVER_NAME_SIZE_MAX); + try_assert(server->description.len <= SERVER_DESC_SIZE_MAX); + + try(bool server_exists, i, SavedServer_exists(q, server->address)); + tsqlite_statement* st = NULL; + Defer(tsqlite_statement_reset(st)); + if(server_exists){ + st = q->servers.update; + try(bool pk_matches, i, SavedServer_comparePublicKey(q, server->address, server->pk_base64)); + if(!pk_matches){ + Return RESULT_ERROR_FMT( + "trying to update server '"FMT_str"' but public keys don't match", + str_unwrap(server->address)); + } + } + else { + st = q->servers.insert; + try_void(tsqlite_statement_bind_str(st, "$pk_base64", server->pk_base64, NULL)); + } + try_void(tsqlite_statement_bind_str(st, "$address", server->address, NULL)); + try_void(tsqlite_statement_bind_str(st, "$name", server->name, NULL)); + try_void(tsqlite_statement_bind_str(st, "$description", server->description, NULL)); + try_void(tsqlite_statement_step(st)); + + Return RESULT_VALUE(i, !server_exists); } Result(void) SavedServer_getAll(ClientQueries* q, List(SavedServer)* dst_list){ - (void)q; - (void)dst_list; Deferral(4); + + tsqlite_statement* st = q->servers.get_all; + Defer(tsqlite_statement_reset(st)); + + SavedServer server = SavedServer_construct(str_null, str_null, str_null, str_null); + str tmp_str = str_null; + while(true){ + try(bool has_result, i, tsqlite_statement_step(st)); + if(!has_result) + break; + + // address + try_void(tsqlite_statement_getResult_str(st, &tmp_str)); + server.address = str_copy(tmp_str); + // pk_base64 + try_void(tsqlite_statement_getResult_str(st, &tmp_str)); + server.pk_base64 = str_copy(tmp_str); + // name + try_void(tsqlite_statement_getResult_str(st, &tmp_str)); + server.name = str_copy(tmp_str); + // description + try_void(tsqlite_statement_getResult_str(st, &tmp_str)); + server.description = str_copy(tmp_str); + + List_SavedServer_pushMany(dst_list, &server, 1); + } + Return RESULT_VOID; } diff --git a/src/cli/ClientCLI/db/client_db.c b/src/cli/ClientCLI/db/client_db.c index 68e4434..e6d84a0 100644 --- a/src/cli/ClientCLI/db/client_db.c +++ b/src/cli/ClientCLI/db/client_db.c @@ -9,16 +9,73 @@ Result(tsqlite_connection* db) ClientDatabase_open(cstr file_path){ bool success = false; Defer(if(!success) tsqlite_connection_close(db)); + /////////////////////////////////////////////////////////////////////////// + // SERVERS // + /////////////////////////////////////////////////////////////////////////// + try(tsqlite_statement* create_table_servers, p, tsqlite_statement_compile(db, STR( + "CREATE TABLE IF NOT EXISTS servers (\n" + " address VARCHAR PRIMARY KEY,\n" + " pk_base64 VARCHAR NOT NULL,\n" + " name VARCHAR NOT NULL,\n" + " description VARCHAR NOT NULL\n" + ");" + ))); + Defer(tsqlite_statement_free(create_table_servers)); + try_void(tsqlite_statement_step(create_table_servers)); + success = true; Return RESULT_VALUE(p, db); } -Result(ClientQueries*) ClientQueries_compile(tsqlite_connection* db){ - (void)db; - Deferral(4); - Return RESULT_VOID; + +void ClientQueries_free(ClientQueries* q){ + if(!q) + return; + + tsqlite_statement_free(q->servers.insert); + tsqlite_statement_free(q->servers.update); + tsqlite_statement_free(q->servers.exists); + tsqlite_statement_free(q->servers.compare_pk); + tsqlite_statement_free(q->servers.get_all); + + free(q); } -void ClientQueries_free(ClientQueries* self){ - (void)self; -} \ No newline at end of file +Result(ClientQueries*) ClientQueries_compile(tsqlite_connection* db){ + Deferral(4); + + ClientQueries* q = (ClientQueries*)malloc(sizeof(*q)); + zeroStruct(q); + bool success = false; + Defer(if(!success) ClientQueries_free(q)); + + /////////////////////////////////////////////////////////////////////////// + // SERVERS // + /////////////////////////////////////////////////////////////////////////// + try(q->servers.insert, p, tsqlite_statement_compile(db, STR( + "INSERT INTO\n" + "servers (address, pk_base64, name, description)\n" + "VALUES ($address, $pk_base64, $name, $description);" + ))); + + try(q->servers.update, p, tsqlite_statement_compile(db, STR( + "UPDATE servers\n" + "SET name = $name, description = $description\n" + "WHERE address = $address;" + ))); + + try(q->servers.exists, p, tsqlite_statement_compile(db, STR( + "SELECT 1 FROM servers WHERE address = $address;" + ))); + + try(q->servers.compare_pk, p, tsqlite_statement_compile(db, STR( + "SELECT 1 FROM servers WHERE address = $address AND pk_base64 = $pk_base64;" + ))); + + try(q->servers.get_all, p, tsqlite_statement_compile(db, STR( + "SELECT * FROM servers;" + ))); + + success = true; + Return RESULT_VALUE(p, q); +} diff --git a/src/cli/ClientCLI/db/client_db.h b/src/cli/ClientCLI/db/client_db.h index c109bb2..ecbe57f 100644 --- a/src/cli/ClientCLI/db/client_db.h +++ b/src/cli/ClientCLI/db/client_db.h @@ -21,10 +21,18 @@ typedef struct SavedServer { List_declare(SavedServer); +#define SavedServer_construct(ADDR, PK, NAME, DESC) ((SavedServer){ \ + .address = ADDR, .pk_base64 = PK, .name = NAME, .description = DESC }) + void SavedServer_destroy(SavedServer* self); -/// @brief insert new DB row or update existing -Result(void) SavedServer_save(ClientQueries* q, SavedServer* server); +/// @return true if new row was created +Result(bool) SavedServer_createOrUpdate(ClientQueries* q, SavedServer* server); /// @param dst_list there SavedServer values are pushed Result(void) SavedServer_getAll(ClientQueries* q, List(SavedServer)* dst_list); + +Result(bool) SavedServer_exists(ClientQueries* q, str address); + +/// @return true if provided key and saved key match +Result(bool) SavedServer_comparePublicKey(ClientQueries* q, str address, str pk_base64); diff --git a/src/cli/ClientCLI/db/client_db_internal.h b/src/cli/ClientCLI/db/client_db_internal.h index 1d5d8a9..54d0be1 100644 --- a/src/cli/ClientCLI/db/client_db_internal.h +++ b/src/cli/ClientCLI/db/client_db_internal.h @@ -3,8 +3,15 @@ typedef struct ClientQueries { struct { - tsqlite_statement* find_by_id; + /* ($address, $pk_base64, $name, $description) -> void */ + tsqlite_statement* insert; + /* ($address, $name, $description) -> void */ + tsqlite_statement* update; + /* ($address) -> 1 or nothing */ + tsqlite_statement* exists; + /* ($address, $pk_base64) -> 1 or nothing */ + tsqlite_statement* compare_pk; + /* () -> [(*)] */ + tsqlite_statement* get_all; } servers; } ClientQueries; - - diff --git a/src/server/db/Channel.c b/src/server/db/Channel.c index 0668fc0..4a0ac2c 100644 --- a/src/server/db/Channel.c +++ b/src/server/db/Channel.c @@ -1,7 +1,7 @@ #include "server_db_internal.h" Result(bool) Channel_exists(ServerQueries* q, i64 id){ - Deferral(1); + Deferral(4); tsqlite_statement* st = q->channels.exists; Defer(tsqlite_statement_reset(st)); @@ -12,7 +12,7 @@ Result(bool) Channel_exists(ServerQueries* q, i64 id){ Return RESULT_VALUE(i, has_result); } -Result(void) Channel_createOrUpdate(ServerQueries* q, +Result(bool) Channel_createOrUpdate(ServerQueries* q, i64 id, str name, str description) { Deferral(4); @@ -20,15 +20,13 @@ Result(void) Channel_createOrUpdate(ServerQueries* q, try_assert(name.len >= CHANNEL_NAME_SIZE_MIN && name.len <= CHANNEL_NAME_SIZE_MAX); try_assert(description.len <= CHANNEL_DESC_SIZE_MAX); + try(bool channel_exists, i, Channel_exists(q, id)); tsqlite_statement* st = NULL; Defer(tsqlite_statement_reset(st)); - try(bool channel_exists, i, Channel_exists(q, id)); if(channel_exists){ - // update existing channel st = q->channels.update; } else { - // insert new channel st = q->channels.insert; } try_void(tsqlite_statement_bind_i64(st, "$id", id)); @@ -36,14 +34,14 @@ Result(void) Channel_createOrUpdate(ServerQueries* q, try_void(tsqlite_statement_bind_str(st, "$description", description, NULL)); try_void(tsqlite_statement_step(st)); - Return RESULT_VOID; + Return RESULT_VALUE(i, !channel_exists); } Result(void) Channel_saveMessage(ServerQueries* q, i64 channel_id, i64 sender_id, Array(u8) content, DateTime* out_timestamp) { - Deferral(1); + Deferral(4); try_assert(content.len >= MESSAGE_SIZE_MIN && content.len <= MESSAGE_SIZE_MAX); tsqlite_statement* st = q->messages.insert; @@ -67,7 +65,7 @@ Result(void) Channel_loadMessageBlock(ServerQueries* q, i64 channel_id, i64 first_message_id, u32 count, MessageBlockMeta* block_meta, Array(u8) block_data) { - Deferral(1); + Deferral(4); try_assert(channel_id > 0); try_assert(block_data.len >= count * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX)); if(count == 0){ diff --git a/src/server/db/User.c b/src/server/db/User.c index 1486fa2..00b545c 100644 --- a/src/server/db/User.c +++ b/src/server/db/User.c @@ -1,7 +1,7 @@ #include "server_db_internal.h" Result(i64) User_findByUsername(ServerQueries* q, str username){ - Deferral(1); + Deferral(4); tsqlite_statement* st = q->users.find_by_username; Defer(tsqlite_statement_reset(st)); @@ -18,7 +18,7 @@ Result(i64) User_findByUsername(ServerQueries* q, str username){ } Result(i64) User_register(ServerQueries* q, str username, Array(u8) token){ - Deferral(1); + Deferral(4); try_assert(username.len >= USERNAME_SIZE_MIN && username.len <= USERNAME_SIZE_MAX); try_assert(token.len == PASSWORD_HASH_SIZE) @@ -36,7 +36,7 @@ Result(i64) User_register(ServerQueries* q, str username, Array(u8) token){ } Result(bool) User_tryAuthorize(ServerQueries* q, u64 id, Array(u8) token){ - Deferral(1); + Deferral(4); try_assert(token.len == PASSWORD_HASH_SIZE) tsqlite_statement* st = q->users.compare_token; diff --git a/src/server/db/server_db.h b/src/server/db/server_db.h index b08dbff..7cceac6 100644 --- a/src/server/db/server_db.h +++ b/src/server/db/server_db.h @@ -13,7 +13,8 @@ void ServerQueries_free(ServerQueries* self); Result(bool) Channel_exists(ServerQueries* q, i64 id); -Result(void) Channel_createOrUpdate(ServerQueries* q, +/// @return true if new row was created +Result(bool) Channel_createOrUpdate(ServerQueries* q, i64 id, str name, str description); /// @return new message id diff --git a/src/server/db/server_db_internal.h b/src/server/db/server_db_internal.h index 212189c..7c6b94e 100644 --- a/src/server/db/server_db_internal.h +++ b/src/server/db/server_db_internal.h @@ -3,25 +3,25 @@ typedef struct ServerQueries { struct { - /* (id, name, description) -> void */ + /* ($id, $name, $description) -> void */ tsqlite_statement* insert; - /* (id, name, description) -> void */ + /* ($id, $name, $description) -> void */ tsqlite_statement* update; - /* (id) -> 1 or nothing */ + /* ($id) -> 1 or nothing */ tsqlite_statement* exists; } channels; struct { - /* (channel_id, sender_id, content) -> (id, timestamp) */ + /* ($channel_id, $sender_id, $content) -> (id, timestamp) */ tsqlite_statement* insert; - /* (channel_id, first_message_id, count) -> [(id, sender_id, content, timestamp)] */ + /* ($channel_id, $first_message_id, $count) -> [(id, sender_id, content, timestamp)] */ tsqlite_statement* get_block; } messages; struct { - /* (username, token) -> (id, registration_time) */ + /* ($username, $token) -> (id, registration_time) */ tsqlite_statement* insert; - /* (username) -> (id) */ + /* ($username) -> (id) */ tsqlite_statement* find_by_username; - /* (id, token) -> 1 or nothing */ + /* ($id, $token) -> 1 or nothing */ tsqlite_statement* compare_token; } users; } ServerQueries; diff --git a/src/server/responses/send_error.c b/src/server/responses/send_error.c index 0866ba4..515a21e 100644 --- a/src/server/responses/send_error.c +++ b/src/server/responses/send_error.c @@ -9,7 +9,7 @@ Result(void) sendErrorMessage( cstr log_ctx, ClientConnection* conn, PacketHeader* res_head, LogSeverity log_severity, str msg) { - Deferral(1); + Deferral(4); //limit ErrorMessage size to fit into EncryptedSocketTCP.internal_buffer_size if(msg.len > ERROR_MESSAGE_MAX_SIZE) @@ -44,7 +44,7 @@ Result(void) sendErrorMessage_f( ClientConnection* conn, PacketHeader* res_head, LogSeverity log_severity, cstr format, ...) { - Deferral(1); + Deferral(4); va_list argv; va_start(argv, format); diff --git a/tcp-chat-server.toml.default b/tcp-chat-server.toml.default index 52f31fd..ff0f50d 100644 --- a/tcp-chat-server.toml.default +++ b/tcp-chat-server.toml.default @@ -13,7 +13,9 @@ id = 1 description = "a text channel" [database] -path = 'server-db/server.sqlite' +path = 'tcp-chat-server/server.sqlite' +# on windows use backslashes +# path = 'tcp-chat-server\server.sqlite' [keys] rsa_private_key = ''