implemented ClientCLI DB queries

This commit is contained in:
2025-12-23 00:27:02 +05:00
parent d461cae077
commit e2edd4070a
12 changed files with 245 additions and 62 deletions

58
.vscode/launch.json vendored
View File

@@ -2,29 +2,65 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "gdb_debug", "name": "(gdb) Client | Build and debug",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/bin/tcp-chat", "program": "${workspaceFolder}/bin/tcp-chat",
"windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" }, "windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" },
// "args": [ "-l" ],
"preLaunchTask": "build_exec_dbg", "preLaunchTask": "build_exec_dbg",
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}/bin", "cwd": "${workspaceFolder}/bin",
"externalConsole": false, "externalConsole": false,
"internalConsoleOptions": "neverOpen", "internalConsoleOptions": "neverOpen",
"MIMode": "gdb", "MIMode": "gdb",
"miDebuggerPath": "gdb", "miDebuggerPath": "gdb"
"setupCommands": [
{
"text": "-enable-pretty-printing",
"ignoreFailures": true
}, },
{ {
"text": "-gdb-set disassembly-flavor intel", "name": "(gdb) Client | Just debug",
"ignoreFailures": true "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"
} }
] ]
} }

View File

@@ -248,13 +248,13 @@ static Result(void) ClientCLI_joinNewServer(ClientCLI* self){
try_void(Client_getServerDescription(self->client, &server_description)); try_void(Client_getServerDescription(self->client, &server_description));
Defer(if(!success) str_destroy(server_description)); Defer(if(!success) str_destroy(server_description));
SavedServer server = { SavedServer server = SavedServer_construct(
.address = address, address,
.pk_base64 = server_pk, server_pk,
.name = server_name, server_name,
.description = server_description server_description
}; );
try_void(SavedServer_save(self->queries, &server)); try_void(SavedServer_createOrUpdate(self->queries, &server));
List_SavedServer_pushMany(&self->saved_servers, &server, 1); List_SavedServer_pushMany(&self->saved_servers, &server, 1);
try_void(ClientCLI_showSavedServer(self, &server)); try_void(ClientCLI_showSavedServer(self, &server));
@@ -324,7 +324,7 @@ static Result(void) ClientCLI_selectServerFromCache(ClientCLI* self){
} }
if(server_info_changed){ 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)); try_void(ClientCLI_showSavedServer(self, selected_server));

View File

@@ -9,16 +9,90 @@ void SavedServer_destroy(SavedServer* self){
str_destroy(self->description); str_destroy(self->description);
} }
Result(void) SavedServer_save(ClientQueries* q, SavedServer* server){ Result(bool) SavedServer_exists(ClientQueries* q, str address){
(void)q;
(void)server;
Deferral(4); 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){ Result(void) SavedServer_getAll(ClientQueries* q, List(SavedServer)* dst_list){
(void)q;
(void)dst_list;
Deferral(4); 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; Return RESULT_VOID;
} }

View File

@@ -9,16 +9,73 @@ Result(tsqlite_connection* db) ClientDatabase_open(cstr file_path){
bool success = false; bool success = false;
Defer(if(!success) tsqlite_connection_close(db)); 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; success = true;
Return RESULT_VALUE(p, db); Return RESULT_VALUE(p, db);
} }
Result(ClientQueries*) ClientQueries_compile(tsqlite_connection* db){
(void)db; void ClientQueries_free(ClientQueries* q){
Deferral(4); if(!q)
Return RESULT_VOID; 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){ Result(ClientQueries*) ClientQueries_compile(tsqlite_connection* db){
(void)self; 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);
} }

View File

@@ -21,10 +21,18 @@ typedef struct SavedServer {
List_declare(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); void SavedServer_destroy(SavedServer* self);
/// @brief insert new DB row or update existing /// @return true if new row was created
Result(void) SavedServer_save(ClientQueries* q, SavedServer* server); Result(bool) SavedServer_createOrUpdate(ClientQueries* q, SavedServer* server);
/// @param dst_list there SavedServer values are pushed /// @param dst_list there SavedServer values are pushed
Result(void) SavedServer_getAll(ClientQueries* q, List(SavedServer)* dst_list); 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);

View File

@@ -3,8 +3,15 @@
typedef struct ClientQueries { typedef struct ClientQueries {
struct { 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; } servers;
} ClientQueries; } ClientQueries;

View File

@@ -1,7 +1,7 @@
#include "server_db_internal.h" #include "server_db_internal.h"
Result(bool) Channel_exists(ServerQueries* q, i64 id){ Result(bool) Channel_exists(ServerQueries* q, i64 id){
Deferral(1); Deferral(4);
tsqlite_statement* st = q->channels.exists; tsqlite_statement* st = q->channels.exists;
Defer(tsqlite_statement_reset(st)); Defer(tsqlite_statement_reset(st));
@@ -12,7 +12,7 @@ Result(bool) Channel_exists(ServerQueries* q, i64 id){
Return RESULT_VALUE(i, has_result); Return RESULT_VALUE(i, has_result);
} }
Result(void) Channel_createOrUpdate(ServerQueries* q, Result(bool) Channel_createOrUpdate(ServerQueries* q,
i64 id, str name, str description) i64 id, str name, str description)
{ {
Deferral(4); 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(name.len >= CHANNEL_NAME_SIZE_MIN && name.len <= CHANNEL_NAME_SIZE_MAX);
try_assert(description.len <= CHANNEL_DESC_SIZE_MAX); try_assert(description.len <= CHANNEL_DESC_SIZE_MAX);
try(bool channel_exists, i, Channel_exists(q, id));
tsqlite_statement* st = NULL; tsqlite_statement* st = NULL;
Defer(tsqlite_statement_reset(st)); Defer(tsqlite_statement_reset(st));
try(bool channel_exists, i, Channel_exists(q, id));
if(channel_exists){ if(channel_exists){
// update existing channel
st = q->channels.update; st = q->channels.update;
} }
else { else {
// insert new channel
st = q->channels.insert; st = q->channels.insert;
} }
try_void(tsqlite_statement_bind_i64(st, "$id", id)); 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_bind_str(st, "$description", description, NULL));
try_void(tsqlite_statement_step(st)); try_void(tsqlite_statement_step(st));
Return RESULT_VOID; Return RESULT_VALUE(i, !channel_exists);
} }
Result(void) Channel_saveMessage(ServerQueries* q, Result(void) Channel_saveMessage(ServerQueries* q,
i64 channel_id, i64 sender_id, Array(u8) content, i64 channel_id, i64 sender_id, Array(u8) content,
DateTime* out_timestamp) DateTime* out_timestamp)
{ {
Deferral(1); Deferral(4);
try_assert(content.len >= MESSAGE_SIZE_MIN && content.len <= MESSAGE_SIZE_MAX); try_assert(content.len >= MESSAGE_SIZE_MIN && content.len <= MESSAGE_SIZE_MAX);
tsqlite_statement* st = q->messages.insert; 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, i64 channel_id, i64 first_message_id, u32 count,
MessageBlockMeta* block_meta, Array(u8) block_data) MessageBlockMeta* block_meta, Array(u8) block_data)
{ {
Deferral(1); Deferral(4);
try_assert(channel_id > 0); try_assert(channel_id > 0);
try_assert(block_data.len >= count * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX)); try_assert(block_data.len >= count * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX));
if(count == 0){ if(count == 0){

View File

@@ -1,7 +1,7 @@
#include "server_db_internal.h" #include "server_db_internal.h"
Result(i64) User_findByUsername(ServerQueries* q, str username){ Result(i64) User_findByUsername(ServerQueries* q, str username){
Deferral(1); Deferral(4);
tsqlite_statement* st = q->users.find_by_username; tsqlite_statement* st = q->users.find_by_username;
Defer(tsqlite_statement_reset(st)); 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){ 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(username.len >= USERNAME_SIZE_MIN && username.len <= USERNAME_SIZE_MAX);
try_assert(token.len == PASSWORD_HASH_SIZE) 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){ Result(bool) User_tryAuthorize(ServerQueries* q, u64 id, Array(u8) token){
Deferral(1); Deferral(4);
try_assert(token.len == PASSWORD_HASH_SIZE) try_assert(token.len == PASSWORD_HASH_SIZE)
tsqlite_statement* st = q->users.compare_token; tsqlite_statement* st = q->users.compare_token;

View File

@@ -13,7 +13,8 @@ void ServerQueries_free(ServerQueries* self);
Result(bool) Channel_exists(ServerQueries* q, i64 id); 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); i64 id, str name, str description);
/// @return new message id /// @return new message id

View File

@@ -3,25 +3,25 @@
typedef struct ServerQueries { typedef struct ServerQueries {
struct { struct {
/* (id, name, description) -> void */ /* ($id, $name, $description) -> void */
tsqlite_statement* insert; tsqlite_statement* insert;
/* (id, name, description) -> void */ /* ($id, $name, $description) -> void */
tsqlite_statement* update; tsqlite_statement* update;
/* (id) -> 1 or nothing */ /* ($id) -> 1 or nothing */
tsqlite_statement* exists; tsqlite_statement* exists;
} channels; } channels;
struct { struct {
/* (channel_id, sender_id, content) -> (id, timestamp) */ /* ($channel_id, $sender_id, $content) -> (id, timestamp) */
tsqlite_statement* insert; 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; tsqlite_statement* get_block;
} messages; } messages;
struct { struct {
/* (username, token) -> (id, registration_time) */ /* ($username, $token) -> (id, registration_time) */
tsqlite_statement* insert; tsqlite_statement* insert;
/* (username) -> (id) */ /* ($username) -> (id) */
tsqlite_statement* find_by_username; tsqlite_statement* find_by_username;
/* (id, token) -> 1 or nothing */ /* ($id, $token) -> 1 or nothing */
tsqlite_statement* compare_token; tsqlite_statement* compare_token;
} users; } users;
} ServerQueries; } ServerQueries;

View File

@@ -9,7 +9,7 @@ Result(void) sendErrorMessage(
cstr log_ctx, ClientConnection* conn, PacketHeader* res_head, cstr log_ctx, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, str msg) LogSeverity log_severity, str msg)
{ {
Deferral(1); Deferral(4);
//limit ErrorMessage size to fit into EncryptedSocketTCP.internal_buffer_size //limit ErrorMessage size to fit into EncryptedSocketTCP.internal_buffer_size
if(msg.len > ERROR_MESSAGE_MAX_SIZE) if(msg.len > ERROR_MESSAGE_MAX_SIZE)
@@ -44,7 +44,7 @@ Result(void) sendErrorMessage_f(
ClientConnection* conn, PacketHeader* res_head, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, cstr format, ...) LogSeverity log_severity, cstr format, ...)
{ {
Deferral(1); Deferral(4);
va_list argv; va_list argv;
va_start(argv, format); va_start(argv, format);

View File

@@ -13,7 +13,9 @@ id = 1
description = "a text channel" description = "a text channel"
[database] [database]
path = 'server-db/server.sqlite' path = 'tcp-chat-server/server.sqlite'
# on windows use backslashes
# path = 'tcp-chat-server\server.sqlite'
[keys] [keys]
rsa_private_key = '<generate with './tcp-chat --rsa-gen-random'>' rsa_private_key = '<generate with './tcp-chat --rsa-gen-random'>'