Compare commits
10 Commits
e2edd4070a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| db861cd698 | |||
| b9a622c9d1 | |||
| 75891025d0 | |||
| d68ce8d4f0 | |||
| b081e52d6e | |||
| 310e4867d5 | |||
| bfcb2f931f | |||
| 151ad13853 | |||
| 0132e71c88 | |||
| 90e21bc5ae |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,3 +10,6 @@
|
|||||||
[submodule "dependencies/tsqlite"]
|
[submodule "dependencies/tsqlite"]
|
||||||
path = dependencies/tsqlite
|
path = dependencies/tsqlite
|
||||||
url = https://timerix.ddns.net/git/Timerix/tsqlite.git
|
url = https://timerix.ddns.net/git/Timerix/tsqlite.git
|
||||||
|
[submodule "dependencies/tim"]
|
||||||
|
path = dependencies/tim
|
||||||
|
url = https://timerix.ddns.net/git/Timerix/tim.git
|
||||||
|
|||||||
1
.vscode/c_cpp_properties.json
vendored
1
.vscode/c_cpp_properties.json
vendored
@@ -10,6 +10,7 @@
|
|||||||
"dependencies/tlibc/include",
|
"dependencies/tlibc/include",
|
||||||
"dependencies/tlibtoml/include",
|
"dependencies/tlibtoml/include",
|
||||||
"dependencies/tsqlite/include",
|
"dependencies/tsqlite/include",
|
||||||
|
"dependencies/tim/include",
|
||||||
"${default}"
|
"${default}"
|
||||||
],
|
],
|
||||||
"cStandard": "c99"
|
"cStandard": "c99"
|
||||||
|
|||||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@@ -5,12 +5,15 @@
|
|||||||
"name": "(gdb) Client | Build and debug",
|
"name": "(gdb) Client | Build and debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/bin",
|
||||||
"program": "${workspaceFolder}/bin/tcp-chat",
|
"program": "${workspaceFolder}/bin/tcp-chat",
|
||||||
"windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" },
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/bin/tcp-chat.exe",
|
||||||
|
"externalConsole": true
|
||||||
|
},
|
||||||
"preLaunchTask": "build_exec_dbg",
|
"preLaunchTask": "build_exec_dbg",
|
||||||
|
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}/bin",
|
|
||||||
"externalConsole": false,
|
"externalConsole": false,
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
@@ -20,11 +23,14 @@
|
|||||||
"name": "(gdb) Client | Just debug",
|
"name": "(gdb) Client | Just debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/bin",
|
||||||
"program": "${workspaceFolder}/bin/tcp-chat",
|
"program": "${workspaceFolder}/bin/tcp-chat",
|
||||||
"windows": { "program": "${workspaceFolder}/bin/tcp-chat.exe" },
|
"windows": {
|
||||||
|
"program": "${workspaceFolder}/bin/tcp-chat.exe",
|
||||||
|
"externalConsole": true
|
||||||
|
},
|
||||||
|
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}/bin",
|
|
||||||
"externalConsole": false,
|
"externalConsole": false,
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
@@ -35,13 +41,13 @@
|
|||||||
"name": "(gdb) Server | Build and debug",
|
"name": "(gdb) Server | Build and debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/bin",
|
||||||
"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" ],
|
"args": [ "-l" ],
|
||||||
"preLaunchTask": "build_exec_dbg",
|
"preLaunchTask": "build_exec_dbg",
|
||||||
|
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}/bin",
|
|
||||||
"externalConsole": false,
|
"externalConsole": false,
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
@@ -51,12 +57,12 @@
|
|||||||
"name": "(gdb) Server | Just debug",
|
"name": "(gdb) Server | Just debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}/bin",
|
||||||
"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" ],
|
"args": [ "-l" ],
|
||||||
|
|
||||||
"stopAtEntry": false,
|
"stopAtEntry": false,
|
||||||
"cwd": "${workspaceFolder}/bin",
|
|
||||||
"externalConsole": false,
|
"externalConsole": false,
|
||||||
"internalConsoleOptions": "neverOpen",
|
"internalConsoleOptions": "neverOpen",
|
||||||
"MIMode": "gdb",
|
"MIMode": "gdb",
|
||||||
|
|||||||
1
dependencies/tim
vendored
Submodule
1
dependencies/tim
vendored
Submodule
Submodule dependencies/tim added at ee6375f553
19
dependencies/tim.config
vendored
Normal file
19
dependencies/tim.config
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This is a dependency config.
|
||||||
|
# You can copy it to another cbuild project to add this lib as dependency.
|
||||||
|
|
||||||
|
DEP_WORKING_DIR="$DEPENDENCIES_DIR/tim"
|
||||||
|
if [[ "$TASK" = *_dbg ]]; then
|
||||||
|
dep_build_target="build_static_lib_dbg"
|
||||||
|
else
|
||||||
|
dep_build_target="build_static_lib"
|
||||||
|
fi
|
||||||
|
DEP_PRE_BUILD_COMMAND=""
|
||||||
|
DEP_BUILD_COMMAND="cbuild $dep_build_target"
|
||||||
|
DEP_POST_BUILD_COMMAND=""
|
||||||
|
DEP_CLEAN_COMMAND="cbuild clean"
|
||||||
|
DEP_DYNAMIC_OUT_FILES=""
|
||||||
|
DEP_STATIC_OUT_FILES="bin/tim.a"
|
||||||
|
DEP_OTHER_OUT_FILES=""
|
||||||
|
PRESERVE_OUT_DIRECTORY_STRUCTURE=false
|
||||||
2
dependencies/tlibc
vendored
2
dependencies/tlibc
vendored
Submodule dependencies/tlibc updated: de88e9ff16...82a6293f21
135
include/tcp-chat.h
Normal file
135
include/tcp-chat.h
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "tlibc/errors.h"
|
||||||
|
#include "tlibc/time.h"
|
||||||
|
#include "tlibc/magic.h"
|
||||||
|
|
||||||
|
/// requires tlibc and tlibtoml init
|
||||||
|
Result(void) TcpChat_init();
|
||||||
|
void TcpChat_deinit();
|
||||||
|
|
||||||
|
#define USERNAME_SIZE_MIN 2
|
||||||
|
#define USERNAME_SIZE_MAX 31
|
||||||
|
#define PASSWORD_SIZE_MIN 8
|
||||||
|
#define PASSWORD_SIZE_MAX 31
|
||||||
|
#define PASSWORD_HASH_SIZE 32
|
||||||
|
#define HOSTADDR_SIZE_MIN 4
|
||||||
|
#define HOSTADDR_SIZE_MAX 255
|
||||||
|
#define PRIVATE_KEY_BASE64_SIZE_MAX 1724
|
||||||
|
#define PUBLIC_KEY_BASE64_SIZE_MAX 699
|
||||||
|
#define SERVER_NAME_SIZE_MIN 1
|
||||||
|
#define SERVER_NAME_SIZE_MAX 127
|
||||||
|
#define SERVER_DESC_SIZE_MAX 1023
|
||||||
|
#define CHANNEL_NAME_SIZE_MIN 1
|
||||||
|
#define CHANNEL_NAME_SIZE_MAX 127
|
||||||
|
#define CHANNEL_DESC_SIZE_MAX 1023
|
||||||
|
#define MESSAGE_SIZE_MIN 1
|
||||||
|
#define MESSAGE_SIZE_MAX 4000
|
||||||
|
#define MESSAGE_BLOCK_COUNT_MAX 50
|
||||||
|
|
||||||
|
#define MESSAGE_TIMESTAMP_FMT_SQL "%Y.%m.%d-%H:%M:%f"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Logging //
|
||||||
|
// //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ErrorCodePage_declare(WINSOCK2);
|
||||||
|
ErrorCodePage_declare(TcpChat);
|
||||||
|
|
||||||
|
typedef enum TcpChatError {
|
||||||
|
TcpChatError_Unknown,
|
||||||
|
TcpChatError_RejectIncoming,
|
||||||
|
} TcpChatError;
|
||||||
|
|
||||||
|
typedef enum LogSeverity {
|
||||||
|
LogSeverity_Debug,
|
||||||
|
LogSeverity_Info,
|
||||||
|
LogSeverity_Warn,
|
||||||
|
LogSeverity_Error,
|
||||||
|
} LogSeverity;
|
||||||
|
|
||||||
|
typedef void (*LogFunction_t)(void* logger, cstr context, LogSeverity severity, cstr msg);
|
||||||
|
|
||||||
|
// requires defined LOGGER, LOG_FUNC, LOG_CONTEXT
|
||||||
|
#define log(severity, format, ...) { \
|
||||||
|
if(LOG_FUNC) { \
|
||||||
|
char* ___log_msg = sprintf_malloc(format ,##__VA_ARGS__); \
|
||||||
|
LOG_FUNC(LOGGER, LOG_CONTEXT, severity, ___log_msg); \
|
||||||
|
free(___log_msg); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define logDebug(format, ...) log(LogSeverity_Debug, format ,##__VA_ARGS__)
|
||||||
|
#define logInfo(format, ...) log(LogSeverity_Info, format ,##__VA_ARGS__)
|
||||||
|
#define logWarn(format, ...) log(LogSeverity_Warn, format ,##__VA_ARGS__)
|
||||||
|
#define logError(format, ...) log(LogSeverity_Error, format ,##__VA_ARGS__)
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Server //
|
||||||
|
// //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Server Server;
|
||||||
|
|
||||||
|
/// @param config_file_content config in toml format
|
||||||
|
/// @param config_file_name to use in error messages
|
||||||
|
/// @param logger some shared data for your log function
|
||||||
|
/// @param log_func log function that you have to implement
|
||||||
|
/// @return
|
||||||
|
Result(Server*) Server_create(str config_file_content, cstr config_file_name,
|
||||||
|
void* logger, LogFunction_t log_func);
|
||||||
|
|
||||||
|
void Server_free(Server* server);
|
||||||
|
|
||||||
|
Result(void) Server_run(Server* server);
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Client //
|
||||||
|
// //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Client Client;
|
||||||
|
|
||||||
|
Result(Client*) Client_create(str username, str password);
|
||||||
|
void Client_free(Client* client);
|
||||||
|
|
||||||
|
/// @return username saved during client initialization
|
||||||
|
str Client_getUserName(Client* client);
|
||||||
|
|
||||||
|
/// @return AES key calculated from password that can be used to encrypt user data
|
||||||
|
Array(u8) Client_getUserDataKey(Client* client);
|
||||||
|
|
||||||
|
/// @param server_addr_cstr ip:port
|
||||||
|
/// @param server_pk_base64 public key encoded by `RSA_serializePublicKey_base64()`
|
||||||
|
Result(void) Client_connect(Client* client, cstr server_addr_cstr, cstr server_pk_base64);
|
||||||
|
/// disconnect from current server
|
||||||
|
void Client_disconnect(Client* client);
|
||||||
|
|
||||||
|
/// @param self connected client
|
||||||
|
/// @param out_str heap-allocated string
|
||||||
|
Result(void) Client_getServerName(Client* self, str* out_str);
|
||||||
|
|
||||||
|
/// @param self connected client
|
||||||
|
/// @param out_str heap-allocated string
|
||||||
|
Result(void) Client_getServerDescription(Client* self, str* out_str);
|
||||||
|
|
||||||
|
/// Create new account on connected server
|
||||||
|
Result(void) Client_register(Client* self, i64* out_user_id);
|
||||||
|
|
||||||
|
/// Authorize on connected server
|
||||||
|
Result(void) Client_login(Client* self, i64* out_user_id, i64* out_landing_channel_id);
|
||||||
|
|
||||||
|
/// @param out_timestamp timestamp received from server
|
||||||
|
/// @return message id received from server
|
||||||
|
Result(i64) Client_sendMessage(Client* self, i64 channel_id, Array(u8) content, DateTime* out_timestamp);
|
||||||
|
|
||||||
|
/// Receive a bunch of messages from the server to a client internal buffer
|
||||||
|
/// @return number of messages received
|
||||||
|
Result(u32) Client_receiveMessageBlock(Client* self, i64 channel_id, i64 first_message_id, u32 messages_count);
|
||||||
|
|
||||||
|
/// Read message saved in client internal buffer.
|
||||||
|
/// @return number of bytes written in dst_content
|
||||||
|
Result(u32) Client_popMessage(Client* self, Array(u8) dst_content, i64* message_id, i64* sender_id, DateTime* timestamp_utc);
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/errors.h"
|
|
||||||
#include "tlibc/string/str.h"
|
|
||||||
|
|
||||||
typedef struct Client Client;
|
|
||||||
|
|
||||||
Result(Client*) Client_create(str username, str password);
|
|
||||||
void Client_free(Client* client);
|
|
||||||
|
|
||||||
/// @return username saved during client initialization
|
|
||||||
str Client_getUserName(Client* client);
|
|
||||||
|
|
||||||
/// @return AES key calculated from password that can be used to encrypt user data
|
|
||||||
Array(u8) Client_getUserDataKey(Client* client);
|
|
||||||
|
|
||||||
/// @param server_addr_cstr ip:port
|
|
||||||
/// @param server_pk_base64 public key encoded by `RSA_serializePublicKey_base64()`
|
|
||||||
Result(void) Client_connect(Client* client, cstr server_addr_cstr, cstr server_pk_base64);
|
|
||||||
/// disconnect from current server
|
|
||||||
void Client_disconnect(Client* client);
|
|
||||||
|
|
||||||
/// @param self connected client
|
|
||||||
/// @param out_str heap-allocated string
|
|
||||||
Result(void) Client_getServerName(Client* self, str* out_str);
|
|
||||||
|
|
||||||
/// @param self connected client
|
|
||||||
/// @param out_str heap-allocated string
|
|
||||||
Result(void) Client_getServerDescription(Client* self, str* out_str);
|
|
||||||
|
|
||||||
/// Create new account on connected server
|
|
||||||
Result(void) Client_register(Client* self, i64* out_user_id);
|
|
||||||
|
|
||||||
/// Authorize on connected server
|
|
||||||
Result(void) Client_login(Client* self, i64* out_user_id, i64* out_landing_channel_id);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/std.h"
|
|
||||||
|
|
||||||
#define USERNAME_SIZE_MIN 2
|
|
||||||
#define USERNAME_SIZE_MAX 31
|
|
||||||
#define PASSWORD_SIZE_MIN 8
|
|
||||||
#define PASSWORD_SIZE_MAX 31
|
|
||||||
#define PASSWORD_HASH_SIZE 32
|
|
||||||
#define HOSTADDR_SIZE_MIN 4
|
|
||||||
#define HOSTADDR_SIZE_MAX 255
|
|
||||||
#define PRIVATE_KEY_BASE64_SIZE_MAX 1724
|
|
||||||
#define PUBLIC_KEY_BASE64_SIZE_MAX 699
|
|
||||||
#define SERVER_NAME_SIZE_MIN 1
|
|
||||||
#define SERVER_NAME_SIZE_MAX 127
|
|
||||||
#define SERVER_DESC_SIZE_MAX 1023
|
|
||||||
#define CHANNEL_NAME_SIZE_MIN 1
|
|
||||||
#define CHANNEL_NAME_SIZE_MAX 127
|
|
||||||
#define CHANNEL_DESC_SIZE_MAX 1023
|
|
||||||
#define MESSAGE_SIZE_MIN 1
|
|
||||||
#define MESSAGE_SIZE_MAX 4000
|
|
||||||
#define MESSAGE_BLOCK_COUNT_MAX 50
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "tlibc/std.h"
|
|
||||||
#include "tlibc/string/cstr.h"
|
|
||||||
|
|
||||||
typedef enum LogSeverity {
|
|
||||||
LogSeverity_Debug,
|
|
||||||
LogSeverity_Info,
|
|
||||||
LogSeverity_Warn,
|
|
||||||
LogSeverity_Error,
|
|
||||||
} LogSeverity;
|
|
||||||
|
|
||||||
typedef void (*LogFunction_t)(void* logger, cstr context, LogSeverity severity, cstr msg);
|
|
||||||
|
|
||||||
|
|
||||||
// requires defined LOGGER, LOG_FUNC, LOG_CONTEXT
|
|
||||||
#define log(severity, format, ...) { \
|
|
||||||
if(LOG_FUNC) { \
|
|
||||||
char* ___log_msg = sprintf_malloc(format ,##__VA_ARGS__); \
|
|
||||||
LOG_FUNC(LOGGER, LOG_CONTEXT, severity, ___log_msg); \
|
|
||||||
free(___log_msg); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define logDebug(format, ...) log(LogSeverity_Debug, format ,##__VA_ARGS__)
|
|
||||||
#define logInfo(format, ...) log(LogSeverity_Info, format ,##__VA_ARGS__)
|
|
||||||
#define logWarn(format, ...) log(LogSeverity_Warn, format ,##__VA_ARGS__)
|
|
||||||
#define logError(format, ...) log(LogSeverity_Error, format ,##__VA_ARGS__)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/errors.h"
|
|
||||||
#include "tlibc/string/str.h"
|
|
||||||
#include "tcp-chat/log.h"
|
|
||||||
|
|
||||||
typedef struct Server Server;
|
|
||||||
|
|
||||||
/// @param config_file_content config in toml format
|
|
||||||
/// @param config_file_name to use in error messages
|
|
||||||
/// @param logger some shared data for your log function
|
|
||||||
/// @param log_func log function that you have to implement
|
|
||||||
/// @return
|
|
||||||
Result(Server*) Server_create(str config_file_content, cstr config_file_name,
|
|
||||||
void* logger, LogFunction_t log_func);
|
|
||||||
|
|
||||||
void Server_free(Server* server);
|
|
||||||
|
|
||||||
Result(void) Server_run(Server* server);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/errors.h"
|
|
||||||
|
|
||||||
/// requires tlibc and tlibtoml init
|
|
||||||
Result(void) TcpChat_init();
|
|
||||||
void TcpChat_deinit();
|
|
||||||
|
|
||||||
ErrorCodePage_declare(WINSOCK2);
|
|
||||||
ErrorCodePage_declare(TcpChat);
|
|
||||||
|
|
||||||
typedef enum TcpChatError {
|
|
||||||
TcpChatError_Unknown,
|
|
||||||
TcpChatError_RejectIncoming,
|
|
||||||
} TcpChatError;
|
|
||||||
|
|
||||||
#define MESSAGE_TIMESTAMP_FMT_SQL "%Y.%m.%d-%H:%M:%f"
|
|
||||||
@@ -24,7 +24,7 @@ SRC_CPP="$(find src -name '*.cpp')"
|
|||||||
# See cbuild/example_dependency_configs
|
# See cbuild/example_dependency_configs
|
||||||
DEPENDENCY_CONFIGS_DIR='dependencies'
|
DEPENDENCY_CONFIGS_DIR='dependencies'
|
||||||
# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space.
|
# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space.
|
||||||
ENABLED_DEPENDENCIES='bearssl tlibc tlibtoml tsqlite'
|
ENABLED_DEPENDENCIES='bearssl tlibc tlibtoml tsqlite tim'
|
||||||
|
|
||||||
# OBJDIR structure:
|
# OBJDIR structure:
|
||||||
# ├── objects/ - Compiled object files. Cleans on each call of build task
|
# ├── objects/ - Compiled object files. Cleans on each call of build task
|
||||||
@@ -39,7 +39,8 @@ INCLUDE="-Isrc -Iinclude
|
|||||||
-I$DEPENDENCIES_DIR/BearSSL/inc
|
-I$DEPENDENCIES_DIR/BearSSL/inc
|
||||||
-I$DEPENDENCIES_DIR/tlibc/include
|
-I$DEPENDENCIES_DIR/tlibc/include
|
||||||
-I$DEPENDENCIES_DIR/tlibtoml/include
|
-I$DEPENDENCIES_DIR/tlibtoml/include
|
||||||
-I$DEPENDENCIES_DIR/tsqlite/include"
|
-I$DEPENDENCIES_DIR/tsqlite/include
|
||||||
|
-I$DEPENDENCIES_DIR/tim/include"
|
||||||
|
|
||||||
# OS-specific options
|
# OS-specific options
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
|
|||||||
@@ -1,35 +1,8 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
#include "tlibc/filesystem.h"
|
#include "tlibc/filesystem.h"
|
||||||
#include "tlibc/term.h"
|
#include "tlibc/term.h"
|
||||||
#include "tcp-chat/common_constants.h"
|
|
||||||
#include "cli/ClientCLI/ClientCLI.h"
|
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
#include <assert.h>
|
||||||
static const str greeting_art = STR(
|
|
||||||
" ^,,^ ╱|\n"
|
|
||||||
" ( •·•) Meum! (o.o`7\n"
|
|
||||||
" / ` | Meum... |`˜ \\\n"
|
|
||||||
"\\(_,J J L l`,)/\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
static const str farewell_art = STR(
|
|
||||||
" ^,,^ ╱|\n"
|
|
||||||
" ( -.-) (>,<`7\n"
|
|
||||||
" / ` | Goodbye! |`˜ \\\n"
|
|
||||||
"\\(_,J J L l`,)/\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
#define is_alias(LITERAL) str_equals(command, STR(LITERAL))
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_askUserNameAndPassword(str* username_out, str* password_out);
|
|
||||||
static Result(void) ClientCLI_openUserDB(ClientCLI* self);
|
|
||||||
static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop);
|
|
||||||
static Result(SavedServer*) ClientCLI_joinNewServer(ClientCLI* self);
|
|
||||||
static Result(SavedServer*) ClientCLI_selectServerFromCache(ClientCLI* self);
|
|
||||||
static Result(void) ClientCLI_showSavedServer(ClientCLI* self, SavedServer* server);
|
|
||||||
static Result(void) ClientCLI_register(ClientCLI* self);
|
|
||||||
static Result(void) ClientCLI_login(ClientCLI* self);
|
|
||||||
|
|
||||||
|
|
||||||
void ClientCLI_destroy(ClientCLI* self){
|
void ClientCLI_destroy(ClientCLI* self){
|
||||||
if(!self)
|
if(!self)
|
||||||
@@ -43,332 +16,51 @@ void ClientCLI_destroy(ClientCLI* self){
|
|||||||
|
|
||||||
void ClientCLI_construct(ClientCLI* self){
|
void ClientCLI_construct(ClientCLI* self){
|
||||||
zeroStruct(self);
|
zeroStruct(self);
|
||||||
|
|
||||||
|
self->style.common = (TimStyle){
|
||||||
|
.brd = Color256_LightGray,
|
||||||
|
.bg = Color256_NavyBlue,
|
||||||
|
.fg = Color256_LightGray
|
||||||
|
};
|
||||||
|
self->style.focused = (TimStyle){
|
||||||
|
.brd = Color256_White,
|
||||||
|
.bg = Color256_DeepSkyBlue,
|
||||||
|
.fg = Color256_White
|
||||||
|
};
|
||||||
|
self->style.error = (TimStyle){
|
||||||
|
.brd = Color256_LightGray,
|
||||||
|
.bg = Color256_DarkRed,
|
||||||
|
.fg = Color256_White
|
||||||
|
};
|
||||||
|
|
||||||
self->saved_servers = List_SavedServer_alloc(0);
|
self->saved_servers = List_SavedServer_alloc(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) ClientCLI_run(ClientCLI* self) {
|
void ClientCLI_run(ClientCLI* self) {
|
||||||
Deferral(16);
|
Deferral(32);
|
||||||
|
|
||||||
try_void(term_init());
|
StartScreenContext start_screen_ctx;
|
||||||
term_clear();
|
StartScreenContext_construct(&start_screen_ctx, self);
|
||||||
printf(FMT_str"\n", greeting_art.len, greeting_art.data);
|
Defer(StartScreenContext_destroy(&start_screen_ctx));
|
||||||
|
MainScreenContext main_screen_ctx;
|
||||||
|
MainScreenContext_construct(&main_screen_ctx, self);
|
||||||
|
Defer(MainScreenContext_destroy(&main_screen_ctx));
|
||||||
|
|
||||||
// create Client
|
while(tim_run(FPS)){
|
||||||
str username = str_null, password = str_null;
|
switch(self->state){
|
||||||
try_void(ClientCLI_askUserNameAndPassword(&username, &password));
|
case ClientCLIState_Exit:
|
||||||
Defer(
|
Return;
|
||||||
str_destroy(username);
|
default:
|
||||||
str_destroy(password);
|
assert(false && "invalid ClientCLI state");
|
||||||
);
|
break;
|
||||||
Client_free(self->client);
|
case ClientCLIState_StartScreen:
|
||||||
try(self->client, p, Client_create(username, password));
|
start_screen(&start_screen_ctx);
|
||||||
memset(password.data, 0, password.len);
|
break;
|
||||||
|
case ClientCLIState_MainScreen:
|
||||||
// init db
|
main_screen(&main_screen_ctx);
|
||||||
try_void(ClientCLI_openUserDB(self));
|
break;
|
||||||
|
|
||||||
char input_buf[1024];
|
|
||||||
str command_input = str_null;
|
|
||||||
bool stop = false;
|
|
||||||
while(!stop){
|
|
||||||
sleepMsec(50);
|
|
||||||
printf("> ");
|
|
||||||
try_void(term_readLine(input_buf, sizeof(input_buf)));
|
|
||||||
|
|
||||||
command_input = str_from_cstr(input_buf);
|
|
||||||
str_trim(&command_input, true);
|
|
||||||
if(command_input.len == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ResultVar(void) com_result = ClientCLI_execCommand(self, command_input, &stop);
|
|
||||||
if(com_result.error){
|
|
||||||
Error_addCallPos(com_result.error, ErrorCallPos_here());
|
|
||||||
str e_str = Error_toStr(com_result.error);
|
|
||||||
printf("\n"FMT_str"\n", e_str.len, e_str.data);
|
|
||||||
str_destroy(e_str);
|
|
||||||
Error_free(com_result.error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return;
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_askUserNameAndPassword(str* username_out, str* password_out){
|
|
||||||
Deferral(8);
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
// ask username
|
|
||||||
Array(char) username_buf = Array_char_alloc(128);
|
|
||||||
Defer(if(!success) Array_char_destroy(&username_buf));
|
|
||||||
str username = str_null;
|
|
||||||
while(true) {
|
|
||||||
printf("username: ");
|
|
||||||
try_void(term_readLine(username_buf.data, username_buf.len));
|
|
||||||
username = str_from_cstr(username_buf.data);
|
|
||||||
str_trim(&username, true);
|
|
||||||
str name_error_str = validateUsername_str(username);
|
|
||||||
if(name_error_str.data){
|
|
||||||
printf("ERROR: "FMT_str"\n",
|
|
||||||
name_error_str.len, name_error_str.data);
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ask password
|
|
||||||
Array(char) password_buf = Array_char_alloc(128);
|
|
||||||
Defer(if(!success) Array_char_destroy(&password_buf));
|
|
||||||
str password = str_null;
|
|
||||||
while(true) {
|
|
||||||
printf("password: ");
|
|
||||||
// TODO: hide password
|
|
||||||
try_void(term_readLineHidden(password_buf.data, password_buf.len));
|
|
||||||
password = str_from_cstr(password_buf.data);
|
|
||||||
str_trim(&password, true);
|
|
||||||
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
|
|
||||||
printf("ERROR: password length (in bytes) must be >= %i and <= %i\n",
|
|
||||||
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX);
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
*username_out = username;
|
|
||||||
*password_out = password;
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_openUserDB(ClientCLI* self){
|
|
||||||
Deferral(8);
|
|
||||||
|
|
||||||
str username = Client_getUserName(self->client);
|
|
||||||
// TODO: encrypt user database
|
|
||||||
// Array(u8) user_data_key = Client_getUserDataKey(self->client);
|
|
||||||
|
|
||||||
// build database file path
|
|
||||||
try(char* user_dir, p, path_getUserDir());
|
|
||||||
Defer(free(user_dir));
|
|
||||||
char* db_path = strcat_malloc(
|
|
||||||
user_dir,
|
|
||||||
path_seps".local"path_seps"tcp-chat-client"path_seps"user-db"path_seps,
|
|
||||||
username.data, ".sqlite"
|
|
||||||
);
|
|
||||||
Defer(free(db_path));
|
|
||||||
printf("loading database '%s'\n", db_path);
|
|
||||||
|
|
||||||
try(self->db, p, ClientDatabase_open(db_path));
|
|
||||||
try(self->queries, p, ClientQueries_compile(self->db));
|
|
||||||
|
|
||||||
// load whole servers table to list
|
|
||||||
try_void(SavedServer_getAll(self->queries, &self->saved_servers));
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop){
|
|
||||||
Deferral(64);
|
|
||||||
|
|
||||||
if(is_alias("q") || is_alias("quit") || is_alias("exit")){
|
|
||||||
printf(FMT_str"\n", farewell_art.len, farewell_art.data);
|
|
||||||
*stop = true;
|
|
||||||
}
|
|
||||||
else if(is_alias("clear")){
|
|
||||||
term_clear();
|
|
||||||
}
|
|
||||||
else if(is_alias("h") || is_alias("help")){
|
|
||||||
printf(
|
|
||||||
"COMMANDS:\n"
|
|
||||||
"Without connection:\n"
|
|
||||||
" h, help Show this message.\n"
|
|
||||||
" q, quit, exit Close the program.\n"
|
|
||||||
" clear Clear the screen.\n"
|
|
||||||
"Connection:\n"
|
|
||||||
" j, join Join a new server and select it.\n"
|
|
||||||
" s, select Select a server you joined before.\n"
|
|
||||||
"After connection:\n"
|
|
||||||
" r, register Create account on selected server\n"
|
|
||||||
" l, login Authorize on selected server\n"
|
|
||||||
"Authorized:\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (is_alias("j") || is_alias("join")){
|
|
||||||
// ask address and key, connect to server
|
|
||||||
try_void(ClientCLI_joinNewServer(self));
|
|
||||||
}
|
|
||||||
else if(is_alias("s") || is_alias("select")){
|
|
||||||
// show scrollable list of servers, get selected one
|
|
||||||
try_void(ClientCLI_selectServerFromCache(self));
|
|
||||||
}
|
|
||||||
else if(is_alias("r") || is_alias("register")){
|
|
||||||
try_void(ClientCLI_register(self));
|
|
||||||
}
|
|
||||||
else if(is_alias("l") || is_alias("login")){
|
|
||||||
try_void(ClientCLI_login(self));
|
|
||||||
// TODO: call Client_runIO():
|
|
||||||
// function with infinite loop which sends and receives messages
|
|
||||||
// with navigation across server channels
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("ERROR: unknown command.\n"
|
|
||||||
"Use 'h' to see list of avaliable commands\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_joinNewServer(ClientCLI* self){
|
|
||||||
Deferral(8);
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
// ask server address
|
|
||||||
const u32 address_alloc_size = HOSTADDR_SIZE_MAX + 1;
|
|
||||||
str address = str_construct((char*)malloc(address_alloc_size), address_alloc_size, true);
|
|
||||||
Defer(if(!success) str_destroy(address));
|
|
||||||
printf("Enter server address (ip:port):\n");
|
|
||||||
try_void(term_readLine(address.data, address.len));
|
|
||||||
address.len = strlen(address.data);
|
|
||||||
str_trim(&address, true);
|
|
||||||
|
|
||||||
// ask server public key
|
|
||||||
const u32 server_pk_alloc_size = PUBLIC_KEY_BASE64_SIZE_MAX + 1;
|
|
||||||
str server_pk = str_construct((char*)malloc(server_pk_alloc_size), server_pk_alloc_size, true);
|
|
||||||
Defer(if(!success) str_destroy(server_pk));
|
|
||||||
printf("Enter server public key (RSA-Public-<SIZE>:<DATA>):\n");
|
|
||||||
try_void(term_readLine(server_pk.data, server_pk.len));
|
|
||||||
server_pk.len = strlen(server_pk.data);
|
|
||||||
str_trim(&server_pk, true);
|
|
||||||
|
|
||||||
printf("Connecting to server...\n");
|
|
||||||
try_void(Client_connect(self->client, address.data, server_pk.data));
|
|
||||||
printf("Connection established\n");
|
|
||||||
|
|
||||||
str server_name = str_null;
|
|
||||||
try_void(Client_getServerName(self->client, &server_name));
|
|
||||||
Defer(if(!success) str_destroy(server_name));
|
|
||||||
str server_description = str_null;
|
|
||||||
try_void(Client_getServerDescription(self->client, &server_description));
|
|
||||||
Defer(if(!success) str_destroy(server_description));
|
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_selectServerFromCache(ClientCLI* self){
|
|
||||||
Deferral(8);
|
|
||||||
bool success = false;
|
|
||||||
|
|
||||||
u32 servers_count = self->saved_servers.len;
|
|
||||||
if(servers_count == 0){
|
|
||||||
printf("No saved servers found\n");
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(u32 i = 0; i < servers_count; i++){
|
|
||||||
SavedServer* server = &self->saved_servers.data[i];
|
|
||||||
printf("[%02u] "FMT_str" "FMT_str"\n",
|
|
||||||
i, str_unwrap(server->address), str_unwrap(server->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[32];
|
|
||||||
u32 selected_i = -1;
|
|
||||||
while(true) {
|
|
||||||
printf("Type 'q' to cancel\n");
|
|
||||||
printf("Select server number: ");
|
|
||||||
try_void(term_readLine(buf, sizeof(buf)));
|
|
||||||
str input_line = str_from_cstr(buf);
|
|
||||||
str_trim(&input_line, true);
|
|
||||||
if(str_equals(input_line, STR("q"))){
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
if(sscanf(buf, FMT_u32, &selected_i) != 1){
|
|
||||||
printf("ERROR: not a number\n");
|
|
||||||
}
|
|
||||||
else if(selected_i >= servers_count){
|
|
||||||
printf("ERROR: not a server number\n");
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
SavedServer* selected_server = &self->saved_servers.data[selected_i];
|
|
||||||
|
|
||||||
printf("Connecting to '"FMT_str"'...\n", str_unwrap(selected_server->address));
|
|
||||||
try_void(Client_connect(self->client, selected_server->address.data, selected_server->pk_base64.data));
|
|
||||||
printf("Connection established\n");
|
|
||||||
|
|
||||||
// update server name
|
|
||||||
bool server_info_changed = false;
|
|
||||||
str updated_server_name = str_null;
|
|
||||||
try_void(Client_getServerName(self->client, &updated_server_name));
|
|
||||||
Defer(if(!success) str_destroy(updated_server_name));
|
|
||||||
if(!str_equals(updated_server_name, selected_server->name)){
|
|
||||||
server_info_changed = true;
|
|
||||||
selected_server->name = updated_server_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update server description
|
|
||||||
str updated_server_description = str_null;
|
|
||||||
try_void(Client_getServerDescription(self->client, &updated_server_description));
|
|
||||||
Defer(if(!success) str_destroy(updated_server_description));
|
|
||||||
if(!str_equals(updated_server_description, selected_server->description)){
|
|
||||||
server_info_changed = true;
|
|
||||||
selected_server->description = updated_server_description;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(server_info_changed){
|
|
||||||
try_void(SavedServer_createOrUpdate(self->queries, selected_server));
|
|
||||||
}
|
|
||||||
|
|
||||||
try_void(ClientCLI_showSavedServer(self, selected_server));
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_showSavedServer(ClientCLI* self, SavedServer* server){
|
|
||||||
Deferral(8);
|
|
||||||
(void)self;
|
|
||||||
|
|
||||||
printf("Server Name: "FMT_str"\n", str_unwrap(server->name));
|
|
||||||
printf("Host Address: "FMT_str"\n", str_unwrap(server->address));
|
|
||||||
printf("Description:\n"FMT_str"\n\n", str_unwrap(server->description));
|
|
||||||
printf("Public Key:\n" FMT_str"\n\n", str_unwrap(server->pk_base64));
|
|
||||||
printf("Type 'register' if you don't have an account on the server.\n");
|
|
||||||
printf("Type 'login' to authorize on the server.\n");
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_register(ClientCLI* self){
|
|
||||||
Deferral(8);
|
|
||||||
|
|
||||||
i64 user_id = 0;
|
|
||||||
try_void(Client_register(self->client, &user_id));
|
|
||||||
printf("Registered successfully\n");
|
|
||||||
printf("user_id: "FMT_i64"\n", user_id);
|
|
||||||
try_assert(user_id > 0);
|
|
||||||
// TODO: use user_id somewhere
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result(void) ClientCLI_login(ClientCLI* self){
|
|
||||||
Deferral(8);
|
|
||||||
|
|
||||||
i64 user_id = 0, landing_channel_id = 0;
|
|
||||||
try_void(Client_login(self->client, &user_id, &landing_channel_id));
|
|
||||||
printf("Authorized successfully\n");
|
|
||||||
printf("user_id: "FMT_i64", landing_channel_id: "FMT_i64"\n", user_id, landing_channel_id);
|
|
||||||
try_assert(user_id > 0);
|
|
||||||
// TODO: use user_id, landing_channel_id somewhere
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,28 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/collections/HashMap.h"
|
#include "tlibc/collections/HashMap.h"
|
||||||
#include "tlibc/collections/List.h"
|
#include "tlibc/collections/List.h"
|
||||||
#include "tcp-chat/client.h"
|
|
||||||
#include "db/client_db.h"
|
#include "db/client_db.h"
|
||||||
|
#include "tim.h"
|
||||||
|
|
||||||
|
#define FPS 30
|
||||||
|
|
||||||
|
typedef enum ClientCLIState {
|
||||||
|
ClientCLIState_StartScreen,
|
||||||
|
ClientCLIState_Exit,
|
||||||
|
ClientCLIState_MainScreen,
|
||||||
|
ClientCLIState_ServerChannels,
|
||||||
|
ClientCLIState_ChannelChat,
|
||||||
|
} ClientCLIState;
|
||||||
|
|
||||||
typedef struct ClientCLI {
|
typedef struct ClientCLI {
|
||||||
|
ClientCLIState state;
|
||||||
|
struct {
|
||||||
|
TimStyle common;
|
||||||
|
TimStyle focused;
|
||||||
|
TimStyle error;
|
||||||
|
} style;
|
||||||
Client* client;
|
Client* client;
|
||||||
tsqlite_connection* db;
|
tsqlite_connection* db;
|
||||||
ClientQueries* queries;
|
ClientQueries* queries;
|
||||||
@@ -14,4 +31,65 @@ typedef struct ClientCLI {
|
|||||||
|
|
||||||
void ClientCLI_construct(ClientCLI* self);
|
void ClientCLI_construct(ClientCLI* self);
|
||||||
void ClientCLI_destroy(ClientCLI* self);
|
void ClientCLI_destroy(ClientCLI* self);
|
||||||
Result(void) ClientCLI_run(ClientCLI* self);
|
void ClientCLI_run(ClientCLI* self);
|
||||||
|
|
||||||
|
|
||||||
|
enum {
|
||||||
|
Color256_Black = 0x10,
|
||||||
|
Color256_MidGray = 0xf8,
|
||||||
|
Color256_LightGray = 0xfb,
|
||||||
|
Color256_White = 0xe7,
|
||||||
|
Color256_NavyBlue = 0x11,
|
||||||
|
Color256_DeepSkyBlue = 0x18,
|
||||||
|
Color256_DarkRed = 0x58,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct TextInputState {
|
||||||
|
TimEditState edit;
|
||||||
|
cstr label;
|
||||||
|
struct {
|
||||||
|
TimStyle common;
|
||||||
|
TimStyle focused;
|
||||||
|
} style;
|
||||||
|
TimKey result_key;
|
||||||
|
} TextInputState;
|
||||||
|
|
||||||
|
void TextInputState_construct(TextInputState* ctx, cstr label,
|
||||||
|
Array(char) buf, NULLABLE(cstr) initial_value, bool masked,
|
||||||
|
TimStyle common, TimStyle focused);
|
||||||
|
|
||||||
|
/// tim_edit with tim_label over its upper border.
|
||||||
|
void text_input(TextInputState* ctx, i32 x, i32 y, i32 w, TimStyle style);
|
||||||
|
|
||||||
|
void TimPanelItem_fromTextInputState(TimPanelItem* item, TextInputState* input);
|
||||||
|
|
||||||
|
/// Intended to use in TimPanelItem
|
||||||
|
/// @param data TextInputState*
|
||||||
|
void draw_item_text_input(void* data, TimRect place, bool is_selected);
|
||||||
|
|
||||||
|
|
||||||
|
List_declare(TimPanelItem);
|
||||||
|
|
||||||
|
typedef struct StartScreenContext {
|
||||||
|
ClientCLI* client;
|
||||||
|
char* err_msg; // heap only
|
||||||
|
TextInputState input_username;
|
||||||
|
TextInputState input_password;
|
||||||
|
TimPanel central_panel;
|
||||||
|
TimPanel central_buttons_panel;
|
||||||
|
} StartScreenContext;
|
||||||
|
|
||||||
|
void StartScreenContext_construct(StartScreenContext* ctx, ClientCLI* client);
|
||||||
|
void StartScreenContext_destroy(StartScreenContext* ctx);
|
||||||
|
void start_screen(StartScreenContext* ctx);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct MainScreenContext {
|
||||||
|
ClientCLI* client;
|
||||||
|
TimPanel central_panel;
|
||||||
|
TimScrollView central_scroll_view;
|
||||||
|
} MainScreenContext;
|
||||||
|
|
||||||
|
void MainScreenContext_construct(MainScreenContext* ctx, ClientCLI* client);
|
||||||
|
void MainScreenContext_destroy(MainScreenContext* ctx);
|
||||||
|
void main_screen(MainScreenContext* ctx);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tcp-chat/tcp-chat.h"
|
#include "tcp-chat.h"
|
||||||
#include "tsqlite.h"
|
#include "tsqlite.h"
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
#include "tlibc/collections/List.h"
|
#include "tlibc/collections/List.h"
|
||||||
|
|||||||
302
src/cli/ClientCLI/main_screen.c
Normal file
302
src/cli/ClientCLI/main_screen.c
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
#include "tlibc/term.h"
|
||||||
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
#include "tim.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const str greeting_art = STR(
|
||||||
|
" ^,,^ ╱|\n"
|
||||||
|
" ( •·•) Meum! (o.o`7\n"
|
||||||
|
" / ` | Meum... |`˜ \\\n"
|
||||||
|
"\\(_,J J L l`,)/\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
static const str farewell_art = STR(
|
||||||
|
" ^,,^ ╱|\n"
|
||||||
|
" ( -.-) (>,<`7\n"
|
||||||
|
" / ` | Goodbye! |`˜ \\\n"
|
||||||
|
"\\(_,J J L l`,)/\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_test_label(void* data, TimRect place, bool is_selected);
|
||||||
|
static void draw_central_panel(void* data, TimRect place);
|
||||||
|
static Result(SavedServer*) joinNewServer(ClientCLI* self);
|
||||||
|
static Result(SavedServer*) selectServerFromCache(ClientCLI* self);
|
||||||
|
static Result(void) showSavedServer(ClientCLI* self, SavedServer* server);
|
||||||
|
static Result(void) registerAtServer(ClientCLI* self);
|
||||||
|
static Result(void) loginAtServer(ClientCLI* self);
|
||||||
|
|
||||||
|
|
||||||
|
void MainScreenContext_construct(MainScreenContext* ctx, ClientCLI* client){
|
||||||
|
zeroStruct(ctx);
|
||||||
|
ctx->client = client;
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_panel //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List(TimPanelItem) items = List_TimPanelItem_alloc(4);
|
||||||
|
|
||||||
|
TimPanelItem item_test_label = {
|
||||||
|
.w = A, .h = 12, .data = ctx, .draw = draw_test_label
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
|
||||||
|
ctx->central_panel.items = items.data;
|
||||||
|
ctx->central_panel.len = items.len;
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_scroll_view //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
ctx->central_scroll_view.content_h = 50;
|
||||||
|
ctx->central_scroll_view.data = ctx;
|
||||||
|
ctx->central_scroll_view.draw = draw_central_panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainScreenContext_destroy(MainScreenContext* ctx){
|
||||||
|
free(ctx->central_panel.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_screen(MainScreenContext* ctx){
|
||||||
|
if (tim->event.type == TimEvent_Draw) {
|
||||||
|
tim_fill(tim_cell(" ", ctx->client->style.common.fg, ctx->client->style.common.bg), 0, 0, A, A);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tim_button_noborder("[Esc/Q] Exit", 1, 0, 14, 1, ctx->client->style.common)
|
||||||
|
|| tim_is_key_press('q')
|
||||||
|
|| tim_is_key_press(TimKey_Escape))
|
||||||
|
{
|
||||||
|
ctx->client->state = ClientCLIState_Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
tim_scroll_view(&ctx->central_scroll_view, 0, 1, ~0, ~0, ctx->client->style.common);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_test_label(void* data, TimRect place, bool is_selected){
|
||||||
|
MainScreenContext* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
|
tim_frame(place.x, place.y, place.w, place.h, style);
|
||||||
|
tim_label("0\n1\n2\n3\n4\n5\n6\n7\n8\n9",
|
||||||
|
place.x + 1, place.y + 1, place.w - 2, place.h - 2, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_central_panel(void* data, TimRect place){
|
||||||
|
MainScreenContext* ctx = data;
|
||||||
|
tim_panel(&ctx->central_panel, false, place.x, place.y, place.w, place.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop){
|
||||||
|
Deferral(64);
|
||||||
|
|
||||||
|
if(is_alias("q") || is_alias("quit") || is_alias("exit")){
|
||||||
|
printf(FMT_str"\n", farewell_art.len, farewell_art.data);
|
||||||
|
*stop = true;
|
||||||
|
}
|
||||||
|
else if(is_alias("clear")){
|
||||||
|
term_clear();
|
||||||
|
}
|
||||||
|
else if(is_alias("h") || is_alias("help")){
|
||||||
|
printf(
|
||||||
|
"COMMANDS:\n"
|
||||||
|
"Without connection:\n"
|
||||||
|
" h, help Show this message.\n"
|
||||||
|
" q, quit, exit Close the program.\n"
|
||||||
|
" clear Clear the screen.\n"
|
||||||
|
"Connection:\n"
|
||||||
|
" j, join Join a new server and select it.\n"
|
||||||
|
" s, select Select a server you joined before.\n"
|
||||||
|
"After connection:\n"
|
||||||
|
" r, register Create account on selected server\n"
|
||||||
|
" l, login Authorize on selected server\n"
|
||||||
|
"Authorized:\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (is_alias("j") || is_alias("join")){
|
||||||
|
// ask address and key, connect to server
|
||||||
|
try_void(joinNewServer(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("s") || is_alias("select")){
|
||||||
|
// show scrollable list of servers, get selected one
|
||||||
|
try_void(selectServerFromCache(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("r") || is_alias("register")){
|
||||||
|
try_void(registerAtServer(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("l") || is_alias("login")){
|
||||||
|
try_void(loginAtServer(self));
|
||||||
|
// TODO: call Client_runIO():
|
||||||
|
// function with infinite loop which sends and receives messages
|
||||||
|
// with navigation across server channels
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("ERROR: unknown command.\n"
|
||||||
|
"Use 'h' to see list of avaliable commands\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static Result(void) joinNewServer(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
// ask server address
|
||||||
|
const u32 address_alloc_size = HOSTADDR_SIZE_MAX + 1;
|
||||||
|
str address = str_construct((char*)malloc(address_alloc_size), address_alloc_size, true);
|
||||||
|
Defer(if(!success) str_destroy(address));
|
||||||
|
printf("Enter server address (ip:port):\n");
|
||||||
|
try_void(term_readLine(address.data, address.len));
|
||||||
|
address.len = strlen(address.data);
|
||||||
|
str_trim(&address, true);
|
||||||
|
|
||||||
|
// ask server public key
|
||||||
|
const u32 server_pk_alloc_size = PUBLIC_KEY_BASE64_SIZE_MAX + 1;
|
||||||
|
str server_pk = str_construct((char*)malloc(server_pk_alloc_size), server_pk_alloc_size, true);
|
||||||
|
Defer(if(!success) str_destroy(server_pk));
|
||||||
|
printf("Enter server public key (RSA-Public-<SIZE>:<DATA>):\n");
|
||||||
|
try_void(term_readLine(server_pk.data, server_pk.len));
|
||||||
|
server_pk.len = strlen(server_pk.data);
|
||||||
|
str_trim(&server_pk, true);
|
||||||
|
|
||||||
|
printf("Connecting to server...\n");
|
||||||
|
try_void(Client_connect(self->client, address.data, server_pk.data));
|
||||||
|
printf("Connection established\n");
|
||||||
|
|
||||||
|
str server_name = str_null;
|
||||||
|
try_void(Client_getServerName(self->client, &server_name));
|
||||||
|
Defer(if(!success) str_destroy(server_name));
|
||||||
|
str server_description = str_null;
|
||||||
|
try_void(Client_getServerDescription(self->client, &server_description));
|
||||||
|
Defer(if(!success) str_destroy(server_description));
|
||||||
|
|
||||||
|
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(showSavedServer(self, &server));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) selectServerFromCache(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
u32 servers_count = self->saved_servers.len;
|
||||||
|
if(servers_count == 0){
|
||||||
|
printf("No saved servers found\n");
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u32 i = 0; i < servers_count; i++){
|
||||||
|
SavedServer* server = &self->saved_servers.data[i];
|
||||||
|
printf("[%02u] "FMT_str" "FMT_str"\n",
|
||||||
|
i, str_unwrap(server->address), str_unwrap(server->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
u32 selected_i = -1;
|
||||||
|
while(true) {
|
||||||
|
printf("Type 'q' to cancel\n");
|
||||||
|
printf("Select server number: ");
|
||||||
|
try_void(term_readLine(buf, sizeof(buf)));
|
||||||
|
str input_line = str_from_cstr(buf);
|
||||||
|
str_trim(&input_line, true);
|
||||||
|
if(str_equals(input_line, STR("q"))){
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
if(sscanf(buf, FMT_u32, &selected_i) != 1){
|
||||||
|
printf("ERROR: not a number\n");
|
||||||
|
}
|
||||||
|
else if(selected_i >= servers_count){
|
||||||
|
printf("ERROR: not a server number\n");
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
SavedServer* selected_server = &self->saved_servers.data[selected_i];
|
||||||
|
|
||||||
|
printf("Connecting to '"FMT_str"'...\n", str_unwrap(selected_server->address));
|
||||||
|
try_void(Client_connect(self->client, selected_server->address.data, selected_server->pk_base64.data));
|
||||||
|
printf("Connection established\n");
|
||||||
|
|
||||||
|
// update server name
|
||||||
|
bool server_info_changed = false;
|
||||||
|
str updated_server_name = str_null;
|
||||||
|
try_void(Client_getServerName(self->client, &updated_server_name));
|
||||||
|
Defer(if(!success) str_destroy(updated_server_name));
|
||||||
|
if(!str_equals(updated_server_name, selected_server->name)){
|
||||||
|
server_info_changed = true;
|
||||||
|
selected_server->name = updated_server_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update server description
|
||||||
|
str updated_server_description = str_null;
|
||||||
|
try_void(Client_getServerDescription(self->client, &updated_server_description));
|
||||||
|
Defer(if(!success) str_destroy(updated_server_description));
|
||||||
|
if(!str_equals(updated_server_description, selected_server->description)){
|
||||||
|
server_info_changed = true;
|
||||||
|
selected_server->description = updated_server_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(server_info_changed){
|
||||||
|
try_void(SavedServer_createOrUpdate(self->queries, selected_server));
|
||||||
|
}
|
||||||
|
|
||||||
|
try_void(showSavedServer(self, selected_server));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) showSavedServer(ClientCLI* self, SavedServer* server){
|
||||||
|
Deferral(8);
|
||||||
|
(void)self;
|
||||||
|
|
||||||
|
printf("Server Name: "FMT_str"\n", str_unwrap(server->name));
|
||||||
|
printf("Host Address: "FMT_str"\n", str_unwrap(server->address));
|
||||||
|
printf("Description:\n"FMT_str"\n\n", str_unwrap(server->description));
|
||||||
|
printf("Public Key:\n" FMT_str"\n\n", str_unwrap(server->pk_base64));
|
||||||
|
printf("Type 'register' if you don't have an account on the server.\n");
|
||||||
|
printf("Type 'login' to authorize on the server.\n");
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) registerAtServer(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
i64 user_id = 0;
|
||||||
|
try_void(Client_register(self->client, &user_id));
|
||||||
|
printf("Registered successfully\n");
|
||||||
|
printf("user_id: "FMT_i64"\n", user_id);
|
||||||
|
try_assert(user_id > 0);
|
||||||
|
// TODO: use user_id somewhere
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) loginAtServer(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
i64 user_id = 0, landing_channel_id = 0;
|
||||||
|
try_void(Client_login(self->client, &user_id, &landing_channel_id));
|
||||||
|
printf("Authorized successfully\n");
|
||||||
|
printf("user_id: "FMT_i64", landing_channel_id: "FMT_i64"\n", user_id, landing_channel_id);
|
||||||
|
try_assert(user_id > 0);
|
||||||
|
// TODO: use user_id, landing_channel_id somewhere
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
195
src/cli/ClientCLI/start_screen.c
Normal file
195
src/cli/ClientCLI/start_screen.c
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
#include "tlibc/filesystem.h"
|
||||||
|
|
||||||
|
static void draw_central_buttons_panel(void* data, TimRect place, bool is_selected);
|
||||||
|
static void draw_start_button(void* data, TimRect place, bool is_selected);
|
||||||
|
static void draw_exit_button(void* data, TimRect place, bool is_selected);
|
||||||
|
static Result(void) openUserDB(StartScreenContext* ctx);
|
||||||
|
|
||||||
|
|
||||||
|
#define handleError(R) _handleError(ctx, R)
|
||||||
|
static void _handleError(StartScreenContext* ctx, ResultVar(void) r){
|
||||||
|
free(ctx->err_msg);
|
||||||
|
ctx->err_msg = Error_toStr(r.error).data;
|
||||||
|
Error_free(r.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearError(StartScreenContext* ctx){
|
||||||
|
free(ctx->err_msg);
|
||||||
|
ctx->err_msg = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScreenContext_construct(StartScreenContext* ctx, ClientCLI* client){
|
||||||
|
zeroStruct(ctx);
|
||||||
|
ctx->client = client;
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// input_username //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1);
|
||||||
|
TextInputState_construct(&ctx->input_username, "[username]",
|
||||||
|
username_buf, NULL, false,
|
||||||
|
ctx->client->style.common, ctx->client->style.focused);
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// input_password //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1);
|
||||||
|
TextInputState_construct(&ctx->input_password, "[password]",
|
||||||
|
password_buf, NULL, true,
|
||||||
|
ctx->client->style.common, ctx->client->style.focused);
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_panel //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List(TimPanelItem) items = List_TimPanelItem_alloc(4);
|
||||||
|
|
||||||
|
TimPanelItem item_username_input;
|
||||||
|
TimPanelItem_fromTextInputState(&item_username_input, &ctx->input_username);
|
||||||
|
List_TimPanelItem_push(&items, item_username_input);
|
||||||
|
|
||||||
|
TimPanelItem item_password_input;
|
||||||
|
TimPanelItem_fromTextInputState(&item_password_input, &ctx->input_password);
|
||||||
|
List_TimPanelItem_push(&items, item_password_input);
|
||||||
|
|
||||||
|
TimPanelItem item_central_buttons_panel = {
|
||||||
|
.w = A, .h = 3, .data = ctx, .draw = draw_central_buttons_panel
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_central_buttons_panel);
|
||||||
|
|
||||||
|
ctx->central_panel.items = items.data;
|
||||||
|
ctx->central_panel.len = items.len;
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_buttons_panel //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List(TimPanelItem) items = List_TimPanelItem_alloc(4);
|
||||||
|
|
||||||
|
TimPanelItem item_start_button = {
|
||||||
|
.w = A, .h = 3, .data = ctx, .draw = draw_start_button
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_start_button);
|
||||||
|
|
||||||
|
TimPanelItem item_exit_button = {
|
||||||
|
.w = A, .h = 3, .data = ctx, .draw = draw_exit_button
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_exit_button);
|
||||||
|
|
||||||
|
ctx->central_buttons_panel.items = items.data;
|
||||||
|
ctx->central_buttons_panel.len = items.len;
|
||||||
|
ctx->central_buttons_panel.is_horizontal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScreenContext_destroy(StartScreenContext* ctx){
|
||||||
|
free(ctx->input_username.edit.s);
|
||||||
|
free(ctx->input_password.edit.s);
|
||||||
|
free(ctx->err_msg);
|
||||||
|
free(ctx->central_panel.items);
|
||||||
|
free(ctx->central_buttons_panel.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_screen(StartScreenContext* ctx)
|
||||||
|
{
|
||||||
|
if (tim->event.type == TimEvent_Draw) {
|
||||||
|
tim_fill(tim_cell(" ", ctx->client->style.common.fg, ctx->client->style.common.bg), 0, 0, A, A);
|
||||||
|
}
|
||||||
|
|
||||||
|
tim_frame(A, A, 40, 11, ctx->client->style.common);
|
||||||
|
tim_panel(&ctx->central_panel, true, A, A, 38, 9);
|
||||||
|
|
||||||
|
if(ctx->err_msg){
|
||||||
|
i32 below_list = tim->scopes[tim->scope].h/2 + 6;
|
||||||
|
tim_label("ERROR: ", A, below_list, A, A, ctx->client->style.error);
|
||||||
|
tim_label(ctx->err_msg, A, below_list + 1, A, A, ctx->client->style.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_central_buttons_panel(void* data, TimRect place, bool is_selected){
|
||||||
|
StartScreenContext* ctx = data;
|
||||||
|
tim_panel(&ctx->central_buttons_panel, is_selected, place.x, place.y, place.w, place.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_start_button(void* data, TimRect place, bool is_selected){
|
||||||
|
StartScreenContext* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
|
if(tim_button("[Enter] Start", place.x, place.y, place.w, A, style)
|
||||||
|
|| tim_is_key_press(TimKey_Enter))
|
||||||
|
{
|
||||||
|
clearError(ctx);
|
||||||
|
|
||||||
|
// check username
|
||||||
|
str username = str_from_cstr(ctx->input_username.edit.s);
|
||||||
|
str_trim(&username, true);
|
||||||
|
str name_error_str = validateUsername_str(username);
|
||||||
|
if(name_error_str.data){
|
||||||
|
ctx->err_msg = name_error_str.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check password
|
||||||
|
str password = str_from_cstr(ctx->input_password.edit.s);
|
||||||
|
str_trim(&password, true);
|
||||||
|
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
|
||||||
|
ctx->err_msg = sprintf_malloc(
|
||||||
|
"password length (in bytes) must be >= %i and <= %i",
|
||||||
|
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create client
|
||||||
|
try_handle(ctx->client->client, p, Client_create(username, password), handleError);
|
||||||
|
// init user DB
|
||||||
|
try_handle_void(openUserDB(ctx), handleError);
|
||||||
|
// erase password from memory
|
||||||
|
memset(ctx->input_password.edit.s, 0, ctx->input_password.edit.capacity);
|
||||||
|
// switch to next screen
|
||||||
|
ctx->client->state = ClientCLIState_MainScreen;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_exit_button(void* data, TimRect place, bool is_selected){
|
||||||
|
StartScreenContext* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
|
if(tim_button("[Esc/Q] Exit", place.x, place.y, place.w, A, style)
|
||||||
|
|| tim_is_key_press('q')
|
||||||
|
|| tim_is_key_press(TimKey_Escape)
|
||||||
|
|| ctx->input_username.result_key == TimKey_Escape
|
||||||
|
|| ctx->input_password.result_key == TimKey_Escape)
|
||||||
|
{
|
||||||
|
ctx->client->state = ClientCLIState_Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) openUserDB(StartScreenContext* ctx){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
str username = Client_getUserName(ctx->client->client);
|
||||||
|
// TODO: encrypt user database
|
||||||
|
// Array(u8) user_data_key = Client_getUserDataKey(ctx->client->client);
|
||||||
|
|
||||||
|
// build database file path
|
||||||
|
try(char* user_dir, p, path_getUserDir());
|
||||||
|
Defer(free(user_dir));
|
||||||
|
char* db_path = strcat_malloc(
|
||||||
|
user_dir,
|
||||||
|
path_seps".local"path_seps"tcp-chat-client"path_seps"user-db"path_seps,
|
||||||
|
username.data, ".sqlite"
|
||||||
|
);
|
||||||
|
Defer(free(db_path));
|
||||||
|
|
||||||
|
try(ctx->client->db, p, ClientDatabase_open(db_path));
|
||||||
|
try(ctx->client->queries, p, ClientQueries_compile(ctx->client->db));
|
||||||
|
|
||||||
|
// load whole servers table to list
|
||||||
|
try_void(SavedServer_getAll(ctx->client->queries, &ctx->client->saved_servers));
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
32
src/cli/ClientCLI/widgets.c
Normal file
32
src/cli/ClientCLI/widgets.c
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
|
||||||
|
void TextInputState_construct(TextInputState* ctx, cstr label,
|
||||||
|
Array(char) buf, NULLABLE(cstr) initial_value, bool masked,
|
||||||
|
TimStyle common, TimStyle focused)
|
||||||
|
{
|
||||||
|
TimEditState_construct(&ctx->edit, buf.data, buf.len, initial_value);
|
||||||
|
ctx->edit.masked = masked;
|
||||||
|
ctx->label = label;
|
||||||
|
ctx->style.common = common;
|
||||||
|
ctx->style.focused = focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void text_input(TextInputState* ctx, i32 x, i32 y, i32 w, TimStyle style){
|
||||||
|
ctx->result_key = tim_edit(&ctx->edit, x, y, w, style);
|
||||||
|
tim_label(ctx->label, x + 3, y, A, 1, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimPanelItem_fromTextInputState(TimPanelItem* item, TextInputState* input){
|
||||||
|
zeroStruct(item);
|
||||||
|
item->w = A;
|
||||||
|
item->h = 3;
|
||||||
|
item->data = input;
|
||||||
|
item->focus_target = &input->edit;
|
||||||
|
item->draw = draw_item_text_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_item_text_input(void* data, TimRect place, bool is_selected){
|
||||||
|
TextInputState* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->style.focused : ctx->style.common;
|
||||||
|
text_input(ctx, place.x, place.y, place.w, style);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/tlibc.h"
|
#include "tlibc/tlibc.h"
|
||||||
#include "tlibtoml.h"
|
#include "tlibtoml.h"
|
||||||
#include "tcp-chat/tcp-chat.h"
|
|
||||||
#include "cryptography/RSA.h"
|
#include "cryptography/RSA.h"
|
||||||
#include "cli/modes/modes.h"
|
#include "cli/modes/modes.h"
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Result(void) run_ClientMode(cstr config_path) {
|
|||||||
ClientCLI_construct(&client);
|
ClientCLI_construct(&client);
|
||||||
Defer(ClientCLI_destroy(&client));
|
Defer(ClientCLI_destroy(&client));
|
||||||
// start infinite loop on main thread
|
// start infinite loop on main thread
|
||||||
try_void(ClientCLI_run(&client));
|
ClientCLI_run(&client);
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
#include <pthread.h>
|
||||||
|
#include "tcp-chat.h"
|
||||||
#include "modes.h"
|
#include "modes.h"
|
||||||
#include "tcp-chat/server.h"
|
|
||||||
#include "tlibc/time.h"
|
#include "tlibc/time.h"
|
||||||
#include "tlibc/term.h"
|
#include "tlibc/term.h"
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
typedef struct ServerLogger {
|
typedef struct ServerLogger {
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ void ServerConnection_close(ServerConnection* self){
|
|||||||
EncryptedSocketTCP_destroy(&self->sock);
|
EncryptedSocketTCP_destroy(&self->sock);
|
||||||
Array_u8_destroy(&self->token);
|
Array_u8_destroy(&self->token);
|
||||||
Array_u8_destroy(&self->session_key);
|
Array_u8_destroy(&self->session_key);
|
||||||
|
MessageBlock_destroy(&self->received_message_block);
|
||||||
free(self);
|
free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +74,8 @@ Result(ServerConnection*) ServerConnection_open(Client* client, cstr server_addr
|
|||||||
PacketType_ServerHandshake));
|
PacketType_ServerHandshake));
|
||||||
conn->session_id = server_handshake.session_id;
|
conn->session_id = server_handshake.session_id;
|
||||||
|
|
||||||
|
MessageBlock_alloc(&conn->received_message_block);
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
Return RESULT_VALUE(p, conn);
|
Return RESULT_VALUE(p, conn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ Result(void) Client_register(Client* self, i64* out_user_id){
|
|||||||
PacketHeader req_head, res_head;
|
PacketHeader req_head, res_head;
|
||||||
RegisterRequest req;
|
RegisterRequest req;
|
||||||
RegisterResponse res;
|
RegisterResponse res;
|
||||||
// TODO: hash token with server public key
|
|
||||||
try_void(RegisterRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
|
try_void(RegisterRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
|
||||||
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
||||||
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_RegisterResponse));
|
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_RegisterResponse));
|
||||||
@@ -98,7 +97,6 @@ Result(void) Client_login(Client* self, i64* out_user_id, i64* out_landing_chann
|
|||||||
PacketHeader req_head, res_head;
|
PacketHeader req_head, res_head;
|
||||||
LoginRequest req;
|
LoginRequest req;
|
||||||
LoginResponse res;
|
LoginResponse res;
|
||||||
// TODO: hash token with server public key
|
|
||||||
try_void(LoginRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
|
try_void(LoginRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
|
||||||
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
||||||
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_LoginResponse));
|
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_LoginResponse));
|
||||||
@@ -108,3 +106,71 @@ Result(void) Client_login(Client* self, i64* out_user_id, i64* out_landing_chann
|
|||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result(i64) Client_sendMessage(Client* self, i64 channel_id, Array(u8) content, DateTime* out_timestamp){
|
||||||
|
Deferral(1);
|
||||||
|
try_assert(self != NULL);
|
||||||
|
try_assert(self->conn != NULL && "didn't connect to a server yet");
|
||||||
|
try_assert(content.len >= MESSAGE_SIZE_MIN && content.len <= MESSAGE_SIZE_MAX);
|
||||||
|
|
||||||
|
PacketHeader req_head, res_head;
|
||||||
|
SendMessageRequest req;
|
||||||
|
SendMessageResponse res;
|
||||||
|
SendMessageRequest_construct(&req, &req_head, channel_id, content.len);
|
||||||
|
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
||||||
|
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_SendMessageResponse));
|
||||||
|
*out_timestamp = res.timestamp;
|
||||||
|
|
||||||
|
Return RESULT_VALUE(i, res.message_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result(u32) Client_receiveMessageBlock(Client* self, i64 channel_id, i64 first_message_id, u32 messages_count){
|
||||||
|
Deferral(1);
|
||||||
|
try_assert(self != NULL);
|
||||||
|
try_assert(self->conn != NULL && "didn't connect to a server yet");
|
||||||
|
|
||||||
|
PacketHeader req_head, res_head;
|
||||||
|
GetMessageBlockRequest req;
|
||||||
|
GetMessageBlockResponse res;
|
||||||
|
GetMessageBlockRequest_construct(&req, &req_head, channel_id, first_message_id, messages_count);
|
||||||
|
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
||||||
|
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_GetMessageBlockResponse));
|
||||||
|
self->conn->received_message_block.messages_count = res.messages_count;
|
||||||
|
self->conn->received_message_block.datum.len = res.data_size;
|
||||||
|
try_void(
|
||||||
|
EncryptedSocketTCP_recv(
|
||||||
|
&self->conn->sock,
|
||||||
|
self->conn->received_message_block.datum,
|
||||||
|
SocketRecvFlag_WholeBuffer
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Return RESULT_VALUE(u, res.messages_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result(u32) Client_popMessage(Client* self, Array(u8) dst_content,
|
||||||
|
i64* message_id, i64* sender_id, DateTime* timestamp_utc)
|
||||||
|
{
|
||||||
|
Deferral(1);
|
||||||
|
try_assert(self != NULL);
|
||||||
|
try_assert(self->conn != NULL && "didn't connect to a server yet");
|
||||||
|
try_assert(dst_content.len >= MESSAGE_SIZE_MAX);
|
||||||
|
|
||||||
|
MessageMeta msg_meta = {0};
|
||||||
|
try(bool read_success, u,
|
||||||
|
MessageBlock_readMessage(
|
||||||
|
&self->conn->received_message_block,
|
||||||
|
&msg_meta,
|
||||||
|
dst_content
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if(!read_success){
|
||||||
|
Return RESULT_VALUE(u, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
*message_id = msg_meta.id;
|
||||||
|
*sender_id = msg_meta.sender_id;
|
||||||
|
*timestamp_utc = msg_meta.timestamp;
|
||||||
|
|
||||||
|
Return RESULT_VALUE(u, msg_meta.data_size);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tcp-chat/client.h"
|
#include "tcp-chat.h"
|
||||||
#include "cryptography/AES.h"
|
#include "cryptography/AES.h"
|
||||||
#include "cryptography/RSA.h"
|
#include "cryptography/RSA.h"
|
||||||
#include "network/encrypted_sockets.h"
|
#include "network/encrypted_sockets.h"
|
||||||
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
|
||||||
typedef struct ServerConnection ServerConnection;
|
typedef struct ServerConnection ServerConnection;
|
||||||
|
|
||||||
@@ -12,7 +13,6 @@ typedef struct Client {
|
|||||||
ServerConnection* conn;
|
ServerConnection* conn;
|
||||||
} Client;
|
} Client;
|
||||||
|
|
||||||
|
|
||||||
typedef struct ServerConnection {
|
typedef struct ServerConnection {
|
||||||
Client* client;
|
Client* client;
|
||||||
EndpointIPv4 server_end;
|
EndpointIPv4 server_end;
|
||||||
@@ -23,6 +23,7 @@ typedef struct ServerConnection {
|
|||||||
EncryptedSocketTCP sock;
|
EncryptedSocketTCP sock;
|
||||||
i64 session_id;
|
i64 session_id;
|
||||||
i64 user_id;
|
i64 user_id;
|
||||||
|
MessageBlock received_message_block;
|
||||||
} ServerConnection;
|
} ServerConnection;
|
||||||
|
|
||||||
/// @param server_addr_cstr
|
/// @param server_addr_cstr
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "RSA.h"
|
#include "RSA.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "bearssl_x509.h"
|
|
||||||
#include "bearssl_pem.h"
|
|
||||||
#include "tlibc/base64.h"
|
#include "tlibc/base64.h"
|
||||||
|
|
||||||
// https://crypto.stackexchange.com/questions/3110/impacts-of-not-using-rsa-exponent-of-65537
|
// https://crypto.stackexchange.com/questions/3110/impacts-of-not-using-rsa-exponent-of-65537
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/errors.h"
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/collections/Array.h"
|
#include "tlibc/collections/Array.h"
|
||||||
#include "tlibc/collections/Array_impl/Array_u8.h"
|
#include "tlibc/collections/Array_impl/Array_u8.h"
|
||||||
#include "bearssl_rand.h"
|
#include "bearssl_rand.h"
|
||||||
#include "bearssl_hash.h"
|
#include "bearssl_hash.h"
|
||||||
#include "tcp-chat/common_constants.h"
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tcp-chat/tcp-chat.h"
|
#include "tcp-chat.h"
|
||||||
#include "endpoint.h"
|
#include "endpoint.h"
|
||||||
|
|
||||||
#if !defined(KN_USE_WINSOCK)
|
#if !defined(KN_USE_WINSOCK)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/errors.h"
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/magic.h"
|
#include "tlibc/magic.h"
|
||||||
#include "tcp-chat/common_constants.h"
|
|
||||||
|
|
||||||
#define AES_SESSION_KEY_SIZE 32
|
#define AES_SESSION_KEY_SIZE 32
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ str validateUsername_str(str username){
|
|||||||
if(username.len < USERNAME_SIZE_MIN || username.len > USERNAME_SIZE_MAX){
|
if(username.len < USERNAME_SIZE_MIN || username.len > USERNAME_SIZE_MAX){
|
||||||
return str_from_cstr(
|
return str_from_cstr(
|
||||||
sprintf_malloc(
|
sprintf_malloc(
|
||||||
"username length (in bytes) must be >= %i and <= %i\n",
|
"username length (in bytes) must be >= %i and <= %i",
|
||||||
USERNAME_SIZE_MIN, USERNAME_SIZE_MAX
|
USERNAME_SIZE_MIN, USERNAME_SIZE_MAX
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -142,62 +142,67 @@ void RegisterResponse_construct(RegisterResponse *ptr, PacketHeader* header,
|
|||||||
ptr->user_id = user_id;
|
ptr->user_id = user_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(u32) MessageBlock_writeMessage(
|
Result(bool) MessageBlock_writeMessage(MessageBlock* block,
|
||||||
MessageMeta* msg, Array(u8) msg_content,
|
const MessageMeta* msg_meta, const Array(u8) msg_content)
|
||||||
MessageBlockMeta* block_meta, Array(u8)* block_free_part)
|
|
||||||
{
|
{
|
||||||
Deferral(1);
|
Deferral(1);
|
||||||
try_assert(msg->data_size >= MESSAGE_SIZE_MIN && msg->data_size <= MESSAGE_SIZE_MAX);
|
|
||||||
try_assert(msg->data_size <= msg_content.len);
|
|
||||||
|
|
||||||
u32 offset_increment = sizeof(MessageMeta) + msg->data_size;
|
// check msg_meta
|
||||||
if(block_free_part->len < offset_increment){
|
try_assert(msg_meta->magic.n == MESSAGE_MAGIC.n);
|
||||||
Return RESULT_VALUE(u, 0);
|
try_assert(msg_meta->data_size >= MESSAGE_SIZE_MIN && msg_meta->data_size <= MESSAGE_SIZE_MAX);
|
||||||
|
try_assert(msg_meta->data_size <= msg_content.len);
|
||||||
|
try_assert(msg_meta->id > 0);
|
||||||
|
try_assert(msg_meta->sender_id > 0);
|
||||||
|
try_assert(msg_meta->timestamp.d.year > 2024);
|
||||||
|
// check block->datum.len
|
||||||
|
if(block->datum.len < block->offset + sizeof(MessageMeta) + msg_meta->data_size){
|
||||||
|
Return RESULT_VALUE(u, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(block_free_part->data, msg, sizeof(MessageMeta));
|
// write msg_meta
|
||||||
block_free_part->data += sizeof(MessageMeta);
|
memcpy(block->datum.data + block->offset, msg_meta, sizeof(MessageMeta));
|
||||||
block_free_part->len -= sizeof(MessageMeta);
|
block->offset += sizeof(MessageMeta);
|
||||||
|
|
||||||
memcpy(block_free_part->data, msg_content.data, msg->data_size);
|
// write msg_content
|
||||||
block_free_part->data += msg->data_size;
|
memcpy(block->datum.data + block->offset, msg_content.data, msg_meta->data_size);
|
||||||
block_free_part->len -= msg->data_size;
|
block->offset += msg_meta->data_size;
|
||||||
|
|
||||||
if(block_meta->message_count == 0)
|
|
||||||
block_meta->first_message_id = msg->id;
|
|
||||||
block_meta->message_count++;
|
|
||||||
block_meta->data_size += offset_increment;
|
|
||||||
|
|
||||||
Return RESULT_VALUE(u, offset_increment);
|
block->messages_count++;
|
||||||
|
Return RESULT_VALUE(u, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(u32) MessageBlock_readMessage(
|
Result(bool) MessageBlock_readMessage(MessageBlock* block,
|
||||||
Array(u8)* block_unread_part,
|
MessageMeta* msg_meta, Array(u8) msg_content)
|
||||||
MessageMeta* msg, Array(u8) msg_content)
|
|
||||||
{
|
{
|
||||||
Deferral(1);
|
Deferral(1);
|
||||||
try_assert(block_unread_part->len >= sizeof(MessageMeta) + MESSAGE_SIZE_MIN);
|
|
||||||
try_assert(msg_content.len >= MESSAGE_SIZE_MIN && msg_content.len <= MESSAGE_SIZE_MAX);
|
|
||||||
|
|
||||||
memcpy(msg, block_unread_part->data, sizeof(MessageMeta));
|
|
||||||
block_unread_part->data += sizeof(MessageMeta);
|
|
||||||
block_unread_part->len -= sizeof(MessageMeta);
|
|
||||||
|
|
||||||
if(msg->magic.n != MESSAGE_MAGIC.n){
|
// check block
|
||||||
Return RESULT_VALUE(u, 0);
|
if(block->messages_count == 0){
|
||||||
|
Return RESULT_VALUE(u, false);
|
||||||
}
|
}
|
||||||
try_assert(block_unread_part->len >= msg->data_size);
|
try_assert(block->datum.len >= block->offset + sizeof(MessageMeta) + MESSAGE_SIZE_MIN);
|
||||||
try_assert(msg->data_size >= MESSAGE_SIZE_MIN && msg->data_size <= MESSAGE_SIZE_MAX);
|
// check msg_content.len
|
||||||
try_assert(msg->id > 0);
|
try_assert(msg_content.len >= MESSAGE_SIZE_MAX);
|
||||||
try_assert(msg->sender_id > 0);
|
|
||||||
try_assert(msg->timestamp.d.year > 2024);
|
// read msg_meta
|
||||||
|
memcpy(msg_meta, block->datum.data + block->offset, sizeof(MessageMeta));
|
||||||
|
block->offset += sizeof(MessageMeta);
|
||||||
|
|
||||||
memcpy(msg_content.data, block_unread_part->data, msg->data_size);
|
// check msg_meta
|
||||||
block_unread_part->data += msg->data_size;
|
try_assert(msg_meta->magic.n == MESSAGE_MAGIC.n);
|
||||||
block_unread_part->len -= msg->data_size;
|
try_assert(msg_meta->data_size >= MESSAGE_SIZE_MIN && msg_meta->data_size <= MESSAGE_SIZE_MAX);
|
||||||
|
try_assert(msg_meta->data_size <= msg_content.len);
|
||||||
|
try_assert(msg_meta->id > 0);
|
||||||
|
try_assert(msg_meta->sender_id > 0);
|
||||||
|
try_assert(msg_meta->timestamp.d.year > 2024);
|
||||||
|
try_assert(block->datum.len >= block->offset + msg_meta->data_size);
|
||||||
|
|
||||||
u32 offset_increment = sizeof(MessageMeta) + msg->data_size;
|
// read msg_content
|
||||||
Return RESULT_VALUE(u, offset_increment);
|
memcpy(msg_content.data, block->datum.data + block->offset, msg_meta->data_size);
|
||||||
|
block->offset += msg_meta->data_size;
|
||||||
|
|
||||||
|
block->messages_count--;
|
||||||
|
Return RESULT_VALUE(u, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendMessageRequest_construct(SendMessageRequest *ptr, PacketHeader *header,
|
void SendMessageRequest_construct(SendMessageRequest *ptr, PacketHeader *header,
|
||||||
@@ -219,19 +224,20 @@ void SendMessageResponse_construct(SendMessageResponse *ptr, PacketHeader *heade
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GetMessageBlockRequest_construct(GetMessageBlockRequest *ptr, PacketHeader *header,
|
void GetMessageBlockRequest_construct(GetMessageBlockRequest *ptr, PacketHeader *header,
|
||||||
i64 channel_id, i64 first_message_id, u32 message_count)
|
i64 channel_id, i64 first_message_id, u32 messages_count)
|
||||||
{
|
{
|
||||||
_PacketHeader_construct(GetMessageBlockRequest);
|
_PacketHeader_construct(GetMessageBlockRequest);
|
||||||
zeroStruct(ptr);
|
zeroStruct(ptr);
|
||||||
ptr->channel_id = channel_id;
|
ptr->channel_id = channel_id;
|
||||||
ptr->first_message_id = first_message_id;
|
ptr->first_message_id = first_message_id;
|
||||||
ptr->message_count = message_count;
|
ptr->messages_count = messages_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetMessageBlockResponse_construct(GetMessageBlockResponse *ptr, PacketHeader *header,
|
void GetMessageBlockResponse_construct(GetMessageBlockResponse *ptr, PacketHeader *header,
|
||||||
MessageBlockMeta* block_meta)
|
u32 messages_count, u32 data_size)
|
||||||
{
|
{
|
||||||
_PacketHeader_construct(GetMessageBlockResponse);
|
_PacketHeader_construct(GetMessageBlockResponse);
|
||||||
zeroStruct(ptr);
|
zeroStruct(ptr);
|
||||||
memcpy(&ptr->block_meta, block_meta, sizeof(MessageBlockMeta));
|
ptr->messages_count = messages_count;
|
||||||
|
ptr->data_size = data_size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/errors.h"
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/string/str.h"
|
|
||||||
#include "tlibc/time.h"
|
#include "tlibc/time.h"
|
||||||
#include "network/tcp-chat-protocol/constant.h"
|
#include "network/tcp-chat-protocol/constant.h"
|
||||||
|
|
||||||
@@ -130,30 +129,63 @@ typedef struct MessageMeta {
|
|||||||
i64 id;
|
i64 id;
|
||||||
i64 sender_id;
|
i64 sender_id;
|
||||||
DateTime timestamp; /* UTC */
|
DateTime timestamp; /* UTC */
|
||||||
} ALIGN_PACKET_STRUCT MessageMeta;
|
} ATTRIBUTE_ALIGNED(8) MessageMeta;
|
||||||
|
|
||||||
#define MESSAGE_MAGIC ((Magic32){ .bytes = { 'M', 's', 'g', 'S' } })
|
#define MessageMeta_construct(DATA_SIZE, MESSAGE_ID, SENDER_ID, TIMESTAMP) ((MessageMeta){ \
|
||||||
|
.magic = MESSAGE_MAGIC, \
|
||||||
|
.data_size = DATA_SIZE, \
|
||||||
|
.id = MESSAGE_ID, \
|
||||||
|
.sender_id = SENDER_ID, \
|
||||||
|
.timestamp = TIMESTAMP \
|
||||||
|
})
|
||||||
|
|
||||||
typedef struct MessageBlockMeta {
|
#define MESSAGE_MAGIC ((Magic32){ .bytes = { 'M', 's', 'g', '1' } })
|
||||||
i64 first_message_id;
|
|
||||||
u32 message_count;
|
|
||||||
u32 data_size;
|
|
||||||
} ALIGN_PACKET_STRUCT MessageBlockMeta;
|
|
||||||
|
|
||||||
/// @brief write msg_meta and msg_meta->data_size bytes from msg_content to buffer
|
typedef struct MessageBlock {
|
||||||
/// @param block_meta set to {0} if block is empty yet
|
Array(u8) datum; // sequence(MessageMeta, byte[MessageMeta.data_size])
|
||||||
/// @param block_free_part .data and .len are adjusted to point to free part
|
u32 messages_count;
|
||||||
/// @return amount of bytes written to block, may be 0 if msg_meta and msg_content don't fit
|
u32 offset;
|
||||||
Result(u32) MessageBlock_writeMessage(
|
} MessageBlock;
|
||||||
MessageMeta* msg_meta, Array(u8) msg_content,
|
|
||||||
MessageBlockMeta* block_meta, Array(u8)* block_free_part);
|
|
||||||
|
|
||||||
/// @brief read message meta and content from buffer
|
static inline void MessageBlock_construct(MessageBlock* self, Array(u8) datum, u32 messages_count){
|
||||||
/// @param block_unread_part .data and .len are adjusted to point to unread part
|
self->datum = datum;
|
||||||
/// @param msg_content .len must be >= MESSAGE_SIZE_MAX
|
self->messages_count = messages_count;
|
||||||
/// @return amount of bytes read from block, may be 0 if it doesn't start with MESSAGE_MAGIC
|
self->offset = 0;
|
||||||
Result(u32) MessageBlock_readMessage(
|
}
|
||||||
Array(u8)* block_unread_part,
|
|
||||||
|
static inline void MessageBlock_alloc(MessageBlock* self){
|
||||||
|
self->datum = Array_u8_alloc(MESSAGE_BLOCK_COUNT_MAX * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX));
|
||||||
|
Array_u8_memset(&self->datum, 0);
|
||||||
|
self->messages_count = 0;
|
||||||
|
self->offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void MessageBlock_reset(MessageBlock* self){
|
||||||
|
Array_u8_memset(&self->datum, 0);
|
||||||
|
self->messages_count = 0;
|
||||||
|
self->offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void MessageBlock_destroy(MessageBlock* self){
|
||||||
|
if(!self)
|
||||||
|
return;
|
||||||
|
Array_u8_destroy(&self->datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief write msg_meta and msg_meta->data_size bytes from msg_content to block and increase block.messages_count
|
||||||
|
/// @param block use MessageBlock_alloc() to create empty block
|
||||||
|
/// @param msg_meta use MessageMeta_construct() to create message metadata
|
||||||
|
/// @param msg_content array of size >= msg_meta.data_size
|
||||||
|
/// @return false if msg_meta and msg_content don't fit in block.datum
|
||||||
|
Result(bool) MessageBlock_writeMessage(MessageBlock* block,
|
||||||
|
const MessageMeta* msg_meta, const Array(u8) msg_content);
|
||||||
|
|
||||||
|
/// @brief read msg_meta and msg_content from block and decrease block.messages_count
|
||||||
|
/// @param block a block with correct .datum and .messages_count
|
||||||
|
/// @param msg_meta out meta copied from block_data
|
||||||
|
/// @param msg_content out content copied from block_data. Array of size >= MESSAGE_SIZE_MAX
|
||||||
|
/// @return false if there are no messages to read (block.messages_count == 0)
|
||||||
|
Result(bool) MessageBlock_readMessage(MessageBlock* block,
|
||||||
MessageMeta* msg_meta, Array(u8) msg_content);
|
MessageMeta* msg_meta, Array(u8) msg_content);
|
||||||
|
|
||||||
|
|
||||||
@@ -179,18 +211,19 @@ void SendMessageResponse_construct(SendMessageResponse* ptr, PacketHeader* heade
|
|||||||
typedef struct GetMessageBlockRequest {
|
typedef struct GetMessageBlockRequest {
|
||||||
i64 channel_id;
|
i64 channel_id;
|
||||||
i64 first_message_id;
|
i64 first_message_id;
|
||||||
u32 message_count;
|
u32 messages_count;
|
||||||
} ALIGN_PACKET_STRUCT GetMessageBlockRequest;
|
} ALIGN_PACKET_STRUCT GetMessageBlockRequest;
|
||||||
|
|
||||||
void GetMessageBlockRequest_construct(GetMessageBlockRequest* ptr, PacketHeader* header,
|
void GetMessageBlockRequest_construct(GetMessageBlockRequest* ptr, PacketHeader* header,
|
||||||
i64 channel_id, i64 first_message_id, u32 message_count);
|
i64 channel_id, i64 first_message_id, u32 messages_count);
|
||||||
|
|
||||||
|
|
||||||
typedef struct GetMessageBlockResponse {
|
typedef struct GetMessageBlockResponse {
|
||||||
MessageBlockMeta block_meta;
|
u32 messages_count;
|
||||||
|
u32 data_size;
|
||||||
/* stream of size data_size : sequence (MessageMeta, byte[MessageMeta.data_size]) */
|
/* stream of size data_size : sequence (MessageMeta, byte[MessageMeta.data_size]) */
|
||||||
} ALIGN_PACKET_STRUCT GetMessageBlockResponse;
|
} ALIGN_PACKET_STRUCT GetMessageBlockResponse;
|
||||||
|
|
||||||
void GetMessageBlockResponse_construct(GetMessageBlockResponse* ptr, PacketHeader* header,
|
void GetMessageBlockResponse_construct(GetMessageBlockResponse* ptr, PacketHeader* header,
|
||||||
MessageBlockMeta* block_meta);
|
u32 messages_count, u32 data_size);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ void ClientConnection_close(ClientConnection* conn){
|
|||||||
return;
|
return;
|
||||||
EncryptedSocketTCP_destroy(&conn->sock);
|
EncryptedSocketTCP_destroy(&conn->sock);
|
||||||
Array_u8_destroy(&conn->session_key);
|
Array_u8_destroy(&conn->session_key);
|
||||||
Array_u8_destroy(&conn->message_block);
|
MessageBlock_destroy(&conn->message_block);
|
||||||
Array_u8_destroy(&conn->message_content);
|
Array_u8_destroy(&conn->message_content);
|
||||||
ServerQueries_free(conn->queries);
|
ServerQueries_free(conn->queries);
|
||||||
tsqlite_connection_close(conn->db);
|
tsqlite_connection_close(conn->db);
|
||||||
@@ -26,7 +26,7 @@ Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args)
|
|||||||
conn->session_id = args->session_id;
|
conn->session_id = args->session_id;
|
||||||
|
|
||||||
// buffers
|
// buffers
|
||||||
conn->message_block = Array_u8_alloc(MESSAGE_BLOCK_COUNT_MAX * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX));
|
MessageBlock_alloc(&conn->message_block);
|
||||||
conn->message_content = Array_u8_alloc(MESSAGE_SIZE_MAX);
|
conn->message_content = Array_u8_alloc(MESSAGE_SIZE_MAX);
|
||||||
|
|
||||||
// database
|
// database
|
||||||
|
|||||||
@@ -63,14 +63,14 @@ Result(void) Channel_saveMessage(ServerQueries* q,
|
|||||||
|
|
||||||
Result(void) Channel_loadMessageBlock(ServerQueries* q,
|
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)
|
MessageBlock* block)
|
||||||
{
|
{
|
||||||
Deferral(4);
|
Deferral(4);
|
||||||
try_assert(channel_id > 0);
|
|
||||||
try_assert(block_data.len >= count * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX));
|
|
||||||
if(count == 0){
|
if(count == 0){
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
try_assert(channel_id > 0);
|
||||||
|
try_assert(block->datum.len >= count * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX));
|
||||||
|
|
||||||
tsqlite_statement* st = q->messages.get_block;
|
tsqlite_statement* st = q->messages.get_block;
|
||||||
Defer(tsqlite_statement_reset(st));
|
Defer(tsqlite_statement_reset(st));
|
||||||
@@ -78,9 +78,7 @@ Result(void) Channel_loadMessageBlock(ServerQueries* q,
|
|||||||
try_void(tsqlite_statement_bind_i64(st, "$first_message_id", first_message_id));
|
try_void(tsqlite_statement_bind_i64(st, "$first_message_id", first_message_id));
|
||||||
try_void(tsqlite_statement_bind_i64(st, "$count", count));
|
try_void(tsqlite_statement_bind_i64(st, "$count", count));
|
||||||
|
|
||||||
zeroStruct(block_meta);
|
MessageBlock_reset(block);
|
||||||
MessageMeta msg_meta = {0};
|
|
||||||
Array(u8) msg_content;
|
|
||||||
str tmp_str = str_null;
|
str tmp_str = str_null;
|
||||||
while(true){
|
while(true){
|
||||||
try(bool has_result, i, tsqlite_statement_step(st));
|
try(bool has_result, i, tsqlite_statement_step(st));
|
||||||
@@ -88,17 +86,24 @@ Result(void) Channel_loadMessageBlock(ServerQueries* q,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
// id
|
// id
|
||||||
try(msg_meta.id, i, tsqlite_statement_getResult_i64(st));
|
try(i64 message_id, i, tsqlite_statement_getResult_i64(st));
|
||||||
// sender_id
|
// sender_id
|
||||||
try(msg_meta.sender_id, i, tsqlite_statement_getResult_i64(st));
|
try(i64 sender_id, i, tsqlite_statement_getResult_i64(st));
|
||||||
// content
|
// content
|
||||||
|
Array(u8) msg_content;
|
||||||
try_void(tsqlite_statement_getResult_blob(st, &msg_content));
|
try_void(tsqlite_statement_getResult_blob(st, &msg_content));
|
||||||
// timestamp
|
// timestamp
|
||||||
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
|
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
|
||||||
try_void(DateTime_parse(tmp_str.data, &msg_meta.timestamp));
|
DateTime timestamp;
|
||||||
|
try_void(DateTime_parse(tmp_str.data, ×tamp));
|
||||||
|
|
||||||
try(u32 write_n, u, MessageBlock_writeMessage(&msg_meta, msg_content, block_meta, &block_data));
|
MessageMeta msg_meta = MessageMeta_construct(
|
||||||
try_assert(write_n > 0);
|
msg_content.len,
|
||||||
|
message_id,
|
||||||
|
sender_id,
|
||||||
|
timestamp);
|
||||||
|
try(bool write_success, u, MessageBlock_writeMessage(block, &msg_meta, msg_content));
|
||||||
|
try_assert(write_success == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tcp-chat/tcp-chat.h"
|
#include "tcp-chat.h"
|
||||||
#include "tsqlite.h"
|
#include "tsqlite.h"
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
|
||||||
@@ -22,12 +22,11 @@ Result(i64) 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_utc);
|
DateTime* out_timestamp_utc);
|
||||||
|
|
||||||
/// @brief try to find `count` messages starting from `first_message_id`
|
/// @brief try to find count messages with id >= first_message_id
|
||||||
/// @param out_meta writes here information about found messages, .count can be 0 if no messages found
|
/// @param dst_block writes messages here. messages_count can be 0 if no messages were found
|
||||||
/// @param out_block .len must be >= count * (sizeof(MessageMeta) + MESSAGE_SIZE_MAX)
|
|
||||||
Result(void) Channel_loadMessageBlock(ServerQueries* q,
|
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* out_block_meta, Array(u8) out_block_data);
|
MessageBlock* dst_block);
|
||||||
|
|
||||||
|
|
||||||
/// @return existing user id or 0
|
/// @return existing user id or 0
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ declare_RequestHandler(GetMessageBlock)
|
|||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate message_count
|
// validate messages_count
|
||||||
if(req.message_count < 1 || req.message_count > MESSAGE_BLOCK_COUNT_MAX){
|
if(req.messages_count < 1 || req.messages_count > MESSAGE_BLOCK_COUNT_MAX){
|
||||||
try_void(sendErrorMessage(log_ctx, conn, res_head,
|
try_void(sendErrorMessage(log_ctx, conn, res_head,
|
||||||
LogSeverity_Warn, STR("invalid message count in request") ));
|
LogSeverity_Warn, STR("invalid message count in request") ));
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
@@ -36,21 +36,20 @@ declare_RequestHandler(GetMessageBlock)
|
|||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset block meta
|
|
||||||
zeroStruct(&conn->message_block_meta);
|
|
||||||
// get message block from channel
|
// get message block from channel
|
||||||
try_void(Channel_loadMessageBlock(conn->queries,
|
try_void(Channel_loadMessageBlock(conn->queries,
|
||||||
req.channel_id, req.first_message_id, req.message_count,
|
req.channel_id, req.first_message_id, req.messages_count,
|
||||||
&conn->message_block_meta, conn->message_block));
|
&conn->message_block));
|
||||||
|
|
||||||
// send response
|
// send response
|
||||||
GetMessageBlockResponse res;
|
GetMessageBlockResponse res;
|
||||||
GetMessageBlockResponse_construct(&res, res_head, &conn->message_block_meta);
|
GetMessageBlockResponse_construct(&res, res_head,
|
||||||
|
conn->message_block.messages_count, conn->message_block.offset);
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
|
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
|
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
|
||||||
if(conn->message_block_meta.data_size != 0){
|
if(conn->message_block.offset != 0){
|
||||||
try_void(EncryptedSocketTCP_send(&conn->sock,
|
try_void(EncryptedSocketTCP_send(&conn->sock,
|
||||||
Array_u8_sliceTo(conn->message_block, conn->message_block_meta.data_size))
|
Array_u8_sliceTo(conn->message_block.datum, conn->message_block.offset))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "tlibc/filesystem.h"
|
|
||||||
#include "tlibc/time.h"
|
#include "tlibc/time.h"
|
||||||
#include "server/server_internal.h"
|
#include "server/server_internal.h"
|
||||||
#include "server/responses/responses.h"
|
#include "server/responses/responses.h"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tcp-chat/tcp-chat.h"
|
#include "tcp-chat.h"
|
||||||
#include "tcp-chat/server.h"
|
|
||||||
#include "cryptography/AES.h"
|
#include "cryptography/AES.h"
|
||||||
#include "cryptography/RSA.h"
|
#include "cryptography/RSA.h"
|
||||||
#include "network/encrypted_sockets.h"
|
#include "network/encrypted_sockets.h"
|
||||||
@@ -39,9 +38,8 @@ typedef struct ClientConnection {
|
|||||||
i64 user_id; // 0 for unauthorized
|
i64 user_id; // 0 for unauthorized
|
||||||
|
|
||||||
/* buffers */
|
/* buffers */
|
||||||
MessageBlockMeta message_block_meta;
|
MessageBlock message_block; // requested message block
|
||||||
Array(u8) message_block;
|
Array(u8) message_content; // sent message
|
||||||
Array(u8) message_content;
|
|
||||||
|
|
||||||
/* database */
|
/* database */
|
||||||
tsqlite_connection* db;
|
tsqlite_connection* db;
|
||||||
|
|||||||
Reference in New Issue
Block a user