Compare commits

...

85 Commits

Author SHA1 Message Date
db861cd698 panel and scroll_view 2026-01-16 19:35:11 +05:00
b9a622c9d1 turned ClientCLI to a state machine 2026-01-13 18:44:38 +05:00
75891025d0 xterm256 colors 2026-01-13 13:06:32 +05:00
d68ce8d4f0 changed .vscode/launch.json 2026-01-13 11:17:19 +05:00
b081e52d6e fixed visual bugs 2026-01-12 23:06:00 +05:00
310e4867d5 rewrite askUserNameAndPassword() to use tim 2026-01-12 23:00:01 +05:00
bfcb2f931f tim updated 2026-01-09 11:13:55 +05:00
151ad13853 added dependency: tim 2026-01-09 06:12:17 +05:00
0132e71c88 added methods for Client to send and receive messages 2026-01-04 00:53:35 +05:00
90e21bc5ae merged all public headers into single tcp-chat.h 2025-12-23 00:43:11 +05:00
e2edd4070a implemented ClientCLI DB queries 2025-12-23 00:27:02 +05:00
d461cae077 implemented server database and api for client database 2025-12-21 20:29:35 +05:00
49793e2929 implemented CommonQueries 2025-12-15 23:26:32 +05:00
72696dea70 replaced idb with sqlite 2025-12-13 03:44:46 +05:00
084a1828b2 RESULT_ERROR_LITERAL 2025-12-13 02:31:48 +05:00
88c2f8aa51 Channel_loadMessageBlock 2025-12-08 05:23:24 +05:00
6d1f450f32 implemented channels on server 2025-12-02 20:28:56 +05:00
c263d02b36 added tlibtoml submodule 2025-11-27 01:46:44 +05:00
f5169e8a8f str_destroy 2025-11-26 17:06:32 +05:00
2686ca6bcf implemented ServerLogger colors 2025-11-25 18:22:55 +05:00
8bc98a321d refactored tlibc collections 2025-11-25 16:39:28 +05:00
7a3808ba59 added idb_lock functions 2025-11-24 23:51:00 +05:00
571fdd900f added definitions for sending and receiving messages 2025-11-24 19:12:05 +05:00
0abee3f7df fixed bugs in server and moved token hash calculation to client 2025-11-21 21:22:53 +05:00
baca2fb4d3 implemented aes key validation 2025-11-21 20:16:39 +05:00
d32f7d4b89 fixed AESBlockDecryptor bug 2025-11-21 13:21:47 +05:00
9dc7de1b41 fixed small bugs 2025-11-21 13:20:59 +05:00
806f0359d0 implemented Login and Register requests 2025-11-18 23:07:31 +05:00
23c98e14df moved magic.h to tlibc 2025-11-18 16:29:37 +05:00
d0d7d26671 moved program modes code to separate files 2025-11-18 16:27:54 +05:00
1b00f503c8 implemented virtual logger 2025-11-18 16:01:39 +05:00
a1a11c10e2 created directory ./include/ 2025-11-18 14:37:01 +05:00
eec45cac71 separated Client from ClientCLI 2025-11-18 13:53:05 +05:00
5266872c2b added a lot of terminal control functions 2025-11-16 00:45:08 +05:00
85c0736c8d request ServerPublicInfo in ServerConnection_open() 2025-11-15 15:26:00 +05:00
0ea241c5db fixed bug in AESStreamEncryptor_encrypt 2025-11-15 15:24:16 +05:00
9942d94c94 implemented LoginRequest handler, changed ErrorMessage and ServerPublicInfoResponse 2025-11-15 12:16:04 +05:00
ef2531c63b implemented RegisterRequest handler 2025-11-13 06:19:16 +05:00
d53557dbb6 implemented ServerPublicInfoResponse and ProgramMode::RandomBytes 2025-11-13 02:34:03 +05:00
4add849b9e implemented idb encryption 2025-11-13 02:31:00 +05:00
2f51cd07ff update to cbuild 2.3.1 2025-11-12 23:52:36 +05:00
344b4375f9 updated to cbuild 2.3.0 2025-11-09 23:46:47 +05:00
083b247329 enabled more warnings 2025-11-09 18:39:50 +05:00
b662a85348 changed password hashing 2025-11-09 18:39:37 +05:00
e03c651cef created structs Client and Server 2025-11-09 03:37:28 +05:00
96a117bc50 moved request handlers to separate files 2025-11-09 02:06:33 +05:00
ea4a649e00 added new packet types 2025-11-09 00:49:29 +05:00
13ccfc7ff9 implemented socket_TCP_enableAliveChecks 2025-11-09 00:49:01 +05:00
ebab072835 implemented socket_setTimeout 2025-11-08 20:04:50 +05:00
2db37bb902 implemented EncryptedSocketTCP_recvStruct and EncryptedSocketTCP_recvRSA 2025-11-08 18:21:47 +05:00
ee522ac401 fixed memory issues 2025-11-06 22:36:02 +05:00
d36fe9e5b3 added internal buffers to encrypted sockets 2025-11-06 22:27:41 +05:00
375dd842d4 added tasks/strip.sh 2025-11-02 15:44:26 +05:00
94fcbe5daf fixed connection bugs 2025-11-02 15:26:30 +05:00
8179609d47 implemented client-server connection, but found out RSA is broken 2025-11-01 19:51:43 +05:00
5cc1484e80 added -static linker argument for windows 2025-11-01 13:42:17 +05:00
acf8e5ff03 switched aes implementation to "big" 2025-11-01 13:41:29 +05:00
6bf06a7d3e removed libreadline dependency 2025-11-01 00:18:05 +05:00
60a4542328 added session_id generation 2025-10-31 23:53:43 +05:00
a0bcd2560a cryptography rework and beginning of tcp-chat-protocol 2025-10-31 23:19:08 +05:00
42702ffbe7 added flags to socket_recv 2025-10-25 19:08:37 +05:00
eea36ec2a3 implemented EncryptedSocket 2025-10-25 16:43:14 +05:00
e0d9bfdcb3 AES wrapper rework 2025-10-25 14:36:22 +05:00
1b968f8b1b replaced seedFromTime with seedFromSystem 2025-10-25 11:29:45 +05:00
60bc501227 Added session key to ServerConnection 2025-10-25 10:47:08 +05:00
b1ca05759e added rsa-gen mode 2025-09-29 12:43:55 +05:00
f933d30863 rsa keys base64 encoding/decoding 2025-09-29 10:26:45 +05:00
4fda6ae9e8 finished ClientCredential_create 2025-09-28 17:18:17 +05:00
37f38feec7 rsa encryption and decryption 2025-09-25 01:11:22 +05:00
7ef2eb31ee rsa key allocation on heap 2025-09-19 17:49:17 +05:00
98ae36ad2f rsa key initialization 2025-09-13 02:29:26 +05:00
f01c5fc8a9 implemented client interactive mode 2025-08-28 19:11:28 +03:00
c008d759ae implemented main function 2025-08-10 21:39:51 +03:00
b08ec27629 added mutexes and defers to idb 2025-08-09 21:44:06 +03:00
9396f33288 removed "./" to evade vscode path finding bug 2025-08-09 21:17:31 +03:00
82d9fae6b5 changed file extensions 2025-08-09 18:03:56 +03:00
07c53c9046 changed bearssl out dir to the git-ignored one 2025-08-08 21:46:54 +03:00
a19c5f7023 implemented database functions 2025-08-08 21:42:35 +03:00
eb8bad55ee enabled additional compiler errors 2025-08-08 21:39:16 +03:00
f1a8a186e0 updated tlibc 2025-08-04 19:28:38 +03:00
eb1daa721a updated tlibc 2025-07-24 18:31:42 +03:00
664ff91e63 added random header to AES and removed malloc calls 2025-07-24 05:47:18 +03:00
e0646139e3 implemented exit from client 2025-07-23 00:26:02 +03:00
7cd7535eb0 implemented socket send and receive 2025-07-22 23:57:42 +03:00
94091d7797 client-server communication test 2025-07-22 15:32:32 +05:00
79 changed files with 5369 additions and 414 deletions

9
.gitmodules vendored
View File

@@ -4,3 +4,12 @@
[submodule "dependencies/tlibc"] [submodule "dependencies/tlibc"]
path = dependencies/tlibc path = dependencies/tlibc
url = https://timerix.ddns.net/git/timerix/tlibc.git url = https://timerix.ddns.net/git/timerix/tlibc.git
[submodule "dependencies/tlibtoml"]
path = dependencies/tlibtoml
url = https://timerix.ddns.net/git/Timerix/tlibtoml.git
[submodule "dependencies/tsqlite"]
path = dependencies/tsqlite
url = https://timerix.ddns.net/git/Timerix/tsqlite.git
[submodule "dependencies/tim"]
path = dependencies/tim
url = https://timerix.ddns.net/git/Timerix/tim.git

View File

@@ -5,8 +5,12 @@
"defines": [], "defines": [],
"includePath": [ "includePath": [
"src", "src",
"dependencies", "include",
"dependencies/BearSSL/inc",
"dependencies/tlibc/include", "dependencies/tlibc/include",
"dependencies/tlibtoml/include",
"dependencies/tsqlite/include",
"dependencies/tim/include",
"${default}" "${default}"
], ],
"cStandard": "c99" "cStandard": "c99"

75
.vscode/launch.json vendored
View File

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

35
README.md Normal file
View File

@@ -0,0 +1,35 @@
# tcp-chat
## Build
1. Clone this repository with submodules.
```
git clone --recurse-submodules --depth 0 https://timerix.ddns.net/git/Timerix/tcp-chat.git
```
2. Install [cbuild](https://timerix.ddns.net/git/Timerix/cbuild/releases).
Select latest version compatible with the one in `project.config`.
Example: For `2.3.0` download latest `2.3.x`.
3. Build executable
```
cd tcp-chat
cbuild build_exec
```
## Usage
**Client:**
```sh
cd bin
./tcp-chat
```
**Server:**
1. ```sh
cp tcp-chat-server.toml.default bin/tcp-chat-server.toml
```
2. Edit config
3. ```sh
cd bin
./tcp-chat -l
```

View File

@@ -1,15 +1,18 @@
#!/usr/bin/env bash #!/usr/bin/env bash
DEP_WORKING_DIR="dependencies/BearSSL" DEP_WORKING_DIR="$DEPENDENCIES_DIR"
DEP_PRE_BUILD_COMMAND=""
DEP_POST_BUILD_COMMAND="" projconfig_path="bearssl.project.config"
projconfig_path="../bearssl.project.config"
if [[ "$TASK" = *_dbg ]]; then if [[ "$TASK" = *_dbg ]]; then
dep_build_target="build_static_lib_dbg" dep_build_target="build_static_lib_dbg"
else else
dep_build_target="build_static_lib" dep_build_target="build_static_lib"
fi fi
DEP_PRE_BUILD_COMMAND=""
DEP_BUILD_COMMAND="cbuild -c $projconfig_path $dep_build_target" DEP_BUILD_COMMAND="cbuild -c $projconfig_path $dep_build_target"
DEP_POST_BUILD_COMMAND=""
DEP_CLEAN_COMMAND="cbuild -c $projconfig_path clean" DEP_CLEAN_COMMAND="cbuild -c $projconfig_path clean"
DEP_DYNAMIC_OUT_FILES="" DEP_DYNAMIC_OUT_FILES=""
DEP_STATIC_OUT_FILES="bin/libbearssl.a" DEP_STATIC_OUT_FILES="bin/libbearssl.a"
DEP_OTHER_OUT_FILES="" DEP_OTHER_OUT_FILES=""

View File

@@ -1,23 +1,20 @@
#!/usr/bin/env bash #!/usr/bin/env bash
CBUILD_VERSION=2.2.3 CBUILD_VERSION=2.3.1
PROJECT="bearssl" PROJECT="bearssl"
CMP_C="gcc" CMP_C="gcc"
CMP_CPP="g++" CMP_CPP="g++"
STD_C="c11" STD_C="c99"
STD_CPP="c++11" STD_CPP="c++11"
WARN_C="-Wno-unknown-pragmas" WARN_C="-Wno-unknown-pragmas"
# WARN_CPP="-Wall -Wextra" WARN_CPP="$WARN_C"
SRC_C="" SRC_C="$(find BearSSL/src -name '*.c')"
for d in codec int hash rand rsa symcipher; do SRC_CPP="$(find BearSSL/src -name '*.cpp')"
SRC_C+=" $(find src/$d -name '*.c')"
done
#SRC_CPP="$(find src -name '*.cpp')"
# Directory with dependency configs. # Directory with dependency configs.
# See cbuild/example_dependency_configs # See cbuild/example_dependency_configs
DEPENDENCY_CONFIGS_DIR='..' DEPENDENCY_CONFIGS_DIR='.'
# 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='' ENABLED_DEPENDENCIES=''
@@ -26,11 +23,11 @@ ENABLED_DEPENDENCIES=''
# ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task. # ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task.
# ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task. # ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task.
# └── profile/ - gcc *.gcda profiling info files # └── profile/ - gcc *.gcda profiling info files
OBJDIR="obj" OBJDIR="BearSSL/obj"
OUTDIR="bin" OUTDIR="bin"
STATIC_LIB_FILE="lib$PROJECT.a" STATIC_LIB_FILE="lib$PROJECT.a"
INCLUDE="-I./src -I./inc" INCLUDE="-IBearSSL/src -IBearSSL/inc"
# OS-specific options # OS-specific options
case "$OS" in case "$OS" in
@@ -38,11 +35,32 @@ case "$OS" in
EXEC_FILE="$PROJECT.exe" EXEC_FILE="$PROJECT.exe"
SHARED_LIB_FILE="$PROJECT.dll" SHARED_LIB_FILE="$PROJECT.dll"
LINKER_LIBS="" LINKER_LIBS=""
INCLUDE="$INCLUDE "
;; ;;
LINUX) LINUX)
EXEC_FILE="$PROJECT" EXEC_FILE="$PROJECT"
SHARED_LIB_FILE="$PROJECT.so" SHARED_LIB_FILE="$PROJECT.so"
LINKER_LIBS="" LINKER_LIBS=""
INCLUDE="$INCLUDE -DBR_USE_GETENTROPY=0"
;;
*)
error "operating system $OS has no configuration variants"
;;
esac
# OS-specific options
case "$OS" in
WINDOWS)
EXEC_FILE="$PROJECT.exe"
SHARED_LIB_FILE="$PROJECT.dll"
INCLUDE="$INCLUDE "
LINKER_LIBS=""
;;
LINUX)
EXEC_FILE="$PROJECT"
SHARED_LIB_FILE="$PROJECT.so"
INCLUDE="$INCLUDE "
LINKER_LIBS=""
;; ;;
*) *)
error "operating system $OS has no configuration variants" error "operating system $OS has no configuration variants"
@@ -62,61 +80,61 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections" C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates executable with debug info and no optimizations # creates executable with debug info and no optimizations
build_exec_dbg) build_exec_dbg)
C_ARGS="-O0 -g3" C_ARGS="-O0 -g3"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates shared library # creates shared library
build_shared_lib) build_shared_lib)
C_ARGS="-O2 -fpic -flto -shared" C_ARGS="-O2 -fpic -flto -shared"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates shared library with debug symbols and no optimizations # creates shared library with debug symbols and no optimizations
build_shared_lib_dbg) build_shared_lib_dbg)
C_ARGS="-O0 -g3 -fpic -shared" C_ARGS="-O0 -g3 -fpic -shared"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates static library # creates static library
build_static_lib) build_static_lib)
C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections" C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates static library with debug symbols and no optimizations # creates static library with debug symbols and no optimizations
build_static_lib_dbg) build_static_lib_dbg)
C_ARGS="-O0 -g3" C_ARGS="-O0 -g3"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# executes $EXEC_FILE # executes $EXEC_FILE
exec) exec)
TASK_SCRIPT=cbuild/default_tasks/exec.sh TASK_SCRIPT="@cbuild/default_tasks/exec.sh"
;; ;;
# executes $EXEC_FILE with valgrind memory checker # executes $EXEC_FILE with valgrind memory checker
valgrind) valgrind)
VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all" VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all"
TASK_SCRIPT=cbuild/default_tasks/valgrind.sh TASK_SCRIPT="@cbuild/default_tasks/valgrind.sh"
;; ;;
# generates profiling info # generates profiling info
profile) profile)
@@ -130,22 +148,25 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects" C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/profile.sh TASK_SCRIPT="@cbuild/default_tasks/profile.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# compiles program with -pg and runs it with gprof # compiles program with -pg and runs it with gprof
# uses gprof2dot python script to generate function call tree (pip install gprof2dot) # uses gprof2dot python script to generate function call tree (pip install gprof2dot)
# requires graphviz (https://www.graphviz.org/download/source/) # requires graphviz (https://www.graphviz.org/download/source/)
gprof) gprof)
OUTDIR="$OUTDIR/gprof" OUTDIR="$OUTDIR/gprof"
# -pg adds code to executable, that generates file containing function call info (gmon.out) # arguments that emit some call counter code and disable optimizations to see function names
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -pg" # https://github.com/msys2/MINGW-packages/issues/8503#issuecomment-1365475205
C_ARGS="-O0 -g -pg -no-pie -fno-omit-frame-pointer
-fno-inline-functions -fno-inline-functions-called-once
-fno-optimize-sibling-calls -fopenmp"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/gprof.sh TASK_SCRIPT="@cbuild/default_tasks/gprof.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# compiles program and runs it with callgrind (part of valgrind) # compiles program and runs it with callgrind (part of valgrind)
# uses gprof2dot python script to generate function call tree (pip install gprof2dot) # uses gprof2dot python script to generate function call tree (pip install gprof2dot)
@@ -157,9 +178,9 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin" C_ARGS="-O2 -flto=auto -fuse-linker-plugin"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/callgrind.sh TASK_SCRIPT="@cbuild/default_tasks/callgrind.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# compiles executable with sanitizers and executes it to find errors and warnings # compiles executable with sanitizers and executes it to find errors and warnings
sanitize) sanitize)
@@ -167,19 +188,19 @@ case "$TASK" in
C_ARGS="-O0 -g3 -fsanitize=undefined,address" C_ARGS="-O0 -g3 -fsanitize=undefined,address"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/exec.sh TASK_SCRIPT="@cbuild/default_tasks/exec.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# rebuilds specified dependencies # rebuilds specified dependencies
# EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts` # EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts`
# 'all' can be specified to rebuild all dependencies # 'all' can be specified to rebuild all dependencies
rebuild_dependencies) rebuild_dependencies)
TASK_SCRIPT=cbuild/default_tasks/rebuild_dependencies.sh TASK_SCRIPT="@cbuild/default_tasks/rebuild_dependencies.sh"
;; ;;
# deletes generated files # deletes generated files
clean) clean)
TASK_SCRIPT=cbuild/default_tasks/clean.sh TASK_SCRIPT="@cbuild/default_tasks/clean.sh"
;; ;;
# nothing to do # nothing to do
"" | no_task) "" | no_task)

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Project user config is ignored by git.
# Here you can add variables that users might want to change
# on their local machine, without commiting to the repository.
# Directory where you install dependencies.
# Do not confuse with DEPENDENCY_CONFIGS_DIR
# Example:
# libexample source code is at `../libexample`, and dependency config
# that specifies how to build this lib is at `dependencies/libexample.config`
DEPENDENCIES_DIR="."

1
dependencies/tim vendored Submodule

Submodule dependencies/tim added at ee6375f553

19
dependencies/tim.config vendored Normal file
View 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

View File

@@ -1,13 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
DEP_WORKING_DIR="dependencies/tlibc"
DEP_PRE_BUILD_COMMAND="" DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibc"
DEP_POST_BUILD_COMMAND=""
if [[ "$TASK" = *_dbg ]]; then if [[ "$TASK" = *_dbg ]]; then
dep_build_target="build_static_lib_dbg" dep_build_target="build_static_lib_dbg"
else else
dep_build_target="build_static_lib" dep_build_target="build_static_lib"
fi fi
DEP_PRE_BUILD_COMMAND=""
DEP_BUILD_COMMAND="cbuild $dep_build_target" DEP_BUILD_COMMAND="cbuild $dep_build_target"
DEP_POST_BUILD_COMMAND=""
DEP_CLEAN_COMMAND="cbuild clean" DEP_CLEAN_COMMAND="cbuild clean"
DEP_DYNAMIC_OUT_FILES="" DEP_DYNAMIC_OUT_FILES=""
DEP_STATIC_OUT_FILES="bin/tlibc.a" DEP_STATIC_OUT_FILES="bin/tlibc.a"

1
dependencies/tlibtoml vendored Submodule

Submodule dependencies/tlibtoml added at 5cb121d1de

30
dependencies/tlibtoml.config vendored Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# This is a dependency config.
# You can copy it to another project to add tlibtoml dependency.
DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibtoml"
user_config_path="project.config.user"
absolute_dep_dir=$(realpath "$DEPENDENCIES_DIR")
function setup_user_config(){
# Set variable `DEPENDENCIES_DIR`` in `tlibtoml/project.config.user`
# to the directory where `tlibc`` is installed
file_copy_default_if_not_present "$user_config_path" "$user_config_path.default"
replace_var_value_in_script "$user_config_path" "DEPENDENCIES_DIR" "$absolute_dep_dir"
}
if [[ "$TASK" = *_dbg ]]; then
dep_build_target="build_static_lib_dbg"
else
dep_build_target="build_static_lib"
fi
DEP_PRE_BUILD_COMMAND="setup_user_config"
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/tlibtoml.a"
DEP_OTHER_OUT_FILES=""
PRESERVE_OUT_DIRECTORY_STRUCTURE=false

1
dependencies/tsqlite vendored Submodule

Submodule dependencies/tsqlite added at 4b15db7c1f

30
dependencies/tsqlite.config vendored Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# This is a dependency config.
# You can copy it to another project to add tsqlite dependency.
DEP_WORKING_DIR="$DEPENDENCIES_DIR/tsqlite"
user_config_path="project.config.user"
absolute_dep_dir=$(realpath "$DEPENDENCIES_DIR")
function setup_user_config(){
# Set variable `DEPENDENCIES_DIR`` in `tsqlite/project.config.user`
# to the directory where `tlibc`` is installed
file_copy_default_if_not_present "$user_config_path" "$user_config_path.default"
replace_var_value_in_script "$user_config_path" "DEPENDENCIES_DIR" "$absolute_dep_dir"
}
if [[ "$TASK" = *_dbg ]]; then
dep_build_target="build_static_lib_dbg"
else
dep_build_target="build_static_lib"
fi
DEP_PRE_BUILD_COMMAND="setup_user_config"
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/tsqlite.a"
DEP_OTHER_OUT_FILES=""
PRESERVE_OUT_DIRECTORY_STRUCTURE=false

135
include/tcp-chat.h Normal file
View 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);

View File

@@ -1,21 +1,30 @@
#!/usr/bin/env bash #!/usr/bin/env bash
CBUILD_VERSION=2.2.3 CBUILD_VERSION=2.3.1
PROJECT="tcp-chat" PROJECT="tcp-chat"
CMP_C="gcc" CMP_C="gcc"
CMP_CPP="g++" CMP_CPP="g++"
STD_C="c99" STD_C="c99"
STD_CPP="c++11" STD_CPP="c++11"
WARN_C="-Wall -Wextra -Wno-unused-parameter" WARN_C="-Wall -Wextra
WARN_CPP="-Wall -Wextra -Wno-unused-parameter" -Wduplicated-branches
-Wduplicated-cond
-Wformat=2
-Wmissing-include-dirs
-Wshadow
-Werror=return-type
-Werror=pointer-arith
-Werror=init-self
-Werror=incompatible-pointer-types"
WARN_CPP="$WARN_C"
SRC_C="$(find src -name '*.c')" SRC_C="$(find src -name '*.c')"
#SRC_CPP="$(find src -name '*.cpp')" SRC_CPP="$(find src -name '*.cpp')"
# Directory with dependency configs. # Directory with dependency configs.
# 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='tlibc bearssl' 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
@@ -24,9 +33,14 @@ ENABLED_DEPENDENCIES='tlibc bearssl'
# └── profile/ - gcc *.gcda profiling info files # └── profile/ - gcc *.gcda profiling info files
OBJDIR="obj" OBJDIR="obj"
OUTDIR="bin" OUTDIR="bin"
STATIC_LIB_FILE="lib$PROJECT.a" STATIC_LIB_FILE="$PROJECT.a"
INCLUDE="-I./src -I./dependencies -I./dependencies/tlibc/include" INCLUDE="-Isrc -Iinclude
-I$DEPENDENCIES_DIR/BearSSL/inc
-I$DEPENDENCIES_DIR/tlibc/include
-I$DEPENDENCIES_DIR/tlibtoml/include
-I$DEPENDENCIES_DIR/tsqlite/include
-I$DEPENDENCIES_DIR/tim/include"
# OS-specific options # OS-specific options
case "$OS" in case "$OS" in
@@ -34,13 +48,13 @@ case "$OS" in
EXEC_FILE="$PROJECT.exe" EXEC_FILE="$PROJECT.exe"
SHARED_LIB_FILE="$PROJECT.dll" SHARED_LIB_FILE="$PROJECT.dll"
INCLUDE="$INCLUDE " INCLUDE="$INCLUDE "
LINKER_LIBS="-lpthread -lws2_32" LINKER_LIBS="-static -lpthread -lws2_32 -luuid -lsqlite3"
;; ;;
LINUX) LINUX)
EXEC_FILE="$PROJECT" EXEC_FILE="$PROJECT"
SHARED_LIB_FILE="$PROJECT.so" SHARED_LIB_FILE="$PROJECT.so"
INCLUDE="$INCLUDE " INCLUDE="$INCLUDE "
LINKER_LIBS="" LINKER_LIBS="-lsqlite3"
;; ;;
*) *)
error "operating system $OS has no configuration variants" error "operating system $OS has no configuration variants"
@@ -60,61 +74,61 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections" C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT="@cbuild/default_tasks/strip_exec.sh"
;; ;;
# creates executable with debug info and no optimizations # creates executable with debug info and no optimizations
build_exec_dbg) build_exec_dbg)
C_ARGS="-O0 -g3" C_ARGS="-O0 -g3"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates shared library # creates shared library
build_shared_lib) build_shared_lib)
C_ARGS="-O2 -fpic -flto -shared" C_ARGS="-O2 -fpic -flto -shared"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates shared library with debug symbols and no optimizations # creates shared library with debug symbols and no optimizations
build_shared_lib_dbg) build_shared_lib_dbg)
C_ARGS="-O0 -g3 -fpic -shared" C_ARGS="-O0 -g3 -fpic -shared"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates static library # creates static library
build_static_lib) build_static_lib)
C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections" C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# creates static library with debug symbols and no optimizations # creates static library with debug symbols and no optimizations
build_static_lib_dbg) build_static_lib_dbg)
C_ARGS="-O0 -g3" C_ARGS="-O0 -g3"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT= PRE_TASK_SCRIPT=""
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# executes $EXEC_FILE # executes $EXEC_FILE
exec) exec)
TASK_SCRIPT=cbuild/default_tasks/exec.sh TASK_SCRIPT="@cbuild/default_tasks/exec.sh"
;; ;;
# executes $EXEC_FILE with valgrind memory checker # executes $EXEC_FILE with valgrind memory checker
valgrind) valgrind)
VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all" VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all"
TASK_SCRIPT=cbuild/default_tasks/valgrind.sh TASK_SCRIPT="@cbuild/default_tasks/valgrind.sh"
;; ;;
# generates profiling info # generates profiling info
profile) profile)
@@ -125,12 +139,13 @@ case "$TASK" in
# -pg adds code to executable, that generates file containing function call info (gmon.out) # -pg adds code to executable, that generates file containing function call info (gmon.out)
# -fprofile-generate generates executable with profiling code # -fprofile-generate generates executable with profiling code
# -fprofile-prefix-path sets path where profiling info about objects will be saved # -fprofile-prefix-path sets path where profiling info about objects will be saved
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects" C_ARGS="-O2 -flto=auto -fuse-linker-plugin
-fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/profile.sh TASK_SCRIPT="@cbuild/default_tasks/profile.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# compiles program with -pg and runs it with gprof # compiles program with -pg and runs it with gprof
# uses gprof2dot python script to generate function call tree (pip install gprof2dot) # uses gprof2dot python script to generate function call tree (pip install gprof2dot)
@@ -139,12 +154,14 @@ case "$TASK" in
OUTDIR="$OUTDIR/gprof" OUTDIR="$OUTDIR/gprof"
# arguments that emit some call counter code and disable optimizations to see function names # arguments that emit some call counter code and disable optimizations to see function names
# https://github.com/msys2/MINGW-packages/issues/8503#issuecomment-1365475205 # https://github.com/msys2/MINGW-packages/issues/8503#issuecomment-1365475205
C_ARGS="-O0 -g -pg -no-pie -fno-omit-frame-pointer -fno-inline-functions -fno-inline-functions-called-once -fno-optimize-sibling-calls -fopenmp" C_ARGS="-O0 -g -pg -no-pie -fno-omit-frame-pointer
-fno-inline-functions -fno-inline-functions-called-once
-fno-optimize-sibling-calls -fopenmp"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/gprof.sh TASK_SCRIPT="@cbuild/default_tasks/gprof.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# compiles program and runs it with callgrind (part of valgrind) # compiles program and runs it with callgrind (part of valgrind)
# uses gprof2dot python script to generate function call tree (pip install gprof2dot) # uses gprof2dot python script to generate function call tree (pip install gprof2dot)
@@ -156,9 +173,9 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin" C_ARGS="-O2 -flto=auto -fuse-linker-plugin"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/callgrind.sh TASK_SCRIPT="@cbuild/default_tasks/callgrind.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# compiles executable with sanitizers and executes it to find errors and warnings # compiles executable with sanitizers and executes it to find errors and warnings
sanitize) sanitize)
@@ -166,19 +183,19 @@ case "$TASK" in
C_ARGS="-O0 -g3 -fsanitize=undefined,address" C_ARGS="-O0 -g3 -fsanitize=undefined,address"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=cbuild/default_tasks/build_exec.sh PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
TASK_SCRIPT=cbuild/default_tasks/exec.sh TASK_SCRIPT="@cbuild/default_tasks/exec.sh"
POST_TASK_SCRIPT= POST_TASK_SCRIPT=""
;; ;;
# rebuilds specified dependencies # rebuilds specified dependencies
# EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts` # EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts`
# 'all' can be specified to rebuild all dependencies # 'all' can be specified to rebuild all dependencies
rebuild_dependencies) rebuild_dependencies)
TASK_SCRIPT=cbuild/default_tasks/rebuild_dependencies.sh TASK_SCRIPT="@cbuild/default_tasks/rebuild_dependencies.sh"
;; ;;
# deletes generated files # deletes generated files
clean) clean)
TASK_SCRIPT=cbuild/default_tasks/clean.sh TASK_SCRIPT="@cbuild/default_tasks/clean.sh"
;; ;;
# nothing to do # nothing to do
"" | no_task) "" | no_task)

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Project user config is ignored by git.
# Here you can add variables that users might want to change
# on their local machine, without commiting to the repository.
# Directory where you install dependencies.
# Do not confuse with DEPENDENCY_CONFIGS_DIR
# Example:
# libexample source code is at `../libexample`, and dependency config
# that specifies how to build this lib is at `dependencies/libexample.config`
DEPENDENCIES_DIR="dependencies"

View File

@@ -0,0 +1,66 @@
#include "ClientCLI.h"
#include "tlibc/filesystem.h"
#include "tlibc/term.h"
#include "network/tcp-chat-protocol/v1.h"
#include <assert.h>
void ClientCLI_destroy(ClientCLI* self){
if(!self)
return;
Client_free(self->client);
ClientQueries_free(self->queries);
tsqlite_connection_close(self->db);
List_SavedServer_destroyWithElements(&self->saved_servers, SavedServer_destroy);
}
void ClientCLI_construct(ClientCLI* 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);
}
void ClientCLI_run(ClientCLI* self) {
Deferral(32);
StartScreenContext start_screen_ctx;
StartScreenContext_construct(&start_screen_ctx, self);
Defer(StartScreenContext_destroy(&start_screen_ctx));
MainScreenContext main_screen_ctx;
MainScreenContext_construct(&main_screen_ctx, self);
Defer(MainScreenContext_destroy(&main_screen_ctx));
while(tim_run(FPS)){
switch(self->state){
case ClientCLIState_Exit:
Return;
default:
assert(false && "invalid ClientCLI state");
break;
case ClientCLIState_StartScreen:
start_screen(&start_screen_ctx);
break;
case ClientCLIState_MainScreen:
main_screen(&main_screen_ctx);
break;
}
}
Return;
}

View File

@@ -0,0 +1,95 @@
#pragma once
#include <pthread.h>
#include "tcp-chat.h"
#include "tlibc/collections/HashMap.h"
#include "tlibc/collections/List.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 {
ClientCLIState state;
struct {
TimStyle common;
TimStyle focused;
TimStyle error;
} style;
Client* client;
tsqlite_connection* db;
ClientQueries* queries;
List(SavedServer) saved_servers;
} ClientCLI;
void ClientCLI_construct(ClientCLI* self);
void ClientCLI_destroy(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);

View File

@@ -0,0 +1,98 @@
#include "client_db_internal.h"
void SavedServer_destroy(SavedServer* self){
if(!self)
return;
str_destroy(self->address);
str_destroy(self->pk_base64);
str_destroy(self->name);
str_destroy(self->description);
}
Result(bool) SavedServer_exists(ClientQueries* q, str address){
Deferral(4);
tsqlite_statement* st = q->servers.exists;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_str(st, "$address", address, NULL));
try(bool has_result, i, tsqlite_statement_step(st));
Return RESULT_VALUE(i, has_result);
}
Result(bool) SavedServer_comparePublicKey(ClientQueries* q, str address, str pk_base64){
Deferral(4);
tsqlite_statement* st = q->servers.compare_pk;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_str(st, "$address", address, NULL));
try_void(tsqlite_statement_bind_str(st, "$pk_base64", pk_base64, NULL));
try(bool has_result, i, tsqlite_statement_step(st));
Return RESULT_VALUE(i, has_result);
}
Result(void) SavedServer_createOrUpdate(ClientQueries* q, SavedServer* server){
Deferral(4);
try_assert(server->address.len >= HOSTADDR_SIZE_MIN && server->address.len <= HOSTADDR_SIZE_MAX);
try_assert(server->pk_base64.len > 0 && server->pk_base64.len <= PUBLIC_KEY_BASE64_SIZE_MAX);
try_assert(server->name.len >= SERVER_NAME_SIZE_MIN && server->name.len <= SERVER_NAME_SIZE_MAX);
try_assert(server->description.len <= SERVER_DESC_SIZE_MAX);
try(bool server_exists, i, SavedServer_exists(q, server->address));
tsqlite_statement* st = NULL;
Defer(tsqlite_statement_reset(st));
if(server_exists){
st = q->servers.update;
try(bool pk_matches, i, SavedServer_comparePublicKey(q, server->address, server->pk_base64));
if(!pk_matches){
Return RESULT_ERROR_FMT(
"trying to update server '"FMT_str"' but public keys don't match",
str_unwrap(server->address));
}
}
else {
st = q->servers.insert;
try_void(tsqlite_statement_bind_str(st, "$pk_base64", server->pk_base64, NULL));
}
try_void(tsqlite_statement_bind_str(st, "$address", server->address, NULL));
try_void(tsqlite_statement_bind_str(st, "$name", server->name, NULL));
try_void(tsqlite_statement_bind_str(st, "$description", server->description, NULL));
try_void(tsqlite_statement_step(st));
Return RESULT_VALUE(i, !server_exists);
}
Result(void) SavedServer_getAll(ClientQueries* q, List(SavedServer)* dst_list){
Deferral(4);
tsqlite_statement* st = q->servers.get_all;
Defer(tsqlite_statement_reset(st));
SavedServer server = SavedServer_construct(str_null, str_null, str_null, str_null);
str tmp_str = str_null;
while(true){
try(bool has_result, i, tsqlite_statement_step(st));
if(!has_result)
break;
// address
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
server.address = str_copy(tmp_str);
// pk_base64
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
server.pk_base64 = str_copy(tmp_str);
// name
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
server.name = str_copy(tmp_str);
// description
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
server.description = str_copy(tmp_str);
List_SavedServer_pushMany(dst_list, &server, 1);
}
Return RESULT_VOID;
}

View File

@@ -0,0 +1,81 @@
#include "client_db_internal.h"
#include "tlibc/filesystem.h"
Result(tsqlite_connection* db) ClientDatabase_open(cstr file_path){
Deferral(64);
try_void(dir_createParent(file_path));
try(tsqlite_connection* db, p, tsqlite_connection_open(file_path));
bool success = false;
Defer(if(!success) tsqlite_connection_close(db));
///////////////////////////////////////////////////////////////////////////
// SERVERS //
///////////////////////////////////////////////////////////////////////////
try(tsqlite_statement* create_table_servers, p, tsqlite_statement_compile(db, STR(
"CREATE TABLE IF NOT EXISTS servers (\n"
" address VARCHAR PRIMARY KEY,\n"
" pk_base64 VARCHAR NOT NULL,\n"
" name VARCHAR NOT NULL,\n"
" description VARCHAR NOT NULL\n"
");"
)));
Defer(tsqlite_statement_free(create_table_servers));
try_void(tsqlite_statement_step(create_table_servers));
success = true;
Return RESULT_VALUE(p, db);
}
void ClientQueries_free(ClientQueries* q){
if(!q)
return;
tsqlite_statement_free(q->servers.insert);
tsqlite_statement_free(q->servers.update);
tsqlite_statement_free(q->servers.exists);
tsqlite_statement_free(q->servers.compare_pk);
tsqlite_statement_free(q->servers.get_all);
free(q);
}
Result(ClientQueries*) ClientQueries_compile(tsqlite_connection* db){
Deferral(4);
ClientQueries* q = (ClientQueries*)malloc(sizeof(*q));
zeroStruct(q);
bool success = false;
Defer(if(!success) ClientQueries_free(q));
///////////////////////////////////////////////////////////////////////////
// SERVERS //
///////////////////////////////////////////////////////////////////////////
try(q->servers.insert, p, tsqlite_statement_compile(db, STR(
"INSERT INTO\n"
"servers (address, pk_base64, name, description)\n"
"VALUES ($address, $pk_base64, $name, $description);"
)));
try(q->servers.update, p, tsqlite_statement_compile(db, STR(
"UPDATE servers\n"
"SET name = $name, description = $description\n"
"WHERE address = $address;"
)));
try(q->servers.exists, p, tsqlite_statement_compile(db, STR(
"SELECT 1 FROM servers WHERE address = $address;"
)));
try(q->servers.compare_pk, p, tsqlite_statement_compile(db, STR(
"SELECT 1 FROM servers WHERE address = $address AND pk_base64 = $pk_base64;"
)));
try(q->servers.get_all, p, tsqlite_statement_compile(db, STR(
"SELECT * FROM servers;"
)));
success = true;
Return RESULT_VALUE(p, q);
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include "tcp-chat.h"
#include "tsqlite.h"
#include "network/tcp-chat-protocol/v1.h"
#include "tlibc/collections/List.h"
/// @brief open DB and create tables
Result(tsqlite_connection* db) ClientDatabase_open(cstr file_path);
typedef struct ClientQueries ClientQueries;
Result(ClientQueries*) ClientQueries_compile(tsqlite_connection* db);
void ClientQueries_free(ClientQueries* self);
typedef struct SavedServer {
str address;
str pk_base64;
str name;
str description;
} SavedServer;
List_declare(SavedServer);
#define SavedServer_construct(ADDR, PK, NAME, DESC) ((SavedServer){ \
.address = ADDR, .pk_base64 = PK, .name = NAME, .description = DESC })
void SavedServer_destroy(SavedServer* self);
/// @return true if new row was created
Result(bool) SavedServer_createOrUpdate(ClientQueries* q, SavedServer* server);
/// @param dst_list there SavedServer values are pushed
Result(void) SavedServer_getAll(ClientQueries* q, List(SavedServer)* dst_list);
Result(bool) SavedServer_exists(ClientQueries* q, str address);
/// @return true if provided key and saved key match
Result(bool) SavedServer_comparePublicKey(ClientQueries* q, str address, str pk_base64);

View File

@@ -0,0 +1,17 @@
#pragma once
#include "client_db.h"
typedef struct ClientQueries {
struct {
/* ($address, $pk_base64, $name, $description) -> void */
tsqlite_statement* insert;
/* ($address, $name, $description) -> void */
tsqlite_statement* update;
/* ($address) -> 1 or nothing */
tsqlite_statement* exists;
/* ($address, $pk_base64) -> 1 or nothing */
tsqlite_statement* compare_pk;
/* () -> [(*)] */
tsqlite_statement* get_all;
} servers;
} ClientQueries;

View 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;
}

View 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;
}

View 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);
}

161
src/cli/main.c Normal file
View File

@@ -0,0 +1,161 @@
#include "tcp-chat.h"
#include "tlibc/tlibc.h"
#include "tlibtoml.h"
#include "cryptography/RSA.h"
#include "cli/modes/modes.h"
#define _DEFAULT_CONFIG_PATH_CLIENT "tcp-chat-client.config"
#define _DEFAULT_CONFIG_PATH_SERVER "tcp-chat-server.toml"
#define arg_is(LITERAL) str_equals(arg_str, STR(LITERAL))
int main(const int argc, cstr const* argv){
Deferral(32);
try_fatal_void(tlibc_init());
Defer(tlibc_deinit());
try_fatal_void(tlibtoml_init());
Defer(tlibtoml_deinit());
try_fatal_void(TcpChat_init());
Defer(TcpChat_deinit());
if(br_prng_seeder_system(NULL) == NULL){
printfe("Can't get system random seeder. Bearssl is compiled incorrectly.");
return 1;
}
ProgramMode mode = ClientMode;
cstr config_path = NULL;
u32 size_arg = 0;
for(int argi = 1; argi < argc; argi++){
str arg_str = str_from_cstr(argv[argi]);
if(arg_is("-h") || arg_is("--help")){
printf(
"USAGE:\n"
"no arguments Interactive client mode.\n"
"-h, --help Show this message.\n"
"-l, --listen Start server.\n"
"--config [path] Load config from specified path.\n"
" Default path for config is '" _DEFAULT_CONFIG_PATH_CLIENT "' or '" _DEFAULT_CONFIG_PATH_SERVER "'\n"
"--rsa-gen-stdin [size] Generate RSA private and public keys based on stdin data (64Kb max).\n"
" size: 2048 / 3072 (default) / 4096\n"
" Usage: `cat somefile | tcp-chat --gen-rsa-stdin`\n"
"--rsa-gen-random [size] Generate random RSA private and public keys.\n"
" size: 2048 / 3072 (default) / 4096\n"
"--random-bytes [size] Generate random bytes.\n"
" size: any number (default=32)\n"
"--random-bytes-base64 [size] Generate random bytes and print them in base64 encoding.\n"
" size: any number (default=32)\n"
);
Return 0;
}
if(arg_is("-l") || arg_is("--listen")){
if(mode != ClientMode){
printf("program mode is set already\n");
Return 1;
}
mode = ServerMode;
}
else if(arg_is("--config")){
if(++argi >= argc){
printfe("ERROR: no config path specified\n");
Return 1;
}
config_path = argv[argi];
}
else if(arg_is("--rsa-gen-stdin")){
if(mode != ClientMode){
printf("program mode is set already\n");
Return 1;
}
mode = RsaGenStdin;
if(++argi >= argc){
size_arg = RSA_DEFAULT_KEY_SIZE;
}
else if(sscanf(argv[argi], "%u", &size_arg) != 1){
printfe("ERROR: no key size specified\n");
}
}
else if(arg_is("--rsa-gen-random")){
if(mode != ClientMode){
printf("program mode is set already\n");
Return 1;
}
mode = RsaGenRandom;
if(++argi >= argc){
size_arg = RSA_DEFAULT_KEY_SIZE;
}
else if(sscanf(argv[argi], "%u", &size_arg) != 1){
printfe("ERROR: no key size specified\n");
}
}
else if(arg_is("--random-bytes")){
if(mode != ClientMode){
printf("program mode is set already\n");
Return 1;
}
mode = RandomBytes;
if(++argi >= argc){
size_arg = 32;
}
else if(sscanf(argv[argi], "%u", &size_arg) != 1){
printfe("ERROR: no size specified\n");
}
}
else if(arg_is("--random-bytes-base64")){
if(mode != ClientMode){
printf("program mode is set already\n");
Return 1;
}
mode = RandomBytesBase64;
if(++argi >= argc){
size_arg = 32;
}
else if(sscanf(argv[argi], "%u", &size_arg) != 1){
printfe("ERROR: no size specified\n");
}
}
else {
printfe("ERROR: unknown argument '%s'\n"
"Use '-h' to see list of avaliable arguments\n",
argv[argi]);
Return 1;
}
}
switch(mode){
default:
printfe("ERROR: invalid program mode %i\n", mode);
Return 1;
case ClientMode:
if(!config_path)
config_path = _DEFAULT_CONFIG_PATH_CLIENT;
try_fatal_void(run_ClientMode(config_path));
break;
case ServerMode:
if(!config_path)
config_path = _DEFAULT_CONFIG_PATH_SERVER;
try_fatal_void(run_ServerMode(config_path));
break;
case RsaGenStdin:
try_fatal_void(run_RsaGenStdin(size_arg));
break;
case RsaGenRandom:
try_fatal_void(run_RsaGenRandom(size_arg));
break;
case RandomBytes:
try_fatal_void(run_RandomBytes(size_arg));
break;
case RandomBytesBase64:
try_fatal_void(run_RandomBytesBase64(size_arg));
break;
}
Return 0;
}

View File

@@ -0,0 +1,15 @@
#include "modes.h"
#include "cli/ClientCLI/ClientCLI.h"
Result(void) run_ClientMode(cstr config_path) {
Deferral(4);
(void)config_path;
ClientCLI client;
ClientCLI_construct(&client);
Defer(ClientCLI_destroy(&client));
// start infinite loop on main thread
ClientCLI_run(&client);
Return RESULT_VOID;
}

View File

@@ -0,0 +1,49 @@
#include "modes.h"
#include "cryptography/cryptography.h"
#include "tlibc/base64.h"
Result(void) run_RandomBytes(u32 key_size) {
Deferral(4);
printfe("generating random bytes...\n");
br_hmac_drbg_context rng = { .vtable = &br_hmac_drbg_vtable };
rng_init_sha256_seedFromSystem(&rng.vtable);
Array(u8) random_buf = Array_u8_alloc(1024);
u32 full_buffers_n = key_size / random_buf.len;
u32 remaining_n = key_size % random_buf.len;
while(full_buffers_n > 0){
full_buffers_n--;
br_hmac_drbg_generate(&rng, random_buf.data, random_buf.len);
fwrite(random_buf.data, 1, random_buf.len, stdout);
}
br_hmac_drbg_generate(&rng, random_buf.data, remaining_n);
fwrite(random_buf.data, 1, remaining_n, stdout);
Return RESULT_VOID;
}
Result(void) run_RandomBytesBase64(u32 key_size) {
Deferral(4);
printfe("generating random bytes...\n");
br_hmac_drbg_context rng = { .vtable = &br_hmac_drbg_vtable };
rng_init_sha256_seedFromSystem(&rng.vtable);
Array(u8) random_buf = Array_u8_alloc(1024);
Array(char) base64_buf = Array_char_alloc(base64_encodedSize(random_buf.len));
u32 full_buffers_n = key_size / random_buf.len;
u32 remaining_n = key_size % random_buf.len;
u32 enc_size = 0;
while(full_buffers_n > 0){
full_buffers_n--;
br_hmac_drbg_generate(&rng, random_buf.data, random_buf.len);
enc_size = base64_encode(random_buf.data, random_buf.len, base64_buf.data);
fwrite(base64_buf.data, 1, enc_size, stdout);
}
br_hmac_drbg_generate(&rng, random_buf.data, remaining_n);
enc_size = base64_encode(random_buf.data, remaining_n, base64_buf.data);
fwrite(base64_buf.data, 1, enc_size, stdout);
Return RESULT_VOID;
}

64
src/cli/modes/RsaGen.c Normal file
View File

@@ -0,0 +1,64 @@
#include "modes.h"
#include "cryptography/RSA.h"
Result(void) run_RsaGenStdin(u32 key_size) {
Deferral(4);
printfe("reading stdin...\n");
Array(u8) input_buf = Array_u8_alloc(64*1024);
Defer(Array_u8_destroy(&input_buf));
br_hmac_drbg_context rng = { .vtable = &br_hmac_drbg_vtable };
br_hmac_drbg_init(&rng, &br_sha256_vtable, NULL, 0);
i64 read_n = 0;
do {
read_n = fread(input_buf.data, 1, input_buf.len, stdin);
if(read_n < 0){
Return RESULT_ERROR_LITERAL("ERROR: can't read stdin");
}
// put bytes to rng as seed
br_hmac_drbg_update(&rng, input_buf.data, read_n);
} while(read_n == input_buf.len);
printfe("generating RSA key pair based on stdin...\n");
br_rsa_private_key sk;
br_rsa_public_key pk;
try_void(RSA_generateKeyPair(key_size, &sk, &pk, &rng.vtable));
Defer(
RSA_destroyPrivateKey(&sk);
RSA_destroyPublicKey(&pk);
);
str sk_str = RSA_serializePrivateKey_base64(&sk);
printf("rsa_private_key = %s\n", sk_str.data);
str_destroy(sk_str);
str pk_str = RSA_serializePublicKey_base64(&pk);
printf("\nrsa_public_key = %s\n", pk_str.data);
str_destroy(pk_str);
Return RESULT_VOID;
}
Result(void) run_RsaGenRandom(u32 key_size) {
Deferral(4);
printfe("generating random RSA key pair...\n");
br_rsa_private_key sk;
br_rsa_public_key pk;
try_void(RSA_generateKeyPairFromSystemRandom(key_size, &sk, &pk));
Defer(
RSA_destroyPrivateKey(&sk);
RSA_destroyPublicKey(&pk);
);
str sk_str = RSA_serializePrivateKey_base64(&sk);
printf("rsa_private_key = %s\n", sk_str.data);
str_destroy(sk_str);
str pk_str = RSA_serializePublicKey_base64(&pk);
printf("\nrsa_public_key = %s\n", pk_str.data);
str_destroy(pk_str);
Return RESULT_VOID;
}

View File

@@ -0,0 +1,98 @@
#include <pthread.h>
#include "tcp-chat.h"
#include "modes.h"
#include "tlibc/time.h"
#include "tlibc/term.h"
typedef struct ServerLogger {
pthread_mutex_t mutex;
} ServerLogger;
void ServerLogger_construct(ServerLogger* self){
try_fatal_stderrcode(pthread_mutex_init(&self->mutex, NULL));
try_fatal_void(term_init());
}
void ServerLogger_destroy(ServerLogger* self){
pthread_mutex_destroy(&self->mutex);
term_resetColors();
}
static void log_func(void* _logger, cstr context, LogSeverity severity, cstr msg){
ServerLogger* logger = _logger;
cstr severity_cstr;
Color16 fg;
switch(severity){
default:
severity_cstr = "INVALID_LOG_SEVERITY";
fg = Color16_DarkRed;
break;
case LogSeverity_Debug:
severity_cstr = "DBUG";
fg = Color16_Gray;
break;
case LogSeverity_Info:
severity_cstr = "INFO";
fg = Color16_White;
break;
case LogSeverity_Warn:
severity_cstr = "WARN";
fg = Color16_Yellow;
break;
case LogSeverity_Error:
severity_cstr = "EROR";
fg = Color16_Red;
break;
}
pthread_mutex_lock(&logger->mutex);
DateTime dt;
DateTime_getLocal(&dt);
term_setFgColor16(fg);
printf("[" FMT_DateTime_text "][%s/%s]: %s\n", DT_expand(dt), context, severity_cstr, msg);
term_setFgColor16(Color16_Magenta);
pthread_mutex_unlock(&logger->mutex);
}
Result(void) run_ServerMode(cstr config_path) {
Deferral(4);
// open file
try(FILE* config_file, p, file_open(config_path, FO_ReadExisting));
Defer(file_close(config_file));
// read whole file into str
str config_file_content = str_null;
try_void(file_readWholeText(config_file, &config_file_content));
Defer(str_destroy(config_file_content));
// create logger
ServerLogger logger;
ServerLogger_construct(&logger);
Defer(ServerLogger_destroy(&logger));
// init server
try(Server* server, p,
Server_create(
config_file_content, config_path,
&logger, log_func
)
);
Defer(Server_free(server));
// manually close file and free config_buf
file_close(config_file);
config_file = NULL;
str_destroy(config_file_content);
config_file_content.data = NULL;
// start infinite loop on main thread
try_void(Server_run(server));
Return RESULT_VOID;
}

19
src/cli/modes/modes.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "tlibc/errors.h"
#include "tlibc/filesystem.h"
typedef enum ProgramMode {
ClientMode,
ServerMode,
RsaGenStdin,
RsaGenRandom,
RandomBytes,
RandomBytesBase64,
} ProgramMode;
Result(void) run_ClientMode(cstr config_path);
Result(void) run_ServerMode(cstr config_path);
Result(void) run_RsaGenStdin(u32 key_size);
Result(void) run_RsaGenRandom(u32 key_size);
Result(void) run_RandomBytes(u32 key_size);
Result(void) run_RandomBytesBase64(u32 key_size);

View File

@@ -0,0 +1,119 @@
#include "client_internal.h"
#include "requests/requests.h"
void ServerConnection_close(ServerConnection* self){
if(!self)
return;
RSA_destroyPublicKey(&self->server_pk);
EncryptedSocketTCP_destroy(&self->sock);
Array_u8_destroy(&self->token);
Array_u8_destroy(&self->session_key);
MessageBlock_destroy(&self->received_message_block);
free(self);
}
Result(ServerConnection*) ServerConnection_open(Client* client, cstr server_addr_cstr, cstr server_pk_base64)
{
Deferral(16);
ServerConnection* conn = (ServerConnection*)malloc(sizeof(ServerConnection));
zeroStruct(conn);
bool success = false;
Defer(if(!success) ServerConnection_close(conn));
conn->client = client;
// TODO: parse domain name and get ip from it
conn->server_end = EndpointIPv4_INVALID;
try_void(EndpointIPv4_parse(server_addr_cstr, &conn->server_end));
if(EndpointIPv4_is_invalid(conn->server_end)){
Return RESULT_ERROR_FMT("server address or port is invalid: %s", server_addr_cstr);
}
try_void(RSA_parsePublicKey_base64(server_pk_base64, &conn->server_pk));
RSAEncryptor_construct(&conn->rsa_enc, &conn->server_pk);
// lvl 2 hash - is used for authentification
conn->token = Array_u8_alloc(PASSWORD_HASH_SIZE);
// hash user_data_key with server_pk once
Array(u8) server_pk_data = Array_u8_construct(conn->server_pk.n,
BR_RSA_KBUF_PUB_SIZE(conn->server_pk.nlen * 8));
u8 server_pk_hash[PASSWORD_HASH_SIZE];
Array(u8) server_pk_hash_array = Array_u8_construct(server_pk_hash, PASSWORD_HASH_SIZE);
hash_password(conn->client->user_data_key, server_pk_data,
server_pk_hash, 1);
// hash user_data_key with server_pk_hash
hash_password(conn->token, server_pk_hash_array,
conn->token.data, PASSWORD_HASH_LVL_ROUNDS);
// generate session random AES key
conn->session_key = Array_u8_alloc(AES_SESSION_KEY_SIZE);
br_hmac_drbg_context key_rng = { .vtable = &br_hmac_drbg_vtable };
rng_init_sha256_seedFromSystem(&key_rng.vtable);
br_hmac_drbg_generate(&key_rng, conn->session_key.data, conn->session_key.len);
// connect to server address
try(Socket _s, i, socket_open_TCP());
// TODO: client socket waits infinitely if server is paused on breakpoint
try_void(socket_TCP_enableAliveChecks_default(_s));
try_void(socket_connect(_s, conn->server_end));
EncryptedSocketTCP_construct(&conn->sock, _s, NETWORK_BUFFER_SIZE, conn->session_key);
// send ClientHandshake using server public key for encryption
PacketHeader req_header;
ClientHandshake client_handshake;
try_void(ClientHandshake_tryConstruct(&client_handshake, &req_header,
conn->session_key));
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &req_header));
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &client_handshake));
// receive server response
PacketHeader res_header;
ServerHandshake server_handshake;
try_void(recvResponse(&conn->sock, &res_header, &server_handshake,
PacketType_ServerHandshake));
conn->session_id = server_handshake.session_id;
MessageBlock_alloc(&conn->received_message_block);
success = true;
Return RESULT_VALUE(p, conn);
}
Result(void) ServerConnection_requestServerName(ServerConnection* conn, str* out_str){
if(conn == NULL){
return RESULT_ERROR_LITERAL("Client is not connected to a server");
}
Deferral(4);
PacketHeader req_header, res_header;
ServerPublicInfoRequest public_info_req;
ServerPublicInfoResponse public_info_res;
ServerPublicInfoRequest_construct(&public_info_req, &req_header,
ServerPublicInfo_Name);
try_void(sendRequest(&conn->sock, &req_header, &public_info_req));
try_void(recvResponse(&conn->sock, &res_header, &public_info_res,
PacketType_ServerPublicInfoResponse));
try_void(recvStr(&conn->sock, public_info_res.data_size, out_str));
Return RESULT_VOID;
}
Result(void) ServerConnection_requestServerDescription(ServerConnection* conn, str* out_str){
if(conn == NULL){
return RESULT_ERROR_LITERAL("Client is not connected to a server");
}
Deferral(4);
PacketHeader req_header, res_header;
ServerPublicInfoRequest public_info_req;
ServerPublicInfoResponse public_info_res;
ServerPublicInfoRequest_construct(&public_info_req, &req_header,
ServerPublicInfo_Description);
try_void(sendRequest(&conn->sock, &req_header, &public_info_req));
try_void(recvResponse(&conn->sock, &res_header, &public_info_res,
PacketType_ServerPublicInfoResponse));
try_void(recvStr(&conn->sock, public_info_res.data_size, out_str));
Return RESULT_VOID;
}

176
src/client/client.c Normal file
View File

@@ -0,0 +1,176 @@
#include "client/client_internal.h"
#include "client/requests/requests.h"
void Client_free(Client* self){
if(!self)
return;
str_destroy(self->username);
Array_u8_destroy(&self->user_data_key);
ServerConnection_close(self->conn);
free(self);
}
Result(Client*) Client_create(str username, str password){
Deferral(16);
Client* self = (Client*)malloc(sizeof(Client));
zeroStruct(self);
bool success = false;
Defer(if(!success) Client_free(self));
self->username = str_copy(username);
// lvl 1 hash - is used as AES key for user data
self->user_data_key = Array_u8_alloc(PASSWORD_HASH_SIZE);
hash_password(str_castTo_Array_u8(password), str_castTo_Array_u8(username),
self->user_data_key.data, PASSWORD_HASH_LVL_ROUNDS);
success = true;
Return RESULT_VALUE(p, self);
}
Result(void) Client_connect(Client* self, cstr server_addr_cstr, cstr server_pk_base64){
Deferral(8);
Client_disconnect(self);
try(self->conn, p,
ServerConnection_open(self, server_addr_cstr, server_pk_base64)
);
Return RESULT_VOID;
}
void Client_disconnect(Client* self){
ServerConnection_close(self->conn);
self->conn = NULL;
}
str Client_getUserName(Client* client){
return client->username;
}
Array(u8) Client_getUserDataKey(Client* client){
return client->user_data_key;
}
Result(void) Client_getServerName(Client* self, str* out_str){
Deferral(1);
try_assert(self != NULL);
try_assert(self->conn != NULL && "didn't connect to a server yet");
try_void(ServerConnection_requestServerName(self->conn, out_str));
Return RESULT_VOID;
}
Result(void) Client_getServerDescription(Client* self, str* out_str){
Deferral(1);
try_assert(self != NULL);
try_assert(self->conn != NULL && "didn't connect to a server yet");
try_void(ServerConnection_requestServerDescription(self->conn, out_str));
Return RESULT_VOID;
}
Result(void) Client_register(Client* self, i64* out_user_id){
Deferral(1);
try_assert(self != NULL);
try_assert(self->conn != NULL && "didn't connect to a server yet");
PacketHeader req_head, res_head;
RegisterRequest req;
RegisterResponse res;
try_void(RegisterRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
try_void(sendRequest(&self->conn->sock, &req_head, &req));
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_RegisterResponse));
self->conn->user_id = res.user_id;
*out_user_id = res.user_id;
Return RESULT_VOID;
}
Result(void) Client_login(Client* self, i64* out_user_id, i64* out_landing_channel_id){
Deferral(1);
try_assert(self != NULL);
try_assert(self->conn != NULL && "didn't connect to a server yet");
PacketHeader req_head, res_head;
LoginRequest req;
LoginResponse res;
try_void(LoginRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
try_void(sendRequest(&self->conn->sock, &req_head, &req));
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_LoginResponse));
self->conn->user_id = res.user_id;
*out_user_id = res.user_id;
*out_landing_channel_id = res.landing_channel_id;
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);
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include "tcp-chat.h"
#include "cryptography/AES.h"
#include "cryptography/RSA.h"
#include "network/encrypted_sockets.h"
#include "network/tcp-chat-protocol/v1.h"
typedef struct ServerConnection ServerConnection;
typedef struct Client {
str username;
Array(u8) user_data_key;
ServerConnection* conn;
} Client;
typedef struct ServerConnection {
Client* client;
EndpointIPv4 server_end;
br_rsa_public_key server_pk;
RSAEncryptor rsa_enc;
Array(u8) token;
Array(u8) session_key;
EncryptedSocketTCP sock;
i64 session_id;
i64 user_id;
MessageBlock received_message_block;
} ServerConnection;
/// @param server_addr_cstr
/// @param server_pk_base64 public key encoded by `RSA_serializePublicKey_base64()`
Result(ServerConnection*) ServerConnection_open(Client* client,
cstr server_addr_cstr, cstr server_pk_base64);
void ServerConnection_close(ServerConnection* conn);
/// @param out_str heap-allocated string
Result(void) ServerConnection_requestServerName(ServerConnection* conn, str* out_str);
/// @param out_str heap-allocated string
Result(void) ServerConnection_requestServerDescription(ServerConnection* conn, str* out_str);

View File

@@ -0,0 +1,43 @@
#include "requests.h"
Result(void) recvStr(EncryptedSocketTCP* sock, u32 size, str* out_str){
Deferral(4);
str s = str_construct(malloc(size + 1), size, true);
bool success = false;
Defer(if(!success) free(s.data));
// receive message content
try_void(
EncryptedSocketTCP_recv(
sock,
str_castTo_Array_u8(s),
SocketRecvFlag_WholeBuffer
)
);
s.data[s.len] = 0;
*out_str = s;
success = true;
Return RESULT_VOID;
}
Result(void) recvErrorMessage(EncryptedSocketTCP* sock, PacketHeader* res_header,
str* out_err_msg)
{
Deferral(4);
ErrorMessage res;
try_void(PacketHeader_validateContentSize(res_header, sizeof(res)));
try_void(EncryptedSocketTCP_recvStruct(sock, &res));
// limit msg_size to fit in single EncryptedSocketTCP_recv call
// TODO: receive ErrorMessage content in a loop
if(res.msg_size > sock->recv_buf.len)
res.msg_size = sock->recv_buf.len;
str err_msg;
try_void(recvStr(sock, res.msg_size, &err_msg));
*out_err_msg = err_msg;
Return RESULT_VOID;
}

View File

@@ -0,0 +1,34 @@
#include "requests.h"
Result(void) _recvResponse(EncryptedSocketTCP* sock,
PacketHeader* res_header, Array(u8) res, PacketType res_type)
{
Deferral(4);
try_void(EncryptedSocketTCP_recvStruct(sock, res_header));
try_void(PacketHeader_validateMagic(res_header));
if(res_header->type == PacketType_ErrorMessage){
str err_msg;
try_void(recvErrorMessage(sock, res_header, &err_msg));
Return RESULT_ERROR(err_msg, true);
}
try_void(PacketHeader_validateType(res_header, res_type));
try_void(PacketHeader_validateContentSize(res_header, res.len));
try_void(EncryptedSocketTCP_recv(sock, res, SocketRecvFlag_WholeBuffer));
Return RESULT_VOID;
}
Result(void) _sendRequest(EncryptedSocketTCP* sock,
PacketHeader* req_header, Array(u8) req)
{
Deferral(4);
try_void(EncryptedSocketTCP_sendStruct(sock, req_header));
try_void(EncryptedSocketTCP_send(sock, req));
Return RESULT_VOID;
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "network/tcp-chat-protocol/v1.h"
#include "client/client_internal.h"
/// @param out_err_msg heap-allocated string
Result(void) recvErrorMessage(EncryptedSocketTCP* sock, PacketHeader* res_header,
str* out_err_msg);
/// @param out_str heap-allocated string
Result(void) recvStr(EncryptedSocketTCP* sock, u32 size, str* out_str);
Result(void) _recvResponse(EncryptedSocketTCP* sock,
PacketHeader* res_header, Array(u8) res, PacketType res_type);
#define recvResponse(sock, res_header_ptr, res_ptr, res_type) \
_recvResponse(sock, res_header_ptr, struct_castTo_Array_u8(res_ptr), res_type)
Result(void) _sendRequest(EncryptedSocketTCP* sock,
PacketHeader* req_header, Array(u8) req);
#define sendRequest(sock, req_header_ptr, req_ptr) \
_sendRequest(sock, req_header_ptr, struct_castTo_Array_u8(req_ptr))

View File

@@ -1,125 +1,295 @@
#include "cryptography.h" #include "AES.h"
#include "BearSSL/inc/bearssl_block.h"
#include <assert.h> #include <assert.h>
// size must be multiple of 16 // write data from src to array and increment array data pointer
#define __AES_BUFFER_SIZE 256 static inline void __Array_writeNext(Array(u8)* dst, u8* src, size_t size){
memcpy(dst->data, src, size);
typedef struct EncryptorAES { *dst = Array_u8_sliceFrom(*dst, size);
br_aes_ct64_cbcenc_keys enc_ctx;
Array buf;
Array iv;
} EncryptorAES;
typedef struct DecryptorAES {
br_aes_ct64_cbcdec_keys dec_ctx;
Array buf;
Array iv;
} DecryptorAES;
EncryptorAES* EncryptorAES_create(Array key){
assert(key.size == 16 || key.size == 24 || key.size == 32);
EncryptorAES* ptr = (EncryptorAES*)malloc(sizeof(EncryptorAES));
br_aes_ct64_cbcenc_init(&ptr->enc_ctx, key.data, key.size);
// size must be multiple of 16
ptr->buf = Array_alloc(u8, __AES_BUFFER_SIZE);
Array_memset(&ptr->buf, 0);
// size of one SHA block (16 bytes)
ptr->iv = Array_alloc(u8, 16);
Array_memset(&ptr->iv, 0);
return ptr;
} }
void EncryptorAES_destroy(EncryptorAES* ptr){ // read data from array to dst and increment array data pointer
free(ptr->buf.data); static inline void __Array_readNext(u8* dst, Array(u8)* src, size_t size){
free(ptr->iv.data); memcpy(dst, src->data, size);
*src = Array_u8_sliceFrom(*src, size);
} }
void EncryptorAES_encrypt(EncryptorAES* ptr, Array src, Array dst){ static void __calcKeyCheckSum(Array(u8) key, void* dst){
assert(dst.size >= EncryptorAES_calcDstSize(src.size)); br_sha256_context sha_ctx;
br_sha256_init(&sha_ctx);
br_sha256_update(&sha_ctx, key.data, key.len);
br_sha256_out(&sha_ctx, dst);
}
const EncryptedBlockInfo block_info = { .padding_size = 16 - src.size % 16 }; //////////////////////////////////////////////////////////////////////////////
memcpy(ptr->buf.data, &block_info, sizeof(EncryptedBlockInfo)); // AESBlockEncryptor //
// encrypt this struct //////////////////////////////////////////////////////////////////////////////
br_aes_ct64_cbcenc_run(&ptr->enc_ctx, ptr->iv.data, ptr->buf.data, sizeof(EncryptedBlockInfo));
// emit EncryptedBlockInfo to beginning of result
memcpy(dst.data, ptr->buf.data, sizeof(EncryptedBlockInfo));
dst.data += sizeof(EncryptedBlockInfo);
dst.size -= sizeof(EncryptedBlockInfo);
// write full blocks void AESBlockEncryptor_construct(AESBlockEncryptor* ptr,
while(src.size > ptr->buf.size){ Array(u8) key, const br_block_cbcenc_class* enc_class)
memcpy(ptr->buf.data, src.data, ptr->buf.size); {
src.data += ptr->buf.size; ptr->enc_class = enc_class;
src.size -= ptr->buf.size; AESBlockEncryptor_changeKey(ptr, key);
br_aes_ct64_cbcenc_run(&ptr->enc_ctx, ptr->iv.data, ptr->buf.data, ptr->buf.size);
memcpy(dst.data, ptr->buf.data, ptr->buf.size); ptr->rng_ctx.vtable = &br_hmac_drbg_vtable;
dst.data += ptr->buf.size; rng_init_sha256_seedFromSystem(&ptr->rng_ctx.vtable);
dst.size -= ptr->buf.size;
}
void AESBlockEncryptor_changeKey(AESBlockEncryptor* ptr, Array(u8) key)
{
assert(key.len == 16 || key.len == 24 || key.len == 32);
ptr->enc_class->init((void*)ptr->enc_keys, key.data, key.len);
__calcKeyCheckSum(key, ptr->key_checksum);
}
Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr,
Array(u8) src, Array(u8) dst)
{
Deferral(4);
u32 encrypted_size = AESBlockEncryptor_calcDstSize(src.len);
try_assert(dst.len >= encrypted_size);
// generate random initial vector
br_hmac_drbg_generate(&ptr->rng_ctx, ptr->iv, __AES_BLOCK_IV_SIZE);
// write IV to the beginning of dst
__Array_writeNext(&dst, ptr->iv, __AES_BLOCK_IV_SIZE);
EncryptedBlockHeader header;
zeroStruct(&header);
memcpy(header.key_checksum, ptr->key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE);
header.padding_size = (16 - src.len % 16) % 16;
// write header to buffer
memcpy(ptr->buf, &header, sizeof(header));
// encrypt header
ptr->enc_class->run((void*)ptr->enc_keys, ptr->iv, ptr->buf, sizeof(header));
// write encrypted header to dst
__Array_writeNext(&dst, ptr->buf, sizeof(header));
// encrypt full AESBlockEncryptor buffers
while(src.len > __AES_BUFFER_SIZE){
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
ptr->enc_class->run((void*)ptr->enc_keys,
ptr->iv,
ptr->buf, __AES_BUFFER_SIZE);
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
} }
// write incomplete block // encrypt buffer with remaining data
Array_memset(&ptr->buf, 0); if(src.len > 0){
memcpy(ptr->buf.data, src.data, src.size); memcpy(ptr->buf, src.data, src.len);
u32 src_size_padded = src.size + block_info.padding_size; memset(ptr->buf + src.len, 0, header.padding_size);
br_aes_ct64_cbcenc_run(&ptr->enc_ctx, ptr->iv.data, ptr->buf.data, src_size_padded); u32 src_size_padded = src.len + header.padding_size;
memcpy(dst.data, ptr->buf.data, src_size_padded); ptr->enc_class->run((void*)ptr->enc_keys,
} ptr->iv,
ptr->buf, src_size_padded);
memcpy(dst.data, ptr->buf, src_size_padded);
DecryptorAES* DecryptorAES_create(Array key){
assert(key.size == 16 || key.size == 24 || key.size == 32);
DecryptorAES* ptr = (DecryptorAES*)malloc(sizeof(DecryptorAES));
br_aes_ct64_cbcdec_init(&ptr->dec_ctx, key.data, key.size);
// size must be multiple of 16
ptr->buf = Array_alloc(u8, __AES_BUFFER_SIZE);
Array_memset(&ptr->buf, 0);
// size of one SHA block (16 bytes)
ptr->iv = Array_alloc(u8, 16);
Array_memset(&ptr->iv, 0);
return ptr;
}
void DecryptorAES_destroy(DecryptorAES* ptr){
free(ptr->buf.data);
free(ptr->iv.data);
}
void DecryptorAES_decrypt(DecryptorAES* ptr, Array src, Array dst, u32* decrypted_size){
assert(dst.size >= src.size);
// read EncryptedBlockInfo from beginning of data
EncryptedBlockInfo block_info;
memcpy(&block_info, src.data, sizeof(EncryptedBlockInfo));
src.data += sizeof(EncryptedBlockInfo);
src.size -= sizeof(EncryptedBlockInfo);
// decrypt this struct
br_aes_ct64_cbcdec_run(&ptr->dec_ctx, ptr->iv.data, &block_info, sizeof(EncryptedBlockInfo));
*decrypted_size = src.size - block_info.padding_size;
// write full blocks
while(src.size > ptr->buf.size){
memcpy(ptr->buf.data, src.data, ptr->buf.size);
src.data += ptr->buf.size;
src.size -= ptr->buf.size;
br_aes_ct64_cbcdec_run(&ptr->dec_ctx, ptr->iv.data, ptr->buf.data, ptr->buf.size);
memcpy(dst.data, ptr->buf.data, ptr->buf.size);
dst.data += ptr->buf.size;
dst.size -= ptr->buf.size;
} }
// write incomplete block Return RESULT_VALUE(u, encrypted_size);
Array_memset(&ptr->buf, 0); }
memcpy(ptr->buf.data, src.data, src.size);
u32 src_size_padded = src.size + block_info.padding_size;
br_aes_ct64_cbcdec_run(&ptr->dec_ctx, ptr->iv.data, ptr->buf.data, src_size_padded); //////////////////////////////////////////////////////////////////////////////
memcpy(dst.data, ptr->buf.data, src_size_padded); // AESBlockDecryptor //
//////////////////////////////////////////////////////////////////////////////
void AESBlockDecryptor_construct(AESBlockDecryptor* ptr,
Array(u8) key, const br_block_cbcdec_class* dec_class)
{
ptr->dec_class = dec_class;
AESBlockDecryptor_changeKey(ptr, key);
}
void AESBlockDecryptor_changeKey(AESBlockDecryptor* ptr, Array(u8) key)
{
assert(key.len == 16 || key.len == 24 || key.len == 32);
ptr->dec_class->init((void*)ptr->dec_keys, key.data, key.len);
__calcKeyCheckSum(key, ptr->key_checksum);
}
Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr,
Array(u8) src, Array(u8) dst)
{
Deferral(4);
u32 overhead_size = AESBlockEncryptor_calcDstSize(0);
try_assert(src.len >= overhead_size);
try_assert(src.len % 16 == 0 && "src must be array of 16-byte blocks");
try_assert(dst.len >= src.len - overhead_size);
// read IV from the beginning of src
__Array_readNext(ptr->iv, &src, __AES_BLOCK_IV_SIZE);
EncryptedBlockHeader header;
// read encrypted header from src
__Array_readNext((void*)&header, &src, sizeof(header));
// decrypt header
ptr->dec_class->run((void*)ptr->dec_keys, ptr->iv, &header, sizeof(header));
// validate decrypted data
if(memcmp(header.key_checksum, ptr->key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE) != 0){
Return RESULT_ERROR_LITERAL("decrypted data is invalid or key is wrong");
}
// size of decrypted data without padding
try_assert(src.len >= header.padding_size && "invalid padding size");
u32 decrypted_size = src.len - header.padding_size;
src.len = decrypted_size;
// decrypt full buffers
while(src.len > __AES_BUFFER_SIZE){
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
ptr->dec_class->run((void*)ptr->dec_keys,
ptr->iv,
ptr->buf, __AES_BUFFER_SIZE);
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
}
// decrypt buffer with remaining data
if(src.len > 0){
memcpy(ptr->buf, src.data, src.len);
memset(ptr->buf + src.len, 0, header.padding_size);
u32 src_size_padded = src.len + header.padding_size;
ptr->dec_class->run((void*)ptr->dec_keys,
ptr->iv,
ptr->buf, src_size_padded);
memcpy(dst.data, ptr->buf, src.len);
}
Return RESULT_VALUE(u, decrypted_size);
}
//////////////////////////////////////////////////////////////////////////////
// AESStreamEncryptor //
//////////////////////////////////////////////////////////////////////////////
void AESStreamEncryptor_construct(AESStreamEncryptor* ptr,
Array(u8) key, const br_block_ctr_class* ctr_class)
{
ptr->ctr_class = ctr_class;
AESStreamEncryptor_changeKey(ptr, key);
ptr->block_counter = 0;
br_hmac_drbg_context rng_ctx;
rng_ctx.vtable = &br_hmac_drbg_vtable;
rng_init_sha256_seedFromSystem(&rng_ctx.vtable);
br_hmac_drbg_generate(&rng_ctx, ptr->iv, __AES_STREAM_IV_SIZE);
}
void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key)
{
assert(key.len == 16 || key.len == 24 || key.len == 32);
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.len);
__calcKeyCheckSum(key, ptr->key_checksum);
}
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr,
Array(u8) src, Array(u8) dst)
{
Deferral(4);
u32 encrypted_size = src.len;
// if it is the beginning of the stream,
if(ptr->block_counter == 0){
// write IV generated during initialization
__Array_writeNext(&dst, ptr->iv, __AES_STREAM_IV_SIZE);
encrypted_size = AESStreamEncryptor_calcDstSize(encrypted_size);
// encrypt checksum
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
memcpy(key_checksum, ptr->key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE);
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
ptr->iv, ptr->block_counter,
key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE);
// write checksum to dst
__Array_writeNext(&dst, key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE);
}
try_assert(dst.len >= encrypted_size);
// encrypt full buffers
while(src.len > __AES_BUFFER_SIZE){
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
ptr->iv, ptr->block_counter,
ptr->buf, __AES_BUFFER_SIZE);
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
}
// encrypt remaining data
if(src.len > 0){
memcpy(ptr->buf, src.data, src.len);
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
ptr->iv, ptr->block_counter,
ptr->buf, src.len);
memcpy(dst.data, ptr->buf, src.len);
}
Return RESULT_VALUE(u, encrypted_size);
}
//////////////////////////////////////////////////////////////////////////////
// AESStreamDecryptor //
//////////////////////////////////////////////////////////////////////////////
void AESStreamDecryptor_construct(AESStreamDecryptor* ptr,
Array(u8) key, const br_block_ctr_class* ctr_class)
{
ptr->ctr_class = ctr_class;
AESStreamDecryptor_changeKey(ptr, key);
ptr->block_counter = 0;
}
void AESStreamDecryptor_changeKey(AESStreamDecryptor* ptr, Array(u8) key)
{
assert(key.len == 16 || key.len == 24 || key.len == 32);
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.len);
__calcKeyCheckSum(key, ptr->key_checksum);
}
Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr,
Array(u8) src, Array(u8) dst)
{
Deferral(4);
// if it is the beginning of the stream
if(ptr->block_counter == 0){
// read random IV
__Array_readNext(ptr->iv, &src, __AES_STREAM_IV_SIZE);
// read checksum
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
__Array_readNext(key_checksum, &src, __AES_BLOCK_KEY_CHECKSUM_SIZE);
// decrypt checksum
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
ptr->iv, ptr->block_counter,
key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE);
// validate decrypted data
if(memcmp(key_checksum, ptr->key_checksum, __AES_BLOCK_KEY_CHECKSUM_SIZE) != 0){
Return RESULT_ERROR_LITERAL("decrypted data is invalid or key is wrong");
}
}
// size without IV
u32 decrypted_size = src.len;
try_assert(dst.len >= decrypted_size);
// decrypt full buffers
while(src.len > __AES_BUFFER_SIZE){
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
ptr->iv, ptr->block_counter,
ptr->buf, __AES_BUFFER_SIZE);
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
}
// decrypt remaining data
if(src.len > 0){
memcpy(ptr->buf, src.data, src.len);
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
ptr->iv, ptr->block_counter,
ptr->buf, src.len);
memcpy(dst.data, ptr->buf, src.len);
}
Return RESULT_VALUE(u, decrypted_size);
} }

139
src/cryptography/AES.h Normal file
View File

@@ -0,0 +1,139 @@
#pragma once
#include "tlibc/collections/Array.h"
#include "tlibc/errors.h"
#include "tlibc/magic.h"
#include "bearssl_block.h"
#include "cryptography.h"
//////////////////////////////////////////////////////////////////////////////
// //
// AES.c //
// //
//////////////////////////////////////////////////////////////////////////////
#define AESBlockEncryptor_DEFAULT_CLASS (&br_aes_big_cbcenc_vtable)
#define AESBlockDecryptor_DEFAULT_CLASS (&br_aes_big_cbcdec_vtable)
#define AESStream_DEFAULT_CLASS (&br_aes_big_ctr_vtable)
#define __AES_BLOCK_KEY_CHECKSUM_SIZE br_sha256_SIZE
typedef struct EncryptedBlockHeader {
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
u8 padding_size;
} ATTRIBUTE_ALIGNED(16) EncryptedBlockHeader;
//////////////////////////////////////////////////////////////////////////////
// AESBlockEncryptor //
//////////////////////////////////////////////////////////////////////////////
#define __AES_BLOCK_IV_SIZE 16
// must be multiple of 16
#define __AES_BUFFER_SIZE 512
typedef struct AESBlockEncryptor {
const br_block_cbcenc_class* enc_class;
u8 enc_keys[sizeof(br_aes_big_cbcenc_keys)];
u8 buf[__AES_BUFFER_SIZE];
u8 iv[__AES_BLOCK_IV_SIZE];
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
br_hmac_drbg_context rng_ctx;
} AESBlockEncryptor;
/// @param key supported sizes: 16, 24, 32
/// @param enc_class &br_aes_XXX_cbcenc_vtable
void AESBlockEncryptor_construct(AESBlockEncryptor* ptr, Array(u8) key, const br_block_cbcenc_class* enc_class);
/// @param key supported sizes: 16, 24, 32
void AESBlockEncryptor_changeKey(AESBlockEncryptor* ptr, Array(u8) key);
/// @brief Encrypts a complete message. For part-by-part encryption use AESStreamEncryptor.
/// @param src array of any size
/// @param dst array of size >= AESBlockEncryptor_calcDstSize(src.len)
/// @return size of encrypted data
Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr, Array(u8) src, Array(u8) dst);
#define AESBlockEncryptor_calcDstSize(SRC_SIZE) (__AES_BLOCK_IV_SIZE + sizeof(EncryptedBlockHeader) + ALIGN_TO(SRC_SIZE, 16))
//////////////////////////////////////////////////////////////////////////////
// AESBlockDecryptor //
//////////////////////////////////////////////////////////////////////////////
typedef struct AESBlockDecryptor {
const br_block_cbcdec_class* dec_class;
u8 dec_keys[sizeof(br_aes_big_cbcdec_keys)];
u8 buf[__AES_BUFFER_SIZE];
u8 iv[__AES_BLOCK_IV_SIZE];
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
} AESBlockDecryptor;
/// @param key supported sizes: 16, 24, 32
/// @param dec_class &br_aes_XXX_cbcdec_vtable
void AESBlockDecryptor_construct(AESBlockDecryptor* ptr, Array(u8) key, const br_block_cbcdec_class* dec_class);
/// @param key supported sizes: 16, 24, 32
void AESBlockDecryptor_changeKey(AESBlockDecryptor* ptr, Array(u8) key);
/// @brief Decrypts a complete message. For part-by-part decryption use AESStreamEncryptor.
/// @param src array of size at least AESBlockEncryptor_calcDstSize(0). Size must be multiple of 16.
/// @param dst array of size >= src.len
/// @return size of decrypted data
Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr, Array(u8) src, Array(u8) dst);
//////////////////////////////////////////////////////////////////////////////
// AESStreamEncryptor //
//////////////////////////////////////////////////////////////////////////////
#define __AES_STREAM_IV_SIZE 12
typedef struct AESStreamEncryptor {
const br_block_ctr_class* ctr_class;
u8 ctr_keys[sizeof(br_aes_big_ctr_keys)];
u8 buf[__AES_BUFFER_SIZE];
u8 iv[__AES_STREAM_IV_SIZE];
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
u32 block_counter;
} AESStreamEncryptor;
/// @warning If you start from not first block in sequence (example: to continue a previously interrupted operation) set correct values for ptr->block_counter and ptr->iv or encryption/decryption will produce incorrect data.
/// @param key supported sizes: 16, 24, 32
/// @param dec_class &br_aes_XXX_ctr_vtable
void AESStreamEncryptor_construct(AESStreamEncryptor* ptr, Array(u8) key, const br_block_ctr_class* ctr_class);
/// @param key supported sizes: 16, 24, 32
void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key);
/// use this only at the beginning of the stream
#define AESStreamEncryptor_calcDstSize(src_size) (__AES_STREAM_IV_SIZE + __AES_BLOCK_KEY_CHECKSUM_SIZE + src_size)
/// @brief If ptr->block_counter == 0, writes random IV to `dst`. After that writes encrypted data to dst.
/// @param src array of any size
/// @param dst array of size >= `AESStreamEncryptor_calcDstSize(src.len)` for first block and `src.len `for other blocks
/// @return size of encrypted data
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr, Array(u8) src, Array(u8) dst);
//////////////////////////////////////////////////////////////////////////////
// AESStreamDecryptor //
//////////////////////////////////////////////////////////////////////////////
typedef struct AESStreamDecryptor {
const br_block_ctr_class* ctr_class;
u8 ctr_keys[sizeof(br_aes_big_ctr_keys)];
u8 buf[__AES_BUFFER_SIZE];
u8 iv[__AES_STREAM_IV_SIZE];
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
u32 block_counter;
} AESStreamDecryptor;
/// @warning If you start from not first block in sequence (example: to continue a previously interrupted operation) set correct values for ptr->block_counter and ptr->iv or encryption/decryption will produce incorrect data.
/// @param key supported sizes: 16, 24, 32
/// @param dec_class &br_aes_XXX_ctr_vtable
void AESStreamDecryptor_construct(AESStreamDecryptor* ptr, Array(u8) key, const br_block_ctr_class* ctr_class);
/// @param key supported sizes: 16, 24, 32
void AESStreamDecryptor_changeKey(AESStreamDecryptor* ptr, Array(u8) key);
/// @brief Reads IV from `src`, then decrypts data and writes it to dst
/// @param src array of any size
/// @param dst array of size >= src.len
/// @return size of decrypted data
Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr, Array(u8) src, Array(u8) dst);

238
src/cryptography/RSA.c Normal file
View File

@@ -0,0 +1,238 @@
#include "RSA.h"
#include <assert.h>
#include "tlibc/base64.h"
// https://crypto.stackexchange.com/questions/3110/impacts-of-not-using-rsa-exponent-of-65537
#define DEFAULT_PUBLIC_EXPONENT 65537
Result(void) RSA_generateKeyPair(u32 key_size,
br_rsa_private_key* sk, br_rsa_public_key* pk,
const br_prng_class** rng_vtable_ptr)
{
Deferral(4);
bool success = false;
void* sk_buf = malloc(BR_RSA_KBUF_PRIV_SIZE(key_size));
void* pk_buf = malloc(BR_RSA_KBUF_PUB_SIZE(key_size));
Defer(
if(!success){
free(sk_buf);
free(pk_buf);
}
);
success = br_rsa_i31_keygen(rng_vtable_ptr, sk, sk_buf, pk, pk_buf, key_size, DEFAULT_PUBLIC_EXPONENT);
if(!success){
Return RESULT_ERROR_LITERAL("br_rsa_i31_keygen() failed");
}
Return RESULT_VOID;
}
Result(void) RSA_generateKeyPairFromSystemRandom(u32 key_size,
br_rsa_private_key* sk, br_rsa_public_key* pk)
{
Deferral(4);
br_hmac_drbg_context time_based_rng = { .vtable = &br_hmac_drbg_vtable };
rng_init_sha256_seedFromSystem(&time_based_rng.vtable);
try_void(RSA_generateKeyPair(key_size, sk, pk, &time_based_rng.vtable));
Return RESULT_VOID;
}
Result(void) RSA_generateKeyPairFromPassword(u32 key_size,
br_rsa_private_key* sk, br_rsa_public_key* pk, str password)
{
Deferral(4);
br_hmac_drbg_context password_based_rng = { .vtable = &br_hmac_drbg_vtable };
br_hmac_drbg_init(&password_based_rng, &br_sha256_vtable, password.data, password.len);
try_void(RSA_generateKeyPair(key_size, sk, pk, &password_based_rng.vtable));
Return RESULT_VOID;
}
Result(void) RSA_computePublicKey(const br_rsa_private_key* sk, br_rsa_public_key* pk){
Deferral(4);
br_rsa_compute_modulus compute_modulus = br_rsa_i31_compute_modulus;
br_rsa_compute_pubexp compute_pubexp = br_rsa_i31_compute_pubexp;
size_t modulus_size = compute_modulus(NULL, sk);
if (modulus_size == 0) {
Return RESULT_ERROR_LITERAL("compute_modulus");
}
void* modulus = malloc(modulus_size);
bool success = false;
Defer(
if(!success)
free(modulus)
);
if (compute_modulus(modulus, sk) != modulus_size) {
Return RESULT_ERROR_LITERAL("compute_modulus");
}
u32 pubexp_little_endian = compute_pubexp(sk);
if (pubexp_little_endian == 0) {
Return RESULT_ERROR_LITERAL("compute_pubexp");
}
u8 pubexp_big_endian[4];
pubexp_big_endian[0] = pubexp_little_endian >> 24;
pubexp_big_endian[1] = pubexp_little_endian >> 16;
pubexp_big_endian[2] = pubexp_little_endian >> 8;
pubexp_big_endian[3] = pubexp_little_endian;
pk->n = modulus;
pk->nlen = modulus_size;
pk->e = pubexp_big_endian;
pk->elen = sizeof pubexp_big_endian;
success = true;
Return RESULT_VOID;
}
str RSA_serializePrivateKey_base64(const br_rsa_private_key* sk){
u32 key_buffer_size = BR_RSA_KBUF_PRIV_SIZE(sk->n_bitlen);
u32 key_base64_size = base64_encodedSize(key_buffer_size);
char* serialized_buf = malloc(32 + key_base64_size);
sprintf(serialized_buf, "RSA-Private-%u:", sk->n_bitlen);
u32 offset = strlen(serialized_buf);
offset += base64_encode(sk->p, key_buffer_size, serialized_buf + offset);
serialized_buf[offset] = '\0';
return str_construct(serialized_buf, offset, true);
}
str RSA_serializePublicKey_base64(const br_rsa_public_key* pk){
u32 n_bitlen = pk->nlen * 8;
u32 key_buffer_size = BR_RSA_KBUF_PUB_SIZE(n_bitlen);
u32 key_base64_size = base64_encodedSize(key_buffer_size);
char* serialized_buf = malloc(32 + key_base64_size);
sprintf(serialized_buf, "RSA-Public-%u:", n_bitlen);
u32 offset = strlen(serialized_buf);
offset += base64_encode(pk->n, key_buffer_size, serialized_buf + offset);
serialized_buf[offset] = '\0';
return str_construct(serialized_buf, offset, true);
}
Result(void) RSA_parsePublicKey_base64(cstr src, br_rsa_public_key* pk){
Deferral(4);
u32 n_bitlen = 0;
if(sscanf(src, "RSA-Public-%u:", &n_bitlen) != 1){
Return RESULT_ERROR_LITERAL("can't parse key size");
}
u32 key_buffer_size = BR_RSA_KBUF_PUB_SIZE(n_bitlen);
pk->n = malloc(key_buffer_size);
pk->elen = 4;
pk->nlen = key_buffer_size - 4;
pk->e = pk->n + pk->nlen;
str src_str = str_from_cstr(src);
u32 offset = str_seekChar(src_str, ':', 10) + 1;
if(offset == 0){
Return RESULT_ERROR_LITERAL("missing ':' before key data");
}
str key_base64_str = src_str;
key_base64_str.data += offset;
key_base64_str.len -= offset;
u32 decoded_size = base64_decodedSize(key_base64_str.data, key_base64_str.len);
if(decoded_size != key_buffer_size){
Return RESULT_ERROR_FMT("decoded key size is %u, must be %u", decoded_size, key_buffer_size);
}
decoded_size = base64_decode(key_base64_str.data, key_base64_str.len, pk->n);
if(decoded_size != key_buffer_size){
Return RESULT_ERROR_LITERAL("key decoding failed");
}
Return RESULT_VOID;
}
Result(void) RSA_parsePrivateKey_base64(cstr src, br_rsa_private_key* sk){
Deferral(4);
u32 n_bitlen = 0;
if(sscanf(src, "RSA-Private-%u:", &n_bitlen) != 1){
Return RESULT_ERROR_LITERAL("can't parse key size");
}
sk->n_bitlen = n_bitlen;
u32 key_buffer_size = BR_RSA_KBUF_PRIV_SIZE(n_bitlen);
u32 field_len = key_buffer_size / 5;
sk->plen = sk->qlen = sk->dplen = sk->dqlen = sk->iqlen = field_len;
sk->p = malloc(key_buffer_size);
sk->q = sk->p + field_len;
sk->dp = sk->q + field_len;
sk->dq = sk->dp + field_len;
sk->iq = sk->dq + field_len;
str src_str = str_from_cstr(src);
u32 offset = str_seekChar(src_str, ':', 10) + 1;
if(offset == 0){
Return RESULT_ERROR_LITERAL("missing ':' before key data");
}
str key_base64_str = src_str;
key_base64_str.data += offset;
key_base64_str.len -= offset;
u32 decoded_size = base64_decodedSize(key_base64_str.data, key_base64_str.len);
if(decoded_size != key_buffer_size){
Return RESULT_ERROR_FMT("decoded key size is %u, must be %u", decoded_size, key_buffer_size);
}
decoded_size = base64_decode(key_base64_str.data, key_base64_str.len, sk->p);
if(decoded_size != key_buffer_size){
Return RESULT_ERROR_LITERAL("key decoding failed");
}
Return RESULT_VOID;
}
//////////////////////////////////////////////////////////////////////////////
// RSAEncryptor //
//////////////////////////////////////////////////////////////////////////////
void RSAEncryptor_construct(RSAEncryptor* ptr, const br_rsa_public_key* pk){
ptr->pk = pk;
ptr->rng.vtable = &br_hmac_drbg_vtable;
rng_init_sha256_seedFromSystem(&ptr->rng.vtable);
}
Result(u32) RSAEncryptor_encrypt(RSAEncryptor* ptr, Array(u8) src, Array(u8) dst){
u32 key_size_bytes = ptr->pk->nlen;
const u32 max_src_size = RSAEncryptor_calcMaxSrcSize(key_size_bytes * 8, 256);
if(src.len > max_src_size){
return RESULT_ERROR_FMT("src.len (%u) must be <= RSAEncryptor_calcMaxSrcSize() (%u)",
src.len, max_src_size);
}
if(dst.len < key_size_bytes){
return RESULT_ERROR_FMT("dst.len (%u) must be >= key length in bytes (%u)",
dst.len, key_size_bytes);
}
size_t sz = br_rsa_i31_oaep_encrypt(
&ptr->rng.vtable, &br_sha256_vtable,
NULL, 0,
ptr->pk,
dst.data, dst.len,
src.data, src.len);
if(sz == 0){
return RESULT_ERROR_LITERAL("RSA encryption failed");
}
return RESULT_VALUE(u, sz);
}
//////////////////////////////////////////////////////////////////////////////
// RSADecryptor //
//////////////////////////////////////////////////////////////////////////////
void RSADecryptor_construct(RSADecryptor* ptr, const br_rsa_private_key* sk){
ptr->sk = sk;
}
Result(u32) RSADecryptor_decrypt(RSADecryptor* ptr, Array(u8) buffer){
u32 key_size_bits = ptr->sk->n_bitlen;
if(buffer.len != key_size_bits/8){
return RESULT_ERROR_FMT("buffer.len (%u) must be == key length in bytes (%u)",
buffer.len, key_size_bits/8);
}
size_t sz = buffer.len;
size_t r = br_rsa_i31_oaep_decrypt(
&br_sha256_vtable,
NULL, 0,
ptr->sk,
buffer.data, &sz);
if(r == 0){
return RESULT_ERROR_LITERAL("RSA encryption failed");
}
return RESULT_VALUE(u, sz);
}

108
src/cryptography/RSA.h Normal file
View File

@@ -0,0 +1,108 @@
#pragma once
#include "tlibc/collections/Array.h"
#include "tlibc/errors.h"
#include "bearssl_rsa.h"
#include "cryptography.h"
//////////////////////////////////////////////////////////////////////////////
// //
// RSA.c //
// //
//////////////////////////////////////////////////////////////////////////////
#define RSA_DEFAULT_KEY_SIZE 3072
/// @brief generate random key pair
/// @param key_size size of public key in bits (2048/3072/4096)
/// @param sk key for decryption
/// @param pk key for encryption
/// @param rng_vtable_ptr pointer to vtable field in prng context. The context must be initialized
Result(void) RSA_generateKeyPair(u32 key_size,
br_rsa_private_key* sk, br_rsa_public_key* pk,
const br_prng_class** rng_vtable_ptr);
/// @brief generate random key pair using system crypto-rng provider
Result(void) RSA_generateKeyPairFromSystemRandom(u32 key_size,
br_rsa_private_key* sk, br_rsa_public_key* pk);
Result(void) RSA_generateKeyPairFromPassword(u32 key_size,
br_rsa_private_key* sk, br_rsa_public_key* pk, str password);
static inline void RSA_destroyPrivateKey(br_rsa_private_key* sk){
free(sk->p);
}
static inline void RSA_destroyPublicKey(br_rsa_public_key* sk){
free(sk->n);
}
/// @param sk some private key
/// @param pk out public key. WARNING: .n is allocated on heap
Result(void) RSA_computePublicKey(const br_rsa_private_key* sk, br_rsa_public_key* pk);
/// @brief Encode key data in human-readable format
/// @param src some data
/// @return heap-allocated string
str RSA_serializePrivateKey_base64(const br_rsa_private_key* sk);
/// @param src serialized private key format "RSA-Private-%SIZE%:%DATA_BASE64%"
/// @param sk out private key. WARNING: .p is allocated on heap
Result(void) RSA_parsePrivateKey_base64(cstr src, br_rsa_private_key* sk);
/// @brief Encode key data in human-readable format
/// @param src some data
/// @return heap-allocated string
str RSA_serializePublicKey_base64(const br_rsa_public_key* sk);
/// @param src serialized public key format "RSA-Public-%SIZE%:%DATA_BASE64%"
/// @param sk out public key. WARNING: .p is allocated on heap
Result(void) RSA_parsePublicKey_base64(cstr src, br_rsa_public_key* sk);
//////////////////////////////////////////////////////////////////////////////
// RSAEncryptor //
//////////////////////////////////////////////////////////////////////////////
typedef struct RSAEncryptor {
const br_rsa_public_key* pk;
br_hmac_drbg_context rng;
} RSAEncryptor;
/// RSA OAEP encryption with SHA256 hashing algorithm
void RSAEncryptor_construct(RSAEncryptor* ptr, const br_rsa_public_key* pk);
/*
https://crypto.stackexchange.com/a/42100
+---------+----------+-------------------------------------------+
| | overhead | max message size (bytes) |
| Hash | (bytes) | RSA-1024 | RSA-2048 | RSA-3072 | RSA-4096 |
+---------+----------+----------+----------+----------+----------+
| SHA-1 | 42 | 86 | 214 | 342 | 470 |
| SHA-224 | 58 | 70 | 198 | 326 | 454 |
| SHA-256 | 66 | 62 | 190 | 318 | 446 |
| SHA-384 | 98 | 30 | 158 | 286 | 414 |
| SHA-512 | 130 | N/A | 126 | 254 | 382 |
+---------+----------+----------+----------+----------+----------+
*/
#define RSAEncryptor_calcMaxSrcSize(KEY_LEN_BITS, HASH_LEN_BITS) (KEY_LEN_BITS / 8 - 2 * HASH_LEN_BITS / 8 - 2)
/// @param src buffer with size <= `RSAEncryptor_calcMaxSrcSize(key_size_bits, 256)`
/// @param dst buffer with size >= key size in bytes
/// @return size of encrypted data
Result(u32) RSAEncryptor_encrypt(RSAEncryptor* ptr, Array(u8) src, Array(u8) dst);
//////////////////////////////////////////////////////////////////////////////
// RSADecryptor //
//////////////////////////////////////////////////////////////////////////////
typedef struct RSADecryptor {
const br_rsa_private_key* sk;
} RSADecryptor;
/// RSA OAEP encryption with SHA256 hashing algorithm
void RSADecryptor_construct(RSADecryptor* ptr, const br_rsa_private_key* sk);
/// @param src buffer with size == key size in bytes
/// @return size of decrypted data
Result(u32) RSADecryptor_decrypt(RSADecryptor* ptr, Array(u8) buffer);

View File

@@ -1,51 +1,47 @@
#pragma once #pragma once
#include "tlibc/std.h" #include "tcp-chat.h"
#include "tlibc/collections/Array.h" #include "tlibc/collections/Array.h"
#include "tlibc/string/str.h" #include "tlibc/collections/Array_impl/Array_u8.h"
#include "bearssl_rand.h"
#include "bearssl_hash.h"
//////////////////////////////////////////////////////////////////////////////
// //
// hash.c //
// //
//////////////////////////////////////////////////////////////////////////////
/// @brief hashes password multiple times using its own hash as salt /// @brief hashes password multiple times using its own hash as salt
/// @param password some byte array /// @param password some byte array
/// @param iterations number of iterations /// @param salt some byte array
/// @return Array<u8, 32> /// @param out_buffer u8[PASSWORD_HASH_SIZE]
Array hash_password(str password, i32 iterations); /// @param rounds number of rounds
void hash_password(Array(u8) password, Array(u8) salt, u8* out_buffer, i32 rounds);
#define PASSWORD_HASH_LVL_ROUNDS 1e5
//////////////////////////////////////////////////////////////////////////////
// //
// rng.c //
// //
//////////////////////////////////////////////////////////////////////////////
typedef struct EncryptedBlockInfo { /// @brief Initialize prng context with sha256 hashing algorithm
u32 padding_size; /// and seed from system-provided cryptographic random bytes source.
u32 _reserved; /// @param rng_vtable_ptr pointer to vtable field in prng context. The field must be initialized.
u64 __reserved; ///
} EncryptedBlockInfo; /// EXAMPLE:
/// ```
/// br_hmac_drbg_context rng_ctx = { .vtable = &br_hmac_drbg_vtable };
/// rng_init_sha256_seedFromTime(&rng_ctx.vtable);
/// ```
void rng_init_sha256_seedFromSystem(const br_prng_class** rng_vtable_ptr);
typedef struct EncryptorAES EncryptorAES; /// @brief Initialize prng context with sha256 hashing algorithm and seed from CLOCK_REALTIME.
/// @param rng_vtable_ptr pointer to vtable field in prng context. The field must be initialized.
/// @param key Array<u8, 16 | 24 | 32> ///
EncryptorAES* EncryptorAES_create(Array key); /// EXAMPLE:
/// ```
void EncryptorAES_destroy(EncryptorAES* ptr); /// br_hmac_drbg_context rng_ctx = { .vtable = &br_hmac_drbg_vtable };
/// rng_init_sha256_seedFromTime(&rng_ctx.vtable);
/// @brief Encrypts `src` and writes output to `dst`. /// ```
/// @param src array of any size void rng_init_sha256_seedFromTime(const br_prng_class** rng_vtable_ptr);
/// @param dst array of size >= EncryptorAES_calcDstSize(src.size)
void EncryptorAES_encrypt(EncryptorAES* ptr, Array src, Array dst);
#define EncryptorAES_calcDstSize(SRC_SIZE) (ALIGN_TO(SRC_SIZE, 16) + sizeof(EncryptedBlockInfo))
typedef struct DecryptorAES DecryptorAES;
/// @param key Array<u8, 16 | 24 | 32>
DecryptorAES* DecryptorAES_create(Array key);
void DecryptorAES_destroy(DecryptorAES* ptr);
/// @brief Decrypts `src` and writes output to `dst`.
/// @param src array of any size
/// @param dst array of size >= src.size
/// @param decrypted_size size of original data without padding added by EncryptorAES_encrypt
void DecryptorAES_decrypt(DecryptorAES* ptr, Array src, Array dst, u32* decrypted_size);
typedef struct EncryptorRSA EncryptorRSA;
typedef struct DecryptorRSA DecryptorRSA;

View File

@@ -1,18 +1,17 @@
#include "cryptography.h" #include "cryptography.h"
#include "BearSSL/inc/bearssl_hash.h" #include "assert.h"
Array hash_password(str password, i32 iterations){ void hash_password(Array(u8) password, Array(u8) salt, u8* out_buffer, i32 iterations){
Array hash_buffer = Array_alloc(u8, br_sha256_SIZE); assert(PASSWORD_HASH_SIZE == br_sha256_SIZE);;
Array_memset(&hash_buffer, 0); memset(out_buffer, 0, br_sha256_SIZE);
br_sha256_context sha256_ctx; br_sha256_context sha256_ctx;
br_sha256_init(&sha256_ctx); br_sha256_init(&sha256_ctx);
for(i32 i = 0; i < iterations; i++){ for(i32 i = 0; i < iterations; i++){
br_sha256_update(&sha256_ctx, password.data, password.size); br_sha256_update(&sha256_ctx, password.data, password.len);
br_sha256_out(&sha256_ctx, hash_buffer.data); br_sha256_update(&sha256_ctx, salt.data, salt.len);
br_sha256_update(&sha256_ctx, hash_buffer.data, hash_buffer.size); br_sha256_out(&sha256_ctx, out_buffer);
br_sha256_update(&sha256_ctx, out_buffer, PASSWORD_HASH_SIZE);
} }
br_sha256_out(&sha256_ctx, hash_buffer.data); br_sha256_out(&sha256_ctx, out_buffer);
return hash_buffer;
} }

18
src/cryptography/rng.c Normal file
View File

@@ -0,0 +1,18 @@
#include "cryptography.h"
#include "tlibc/time.h"
#include "assert.h"
void rng_init_sha256_seedFromTime(const br_prng_class** rng_vtable_ptr){
nsec_t time_now = getTimeNsec();
const br_prng_class* rng_vtable = *rng_vtable_ptr;
rng_vtable->init(rng_vtable_ptr, &br_sha256_vtable, &time_now, sizeof(time_now));
}
void rng_init_sha256_seedFromSystem(const br_prng_class** rng_vtable_ptr){
br_prng_seeder seeder = br_prng_seeder_system(NULL);
assert(seeder != NULL && "Can't get system random seeder. Bearssl is compiled incorrectly.");
const br_prng_class* rng_vtable = *rng_vtable_ptr;
rng_vtable->init(rng_vtable_ptr, &br_sha256_vtable, NULL, 0);
seeder(rng_vtable_ptr);
}

View File

@@ -1,49 +0,0 @@
#include "cryptography/cryptography.h"
#include "network/network.h"
#include "network/socket.h"
void test_aes(){
const str password = STR("abobus");
const Array data = str_castTo_Array(STR("0123456789_hii_"));
const Array key_hash = hash_password(password, 1e5);
str hash_str = hex_to_str(key_hash, true);
printf("password hash [%i] %s\n", key_hash.size, hash_str.data);
free(hash_str.data);
// SHA256 accepts keys with size 16, 24 or 32
const u32 key_size = 32;
const Array key = Array_construct_size(key_hash.data, key_size);
EncryptorAES* encr = EncryptorAES_create(key);
Array buffer = Array_alloc_size(EncryptorAES_calcDstSize(data.size));
EncryptorAES_encrypt(encr, data, buffer);
EncryptorAES_destroy(encr);
str encrypted_str = hex_to_str(buffer, true);
printf("data encrypted (hex): %s\n", encrypted_str.data);
free(encrypted_str.data);
DecryptorAES* decr = DecryptorAES_create(key);
u32 decrypted_size = 0;
DecryptorAES_decrypt(decr, buffer, buffer, &decrypted_size);
DecryptorAES_destroy(decr);
str decrypted_str = str_copy(str_construct(buffer.data, decrypted_size, false));
printf("data decrypted (utf8): %s\n", decrypted_str.data);
free(decrypted_str.data);
}
void test_server(){
try_fatal(_10, network_init(), );
try_fatal(main_socket, socket_open_TCP(), );
EndpointIPv4 server_end = EndpointIPv4_create(AddressIPv4_LOOPBACK, 24500);
try_fatal(_20, socket_bind(main_socket.v_i64, server_end), );
try_fatal(_30, socket_listen(main_socket.v_i64, 64), );
try_fatal(_100, network_deinit(), );
}
int main(){
test_server();
return 0;
}

View File

@@ -0,0 +1,230 @@
#include "encrypted_sockets.h"
//////////////////////////////////////////////////////////////////////////////
// EncryptedSocketTCP //
//////////////////////////////////////////////////////////////////////////////
void EncryptedSocketTCP_construct(EncryptedSocketTCP* ptr,
Socket sock, u32 crypto_buffer_size, Array(u8) aes_key)
{
ptr->sock = sock;
AESStreamEncryptor_construct(&ptr->enc, aes_key, AESStream_DEFAULT_CLASS);
AESStreamDecryptor_construct(&ptr->dec, aes_key, AESStream_DEFAULT_CLASS);
ptr->recv_buf = Array_u8_alloc(crypto_buffer_size);
ptr->send_buf = Array_u8_alloc(crypto_buffer_size);
}
void EncryptedSocketTCP_destroy(EncryptedSocketTCP* ptr){
if(!ptr)
return;
socket_close(ptr->sock);
Array_u8_destroy(&ptr->recv_buf);
Array_u8_destroy(&ptr->send_buf);
}
void EncryptedSocketTCP_changeKey(EncryptedSocketTCP* ptr, Array(u8) aes_key){
AESStreamEncryptor_changeKey(&ptr->enc, aes_key);
AESStreamDecryptor_changeKey(&ptr->dec, aes_key);
}
Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr,
Array(u8) buffer)
{
Deferral(1);
try(u32 encrypted_size, u,
AESStreamEncryptor_encrypt(
&ptr->enc,
buffer,
ptr->send_buf
)
);
try_void(
socket_send(
ptr->sock,
Array_u8_sliceTo(ptr->send_buf, encrypted_size)
)
);
// printf("SEND data_size: %u, enc_size: %u\n", buffer.len, encrypted_size);
Return RESULT_VOID;
}
Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
Array(u8) buffer, SocketRecvFlag flags)
{
Deferral(1);
u32 size_to_receive = buffer.len;
if(ptr->dec.block_counter == 0){
// There is some metadata at the beginning of AES stream
size_to_receive = AESStreamEncryptor_calcDstSize(size_to_receive);
}
try(i32 received_size, i,
socket_recv(
ptr->sock,
Array_u8_sliceTo(ptr->recv_buf, size_to_receive),
flags
)
);
try(u32 decrypted_size, u,
AESStreamDecryptor_decrypt(
&ptr->dec,
Array_u8_sliceTo(ptr->recv_buf, received_size),
buffer
)
);
// printf("RECV recv_size: %u, dec_size: %u\n", received_size, decrypted_size);
Return RESULT_VALUE(u, decrypted_size);
}
Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
RSAEncryptor* rsa_enc, Array(u8) buffer)
{
Deferral(1);
try(u32 encrypted_size, u,
RSAEncryptor_encrypt(
rsa_enc,
buffer,
ptr->send_buf
)
);
try_void(
socket_send(
ptr->sock,
Array_u8_sliceTo(ptr->send_buf, encrypted_size)
)
);
// printf("SEND-RSA data_size: %u, enc_size: %u\n", buffer.len, encrypted_size);
Return RESULT_VOID;
}
Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr,
RSADecryptor* rsa_dec, Array(u8) buffer, SocketRecvFlag flags)
{
Deferral(1);
// RSA encrypts message in block of size KEY_SIZE_BYTES.
// SocketRecvFlag_WholeBuffer should be always enabled to receive such blocks.
// If this flag is set in `flags` by caller, it means decrypted message size
// must be the same as buffer size.
bool fill_whole_buffer = (flags & SocketRecvFlag_WholeBuffer) != 0;
flags |= SocketRecvFlag_WholeBuffer;
u32 size_to_receive = rsa_dec->sk->n_bitlen / 8;
try(i32 received_size, i,
socket_recv(
ptr->sock,
Array_u8_sliceTo(ptr->recv_buf, size_to_receive),
flags
)
);
try(u32 decrypted_size, u,
RSADecryptor_decrypt(
rsa_dec,
Array_u8_sliceTo(ptr->recv_buf, received_size)
)
);
if(fill_whole_buffer){
if(decrypted_size != buffer.len){
Return RESULT_ERROR_FMT(
"SocketRecvFlag_WholeBuffer is set, "
"but decrypted_size (%u) != buffer.len (%u)",
decrypted_size, buffer.len
);
}
}
else if(decrypted_size > buffer.len){
Return RESULT_ERROR_FMT(
"decrypted_size (%u) > buffer.len (%u)",
decrypted_size, buffer.len
);
}
memcpy(buffer.data, ptr->recv_buf.data, decrypted_size);
// printf("RECV-RSA recv_size: %u, dec_size: %u\n", received_size, decrypted_size);
Return RESULT_VALUE(u, decrypted_size);
}
//////////////////////////////////////////////////////////////////////////////
// EncryptedSocketUDP //
//////////////////////////////////////////////////////////////////////////////
void EncryptedSocketUDP_construct(EncryptedSocketUDP* ptr,
Socket sock, u32 crypto_buffer_size, Array(u8) aes_key)
{
ptr->sock = sock;
AESBlockEncryptor_construct(&ptr->enc, aes_key, AESBlockEncryptor_DEFAULT_CLASS);
AESBlockDecryptor_construct(&ptr->dec, aes_key, AESBlockDecryptor_DEFAULT_CLASS);
ptr->recv_buf = Array_u8_alloc(crypto_buffer_size);
ptr->send_buf = Array_u8_alloc(crypto_buffer_size);
}
void EncryptedSocketUDP_destroy(EncryptedSocketUDP* ptr){
if(!ptr)
return;
socket_close(ptr->sock);
Array_u8_destroy(&ptr->recv_buf);
Array_u8_destroy(&ptr->send_buf);
}
void EncryptedSocketUDP_changeKey(EncryptedSocketUDP* ptr, Array(u8) aes_key){
AESBlockEncryptor_changeKey(&ptr->enc, aes_key);
AESBlockDecryptor_changeKey(&ptr->dec, aes_key);
}
Result(void) EncryptedSocketUDP_sendto(EncryptedSocketUDP* ptr,
Array(u8) buffer, EndpointIPv4 remote_end)
{
Deferral(1);
try(u32 encrypted_size, u,
AESBlockEncryptor_encrypt(
&ptr->enc,
buffer,
ptr->send_buf
)
);
try_void(
socket_sendto(
ptr->sock,
Array_u8_sliceTo(ptr->send_buf, encrypted_size),
remote_end
)
);
Return RESULT_VOID;
}
Result(i32) EncryptedSocketUDP_recvfrom(EncryptedSocketUDP* ptr,
Array(u8) buffer, SocketRecvFlag flags, NULLABLE(EndpointIPv4*) remote_end)
{
Deferral(1);
// There is some metadata at the start of each AES block
u32 size_to_receive = AESBlockEncryptor_calcDstSize(buffer.len);
try(i32 received_size, i,
socket_recvfrom(
ptr->sock,
Array_u8_sliceTo(ptr->recv_buf, size_to_receive),
flags,
remote_end
)
);
try(u32 decrypted_size, u,
AESBlockDecryptor_decrypt(
&ptr->dec,
Array_u8_sliceTo(ptr->recv_buf, received_size),
buffer
)
);
Return RESULT_VALUE(u, decrypted_size);
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include "network/socket.h"
#include "cryptography/AES.h"
#include "cryptography/RSA.h"
//////////////////////////////////////////////////////////////////////////////
// EncryptedSocketTCP //
//////////////////////////////////////////////////////////////////////////////
typedef struct EncryptedSocketTCP {
Socket sock;
AESStreamEncryptor enc;
AESStreamDecryptor dec;
Array(u8) send_buf;
Array(u8) recv_buf;
} EncryptedSocketTCP;
void EncryptedSocketTCP_construct(EncryptedSocketTCP* ptr,
Socket sock, u32 crypto_buffer_size, Array(u8) aes_key);
/// closes the socket
void EncryptedSocketTCP_destroy(EncryptedSocketTCP* ptr);
void EncryptedSocketTCP_changeKey(EncryptedSocketTCP* ptr, Array(u8) aes_key);
Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr,
Array(u8) buffer);
#define EncryptedSocketTCP_sendStruct(socket, structPtr)\
EncryptedSocketTCP_send(socket,\
struct_castTo_Array_u8(structPtr))
Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
Array(u8) buffer, SocketRecvFlag flags);
#define EncryptedSocketTCP_recvStruct(socket, structPtr)\
EncryptedSocketTCP_recv(socket,\
struct_castTo_Array_u8(structPtr),\
SocketRecvFlag_WholeBuffer)
Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
RSAEncryptor* rsa_enc, Array(u8) buffer);
#define EncryptedSocketTCP_sendStructRSA(socket, rsa_enc, structPtr)\
EncryptedSocketTCP_sendRSA(socket, rsa_enc,\
struct_castTo_Array_u8(structPtr))
Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr,
RSADecryptor* rsa_dec, Array(u8) buffer, SocketRecvFlag flags);
#define EncryptedSocketTCP_recvStructRSA(socket, rsa_dec, structPtr)\
EncryptedSocketTCP_recvRSA(socket, rsa_dec,\
struct_castTo_Array_u8(structPtr),\
SocketRecvFlag_WholeBuffer)
//////////////////////////////////////////////////////////////////////////////
// EncryptedSocketUDP //
//////////////////////////////////////////////////////////////////////////////
typedef struct EncryptedSocketUDP {
Socket sock;
AESBlockEncryptor enc;
AESBlockDecryptor dec;
Array(u8) send_buf;
Array(u8) recv_buf;
} EncryptedSocketUDP;
void EncryptedSocketUDP_construct(EncryptedSocketUDP* ptr,
Socket sock, u32 crypto_buffer_size, Array(u8) aes_key);
/// closes the socket
void EncryptedSocketUDP_destroy(EncryptedSocketUDP* ptr);
void EncryptedSocketUDP_changeKey(EncryptedSocketUDP* ptr, Array(u8) aes_key);
Result(void) EncryptedSocketUDP_sendto(EncryptedSocketUDP* ptr,
Array(u8) buffer, EndpointIPv4 remote_end);
Result(u32) EncryptedSocketUDP_recvfrom(EncryptedSocketUDP* ptr,
Array(u8) buffer, SocketRecvFlag flags, NULLABLE(EndpointIPv4*) remote_end);

View File

@@ -1,5 +1,5 @@
#include "internal.h"
#include "endpoint.h" #include "endpoint.h"
#include "network.h"
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end){ struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end){
struct sockaddr_in saddr = {0}; struct sockaddr_in saddr = {0};
@@ -18,11 +18,42 @@ EndpointIPv4 EndpointIPv4_fromSockaddr(struct sockaddr_in saddr){
return end; return end;
} }
AddressIPv4 AddressIPv4_fromStr(cstr); Result(void) AddressIPv4_parse(cstr s, AddressIPv4* addr){
*addr = AddressIPv4_INVALID;
u32 a, b, c, d;
if(sscanf(s, "%u.%u.%u.%u", &a, &b, &c, &d) != 4){
return RESULT_ERROR_FMT("can't parse as AddressIPv4: '%s'", s);
}
*addr = AddressIPv4_fromBytes(a, b, c, d);
return RESULT_VOID;
}
str AddressIPv4_toStr(AddressIPv4 address);
EndpointIPv4 EndpointIPv4_fromStr(cstr s); Result(void) EndpointIPv4_parse(cstr s, EndpointIPv4* end){
*end = EndpointIPv4_INVALID;
u32 a, b, c, d, p;
if(sscanf(s, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &p) != 5){
return RESULT_ERROR_FMT("can't parse as EndpointIPv4: '%s'", s);
}
*end = EndpointIPv4_create(AddressIPv4_fromBytes(a, b, c, d), p);
return RESULT_VOID;
}
str EndpointIPv4_toStr(EndpointIPv4 end); str AddressIPv4_toStr(AddressIPv4 addr){
char* data = malloc(16);
memset(data, 0, 16);
sprintf(data, "%u.%u.%u.%u",
addr.bytes[0], addr.bytes[1],
addr.bytes[2], addr.bytes[3]);
return str_from_cstr(data);
}
str EndpointIPv4_toStr(EndpointIPv4 end){
char* data = malloc(24);
memset(data, 0, 24);
sprintf(data, "%u.%u.%u.%u:%u",
end.address.bytes[0], end.address.bytes[1],
end.address.bytes[2], end.address.bytes[3],
end.port);
return str_from_cstr(data);
}

View File

@@ -1,9 +1,9 @@
#pragma once #pragma once
#include "tlibc/std.h" #include "tlibc/std.h"
#include "tlibc/string/str.h" #include "tlibc/string/str.h"
#include "network.h" #include "tlibc/errors.h"
#define port_INVALID ((port)~0) #define port_INVALID ((u16)~0)
#define port_is_invalid(PORT) (PORT == port_INVALID) #define port_is_invalid(PORT) (PORT == port_INVALID)
@@ -17,10 +17,10 @@ typedef union AddressIPv4 {
#define AddressIPv4_INVALID AddressIPv4_fromBytes(255,255,255,255) #define AddressIPv4_INVALID AddressIPv4_fromBytes(255,255,255,255)
#define AddressIPv4_is_invalid(ADDR) (ADDR.UintBigEndian == (u32)~0) #define AddressIPv4_is_invalid(ADDR) (ADDR.UintBigEndian == (u32)~0)
#define AddressIPv4_fromBytes(A, B, C, D) ((AddressIPv4){.bytes={A,B,C,D}}) #define AddressIPv4_fromBytes(A, B, C, D) ((AddressIPv4){ .bytes = {A,B,C,D} })
#define AddressIPv4_fromU32(N) ((AddressIPv4){.UintBigEndian=N}) #define AddressIPv4_fromU32(N) ((AddressIPv4){ .UintBigEndian = N })
AddressIPv4 AddressIPv4_fromStr(cstr); Result(void) AddressIPv4_parse(cstr s, AddressIPv4* addr);
str AddressIPv4_toStr(AddressIPv4 address); str AddressIPv4_toStr(AddressIPv4 addr);
typedef struct EndpointIPv4 { typedef struct EndpointIPv4 {
@@ -29,11 +29,8 @@ typedef struct EndpointIPv4 {
} EndpointIPv4; } EndpointIPv4;
#define EndpointIPv4_INVALID EndpointIPv4_create(AddressIPv4_INVALID, port_INVALID) #define EndpointIPv4_INVALID EndpointIPv4_create(AddressIPv4_INVALID, port_INVALID)
#define EndpointIPv4_is_invalid(ENDP) (AddressIPv4_is_invalid(ENDP.address) || port_is_invalid(ENDP.port)) #define EndpointIPv4_is_invalid(ENDP) (AddressIPv4_is_invalid((ENDP).address) || port_is_invalid((ENDP).port))
#define EndpointIPv4_create(ADDR, PORT) ((EndpointIPv4){ADDR, PORT}) #define EndpointIPv4_create(ADDR, PORT) ((EndpointIPv4){ADDR, PORT})
EndpointIPv4 EndpointIPv4_fromStr(cstr s); Result(void) EndpointIPv4_parse(cstr s, EndpointIPv4* end);
str EndpointIPv4_toStr(EndpointIPv4 end); str EndpointIPv4_toStr(EndpointIPv4 end);
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end);
EndpointIPv4 EndpointIPv4_fromSockaddr(struct sockaddr_in saddr);

40
src/network/internal.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include "tcp-chat.h"
#include "endpoint.h"
#if !defined(KN_USE_WINSOCK)
#if defined(_WIN64) || defined(_WIN32)
#define KN_USE_WINSOCK 1
#else
#define KN_USE_WINSOCK 0
#endif
#endif
// include OS-dependent socket headers
#if KN_USE_WINSOCK
#include <winsock2.h>
#include <ws2ipdef.h>
// There you can see what error codes mean.
#include <winerror.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#endif
#if KN_USE_WINSOCK
#define RESULT_ERROR_SOCKET()\
RESULT_ERROR_CODE_FMT(WINSOCK2, WSAGetLastError(), "Winsock error %i (look in <winerror.h>)", WSAGetLastError());
#else
#define RESULT_ERROR_SOCKET()\
RESULT_ERROR_ERRNO();
#endif
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end);
EndpointIPv4 EndpointIPv4_fromSockaddr(struct sockaddr_in saddr);

View File

@@ -1,25 +0,0 @@
#include "network.h"
#include "network.h"
Result(void) network_init(){
#if _WIN32
// Initialize Winsock
WSADATA wsaData = {0};
int result = WSAStartup(MAKEWORD(2,2), &wsaData);
if (result != 0) {
return RESULT_ERROR(sprintf_malloc(64, "WSAStartup failed with error code 0x%X", result), true);
}
#endif
return RESULT_VOID;
}
Result(void) network_deinit(){
#if _WIN32
// Deinitialize Winsock
int result = WSACleanup();
if (result != 0) {
return RESULT_ERROR(sprintf_malloc(64, "WSACleanup failed with error code 0x%X", result), true);
}
#endif
return RESULT_VOID;
}

View File

@@ -1,22 +0,0 @@
#pragma once
#include "tlibc/errors.h"
#if defined(_WIN64) || defined(_WIN32)
#define KN_USE_WINSOCK 1
#else
#define KN_USE_WINSOCK 0
#endif
#if KN_USE_WINSOCK
#include <winsock2.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#endif
Result(void) network_init();
Result(void) network_deinit();

View File

@@ -1,14 +1,14 @@
#include "network.h" #include "internal.h"
#include "network.h"
#include "socket.h" #include "socket.h"
#include <assert.h>
Result(Socket) socket_open_TCP(){ Result(Socket) socket_open_TCP(){
Socket s = socket(AF_INET, SOCK_STREAM, 0); Socket s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s == -1){ if(s == -1){
return RESULT_ERROR("can't create socket", false); return RESULT_ERROR_SOCKET();
} }
return RESULT_VALUE(i64, s); return RESULT_VALUE(i, s);
} }
void socket_close(Socket s){ void socket_close(Socket s){
@@ -20,27 +20,163 @@ void socket_close(Socket s){
} }
Result(void) socket_shutdown(Socket s, SocketShutdownType direction){ Result(void) socket_shutdown(Socket s, SocketShutdownType direction){
if(shutdown(s, (int)direction) == -1) if(shutdown(s, (int)direction) != 0)
return RESULT_ERROR("shutdown() failed", false); return RESULT_ERROR_SOCKET();
return RESULT_VOID; return RESULT_VOID;
} }
Result(void) socket_bind(Socket s, EndpointIPv4 local_end){ Result(void) socket_bind(Socket s, EndpointIPv4 local_end){
struct sockaddr_in sockaddr = EndpointIPv4_toSockaddr(local_end); struct sockaddr_in sockaddr = EndpointIPv4_toSockaddr(local_end);
if(bind(s, (void*)&sockaddr, sizeof(sockaddr)) != 0) if(bind(s, (void*)&sockaddr, sizeof(sockaddr)) != 0)
return RESULT_ERROR("bind() failed", false); return RESULT_ERROR_SOCKET();
return RESULT_VOID; return RESULT_VOID;
} }
Result(void) socket_listen(Socket s, i32 backlog){ Result(void) socket_listen(Socket s, i32 backlog){
if(listen(s, backlog) != 0) if(listen(s, backlog) != 0)
return RESULT_ERROR("listen() failed", false); return RESULT_ERROR_SOCKET();
return RESULT_VOID; return RESULT_VOID;
} }
Result(Socket) socket_accept(Socket main_socket) { Result(Socket) socket_accept(Socket listening_sock, NULLABLE(EndpointIPv4*) remote_end) {
struct sockaddr_in remote_addr = {0}; struct sockaddr_in remote_addr = {0};
i32 sockaddr_size = sizeof(remote_addr); i32 sockaddr_size = sizeof(remote_addr);
Socket user_connection = accept(main_socket, (struct sockaddr*)&remote_addr, (void*)&sockaddr_size); Socket accepted_sock = accept(listening_sock, (void*)&remote_addr, (void*)&sockaddr_size);
return RESULT_VALUE(i64, user_connection); if(accepted_sock == -1)
return RESULT_ERROR_SOCKET();
//TODO: add IPV6 support (struct sockaddr_in6)
assert(sockaddr_size == sizeof(remote_addr));
if(remote_end)
*remote_end = EndpointIPv4_fromSockaddr(remote_addr);
return RESULT_VALUE(i, accepted_sock);
}
Result(void) socket_connect(Socket s, EndpointIPv4 remote_end){
struct sockaddr_in sockaddr = EndpointIPv4_toSockaddr(remote_end);
if(connect(s, (void*)&sockaddr, sizeof(sockaddr)) != 0)
return RESULT_ERROR_SOCKET();
return RESULT_VOID;
}
Result(void) socket_send(Socket s, Array(u8) buffer){
i32 r = send(s, (void*)buffer.data, buffer.len, 0);
if(r < 0){
return RESULT_ERROR_SOCKET();
}
if((u32)r != buffer.len){
return RESULT_ERROR_FMT("Socket was unable to send data");
}
return RESULT_VOID;
}
Result(void) socket_sendto(Socket s, Array(u8) buffer, EndpointIPv4 dst){
struct sockaddr_in sockaddr = EndpointIPv4_toSockaddr(dst);
i32 r = sendto(s, (void*)buffer.data, buffer.len, 0, (void*)&sockaddr, sizeof(sockaddr));
if(r < 0){
return RESULT_ERROR_SOCKET();
}
if((u32)r != buffer.len){
return RESULT_ERROR_FMT("Socket was unable to send data");
}
return RESULT_VOID;
}
static inline int SocketRecvFlags_toStd(SocketRecvFlag flags){
int f = 0;
if (flags & SocketRecvFlag_Peek)
f |= MSG_PEEK;
if (flags & SocketRecvFlag_WholeBuffer)
f |= MSG_WAITALL;
return f;
}
Result(i32) socket_recv(Socket s, Array(u8) buffer, SocketRecvFlag flags){
i32 r = recv(s, (void*)buffer.data, buffer.len, SocketRecvFlags_toStd(flags));
if(r < 0){
return RESULT_ERROR_SOCKET();
}
if(r == 0 || (flags & SocketRecvFlag_WholeBuffer && (u32)r != buffer.len))
{
return RESULT_ERROR_LITERAL("Socket closed");
}
return RESULT_VALUE(i, r);
}
Result(i32) socket_recvfrom(Socket s, Array(u8) buffer, SocketRecvFlag flags, NULLABLE(EndpointIPv4*) remote_end){
struct sockaddr_in remote_addr = {0};
i32 sockaddr_size = sizeof(remote_addr);
i32 r = recvfrom(s, (void*)buffer.data, buffer.len, SocketRecvFlags_toStd(flags),
(struct sockaddr*)&remote_addr, (void*)&sockaddr_size);
if(r < 0){
return RESULT_ERROR_SOCKET();
}
if(r == 0 || (flags & SocketRecvFlag_WholeBuffer && (u32)r != buffer.len))
{
return RESULT_ERROR_LITERAL("Socket closed");
}
//TODO: add IPV6 support (struct sockaddr_in6)
assert(sockaddr_size == sizeof(remote_addr));
if(remote_end)
*remote_end = EndpointIPv4_fromSockaddr(remote_addr);
return RESULT_VALUE(i, r);
}
#define try_setsockopt(socket, level, OPT){ \
if(setsockopt(socket, level, OPT, (void*)&opt_##OPT, sizeof(opt_##OPT)) != 0)\
return RESULT_ERROR_SOCKET();\
}
Result(void) socket_TCP_enableAliveChecks(Socket s,
sec_t first_check_time, u32 check_count, sec_t checks_interval)
{
#if KN_USE_WINSOCK
BOOL opt_SO_KEEPALIVE = 1; // enable keepalives
DWORD opt_TCP_KEEPIDLE = first_check_time;
DWORD opt_TCP_KEEPCNT = check_count;
DWORD opt_TCP_KEEPINTVL = checks_interval;
try_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE);
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE);
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT);
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL);
// timeout for connect()
DWORD opt_TCP_MAXRT = check_count * checks_interval;
try_setsockopt(s, IPPROTO_TCP, TCP_MAXRT);
#else
int opt_SO_KEEPALIVE = 1; // enable keepalives
int opt_TCP_KEEPIDLE = first_check_time;
int opt_TCP_KEEPCNT = check_count;
int opt_TCP_KEEPINTVL = checks_interval;
try_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE);
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE);
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT);
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL);
// read more in the article
int opt_TCP_USER_TIMEOUT = check_count * checks_interval * 1000;
try_setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT);
#endif
return RESULT_VOID;
}
Result(void) socket_setTimeout(Socket s, u32 ms){
#if KN_USE_WINSOCK
DWORD opt_SO_SNDTIMEO = ms;
DWORD opt_SO_RCVTIMEO = opt_SO_SNDTIMEO;
#else
struct timeval opt_SO_SNDTIMEO = {
.tv_sec = ms/1000,
.tv_usec = (ms%1000)*1000
};
struct timeval opt_SO_RCVTIMEO = opt_SO_SNDTIMEO;
#endif
try_setsockopt(s, SOL_SOCKET, SO_SNDTIMEO);
try_setsockopt(s, SOL_SOCKET, SO_RCVTIMEO);
return RESULT_VOID;
} }

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include "endpoint.h" #include "endpoint.h"
#include "tlibc/errors.h" #include "tlibc/errors.h"
#include "tlibc/time.h"
#include "tlibc/collections/Array.h"
typedef enum SocketShutdownType { typedef enum SocketShutdownType {
SocketShutdownType_Receive = 0, SocketShutdownType_Receive = 0,
@@ -8,11 +10,39 @@ typedef enum SocketShutdownType {
SocketShutdownType_Both = 2, SocketShutdownType_Both = 2,
} SocketShutdownType; } SocketShutdownType;
typedef enum SocketRecvFlag {
SocketRecvFlag_None = 0,
SocketRecvFlag_Peek = 0b1 /* next recv call will read the same data */,
SocketRecvFlag_WholeBuffer = 0b10 /* waits until buffer is full */,
} SocketRecvFlag;
typedef i64 Socket; typedef i64 Socket;
Result(Socket) socket_open_TCP(); Result(Socket) socket_open_TCP();
void socket_close(Socket s); void socket_close(Socket s);
Result(void) socket_shutdown(Socket s, SocketShutdownType direction); Result(void) socket_shutdown(Socket s, SocketShutdownType direction);
Result(void) socket_bind(Socket s, EndpointIPv4 local_end);
Result(void) socket_listen(Socket s, i32 backlog); Result(void) socket_bind(Socket s, EndpointIPv4 local_end);
Result(Socket) socket_accept(Socket main_socket); Result(void) socket_listen(Socket s, i32 backlog);
Result(Socket) socket_accept(Socket listening_sock, NULLABLE(EndpointIPv4*) remote_end);
Result(void) socket_connect(Socket s, EndpointIPv4 remote_end);
Result(void) socket_send(Socket s, Array(u8) buffer);
Result(void) socket_sendto(Socket s, Array(u8) buffer, EndpointIPv4 dst);
Result(i32) socket_recv(Socket s, Array(u8) buffer, SocketRecvFlag flags);
Result(i32) socket_recvfrom(Socket s, Array(u8) buffer, SocketRecvFlag flags, NULLABLE(EndpointIPv4*) remote_end);
/// Enables sending SO_KEEPALIVE packets when socket is idling.
/// Also enables TCP_USER_TIMEOUT to handle situations
/// when socket is not sending KEEPALIVE packets.
/// Read more: https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/
/// RU translaton: https://habr.com/ru/articles/700470/
Result(void) socket_TCP_enableAliveChecks(Socket s,
sec_t first_check_time, u32 check_count, sec_t checks_interval);
#define socket_TCP_enableAliveChecks_default(socket) \
socket_TCP_enableAliveChecks(socket, 1, 4, 5)
#define SOCKET_TIMEOUT_MS_DEFAULT 5000
#define SOCKET_TIMEOUT_MS_INFINITE 0
/// @brief sets general timeout for send() and recv()
Result(void) socket_setTimeout(Socket s, u32 ms);

View File

@@ -0,0 +1,37 @@
#include "constant.h"
const Magic64 PacketHeader_MAGIC = { .bytes = { 't', 'c', 'p', '-', 'c', 'h', 'a', 't' } };
Result(void) PacketHeader_validateMagic(PacketHeader* ptr){
if (ptr->magic.n != PacketHeader_MAGIC.n){
return RESULT_ERROR_LITERAL("invalid packet magic");
}
return RESULT_VOID;
}
Result(void) PacketHeader_validateType(PacketHeader* ptr, u16 expected_type){
if(ptr->type != expected_type){
return RESULT_ERROR_FMT(
"expected message of type %u, but received of type %u",
expected_type, ptr->type);
}
return RESULT_VOID;
}
Result(void) PacketHeader_validateContentSize(PacketHeader* ptr, u64 expected_size){
if(ptr->content_size != expected_size){
return RESULT_ERROR_FMT(
"expected message with content_size "FMT_u64
", but received with content_size "FMT_u64,
expected_size, ptr->content_size);
}
return RESULT_VOID;
}
void PacketHeader_construct(PacketHeader* ptr, u8 protocol_version, u16 type, u64 content_size){
zeroStruct(ptr);
ptr->magic.n = PacketHeader_MAGIC.n;
ptr->protocol_version = protocol_version;
ptr->type = type;
ptr->content_size = content_size;
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include "tcp-chat.h"
#include "tlibc/magic.h"
#define AES_SESSION_KEY_SIZE 32
extern const Magic64 PacketHeader_MAGIC;
// sizeof(PacketHeader) must be 64
typedef struct PacketHeader {
Magic64 magic;
u8 protocol_version;
u8 _reserved1;
u16 type;
u32 _reserved4;
/* size of request/response struct */
u64 content_size;
} ATTRIBUTE_ALIGNED(64) PacketHeader;
void PacketHeader_construct(PacketHeader* ptr, u8 protocol_version, u16 type, u64 content_size);
Result(void) PacketHeader_validateMagic(PacketHeader* ptr);
Result(void) PacketHeader_validateType(PacketHeader* ptr, u16 expected_type);
Result(void) PacketHeader_validateContentSize(PacketHeader* ptr, u64 expected_size);

View File

@@ -0,0 +1,243 @@
#include "v1.h"
str validateUsername_cstr(char username[USERNAME_SIZE_MAX+1], str* out_username_str){
// must end with 0
if(username[USERNAME_SIZE_MAX] != '\0'){
return str_copy(STR("Username string doesn't end correctly"));
}
str u = str_from_cstr(username);
str error_str = validateUsername_str(u);
if(error_str.data)
return error_str;
*out_username_str = u;
return str_null;
}
str validateUsername_str(str username){
if(username.len < USERNAME_SIZE_MIN || username.len > USERNAME_SIZE_MAX){
return str_from_cstr(
sprintf_malloc(
"username length (in bytes) must be >= %i and <= %i",
USERNAME_SIZE_MIN, USERNAME_SIZE_MAX
)
);
}
for(u32 i = 0; i < username.len; i++){
char c = username.data[i];
if (char_isLatinLower(c) ||
char_isLatinUpper(c) ||
char_isDigit(c) ||
c == '.' || c == '_' || c == '-')
continue;
return str_copy(STR("Username contains restricted characters. "
"Allowed characters: latin, digits, ._-"));
}
return str_null;
}
#define _PacketHeader_construct(T) \
PacketHeader_construct(header, PROTOCOL_VERSION, PacketType_##T, sizeof(T))
void ErrorMessage_construct(ErrorMessage* ptr, PacketHeader* header, u32 msg_size){
_PacketHeader_construct(ErrorMessage);
zeroStruct(ptr);
ptr->msg_size = msg_size;
}
Result(void) ClientHandshake_tryConstruct(ClientHandshake* ptr, PacketHeader* header,
Array(u8) session_key)
{
Deferral(1);
_PacketHeader_construct(ClientHandshake);
zeroStruct(ptr);
try_assert(session_key.len == sizeof(ptr->session_key));
memcpy(ptr->session_key, session_key.data, session_key.len);
Return RESULT_VOID;
}
void ServerHandshake_construct(ServerHandshake* ptr, PacketHeader* header,
i64 session_id)
{
_PacketHeader_construct(ServerHandshake);
zeroStruct(ptr);
ptr->session_id = session_id;
}
void ServerPublicInfoRequest_construct(ServerPublicInfoRequest *ptr, PacketHeader* header,
ServerPublicInfo property)
{
_PacketHeader_construct(ServerPublicInfoRequest);
zeroStruct(ptr);
ptr->property = property;
}
void ServerPublicInfoResponse_construct(ServerPublicInfoResponse* ptr, PacketHeader* header,
u32 data_size)
{
_PacketHeader_construct(ServerPublicInfoResponse);
zeroStruct(ptr);
ptr->data_size = data_size;
}
Result(void) LoginRequest_tryConstruct(LoginRequest *ptr, PacketHeader* header,
str username, Array(u8) token)
{
Deferral(1);
_PacketHeader_construct(LoginRequest);
zeroStruct(ptr);
str name_error_str = validateUsername_str(username);
if(name_error_str.data){
Return RESULT_ERROR(name_error_str, true);
}
memcpy(ptr->username, username.data, username.len);
try_assert(token.len == sizeof(ptr->token));
memcpy(ptr->token, token.data, token.len);
Return RESULT_VOID;
}
void LoginResponse_construct(LoginResponse* ptr, PacketHeader* header,
i64 user_id, i64 landing_channel_id)
{
_PacketHeader_construct(LoginResponse);
zeroStruct(ptr);
ptr->user_id = user_id;
ptr->landing_channel_id = landing_channel_id;
}
Result(void) RegisterRequest_tryConstruct(RegisterRequest *ptr, PacketHeader* header,
str username, Array(u8) token)
{
Deferral(1);
_PacketHeader_construct(RegisterRequest);
zeroStruct(ptr);
str name_error_str = validateUsername_str(username);
if(name_error_str.data){
Return RESULT_ERROR(name_error_str, true);
}
memcpy(ptr->username, username.data, username.len);
try_assert(token.len == sizeof(ptr->token));
memcpy(ptr->token, token.data, token.len);
Return RESULT_VOID;
}
void RegisterResponse_construct(RegisterResponse *ptr, PacketHeader* header,
i64 user_id)
{
_PacketHeader_construct(RegisterResponse);
zeroStruct(ptr);
ptr->user_id = user_id;
}
Result(bool) MessageBlock_writeMessage(MessageBlock* block,
const MessageMeta* msg_meta, const Array(u8) msg_content)
{
Deferral(1);
// check msg_meta
try_assert(msg_meta->magic.n == MESSAGE_MAGIC.n);
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);
}
// write msg_meta
memcpy(block->datum.data + block->offset, msg_meta, sizeof(MessageMeta));
block->offset += sizeof(MessageMeta);
// write msg_content
memcpy(block->datum.data + block->offset, msg_content.data, msg_meta->data_size);
block->offset += msg_meta->data_size;
block->messages_count++;
Return RESULT_VALUE(u, true);
}
Result(bool) MessageBlock_readMessage(MessageBlock* block,
MessageMeta* msg_meta, Array(u8) msg_content)
{
Deferral(1);
// check block
if(block->messages_count == 0){
Return RESULT_VALUE(u, false);
}
try_assert(block->datum.len >= block->offset + sizeof(MessageMeta) + MESSAGE_SIZE_MIN);
// check msg_content.len
try_assert(msg_content.len >= MESSAGE_SIZE_MAX);
// read msg_meta
memcpy(msg_meta, block->datum.data + block->offset, sizeof(MessageMeta));
block->offset += sizeof(MessageMeta);
// check msg_meta
try_assert(msg_meta->magic.n == MESSAGE_MAGIC.n);
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);
// read msg_content
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,
i64 channel_id, u16 data_size)
{
_PacketHeader_construct(SendMessageRequest);
zeroStruct(ptr);
ptr->channel_id = channel_id;
ptr->data_size = data_size;
}
void SendMessageResponse_construct(SendMessageResponse *ptr, PacketHeader *header,
i64 message_id, DateTime timestamp)
{
_PacketHeader_construct(SendMessageResponse);
zeroStruct(ptr);
ptr->message_id = message_id;
ptr->timestamp = timestamp;
}
void GetMessageBlockRequest_construct(GetMessageBlockRequest *ptr, PacketHeader *header,
i64 channel_id, i64 first_message_id, u32 messages_count)
{
_PacketHeader_construct(GetMessageBlockRequest);
zeroStruct(ptr);
ptr->channel_id = channel_id;
ptr->first_message_id = first_message_id;
ptr->messages_count = messages_count;
}
void GetMessageBlockResponse_construct(GetMessageBlockResponse *ptr, PacketHeader *header,
u32 messages_count, u32 data_size)
{
_PacketHeader_construct(GetMessageBlockResponse);
zeroStruct(ptr);
ptr->messages_count = messages_count;
ptr->data_size = data_size;
}

View File

@@ -0,0 +1,229 @@
#pragma once
#include "tcp-chat.h"
#include "tlibc/time.h"
#include "network/tcp-chat-protocol/constant.h"
#define PROTOCOL_VERSION 1 /* 1.0.0 */
#define NETWORK_BUFFER_SIZE 65536
#define ALIGN_PACKET_STRUCT ATTRIBUTE_ALIGNED(8)
/*
Valid username:
- must end with '\0'
- USERNAME_SIZE_MIN <= size <= USERNAME_SIZE_MAX
- allowed characters: latin, digits, ._-
*/
/// validates username char[] and constructs str from it
/// @return str_null on success, heap-allocated error message on fail
str validateUsername_cstr(char username[USERNAME_SIZE_MAX+1], str* out_username_str) ATTRIBUTE_WARN_UNUSED_RESULT;
str validateUsername_str(str username) ATTRIBUTE_WARN_UNUSED_RESULT;
typedef enum PacketType {
PacketType_Invalid,
PacketType_ErrorMessage,
PacketType_ClientHandshake,
PacketType_ServerHandshake,
PacketType_ServerPublicInfoRequest,
PacketType_ServerPublicInfoResponse,
PacketType_LoginRequest,
PacketType_LoginResponse,
PacketType_RegisterRequest,
PacketType_RegisterResponse,
PacketType_SendMessageRequest,
PacketType_SendMessageResponse,
PacketType_GetMessageBlockRequest,
PacketType_GetMessageBlockResponse,
} ATTRIBUTE_PACKED PacketType;
#define ERROR_MESSAGE_MAX_SIZE 8192
typedef struct ErrorMessage {
u32 msg_size; // <= ERROR_MESSAGE_MAX_SIZE
/* stream of size msg_size */
} ALIGN_PACKET_STRUCT ErrorMessage;
void ErrorMessage_construct(ErrorMessage* ptr, PacketHeader* header,
u32 msg_size);
typedef struct ClientHandshake {
u8 session_key[AES_SESSION_KEY_SIZE];
} ALIGN_PACKET_STRUCT ClientHandshake;
Result(void) ClientHandshake_tryConstruct(ClientHandshake* ptr, PacketHeader* header,
Array(u8) session_key);
typedef struct ServerHandshake {
i64 session_id;
} ALIGN_PACKET_STRUCT ServerHandshake;
void ServerHandshake_construct(ServerHandshake* ptr, PacketHeader* header,
i64 session_id);
typedef enum ServerPublicInfo {
ServerPublicInfo_Name,
ServerPublicInfo_Description,
} ATTRIBUTE_PACKED ServerPublicInfo;
typedef struct ServerPublicInfoRequest {
u32 property;
} ALIGN_PACKET_STRUCT ServerPublicInfoRequest;
void ServerPublicInfoRequest_construct(ServerPublicInfoRequest* ptr, PacketHeader* header,
ServerPublicInfo property);
typedef struct ServerPublicInfoResponse {
u32 data_size;
/* stream of size data_size */
} ALIGN_PACKET_STRUCT ServerPublicInfoResponse;
void ServerPublicInfoResponse_construct(ServerPublicInfoResponse* ptr, PacketHeader* header,
u32 data_size);
typedef struct LoginRequest {
char username[USERNAME_SIZE_MAX + 1]; // null-terminated
u8 token[PASSWORD_HASH_SIZE];
} ALIGN_PACKET_STRUCT LoginRequest;
Result(void) LoginRequest_tryConstruct(LoginRequest* ptr, PacketHeader* header,
str username, Array(u8) token);
typedef struct LoginResponse {
i64 user_id;
i64 landing_channel_id;
} ALIGN_PACKET_STRUCT LoginResponse;
void LoginResponse_construct(LoginResponse* ptr, PacketHeader* header,
i64 user_id, i64 landing_channel_id);
typedef struct RegisterRequest {
char username[USERNAME_SIZE_MAX + 1]; // null-terminated
u8 token[PASSWORD_HASH_SIZE];
} ALIGN_PACKET_STRUCT RegisterRequest;
Result(void) RegisterRequest_tryConstruct(RegisterRequest* ptr, PacketHeader* header,
str username, Array(u8) token);
typedef struct RegisterResponse {
i64 user_id;
} ALIGN_PACKET_STRUCT RegisterResponse;
void RegisterResponse_construct(RegisterResponse* ptr, PacketHeader* header,
i64 user_id);
typedef struct MessageMeta {
Magic32 magic;
u16 data_size;
i64 id;
i64 sender_id;
DateTime timestamp; /* UTC */
} ATTRIBUTE_ALIGNED(8) MessageMeta;
#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 \
})
#define MESSAGE_MAGIC ((Magic32){ .bytes = { 'M', 's', 'g', '1' } })
typedef struct MessageBlock {
Array(u8) datum; // sequence(MessageMeta, byte[MessageMeta.data_size])
u32 messages_count;
u32 offset;
} MessageBlock;
static inline void MessageBlock_construct(MessageBlock* self, Array(u8) datum, u32 messages_count){
self->datum = datum;
self->messages_count = messages_count;
self->offset = 0;
}
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);
typedef struct SendMessageRequest {
i64 channel_id;
u16 data_size;
/* stream of size data_size */
} ALIGN_PACKET_STRUCT SendMessageRequest;
void SendMessageRequest_construct(SendMessageRequest* ptr, PacketHeader* header,
i64 channel_id, u16 data_size);
typedef struct SendMessageResponse {
i64 message_id;
DateTime timestamp; /* UTC */
} ALIGN_PACKET_STRUCT SendMessageResponse;
void SendMessageResponse_construct(SendMessageResponse* ptr, PacketHeader* header,
i64 message_id, DateTime timestamp);
typedef struct GetMessageBlockRequest {
i64 channel_id;
i64 first_message_id;
u32 messages_count;
} ALIGN_PACKET_STRUCT GetMessageBlockRequest;
void GetMessageBlockRequest_construct(GetMessageBlockRequest* ptr, PacketHeader* header,
i64 channel_id, i64 first_message_id, u32 messages_count);
typedef struct GetMessageBlockResponse {
u32 messages_count;
u32 data_size;
/* stream of size data_size : sequence (MessageMeta, byte[MessageMeta.data_size]) */
} ALIGN_PACKET_STRUCT GetMessageBlockResponse;
void GetMessageBlockResponse_construct(GetMessageBlockResponse* ptr, PacketHeader* header,
u32 messages_count, u32 data_size);

View File

@@ -0,0 +1,70 @@
#include "server/server_internal.h"
void ClientConnection_close(ClientConnection* conn){
if(!conn)
return;
EncryptedSocketTCP_destroy(&conn->sock);
Array_u8_destroy(&conn->session_key);
MessageBlock_destroy(&conn->message_block);
Array_u8_destroy(&conn->message_content);
ServerQueries_free(conn->queries);
tsqlite_connection_close(conn->db);
free(conn);
}
Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args)
{
Deferral(8);
ClientConnection* conn = (ClientConnection*)malloc(sizeof(ClientConnection));
zeroStruct(conn);
bool success = false;
Defer(if(!success) ClientConnection_close(conn));
conn->server = args->server;
conn->client_end = args->client_end;
conn->session_id = args->session_id;
// buffers
MessageBlock_alloc(&conn->message_block);
conn->message_content = Array_u8_alloc(MESSAGE_SIZE_MAX);
// database
try(conn->db, p, tsqlite_connection_open(args->server->db_path));
try(conn->queries, p, ServerQueries_compile(conn->db));
// correct session key will be received from client later
conn->session_key = Array_u8_alloc(AES_SESSION_KEY_SIZE);
Array_u8_memset(&conn->session_key, 0);
EncryptedSocketTCP_construct(&conn->sock, args->accepted_socket_tcp, NETWORK_BUFFER_SIZE, conn->session_key);
try_void(socket_TCP_enableAliveChecks_default(args->accepted_socket_tcp));
// decrypt the rsa messages using server private key
RSADecryptor rsa_dec;
RSADecryptor_construct(&rsa_dec, &args->server->rsa_sk);
// receive PacketHeader
PacketHeader packet_header;
try_void(EncryptedSocketTCP_recvStructRSA(&conn->sock, &rsa_dec, &packet_header));
try_void(PacketHeader_validateMagic(&packet_header));
try_void(PacketHeader_validateType(&packet_header, PacketType_ClientHandshake));
try_void(PacketHeader_validateContentSize(&packet_header, sizeof(ClientHandshake)));
// receive ClientHandshake
ClientHandshake client_handshake;
try_void(EncryptedSocketTCP_recvStructRSA(&conn->sock, &rsa_dec, &client_handshake));
// use received session key
memcpy(conn->session_key.data, client_handshake.session_key, conn->session_key.len);
EncryptedSocketTCP_changeKey(&conn->sock, conn->session_key);
// send ServerHandshake
ServerHandshake server_handshake;
ServerHandshake_construct(&server_handshake, &packet_header,
conn->session_id);
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &packet_header));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &server_handshake));
success = true;
Return RESULT_VALUE(p, conn);
}

110
src/server/db/Channel.c Normal file
View File

@@ -0,0 +1,110 @@
#include "server_db_internal.h"
Result(bool) Channel_exists(ServerQueries* q, i64 id){
Deferral(4);
tsqlite_statement* st = q->channels.exists;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_i64(st, "$id", id));
try(bool has_result, i, tsqlite_statement_step(st));
Return RESULT_VALUE(i, has_result);
}
Result(bool) Channel_createOrUpdate(ServerQueries* q,
i64 id, str name, str description)
{
Deferral(4);
try_assert(id > 0);
try_assert(name.len >= CHANNEL_NAME_SIZE_MIN && name.len <= CHANNEL_NAME_SIZE_MAX);
try_assert(description.len <= CHANNEL_DESC_SIZE_MAX);
try(bool channel_exists, i, Channel_exists(q, id));
tsqlite_statement* st = NULL;
Defer(tsqlite_statement_reset(st));
if(channel_exists){
st = q->channels.update;
}
else {
st = q->channels.insert;
}
try_void(tsqlite_statement_bind_i64(st, "$id", id));
try_void(tsqlite_statement_bind_str(st, "$name", name, NULL));
try_void(tsqlite_statement_bind_str(st, "$description", description, NULL));
try_void(tsqlite_statement_step(st));
Return RESULT_VALUE(i, !channel_exists);
}
Result(void) Channel_saveMessage(ServerQueries* q,
i64 channel_id, i64 sender_id, Array(u8) content,
DateTime* out_timestamp)
{
Deferral(4);
try_assert(content.len >= MESSAGE_SIZE_MIN && content.len <= MESSAGE_SIZE_MAX);
tsqlite_statement* st = q->messages.insert;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_i64(st, "$channel_id", channel_id));
try_void(tsqlite_statement_bind_i64(st, "$sender_id", sender_id));
try_void(tsqlite_statement_bind_blob(st, "$content", content, NULL));
try(bool has_result, i, tsqlite_statement_step(st));
try_assert(has_result);
try(i64 message_id, i, tsqlite_statement_getResult_i64(st));
str timestamp_str;
try_void(tsqlite_statement_getResult_str(st, &timestamp_str));
try_void(DateTime_parse(timestamp_str.data, out_timestamp));
Return RESULT_VALUE(i, message_id);
}
Result(void) Channel_loadMessageBlock(ServerQueries* q,
i64 channel_id, i64 first_message_id, u32 count,
MessageBlock* block)
{
Deferral(4);
if(count == 0){
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;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_i64(st, "$channel_id", channel_id));
try_void(tsqlite_statement_bind_i64(st, "$first_message_id", first_message_id));
try_void(tsqlite_statement_bind_i64(st, "$count", count));
MessageBlock_reset(block);
str tmp_str = str_null;
while(true){
try(bool has_result, i, tsqlite_statement_step(st));
if(!has_result)
break;
// id
try(i64 message_id, i, tsqlite_statement_getResult_i64(st));
// sender_id
try(i64 sender_id, i, tsqlite_statement_getResult_i64(st));
// content
Array(u8) msg_content;
try_void(tsqlite_statement_getResult_blob(st, &msg_content));
// timestamp
try_void(tsqlite_statement_getResult_str(st, &tmp_str));
DateTime timestamp;
try_void(DateTime_parse(tmp_str.data, &timestamp));
MessageMeta msg_meta = MessageMeta_construct(
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;
}

50
src/server/db/User.c Normal file
View File

@@ -0,0 +1,50 @@
#include "server_db_internal.h"
Result(i64) User_findByUsername(ServerQueries* q, str username){
Deferral(4);
tsqlite_statement* st = q->users.find_by_username;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_str(st, "$username", username, NULL));
try(bool has_result, i, tsqlite_statement_step(st));
i64 user_id = 0;
if(has_result){
try(user_id, i, tsqlite_statement_getResult_i64(st));
try_assert(user_id > 0);
}
Return RESULT_VALUE(i, user_id);
}
Result(i64) User_register(ServerQueries* q, str username, Array(u8) token){
Deferral(4);
try_assert(username.len >= USERNAME_SIZE_MIN && username.len <= USERNAME_SIZE_MAX);
try_assert(token.len == PASSWORD_HASH_SIZE)
tsqlite_statement* st = q->users.insert;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_str(st, "$username", username, NULL));
try_void(tsqlite_statement_bind_blob(st, "$token", token, NULL));
try(bool has_result, i, tsqlite_statement_step(st));
try_assert(has_result);
try(i64 user_id, i, tsqlite_statement_getResult_i64(st));
try_assert(user_id > 0);
Return RESULT_VALUE(i, user_id);
}
Result(bool) User_tryAuthorize(ServerQueries* q, u64 id, Array(u8) token){
Deferral(4);
try_assert(token.len == PASSWORD_HASH_SIZE)
tsqlite_statement* st = q->users.compare_token;
Defer(tsqlite_statement_reset(st));
try_void(tsqlite_statement_bind_i64(st, "$id", id));
try_void(tsqlite_statement_bind_blob(st, "$token", token, NULL));
try(bool has_result, i, tsqlite_statement_step(st));
Return RESULT_VALUE(i, has_result);
}

153
src/server/db/server_db.c Normal file
View File

@@ -0,0 +1,153 @@
#include "server_db_internal.h"
#include "tlibc/filesystem.h"
Result(tsqlite_connection*) ServerDatabase_open(cstr file_path){
Deferral(64);
try_void(dir_createParent(file_path));
try(tsqlite_connection* db, p, tsqlite_connection_open(file_path));
bool success = false;
Defer(if(!success) tsqlite_connection_close(db));
///////////////////////////////////////////////////////////////////////////
// CHANNELS //
///////////////////////////////////////////////////////////////////////////
try(tsqlite_statement* create_table_channels, p, tsqlite_statement_compile(db, STR(
"CREATE TABLE IF NOT EXISTS channels (\n"
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" name VARCHAR NOT NULL,\n"
" description VARCHAR NOT NULL\n"
");"
)));
Defer(tsqlite_statement_free(create_table_channels));
try_void(tsqlite_statement_step(create_table_channels));
///////////////////////////////////////////////////////////////////////////
// MESSAGES //
///////////////////////////////////////////////////////////////////////////
try(tsqlite_statement* create_table_messages, p, tsqlite_statement_compile(db, STR(
"CREATE TABLE IF NOT EXISTS messages (\n"
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" channel_id INTEGER NOT NULL REFERENCES channels(id),\n"
" sender_id INTEGER NOT NULL REFERENCES users(id),\n"
" content BLOB NOT NULL,\n"
" timestamp DATETIME NOT NULL DEFAULT (\n"
" strftime('"MESSAGE_TIMESTAMP_FMT_SQL"', 'now', 'utc', 'subsecond')\n"
" )\n"
");"
)));
Defer(tsqlite_statement_free(create_table_messages));
try_void(tsqlite_statement_step(create_table_messages));
///////////////////////////////////////////////////////////////////////////
// USERS //
///////////////////////////////////////////////////////////////////////////
try(tsqlite_statement* create_table_users, p, tsqlite_statement_compile(db, STR(
"CREATE TABLE IF NOT EXISTS users (\n"
" id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" username VARCHAR NOT NULL,\n"
" token BLOB NOT NULL,\n"
" registration_time DATETIME NOT NULL DEFAULT (\n"
" strftime('"MESSAGE_TIMESTAMP_FMT_SQL"', 'now', 'utc', 'subsecond')\n"
" )\n"
");"
)));
Defer(tsqlite_statement_free(create_table_users));
try_void(tsqlite_statement_step(create_table_users));
try(tsqlite_statement* create_index_username, p, tsqlite_statement_compile(db, STR(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users(username);"
)));
Defer(tsqlite_statement_free(create_index_username));
try_void(tsqlite_statement_step(create_index_username));
success = true;
Return RESULT_VALUE(p, db);
}
void ServerQueries_free(ServerQueries* q){
if(!q)
return;
tsqlite_statement_free(q->channels.insert);
tsqlite_statement_free(q->channels.update);
tsqlite_statement_free(q->channels.exists);
tsqlite_statement_free(q->messages.insert);
tsqlite_statement_free(q->messages.get_block);
tsqlite_statement_free(q->users.insert);
tsqlite_statement_free(q->users.find_by_username);
tsqlite_statement_free(q->users.compare_token);
free(q);
}
Result(ServerQueries*) ServerQueries_compile(tsqlite_connection* db){
Deferral(4);
ServerQueries* q = (ServerQueries*)malloc(sizeof(*q));
zeroStruct(q);
bool success = false;
Defer(if(!success) ServerQueries_free(q));
///////////////////////////////////////////////////////////////////////////
// CHANNELS //
///////////////////////////////////////////////////////////////////////////
try(q->channels.insert, p, tsqlite_statement_compile(db, STR(
"INSERT INTO\n"
"channels (id, name, description)\n"
"VALUES ($id, $name, $description);"
)));
try(q->channels.exists, p, tsqlite_statement_compile(db, STR(
"SELECT 1 FROM channels WHERE id = $id;"
)));
try(q->channels.update, p, tsqlite_statement_compile(db, STR(
"UPDATE channels\n"
"SET name = $name, description = $description\n"
"WHERE id = $id;"
)));
///////////////////////////////////////////////////////////////////////////
// MESSAGES //
///////////////////////////////////////////////////////////////////////////
try(q->messages.insert, p, tsqlite_statement_compile(db, STR(
"INSERT INTO\n"
"messages (channel_id, sender_id, content)\n"
"VALUES ($channel_id, $sender_id, $content)\n"
"RETURNING id, timestamp;"
)));
try(q->messages.get_block, p, tsqlite_statement_compile(db, STR(
"SELECT id, sender_id, content, timestamp FROM messages\n"
"WHERE id >= $first_message_id\n"
"AND channel_id = $channel_id\n"
"LIMIT $count;"
)));
///////////////////////////////////////////////////////////////////////////
// USERS //
///////////////////////////////////////////////////////////////////////////
try(q->users.insert, p, tsqlite_statement_compile(db, STR(
"INSERT INTO\n"
"users (username, token)\n"
"VALUES ($username, $token)\n"
"RETURNING id, registration_time;"
)));
try(q->users.find_by_username, p, tsqlite_statement_compile(db, STR(
"SELECT id FROM users WHERE username = $username;"
)));
try(q->users.compare_token, p, tsqlite_statement_compile(db, STR(
"SELECT 1 FROM users WHERE id = $id AND token = $token;"
)));
success = true;
Return RESULT_VALUE(p, q);
}

39
src/server/db/server_db.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include "tcp-chat.h"
#include "tsqlite.h"
#include "network/tcp-chat-protocol/v1.h"
/// @brief open DB and create tables
Result(tsqlite_connection*) ServerDatabase_open(cstr file_path);
typedef struct ServerQueries ServerQueries;
Result(ServerQueries*) ServerQueries_compile(tsqlite_connection* db);
void ServerQueries_free(ServerQueries* self);
Result(bool) Channel_exists(ServerQueries* q, i64 id);
/// @return true if new row was created
Result(bool) Channel_createOrUpdate(ServerQueries* q,
i64 id, str name, str description);
/// @return new message id
Result(i64) Channel_saveMessage(ServerQueries* q,
i64 channel_id, i64 sender_id, Array(u8) content,
DateTime* out_timestamp_utc);
/// @brief try to find count messages with id >= first_message_id
/// @param dst_block writes messages here. messages_count can be 0 if no messages were found
Result(void) Channel_loadMessageBlock(ServerQueries* q,
i64 channel_id, i64 first_message_id, u32 count,
MessageBlock* dst_block);
/// @return existing user id or 0
Result(i64) User_findByUsername(ServerQueries* q, str username);
/// @return new user id
Result(i64) User_register(ServerQueries* q, str username, Array(u8) token);
/// @return true for successful authorization
Result(bool) User_tryAuthorize(ServerQueries* q, u64 id, Array(u8) token);

View File

@@ -0,0 +1,27 @@
#pragma once
#include "server_db.h"
typedef struct ServerQueries {
struct {
/* ($id, $name, $description) -> void */
tsqlite_statement* insert;
/* ($id, $name, $description) -> void */
tsqlite_statement* update;
/* ($id) -> 1 or nothing */
tsqlite_statement* exists;
} channels;
struct {
/* ($channel_id, $sender_id, $content) -> (id, timestamp) */
tsqlite_statement* insert;
/* ($channel_id, $first_message_id, $count) -> [(id, sender_id, content, timestamp)] */
tsqlite_statement* get_block;
} messages;
struct {
/* ($username, $token) -> (id, registration_time) */
tsqlite_statement* insert;
/* ($username) -> (id) */
tsqlite_statement* find_by_username;
/* ($id, $token) -> 1 or nothing */
tsqlite_statement* compare_token;
} users;
} ServerQueries;

View File

@@ -0,0 +1,57 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
declare_RequestHandler(GetMessageBlock)
{
Deferral(4);
logInfo("requested %s", req_type_name);
// receive request
GetMessageBlockRequest req;
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
if(!conn->authorized){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("not authorized") ));
Return RESULT_VOID;
}
// validate messages_count
if(req.messages_count < 1 || req.messages_count > MESSAGE_BLOCK_COUNT_MAX){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("invalid message count in request") ));
Return RESULT_VOID;
}
// validate channel id
try(bool channel_exists, i, Channel_exists(conn->queries, req.channel_id));
if(!channel_exists){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("invalid channel id") ));
Return RESULT_VOID;
}
// get message block from channel
try_void(Channel_loadMessageBlock(conn->queries,
req.channel_id, req.first_message_id, req.messages_count,
&conn->message_block));
// send response
GetMessageBlockResponse res;
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));
if(conn->message_block.offset != 0){
try_void(EncryptedSocketTCP_send(&conn->sock,
Array_u8_sliceTo(conn->message_block.datum, conn->message_block.offset))
);
}
Return RESULT_VOID;
}

View File

@@ -0,0 +1,64 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
declare_RequestHandler(Login)
{
Deferral(4);
logInfo("requested %s", req_type_name);
// receive request
LoginRequest req;
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
if(conn->authorized){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("is authorized in already") ));
Return RESULT_VOID;
}
// validate username
str username = str_null;
str name_error_str = validateUsername_cstr(req.username, &username);
if(name_error_str.data){
Defer(str_destroy(name_error_str));
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, name_error_str));
Return RESULT_VOID;
}
// get user by id
try(u64 user_id, i, User_findByUsername(conn->queries, username));
if(user_id == 0){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("Username is not registered") ));
Return RESULT_VOID;
}
// TODO: get user token
Array(u8) token = Array_u8_construct(req.token, sizeof(req.token));
try(bool authorized, i, User_tryAuthorize(conn->queries, user_id, token));
// validate token hash
if(!authorized){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("wrong password") ));
Return RESULT_VOID;
}
// authorize
conn->authorized = true;
conn->user_id = user_id;
logInfo("authorized user '%s' with id "FMT_i64, username.data, user_id);
// send response
LoginResponse res;
LoginResponse_construct(&res, res_head, user_id, srv->landing_channel_id);
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
Return RESULT_VOID;
}

View File

@@ -0,0 +1,55 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
declare_RequestHandler(Register)
{
Deferral(4);
logInfo("requested %s", req_type_name);
// receive request
RegisterRequest req;
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
if(conn->authorized){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("is authorized in already") ));
Return RESULT_VOID;
}
// validate username
str username = str_null;
str name_error_str = validateUsername_cstr(req.username, &username);
if(name_error_str.data){
Defer(str_destroy(name_error_str));
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, name_error_str));
Return RESULT_VOID;
}
// check if name is taken
try(u64 user_id, i, User_findByUsername(conn->queries, username));
if(user_id != 0){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("Username is already taken") ));
Return RESULT_VOID;
}
// register new user
Array(u8) token = Array_u8_construct(req.token, sizeof(req.token));
try(user_id, i, User_register(conn->queries, username, token));
logInfo("registered user '"FMT_str"' with id "FMT_i64,
str_unwrap(username), user_id);
// send response
RegisterResponse res;
RegisterResponse_construct(&res, res_head, user_id);
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
Return RESULT_VOID;
}

View File

@@ -0,0 +1,57 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
declare_RequestHandler(SendMessage)
{
Deferral(4);
logInfo("requested %s", req_type_name);
// receive request
SendMessageRequest req;
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
if(!conn->authorized){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("not authorized") ));
Return RESULT_VOID;
}
// validate content size
if(req.data_size < MESSAGE_SIZE_MIN || req.data_size > MESSAGE_SIZE_MAX){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("invalid message size") ));
// close socket connection to reject incoming data
Return RESULT_ERROR_CODE_FMT(TcpChat, TcpChatError_RejectIncoming, "invalid message size: %u", req.data_size);
}
// receive message data
try_void(EncryptedSocketTCP_recv(&conn->sock, conn->message_content, SocketRecvFlag_WholeBuffer));
// validate channel id
try(bool channel_exists, i, Channel_exists(conn->queries, req.channel_id));
if(!channel_exists){
try_void(sendErrorMessage(log_ctx, conn, res_head,
LogSeverity_Warn, STR("invalid channel id") ));
Return RESULT_VOID;
}
// save message to channel
DateTime timestamp;
try(i64 message_id, i, Channel_saveMessage(conn->queries,
req.channel_id, conn->user_id, conn->message_content,
&timestamp));
// send response
SendMessageResponse res;
SendMessageResponse_construct(&res, res_head,
message_id, timestamp);
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
Return RESULT_VOID;
}

View File

@@ -0,0 +1,44 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
declare_RequestHandler(ServerPublicInfo)
{
Deferral(4);
logInfo("requested %s", req_type_name);
// receive request
ServerPublicInfoRequest req;
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
// find requested info
Array(u8) content;
switch(req.property){
default:{
try_void(sendErrorMessage_f(log_ctx, conn, res_head,
LogSeverity_Warn,
"Unknown ServerPublicInfo property %u",
req.property));
Return RESULT_VOID;
}
case ServerPublicInfo_Name:
content = str_castTo_Array_u8(srv->name);
break;
case ServerPublicInfo_Description:
content = str_castTo_Array_u8(srv->description);
break;
}
// send response
ServerPublicInfoResponse res;
ServerPublicInfoResponse_construct(&res, res_head, content.len);
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
try_void(EncryptedSocketTCP_send(&conn->sock, content));
Return RESULT_VOID;
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include "server/server_internal.h"
Result(void) sendErrorMessage(
cstr log_ctx, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, str msg);
Result(void) __sendErrorMessage_fv(
cstr log_ctx, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, cstr format, va_list argv);
Result(void) sendErrorMessage_f(
cstr log_ctx, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, cstr format, ...) ATTRIBUTE_CHECK_FORMAT_PRINTF(5, 6);
#define declare_RequestHandler(TYPE) \
Result(void) handleRequest_##TYPE(cstr log_ctx, cstr req_type_name, \
ClientConnection* conn, PacketHeader* req_head, PacketHeader* res_head)
#define case_handleRequest(TYPE) \
case PacketType_##TYPE##Request:\
try_void(handleRequest_##TYPE(log_ctx, #TYPE, conn, &req_head, &res_head));\
break;
declare_RequestHandler(ServerPublicInfo);
declare_RequestHandler(Login);
declare_RequestHandler(Register);
declare_RequestHandler(SendMessage);
declare_RequestHandler(GetMessageBlock);

View File

@@ -0,0 +1,55 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
Result(void) sendErrorMessage(
cstr log_ctx, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, str msg)
{
Deferral(4);
//limit ErrorMessage size to fit into EncryptedSocketTCP.internal_buffer_size
if(msg.len > ERROR_MESSAGE_MAX_SIZE)
msg.len = ERROR_MESSAGE_MAX_SIZE;
log(log_severity, FMT_str, msg.len, msg.data);
ErrorMessage res;
ErrorMessage_construct(&res, res_head, msg.len);
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
try_void(EncryptedSocketTCP_send(&conn->sock, str_castTo_Array_u8(msg)));
Return RESULT_VOID;
}
Result(void) __sendErrorMessage_fv(
cstr log_ctx, ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, cstr format, va_list argv)
{
Deferral(4);
str msg = str_from_cstr(vsprintf_malloc(format, argv));
Defer(free(msg.data));
try_void(sendErrorMessage(log_ctx, conn, res_head, log_severity, msg));
Return RESULT_VOID;
}
Result(void) sendErrorMessage_f(
cstr log_ctx,
ClientConnection* conn, PacketHeader* res_head,
LogSeverity log_severity, cstr format, ...)
{
Deferral(4);
va_list argv;
va_start(argv, format);
Defer(va_end(argv));
try_void(__sendErrorMessage_fv(log_ctx, conn, res_head, log_severity, format, argv));
Return RESULT_VOID;
}

View File

@@ -0,0 +1,25 @@
#include "responses.h"
#define srv conn->server
#define LOGGER srv->logger
#define LOG_FUNC srv->log_func
#define LOG_CONTEXT log_ctx
declare_RequestHandler(NAME)
{
Deferral(4);
logInfo("requested %s", req_type_name);
// receive request
NAME##Request req;
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
// send response
NAME##Response res;
NAME##Response_construct(&res, res_head, );
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
Return RESULT_VOID;
}

224
src/server/server.c Normal file
View File

@@ -0,0 +1,224 @@
#include <pthread.h>
#include "tlibc/time.h"
#include "server/server_internal.h"
#include "server/responses/responses.h"
#include "tlibtoml.h"
static void* handleConnection(void* _args);
static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_ctx);
void Server_free(Server* self){
if(!self)
return;
str_destroy(self->name);
str_destroy(self->description);
RSA_destroyPrivateKey(&self->rsa_sk);
RSA_destroyPublicKey(&self->rsa_pk);
free(self->db_path);
ServerQueries_free(self->queries);
tsqlite_connection_close(self->db);
free(self);
}
#define LOGGER logger
#define LOG_FUNC log_func
#define LOG_CONTEXT log_ctx
Result(Server*) Server_create(str config_file_content, cstr config_file_name,
void* logger, LogFunction_t log_func)
{
Deferral(16);
cstr log_ctx = "ServerInit";
Server* self = (Server*)malloc(sizeof(Server));
zeroStruct(self);
bool success = false;
Defer(if(!success) Server_free(self));
self->logger = logger;
self->log_func = log_func;
logDebug("parsing config");
try(TomlTable* config_top, p, toml_load_str_filename(config_file_content, config_file_name));
Defer(TomlTable_free(config_top));
// [server]
try(TomlTable* config_server, p, TomlTable_get_table(config_top, STR("server")))
// name
try(str* v_name, p, TomlTable_get_str(config_server, STR("name")));
self->name = str_copy(*v_name);
// description
try(str* v_desc, p, TomlTable_get_str(config_server, STR("description")));
self->description = str_copy(*v_desc);
// local_address
try(str* v_local_address, p, TomlTable_get_str(config_server, STR("local_address")));
try_assert(v_local_address->isZeroTerminated);
try_void(EndpointIPv4_parse(v_local_address->data, &self->local_end));
// landing_channel_id
try(i64 v_landing_channel_id, i, TomlTable_get_integer(config_server, STR("landing_channel_id")));
self->landing_channel_id = v_landing_channel_id;
// [keys]
try(TomlTable* config_keys, p, TomlTable_get_table(config_top, STR("keys")))
// rsa_private_key
try(str* v_rsa_sk, p, TomlTable_get_str(config_keys, STR("rsa_private_key")));
try_assert(v_rsa_sk->isZeroTerminated);
try_void(RSA_parsePrivateKey_base64(v_rsa_sk->data, &self->rsa_sk));
// rsa_public_key
try(str* v_rsa_pk, p, TomlTable_get_str(config_keys, STR("rsa_public_key")));
try_assert(v_rsa_pk->isZeroTerminated);
try_void(RSA_parsePublicKey_base64(v_rsa_pk->data, &self->rsa_pk));
// [db]
try(TomlTable* config_db, p, TomlTable_get_table(config_top, STR("database")))
// path
try(str* v_db_path, p, TomlTable_get_str(config_db, STR("path")));
self->db_path = str_copy(*v_db_path).data;
// open DB
logInfo("loading database '%s'", self->db_path);
try(self->db, p, ServerDatabase_open(self->db_path));
try(self->queries, p, ServerQueries_compile(self->db));
// [channels]
logDebug("loading channels...");
try(TomlTable* config_channels, p, TomlTable_get_table(config_top, STR("channels")));
HashMapIter channels_iter = HashMapIter_create(config_channels);
while(HashMapIter_moveNext(&channels_iter)){
HashMapKeyValue kv;
HashMapIter_getCurrent(&channels_iter, &kv);
str name = kv.key;
TomlValue* val = kv.value_ptr;
// skip if not table
if(val->type != TLIBTOML_TABLE)
continue;
logInfo("loading channel '"FMT_str"'", str_unwrap(name))
TomlTable* config_channel = val->table;
try(i64 id, u, TomlTable_get_integer(config_channel, STR("id")));
try(str* v_ch_desc, p, TomlTable_get_str(config_channel, STR("description")))
str description = *v_ch_desc;
try_void(Channel_createOrUpdate(self->queries, id, name, description));
}
success = true;
Return RESULT_VALUE(p, self);
}
#undef LOGGER
#undef LOG_FUNC
#undef LOG_CONTEXT
#define LOGGER server->logger
#define LOG_FUNC server->log_func
#define LOG_CONTEXT log_ctx
Result(void) Server_run(Server* server){
Deferral(16);
cstr log_ctx = "ListenerThread";
logInfo("starting server");
logDebug("initializing main socket");
try(Socket main_socket, i, socket_open_TCP());
try_void(socket_bind(main_socket, server->local_end));
try_void(socket_listen(main_socket, 512));
str local_end_str = EndpointIPv4_toStr(server->local_end);
Defer(free(local_end_str.data));
logInfo("server is listening at %s", local_end_str.data);
i64 session_id = 1;
while(true){
ConnectionHandlerArgs* args = (ConnectionHandlerArgs*)malloc(sizeof(ConnectionHandlerArgs));
args->server = server;
try(args->accepted_socket_tcp, i,
socket_accept(main_socket, &args->client_end));
args->session_id = session_id++;
pthread_t conn_thread = {0};
//TODO: use async IO instead of threads to not waste system resources
// while waiting for incoming data in 100500 threads
try_stderrcode(pthread_create(&conn_thread, NULL, handleConnection, args));
try_stderrcode(pthread_detach(conn_thread));
}
Return RESULT_VOID;
}
static void* handleConnection(void* _args){
ConnectionHandlerArgs* args = (ConnectionHandlerArgs*)_args;
Server* server = args->server;
char log_ctx[64];
sprintf(log_ctx, "Session-"FMT_x64, args->session_id);
ResultVar(void) r = try_handleConnection(args, log_ctx);
if(r.error){
Error_addCallPos(r.error, ErrorCallPos_here());
str e_str = Error_toStr(r.error);
LogSeverity severity = LogSeverity_Error;
if(r.error->error_code_page == ErrorCodePage_TcpChat){
if(r.error->error_code == TcpChatError_RejectIncoming){
severity = LogSeverity_Debug;
}
}
log(severity, FMT_str, e_str.len, e_str.data);
str_destroy(e_str);
Error_free(r.error);
}
logInfo("session end");
free(args);
return NULL;
}
static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_ctx){
Deferral(16);
Server* server = args->server;
logInfo("a client is trying to connect");
ClientConnection* conn = NULL;
// establish encrypted connection
try(conn, p, ClientConnection_accept(args));
Defer(ClientConnection_close(conn));
logInfo("session accepted");
// handle requests
PacketHeader req_head;
PacketHeader res_head;
while(true){
sleepMsec(20);
//TODO: implement some additional check if socket is dead or not
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req_head));
try_void(PacketHeader_validateMagic(&req_head));
switch(req_head.type){
// send error message and close connection
default:
try_void(sendErrorMessage_f(log_ctx, conn, &res_head,
LogSeverity_Error,
"Received unexpected packet of type %u",
req_head.type));
Return RESULT_VOID;
// unauthorized requests
case_handleRequest(ServerPublicInfo);
case_handleRequest(Login);
case_handleRequest(Register);
// authorized requests
case_handleRequest(SendMessage);
case_handleRequest(GetMessageBlock);
}
}
Return RESULT_VOID;
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include "tcp-chat.h"
#include "cryptography/AES.h"
#include "cryptography/RSA.h"
#include "network/encrypted_sockets.h"
#include "network/tcp-chat-protocol/v1.h"
#include "db/server_db.h"
typedef struct ClientConnection ClientConnection;
typedef struct Server {
/* from constructor */
void* logger;
LogFunction_t log_func;
/* from config */
str name;
str description;
i64 landing_channel_id;
EndpointIPv4 local_end;
br_rsa_private_key rsa_sk;
br_rsa_public_key rsa_pk;
/* database and cache*/
char* db_path;
tsqlite_connection* db;
ServerQueries* queries; /* for server listener thread only */
} Server;
typedef struct ClientConnection {
Server* server;
i64 session_id;
EndpointIPv4 client_end;
Array(u8) session_key;
EncryptedSocketTCP sock;
bool authorized;
i64 user_id; // 0 for unauthorized
/* buffers */
MessageBlock message_block; // requested message block
Array(u8) message_content; // sent message
/* database */
tsqlite_connection* db;
ServerQueries* queries;
} ClientConnection;
typedef struct ConnectionHandlerArgs {
Server* server;
Socket accepted_socket_tcp;
EndpointIPv4 client_end;
i64 session_id;
} ConnectionHandlerArgs;
Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args);
void ClientConnection_close(ClientConnection* conn);

29
src/tcp-chat.c Normal file
View File

@@ -0,0 +1,29 @@
#include "network/internal.h"
ErrorCodePage_define(WINSOCK2);
ErrorCodePage_define(TcpChat);
Result(void) TcpChat_init(){
Deferral(4);
ErrorCodePage_register(TcpChat);
#if _WIN32
ErrorCodePage_register(WINSOCK2);
// Initialize Winsock
WSADATA wsaData = {0};
int result = WSAStartup(MAKEWORD(2,2), &wsaData);
if (result != 0) {
Return RESULT_ERROR_CODE_FMT(WINSOCK2, result, "WSAStartup failed with error code %i", result);
}
#endif
Return RESULT_VOID;
}
void TcpChat_deinit(){
#if _WIN32
// Deinitialize Winsock
(void)WSACleanup();
#endif
}

View File

@@ -0,0 +1,22 @@
[server]
name = "Test Server"
description = """\
Lorem ipsum labuba aboba.\n\
Qqqqq...\
"""
local_address = '127.0.0.1:9988'
landing_channel_id = 1
# do not create channels with the same id
[channels.general]
id = 1
description = "a text channel"
[database]
path = 'tcp-chat-server/server.sqlite'
# on windows use backslashes
# path = 'tcp-chat-server\server.sqlite'
[keys]
rsa_private_key = '<generate with './tcp-chat --rsa-gen-random'>'
rsa_public_key = '<copy from output of command above>'