implemented ClientCLI DB queries
This commit is contained in:
62
.vscode/launch.json
vendored
62
.vscode/launch.json
vendored
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 = '<generate with './tcp-chat --rsa-gen-random'>'
|
||||
|
||||
Reference in New Issue
Block a user