Compare commits
44 Commits
e03c651cef
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| db861cd698 | |||
| b9a622c9d1 | |||
| 75891025d0 | |||
| d68ce8d4f0 | |||
| b081e52d6e | |||
| 310e4867d5 | |||
| bfcb2f931f | |||
| 151ad13853 | |||
| 0132e71c88 | |||
| 90e21bc5ae | |||
| e2edd4070a | |||
| d461cae077 | |||
| 49793e2929 | |||
| 72696dea70 | |||
| 084a1828b2 | |||
| 88c2f8aa51 | |||
| 6d1f450f32 | |||
| c263d02b36 | |||
| f5169e8a8f | |||
| 2686ca6bcf | |||
| 8bc98a321d | |||
| 7a3808ba59 | |||
| 571fdd900f | |||
| 0abee3f7df | |||
| baca2fb4d3 | |||
| d32f7d4b89 | |||
| 9dc7de1b41 | |||
| 806f0359d0 | |||
| 23c98e14df | |||
| d0d7d26671 | |||
| 1b00f503c8 | |||
| a1a11c10e2 | |||
| eec45cac71 | |||
| 5266872c2b | |||
| 85c0736c8d | |||
| 0ea241c5db | |||
| 9942d94c94 | |||
| ef2531c63b | |||
| d53557dbb6 | |||
| 4add849b9e | |||
| 2f51cd07ff | |||
| 344b4375f9 | |||
| 083b247329 | |||
| b662a85348 |
9
.gitmodules
vendored
9
.gitmodules
vendored
@@ -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
|
||||||
|
|||||||
5
.vscode/c_cpp_properties.json
vendored
5
.vscode/c_cpp_properties.json
vendored
@@ -5,9 +5,12 @@
|
|||||||
"defines": [],
|
"defines": [],
|
||||||
"includePath": [
|
"includePath": [
|
||||||
"src",
|
"src",
|
||||||
|
"include",
|
||||||
"dependencies/BearSSL/inc",
|
"dependencies/BearSSL/inc",
|
||||||
"dependencies/BearSSL/src",
|
|
||||||
"dependencies/tlibc/include",
|
"dependencies/tlibc/include",
|
||||||
|
"dependencies/tlibtoml/include",
|
||||||
|
"dependencies/tsqlite/include",
|
||||||
|
"dependencies/tim/include",
|
||||||
"${default}"
|
"${default}"
|
||||||
],
|
],
|
||||||
"cStandard": "c99"
|
"cStandard": "c99"
|
||||||
|
|||||||
76
.vscode/launch.json
vendored
76
.vscode/launch.json
vendored
@@ -2,29 +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" },
|
|
||||||
"args": [ "-l", "127.0.0.1:9988" ],
|
|
||||||
"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
35
README.md
Normal 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
|
||||||
|
```
|
||||||
8
dependencies/bearssl.config
vendored
8
dependencies/bearssl.config
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
DEP_WORKING_DIR="dependencies/BearSSL"
|
DEP_WORKING_DIR="$DEPENDENCIES_DIR"
|
||||||
|
|
||||||
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
|
||||||
@@ -10,10 +10,10 @@ fi
|
|||||||
|
|
||||||
DEP_PRE_BUILD_COMMAND=""
|
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="mv -v cbuild.log ../bin/cbuild_bearssl.log"
|
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=""
|
||||||
PRESERVE_OUT_DIRECTORY_STRUCTURE=false
|
PRESERVE_OUT_DIRECTORY_STRUCTURE=false
|
||||||
|
|||||||
134
dependencies/bearssl.project.config
vendored
134
dependencies/bearssl.project.config
vendored
@@ -1,20 +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="$(find src -name '*.c')"
|
SRC_C="$(find BearSSL/src -name '*.c')"
|
||||||
#SRC_CPP="$(find src -name '*.cpp')"
|
SRC_CPP="$(find BearSSL/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=''
|
||||||
|
|
||||||
@@ -23,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
|
||||||
@@ -35,13 +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=""
|
||||||
DEFINE=""
|
INCLUDE="$INCLUDE "
|
||||||
;;
|
;;
|
||||||
LINUX)
|
LINUX)
|
||||||
EXEC_FILE="$PROJECT"
|
EXEC_FILE="$PROJECT"
|
||||||
SHARED_LIB_FILE="$PROJECT.so"
|
SHARED_LIB_FILE="$PROJECT.so"
|
||||||
LINKER_LIBS=""
|
LINKER_LIBS=""
|
||||||
DEFINE="-DBR_USE_GETENTROPY=0"
|
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"
|
||||||
@@ -58,64 +77,64 @@ case "$TASK" in
|
|||||||
# -fprofile-use enables compiler to use profiling info files to optimize executable
|
# -fprofile-use enables compiler to use profiling info files to optimize executable
|
||||||
# -fprofile-prefix-path sets path where profiling info about objects are be saved
|
# -fprofile-prefix-path sets path where profiling info about objects are be saved
|
||||||
# -fdata-sections -ffunction-sections -Wl,--gc-sections removes unused code
|
# -fdata-sections -ffunction-sections -Wl,--gc-sections removes unused code
|
||||||
C_ARGS="$DEFINE -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="$DEFINE -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="$DEFINE -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="$DEFINE -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="$DEFINE -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="$DEFINE -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)
|
||||||
@@ -126,25 +145,28 @@ 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="$DEFINE -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="$DEFINE -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)
|
||||||
@@ -153,32 +175,32 @@ case "$TASK" in
|
|||||||
callgrind)
|
callgrind)
|
||||||
OUTDIR="$OUTDIR/callgrind"
|
OUTDIR="$OUTDIR/callgrind"
|
||||||
# -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)
|
||||||
C_ARGS="$DEFINE -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)
|
||||||
OUTDIR="$OUTDIR/sanitize"
|
OUTDIR="$OUTDIR/sanitize"
|
||||||
C_ARGS="$DEFINE -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)
|
||||||
|
|||||||
11
dependencies/bearssl.project.config.user.default
vendored
Normal file
11
dependencies/bearssl.project.config.user.default
vendored
Normal 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
1
dependencies/tim
vendored
Submodule
Submodule dependencies/tim added at ee6375f553
19
dependencies/tim.config
vendored
Normal file
19
dependencies/tim.config
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This is a dependency config.
|
||||||
|
# You can copy it to another cbuild project to add this lib as dependency.
|
||||||
|
|
||||||
|
DEP_WORKING_DIR="$DEPENDENCIES_DIR/tim"
|
||||||
|
if [[ "$TASK" = *_dbg ]]; then
|
||||||
|
dep_build_target="build_static_lib_dbg"
|
||||||
|
else
|
||||||
|
dep_build_target="build_static_lib"
|
||||||
|
fi
|
||||||
|
DEP_PRE_BUILD_COMMAND=""
|
||||||
|
DEP_BUILD_COMMAND="cbuild $dep_build_target"
|
||||||
|
DEP_POST_BUILD_COMMAND=""
|
||||||
|
DEP_CLEAN_COMMAND="cbuild clean"
|
||||||
|
DEP_DYNAMIC_OUT_FILES=""
|
||||||
|
DEP_STATIC_OUT_FILES="bin/tim.a"
|
||||||
|
DEP_OTHER_OUT_FILES=""
|
||||||
|
PRESERVE_OUT_DIRECTORY_STRUCTURE=false
|
||||||
2
dependencies/tlibc
vendored
2
dependencies/tlibc
vendored
Submodule dependencies/tlibc updated: 2c8e6fc601...82a6293f21
7
dependencies/tlibc.config
vendored
7
dependencies/tlibc.config
vendored
@@ -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
1
dependencies/tlibtoml
vendored
Submodule
Submodule dependencies/tlibtoml added at 5cb121d1de
30
dependencies/tlibtoml.config
vendored
Normal file
30
dependencies/tlibtoml.config
vendored
Normal 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
1
dependencies/tsqlite
vendored
Submodule
Submodule dependencies/tsqlite added at 4b15db7c1f
30
dependencies/tsqlite.config
vendored
Normal file
30
dependencies/tsqlite.config
vendored
Normal 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
135
include/tcp-chat.h
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "tlibc/errors.h"
|
||||||
|
#include "tlibc/time.h"
|
||||||
|
#include "tlibc/magic.h"
|
||||||
|
|
||||||
|
/// requires tlibc and tlibtoml init
|
||||||
|
Result(void) TcpChat_init();
|
||||||
|
void TcpChat_deinit();
|
||||||
|
|
||||||
|
#define USERNAME_SIZE_MIN 2
|
||||||
|
#define USERNAME_SIZE_MAX 31
|
||||||
|
#define PASSWORD_SIZE_MIN 8
|
||||||
|
#define PASSWORD_SIZE_MAX 31
|
||||||
|
#define PASSWORD_HASH_SIZE 32
|
||||||
|
#define HOSTADDR_SIZE_MIN 4
|
||||||
|
#define HOSTADDR_SIZE_MAX 255
|
||||||
|
#define PRIVATE_KEY_BASE64_SIZE_MAX 1724
|
||||||
|
#define PUBLIC_KEY_BASE64_SIZE_MAX 699
|
||||||
|
#define SERVER_NAME_SIZE_MIN 1
|
||||||
|
#define SERVER_NAME_SIZE_MAX 127
|
||||||
|
#define SERVER_DESC_SIZE_MAX 1023
|
||||||
|
#define CHANNEL_NAME_SIZE_MIN 1
|
||||||
|
#define CHANNEL_NAME_SIZE_MAX 127
|
||||||
|
#define CHANNEL_DESC_SIZE_MAX 1023
|
||||||
|
#define MESSAGE_SIZE_MIN 1
|
||||||
|
#define MESSAGE_SIZE_MAX 4000
|
||||||
|
#define MESSAGE_BLOCK_COUNT_MAX 50
|
||||||
|
|
||||||
|
#define MESSAGE_TIMESTAMP_FMT_SQL "%Y.%m.%d-%H:%M:%f"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Logging //
|
||||||
|
// //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ErrorCodePage_declare(WINSOCK2);
|
||||||
|
ErrorCodePage_declare(TcpChat);
|
||||||
|
|
||||||
|
typedef enum TcpChatError {
|
||||||
|
TcpChatError_Unknown,
|
||||||
|
TcpChatError_RejectIncoming,
|
||||||
|
} TcpChatError;
|
||||||
|
|
||||||
|
typedef enum LogSeverity {
|
||||||
|
LogSeverity_Debug,
|
||||||
|
LogSeverity_Info,
|
||||||
|
LogSeverity_Warn,
|
||||||
|
LogSeverity_Error,
|
||||||
|
} LogSeverity;
|
||||||
|
|
||||||
|
typedef void (*LogFunction_t)(void* logger, cstr context, LogSeverity severity, cstr msg);
|
||||||
|
|
||||||
|
// requires defined LOGGER, LOG_FUNC, LOG_CONTEXT
|
||||||
|
#define log(severity, format, ...) { \
|
||||||
|
if(LOG_FUNC) { \
|
||||||
|
char* ___log_msg = sprintf_malloc(format ,##__VA_ARGS__); \
|
||||||
|
LOG_FUNC(LOGGER, LOG_CONTEXT, severity, ___log_msg); \
|
||||||
|
free(___log_msg); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define logDebug(format, ...) log(LogSeverity_Debug, format ,##__VA_ARGS__)
|
||||||
|
#define logInfo(format, ...) log(LogSeverity_Info, format ,##__VA_ARGS__)
|
||||||
|
#define logWarn(format, ...) log(LogSeverity_Warn, format ,##__VA_ARGS__)
|
||||||
|
#define logError(format, ...) log(LogSeverity_Error, format ,##__VA_ARGS__)
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Server //
|
||||||
|
// //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Server Server;
|
||||||
|
|
||||||
|
/// @param config_file_content config in toml format
|
||||||
|
/// @param config_file_name to use in error messages
|
||||||
|
/// @param logger some shared data for your log function
|
||||||
|
/// @param log_func log function that you have to implement
|
||||||
|
/// @return
|
||||||
|
Result(Server*) Server_create(str config_file_content, cstr config_file_name,
|
||||||
|
void* logger, LogFunction_t log_func);
|
||||||
|
|
||||||
|
void Server_free(Server* server);
|
||||||
|
|
||||||
|
Result(void) Server_run(Server* server);
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// //
|
||||||
|
// Client //
|
||||||
|
// //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Client Client;
|
||||||
|
|
||||||
|
Result(Client*) Client_create(str username, str password);
|
||||||
|
void Client_free(Client* client);
|
||||||
|
|
||||||
|
/// @return username saved during client initialization
|
||||||
|
str Client_getUserName(Client* client);
|
||||||
|
|
||||||
|
/// @return AES key calculated from password that can be used to encrypt user data
|
||||||
|
Array(u8) Client_getUserDataKey(Client* client);
|
||||||
|
|
||||||
|
/// @param server_addr_cstr ip:port
|
||||||
|
/// @param server_pk_base64 public key encoded by `RSA_serializePublicKey_base64()`
|
||||||
|
Result(void) Client_connect(Client* client, cstr server_addr_cstr, cstr server_pk_base64);
|
||||||
|
/// disconnect from current server
|
||||||
|
void Client_disconnect(Client* client);
|
||||||
|
|
||||||
|
/// @param self connected client
|
||||||
|
/// @param out_str heap-allocated string
|
||||||
|
Result(void) Client_getServerName(Client* self, str* out_str);
|
||||||
|
|
||||||
|
/// @param self connected client
|
||||||
|
/// @param out_str heap-allocated string
|
||||||
|
Result(void) Client_getServerDescription(Client* self, str* out_str);
|
||||||
|
|
||||||
|
/// Create new account on connected server
|
||||||
|
Result(void) Client_register(Client* self, i64* out_user_id);
|
||||||
|
|
||||||
|
/// Authorize on connected server
|
||||||
|
Result(void) Client_login(Client* self, i64* out_user_id, i64* out_landing_channel_id);
|
||||||
|
|
||||||
|
/// @param out_timestamp timestamp received from server
|
||||||
|
/// @return message id received from server
|
||||||
|
Result(i64) Client_sendMessage(Client* self, i64 channel_id, Array(u8) content, DateTime* out_timestamp);
|
||||||
|
|
||||||
|
/// Receive a bunch of messages from the server to a client internal buffer
|
||||||
|
/// @return number of messages received
|
||||||
|
Result(u32) Client_receiveMessageBlock(Client* self, i64 channel_id, i64 first_message_id, u32 messages_count);
|
||||||
|
|
||||||
|
/// Read message saved in client internal buffer.
|
||||||
|
/// @return number of bytes written in dst_content
|
||||||
|
Result(u32) Client_popMessage(Client* self, Array(u8) dst_content, i64* message_id, i64* sender_id, DateTime* timestamp_utc);
|
||||||
107
project.config
107
project.config
@@ -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 -Werror=return-type -Werror=pointer-arith -Wno-unused-parameter"
|
WARN_C="-Wall -Wextra
|
||||||
WARN_CPP="-Wall -Wextra -Werror=return-type -Werror=pointer-arith -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="-Isrc -Idependencies/BearSSL/inc -Idependencies/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="-static -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="tasks/strip.sh"
|
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)
|
||||||
|
|||||||
11
project.config.user.default
Normal file
11
project.config.user.default
Normal 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"
|
||||||
66
src/cli/ClientCLI/ClientCLI.c
Normal file
66
src/cli/ClientCLI/ClientCLI.c
Normal 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;
|
||||||
|
}
|
||||||
95
src/cli/ClientCLI/ClientCLI.h
Normal file
95
src/cli/ClientCLI/ClientCLI.h
Normal 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);
|
||||||
98
src/cli/ClientCLI/db/SavedServer.c
Normal file
98
src/cli/ClientCLI/db/SavedServer.c
Normal 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;
|
||||||
|
}
|
||||||
81
src/cli/ClientCLI/db/client_db.c
Normal file
81
src/cli/ClientCLI/db/client_db.c
Normal 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);
|
||||||
|
}
|
||||||
38
src/cli/ClientCLI/db/client_db.h
Normal file
38
src/cli/ClientCLI/db/client_db.h
Normal 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);
|
||||||
17
src/cli/ClientCLI/db/client_db_internal.h
Normal file
17
src/cli/ClientCLI/db/client_db_internal.h
Normal 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;
|
||||||
302
src/cli/ClientCLI/main_screen.c
Normal file
302
src/cli/ClientCLI/main_screen.c
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
#include "tlibc/term.h"
|
||||||
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
#include "tim.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const str greeting_art = STR(
|
||||||
|
" ^,,^ ╱|\n"
|
||||||
|
" ( •·•) Meum! (o.o`7\n"
|
||||||
|
" / ` | Meum... |`˜ \\\n"
|
||||||
|
"\\(_,J J L l`,)/\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
static const str farewell_art = STR(
|
||||||
|
" ^,,^ ╱|\n"
|
||||||
|
" ( -.-) (>,<`7\n"
|
||||||
|
" / ` | Goodbye! |`˜ \\\n"
|
||||||
|
"\\(_,J J L l`,)/\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
static void draw_test_label(void* data, TimRect place, bool is_selected);
|
||||||
|
static void draw_central_panel(void* data, TimRect place);
|
||||||
|
static Result(SavedServer*) joinNewServer(ClientCLI* self);
|
||||||
|
static Result(SavedServer*) selectServerFromCache(ClientCLI* self);
|
||||||
|
static Result(void) showSavedServer(ClientCLI* self, SavedServer* server);
|
||||||
|
static Result(void) registerAtServer(ClientCLI* self);
|
||||||
|
static Result(void) loginAtServer(ClientCLI* self);
|
||||||
|
|
||||||
|
|
||||||
|
void MainScreenContext_construct(MainScreenContext* ctx, ClientCLI* client){
|
||||||
|
zeroStruct(ctx);
|
||||||
|
ctx->client = client;
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_panel //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List(TimPanelItem) items = List_TimPanelItem_alloc(4);
|
||||||
|
|
||||||
|
TimPanelItem item_test_label = {
|
||||||
|
.w = A, .h = 12, .data = ctx, .draw = draw_test_label
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
List_TimPanelItem_push(&items, item_test_label);
|
||||||
|
|
||||||
|
ctx->central_panel.items = items.data;
|
||||||
|
ctx->central_panel.len = items.len;
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_scroll_view //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
ctx->central_scroll_view.content_h = 50;
|
||||||
|
ctx->central_scroll_view.data = ctx;
|
||||||
|
ctx->central_scroll_view.draw = draw_central_panel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainScreenContext_destroy(MainScreenContext* ctx){
|
||||||
|
free(ctx->central_panel.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_screen(MainScreenContext* ctx){
|
||||||
|
if (tim->event.type == TimEvent_Draw) {
|
||||||
|
tim_fill(tim_cell(" ", ctx->client->style.common.fg, ctx->client->style.common.bg), 0, 0, A, A);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tim_button_noborder("[Esc/Q] Exit", 1, 0, 14, 1, ctx->client->style.common)
|
||||||
|
|| tim_is_key_press('q')
|
||||||
|
|| tim_is_key_press(TimKey_Escape))
|
||||||
|
{
|
||||||
|
ctx->client->state = ClientCLIState_Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
tim_scroll_view(&ctx->central_scroll_view, 0, 1, ~0, ~0, ctx->client->style.common);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_test_label(void* data, TimRect place, bool is_selected){
|
||||||
|
MainScreenContext* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
|
tim_frame(place.x, place.y, place.w, place.h, style);
|
||||||
|
tim_label("0\n1\n2\n3\n4\n5\n6\n7\n8\n9",
|
||||||
|
place.x + 1, place.y + 1, place.w - 2, place.h - 2, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_central_panel(void* data, TimRect place){
|
||||||
|
MainScreenContext* ctx = data;
|
||||||
|
tim_panel(&ctx->central_panel, false, place.x, place.y, place.w, place.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop){
|
||||||
|
Deferral(64);
|
||||||
|
|
||||||
|
if(is_alias("q") || is_alias("quit") || is_alias("exit")){
|
||||||
|
printf(FMT_str"\n", farewell_art.len, farewell_art.data);
|
||||||
|
*stop = true;
|
||||||
|
}
|
||||||
|
else if(is_alias("clear")){
|
||||||
|
term_clear();
|
||||||
|
}
|
||||||
|
else if(is_alias("h") || is_alias("help")){
|
||||||
|
printf(
|
||||||
|
"COMMANDS:\n"
|
||||||
|
"Without connection:\n"
|
||||||
|
" h, help Show this message.\n"
|
||||||
|
" q, quit, exit Close the program.\n"
|
||||||
|
" clear Clear the screen.\n"
|
||||||
|
"Connection:\n"
|
||||||
|
" j, join Join a new server and select it.\n"
|
||||||
|
" s, select Select a server you joined before.\n"
|
||||||
|
"After connection:\n"
|
||||||
|
" r, register Create account on selected server\n"
|
||||||
|
" l, login Authorize on selected server\n"
|
||||||
|
"Authorized:\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (is_alias("j") || is_alias("join")){
|
||||||
|
// ask address and key, connect to server
|
||||||
|
try_void(joinNewServer(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("s") || is_alias("select")){
|
||||||
|
// show scrollable list of servers, get selected one
|
||||||
|
try_void(selectServerFromCache(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("r") || is_alias("register")){
|
||||||
|
try_void(registerAtServer(self));
|
||||||
|
}
|
||||||
|
else if(is_alias("l") || is_alias("login")){
|
||||||
|
try_void(loginAtServer(self));
|
||||||
|
// TODO: call Client_runIO():
|
||||||
|
// function with infinite loop which sends and receives messages
|
||||||
|
// with navigation across server channels
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("ERROR: unknown command.\n"
|
||||||
|
"Use 'h' to see list of avaliable commands\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static Result(void) joinNewServer(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
// ask server address
|
||||||
|
const u32 address_alloc_size = HOSTADDR_SIZE_MAX + 1;
|
||||||
|
str address = str_construct((char*)malloc(address_alloc_size), address_alloc_size, true);
|
||||||
|
Defer(if(!success) str_destroy(address));
|
||||||
|
printf("Enter server address (ip:port):\n");
|
||||||
|
try_void(term_readLine(address.data, address.len));
|
||||||
|
address.len = strlen(address.data);
|
||||||
|
str_trim(&address, true);
|
||||||
|
|
||||||
|
// ask server public key
|
||||||
|
const u32 server_pk_alloc_size = PUBLIC_KEY_BASE64_SIZE_MAX + 1;
|
||||||
|
str server_pk = str_construct((char*)malloc(server_pk_alloc_size), server_pk_alloc_size, true);
|
||||||
|
Defer(if(!success) str_destroy(server_pk));
|
||||||
|
printf("Enter server public key (RSA-Public-<SIZE>:<DATA>):\n");
|
||||||
|
try_void(term_readLine(server_pk.data, server_pk.len));
|
||||||
|
server_pk.len = strlen(server_pk.data);
|
||||||
|
str_trim(&server_pk, true);
|
||||||
|
|
||||||
|
printf("Connecting to server...\n");
|
||||||
|
try_void(Client_connect(self->client, address.data, server_pk.data));
|
||||||
|
printf("Connection established\n");
|
||||||
|
|
||||||
|
str server_name = str_null;
|
||||||
|
try_void(Client_getServerName(self->client, &server_name));
|
||||||
|
Defer(if(!success) str_destroy(server_name));
|
||||||
|
str server_description = str_null;
|
||||||
|
try_void(Client_getServerDescription(self->client, &server_description));
|
||||||
|
Defer(if(!success) str_destroy(server_description));
|
||||||
|
|
||||||
|
SavedServer server = SavedServer_construct(
|
||||||
|
address,
|
||||||
|
server_pk,
|
||||||
|
server_name,
|
||||||
|
server_description
|
||||||
|
);
|
||||||
|
try_void(SavedServer_createOrUpdate(self->queries, &server));
|
||||||
|
List_SavedServer_pushMany(&self->saved_servers, &server, 1);
|
||||||
|
|
||||||
|
try_void(showSavedServer(self, &server));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) selectServerFromCache(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
u32 servers_count = self->saved_servers.len;
|
||||||
|
if(servers_count == 0){
|
||||||
|
printf("No saved servers found\n");
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u32 i = 0; i < servers_count; i++){
|
||||||
|
SavedServer* server = &self->saved_servers.data[i];
|
||||||
|
printf("[%02u] "FMT_str" "FMT_str"\n",
|
||||||
|
i, str_unwrap(server->address), str_unwrap(server->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[32];
|
||||||
|
u32 selected_i = -1;
|
||||||
|
while(true) {
|
||||||
|
printf("Type 'q' to cancel\n");
|
||||||
|
printf("Select server number: ");
|
||||||
|
try_void(term_readLine(buf, sizeof(buf)));
|
||||||
|
str input_line = str_from_cstr(buf);
|
||||||
|
str_trim(&input_line, true);
|
||||||
|
if(str_equals(input_line, STR("q"))){
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
if(sscanf(buf, FMT_u32, &selected_i) != 1){
|
||||||
|
printf("ERROR: not a number\n");
|
||||||
|
}
|
||||||
|
else if(selected_i >= servers_count){
|
||||||
|
printf("ERROR: not a server number\n");
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
SavedServer* selected_server = &self->saved_servers.data[selected_i];
|
||||||
|
|
||||||
|
printf("Connecting to '"FMT_str"'...\n", str_unwrap(selected_server->address));
|
||||||
|
try_void(Client_connect(self->client, selected_server->address.data, selected_server->pk_base64.data));
|
||||||
|
printf("Connection established\n");
|
||||||
|
|
||||||
|
// update server name
|
||||||
|
bool server_info_changed = false;
|
||||||
|
str updated_server_name = str_null;
|
||||||
|
try_void(Client_getServerName(self->client, &updated_server_name));
|
||||||
|
Defer(if(!success) str_destroy(updated_server_name));
|
||||||
|
if(!str_equals(updated_server_name, selected_server->name)){
|
||||||
|
server_info_changed = true;
|
||||||
|
selected_server->name = updated_server_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update server description
|
||||||
|
str updated_server_description = str_null;
|
||||||
|
try_void(Client_getServerDescription(self->client, &updated_server_description));
|
||||||
|
Defer(if(!success) str_destroy(updated_server_description));
|
||||||
|
if(!str_equals(updated_server_description, selected_server->description)){
|
||||||
|
server_info_changed = true;
|
||||||
|
selected_server->description = updated_server_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(server_info_changed){
|
||||||
|
try_void(SavedServer_createOrUpdate(self->queries, selected_server));
|
||||||
|
}
|
||||||
|
|
||||||
|
try_void(showSavedServer(self, selected_server));
|
||||||
|
|
||||||
|
success = true;
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) showSavedServer(ClientCLI* self, SavedServer* server){
|
||||||
|
Deferral(8);
|
||||||
|
(void)self;
|
||||||
|
|
||||||
|
printf("Server Name: "FMT_str"\n", str_unwrap(server->name));
|
||||||
|
printf("Host Address: "FMT_str"\n", str_unwrap(server->address));
|
||||||
|
printf("Description:\n"FMT_str"\n\n", str_unwrap(server->description));
|
||||||
|
printf("Public Key:\n" FMT_str"\n\n", str_unwrap(server->pk_base64));
|
||||||
|
printf("Type 'register' if you don't have an account on the server.\n");
|
||||||
|
printf("Type 'login' to authorize on the server.\n");
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) registerAtServer(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
i64 user_id = 0;
|
||||||
|
try_void(Client_register(self->client, &user_id));
|
||||||
|
printf("Registered successfully\n");
|
||||||
|
printf("user_id: "FMT_i64"\n", user_id);
|
||||||
|
try_assert(user_id > 0);
|
||||||
|
// TODO: use user_id somewhere
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) loginAtServer(ClientCLI* self){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
i64 user_id = 0, landing_channel_id = 0;
|
||||||
|
try_void(Client_login(self->client, &user_id, &landing_channel_id));
|
||||||
|
printf("Authorized successfully\n");
|
||||||
|
printf("user_id: "FMT_i64", landing_channel_id: "FMT_i64"\n", user_id, landing_channel_id);
|
||||||
|
try_assert(user_id > 0);
|
||||||
|
// TODO: use user_id, landing_channel_id somewhere
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
195
src/cli/ClientCLI/start_screen.c
Normal file
195
src/cli/ClientCLI/start_screen.c
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
#include "network/tcp-chat-protocol/v1.h"
|
||||||
|
#include "tlibc/filesystem.h"
|
||||||
|
|
||||||
|
static void draw_central_buttons_panel(void* data, TimRect place, bool is_selected);
|
||||||
|
static void draw_start_button(void* data, TimRect place, bool is_selected);
|
||||||
|
static void draw_exit_button(void* data, TimRect place, bool is_selected);
|
||||||
|
static Result(void) openUserDB(StartScreenContext* ctx);
|
||||||
|
|
||||||
|
|
||||||
|
#define handleError(R) _handleError(ctx, R)
|
||||||
|
static void _handleError(StartScreenContext* ctx, ResultVar(void) r){
|
||||||
|
free(ctx->err_msg);
|
||||||
|
ctx->err_msg = Error_toStr(r.error).data;
|
||||||
|
Error_free(r.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearError(StartScreenContext* ctx){
|
||||||
|
free(ctx->err_msg);
|
||||||
|
ctx->err_msg = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScreenContext_construct(StartScreenContext* ctx, ClientCLI* client){
|
||||||
|
zeroStruct(ctx);
|
||||||
|
ctx->client = client;
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// input_username //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1);
|
||||||
|
TextInputState_construct(&ctx->input_username, "[username]",
|
||||||
|
username_buf, NULL, false,
|
||||||
|
ctx->client->style.common, ctx->client->style.focused);
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// input_password //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1);
|
||||||
|
TextInputState_construct(&ctx->input_password, "[password]",
|
||||||
|
password_buf, NULL, true,
|
||||||
|
ctx->client->style.common, ctx->client->style.focused);
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_panel //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List(TimPanelItem) items = List_TimPanelItem_alloc(4);
|
||||||
|
|
||||||
|
TimPanelItem item_username_input;
|
||||||
|
TimPanelItem_fromTextInputState(&item_username_input, &ctx->input_username);
|
||||||
|
List_TimPanelItem_push(&items, item_username_input);
|
||||||
|
|
||||||
|
TimPanelItem item_password_input;
|
||||||
|
TimPanelItem_fromTextInputState(&item_password_input, &ctx->input_password);
|
||||||
|
List_TimPanelItem_push(&items, item_password_input);
|
||||||
|
|
||||||
|
TimPanelItem item_central_buttons_panel = {
|
||||||
|
.w = A, .h = 3, .data = ctx, .draw = draw_central_buttons_panel
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_central_buttons_panel);
|
||||||
|
|
||||||
|
ctx->central_panel.items = items.data;
|
||||||
|
ctx->central_panel.len = items.len;
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// central_buttons_panel //
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
List(TimPanelItem) items = List_TimPanelItem_alloc(4);
|
||||||
|
|
||||||
|
TimPanelItem item_start_button = {
|
||||||
|
.w = A, .h = 3, .data = ctx, .draw = draw_start_button
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_start_button);
|
||||||
|
|
||||||
|
TimPanelItem item_exit_button = {
|
||||||
|
.w = A, .h = 3, .data = ctx, .draw = draw_exit_button
|
||||||
|
};
|
||||||
|
List_TimPanelItem_push(&items, item_exit_button);
|
||||||
|
|
||||||
|
ctx->central_buttons_panel.items = items.data;
|
||||||
|
ctx->central_buttons_panel.len = items.len;
|
||||||
|
ctx->central_buttons_panel.is_horizontal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScreenContext_destroy(StartScreenContext* ctx){
|
||||||
|
free(ctx->input_username.edit.s);
|
||||||
|
free(ctx->input_password.edit.s);
|
||||||
|
free(ctx->err_msg);
|
||||||
|
free(ctx->central_panel.items);
|
||||||
|
free(ctx->central_buttons_panel.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_screen(StartScreenContext* ctx)
|
||||||
|
{
|
||||||
|
if (tim->event.type == TimEvent_Draw) {
|
||||||
|
tim_fill(tim_cell(" ", ctx->client->style.common.fg, ctx->client->style.common.bg), 0, 0, A, A);
|
||||||
|
}
|
||||||
|
|
||||||
|
tim_frame(A, A, 40, 11, ctx->client->style.common);
|
||||||
|
tim_panel(&ctx->central_panel, true, A, A, 38, 9);
|
||||||
|
|
||||||
|
if(ctx->err_msg){
|
||||||
|
i32 below_list = tim->scopes[tim->scope].h/2 + 6;
|
||||||
|
tim_label("ERROR: ", A, below_list, A, A, ctx->client->style.error);
|
||||||
|
tim_label(ctx->err_msg, A, below_list + 1, A, A, ctx->client->style.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_central_buttons_panel(void* data, TimRect place, bool is_selected){
|
||||||
|
StartScreenContext* ctx = data;
|
||||||
|
tim_panel(&ctx->central_buttons_panel, is_selected, place.x, place.y, place.w, place.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_start_button(void* data, TimRect place, bool is_selected){
|
||||||
|
StartScreenContext* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
|
if(tim_button("[Enter] Start", place.x, place.y, place.w, A, style)
|
||||||
|
|| tim_is_key_press(TimKey_Enter))
|
||||||
|
{
|
||||||
|
clearError(ctx);
|
||||||
|
|
||||||
|
// check username
|
||||||
|
str username = str_from_cstr(ctx->input_username.edit.s);
|
||||||
|
str_trim(&username, true);
|
||||||
|
str name_error_str = validateUsername_str(username);
|
||||||
|
if(name_error_str.data){
|
||||||
|
ctx->err_msg = name_error_str.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check password
|
||||||
|
str password = str_from_cstr(ctx->input_password.edit.s);
|
||||||
|
str_trim(&password, true);
|
||||||
|
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
|
||||||
|
ctx->err_msg = sprintf_malloc(
|
||||||
|
"password length (in bytes) must be >= %i and <= %i",
|
||||||
|
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create client
|
||||||
|
try_handle(ctx->client->client, p, Client_create(username, password), handleError);
|
||||||
|
// init user DB
|
||||||
|
try_handle_void(openUserDB(ctx), handleError);
|
||||||
|
// erase password from memory
|
||||||
|
memset(ctx->input_password.edit.s, 0, ctx->input_password.edit.capacity);
|
||||||
|
// switch to next screen
|
||||||
|
ctx->client->state = ClientCLIState_MainScreen;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_exit_button(void* data, TimRect place, bool is_selected){
|
||||||
|
StartScreenContext* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->client->style.focused : ctx->client->style.common;
|
||||||
|
if(tim_button("[Esc/Q] Exit", place.x, place.y, place.w, A, style)
|
||||||
|
|| tim_is_key_press('q')
|
||||||
|
|| tim_is_key_press(TimKey_Escape)
|
||||||
|
|| ctx->input_username.result_key == TimKey_Escape
|
||||||
|
|| ctx->input_password.result_key == TimKey_Escape)
|
||||||
|
{
|
||||||
|
ctx->client->state = ClientCLIState_Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result(void) openUserDB(StartScreenContext* ctx){
|
||||||
|
Deferral(8);
|
||||||
|
|
||||||
|
str username = Client_getUserName(ctx->client->client);
|
||||||
|
// TODO: encrypt user database
|
||||||
|
// Array(u8) user_data_key = Client_getUserDataKey(ctx->client->client);
|
||||||
|
|
||||||
|
// build database file path
|
||||||
|
try(char* user_dir, p, path_getUserDir());
|
||||||
|
Defer(free(user_dir));
|
||||||
|
char* db_path = strcat_malloc(
|
||||||
|
user_dir,
|
||||||
|
path_seps".local"path_seps"tcp-chat-client"path_seps"user-db"path_seps,
|
||||||
|
username.data, ".sqlite"
|
||||||
|
);
|
||||||
|
Defer(free(db_path));
|
||||||
|
|
||||||
|
try(ctx->client->db, p, ClientDatabase_open(db_path));
|
||||||
|
try(ctx->client->queries, p, ClientQueries_compile(ctx->client->db));
|
||||||
|
|
||||||
|
// load whole servers table to list
|
||||||
|
try_void(SavedServer_getAll(ctx->client->queries, &ctx->client->saved_servers));
|
||||||
|
|
||||||
|
Return RESULT_VOID;
|
||||||
|
}
|
||||||
32
src/cli/ClientCLI/widgets.c
Normal file
32
src/cli/ClientCLI/widgets.c
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include "ClientCLI.h"
|
||||||
|
|
||||||
|
void TextInputState_construct(TextInputState* ctx, cstr label,
|
||||||
|
Array(char) buf, NULLABLE(cstr) initial_value, bool masked,
|
||||||
|
TimStyle common, TimStyle focused)
|
||||||
|
{
|
||||||
|
TimEditState_construct(&ctx->edit, buf.data, buf.len, initial_value);
|
||||||
|
ctx->edit.masked = masked;
|
||||||
|
ctx->label = label;
|
||||||
|
ctx->style.common = common;
|
||||||
|
ctx->style.focused = focused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void text_input(TextInputState* ctx, i32 x, i32 y, i32 w, TimStyle style){
|
||||||
|
ctx->result_key = tim_edit(&ctx->edit, x, y, w, style);
|
||||||
|
tim_label(ctx->label, x + 3, y, A, 1, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimPanelItem_fromTextInputState(TimPanelItem* item, TextInputState* input){
|
||||||
|
zeroStruct(item);
|
||||||
|
item->w = A;
|
||||||
|
item->h = 3;
|
||||||
|
item->data = input;
|
||||||
|
item->focus_target = &input->edit;
|
||||||
|
item->draw = draw_item_text_input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_item_text_input(void* data, TimRect place, bool is_selected){
|
||||||
|
TextInputState* ctx = data;
|
||||||
|
TimStyle style = is_selected ? ctx->style.focused : ctx->style.common;
|
||||||
|
text_input(ctx, place.x, place.y, place.w, style);
|
||||||
|
}
|
||||||
169
src/main.c → src/cli/main.c
Executable file → Normal file
169
src/main.c → src/cli/main.c
Executable file → Normal file
@@ -1,21 +1,23 @@
|
|||||||
#include "network/network.h"
|
#include "tcp-chat.h"
|
||||||
#include "client/client.h"
|
#include "tlibc/tlibc.h"
|
||||||
#include "server/server.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_CLIENT "tcp-chat-client.config"
|
||||||
#define _DEFAULT_CONFIG_PATH_SERVER "tcp-chat-server.config"
|
#define _DEFAULT_CONFIG_PATH_SERVER "tcp-chat-server.toml"
|
||||||
|
|
||||||
typedef enum ProgramMode {
|
|
||||||
ClientMode,
|
|
||||||
ServerMode,
|
|
||||||
RsaGenStdin,
|
|
||||||
RsaGenRandom,
|
|
||||||
} ProgramMode;
|
|
||||||
|
|
||||||
#define arg_is(LITERAL) str_equals(arg_str, STR(LITERAL))
|
#define arg_is(LITERAL) str_equals(arg_str, STR(LITERAL))
|
||||||
|
|
||||||
|
|
||||||
int main(const int argc, cstr const* argv){
|
int main(const int argc, cstr const* argv){
|
||||||
Deferral(32);
|
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){
|
if(br_prng_seeder_system(NULL) == NULL){
|
||||||
printfe("Can't get system random seeder. Bearssl is compiled incorrectly.");
|
printfe("Can't get system random seeder. Bearssl is compiled incorrectly.");
|
||||||
@@ -23,9 +25,8 @@ int main(const int argc, cstr const* argv){
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProgramMode mode = ClientMode;
|
ProgramMode mode = ClientMode;
|
||||||
cstr server_endpoint_cstr = NULL;
|
|
||||||
cstr config_path = NULL;
|
cstr config_path = NULL;
|
||||||
u32 key_size = 0;
|
u32 size_arg = 0;
|
||||||
|
|
||||||
for(int argi = 1; argi < argc; argi++){
|
for(int argi = 1; argi < argc; argi++){
|
||||||
str arg_str = str_from_cstr(argv[argi]);
|
str arg_str = str_from_cstr(argv[argi]);
|
||||||
@@ -34,7 +35,7 @@ int main(const int argc, cstr const* argv){
|
|||||||
"USAGE:\n"
|
"USAGE:\n"
|
||||||
"no arguments Interactive client mode.\n"
|
"no arguments Interactive client mode.\n"
|
||||||
"-h, --help Show this message.\n"
|
"-h, --help Show this message.\n"
|
||||||
"-l, --listen [addr:port] Start server.\n"
|
"-l, --listen Start server.\n"
|
||||||
"--config [path] Load config from specified path.\n"
|
"--config [path] Load config from specified path.\n"
|
||||||
" Default path for config is '" _DEFAULT_CONFIG_PATH_CLIENT "' or '" _DEFAULT_CONFIG_PATH_SERVER "'\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"
|
"--rsa-gen-stdin [size] Generate RSA private and public keys based on stdin data (64Kb max).\n"
|
||||||
@@ -42,6 +43,10 @@ int main(const int argc, cstr const* argv){
|
|||||||
" Usage: `cat somefile | tcp-chat --gen-rsa-stdin`\n"
|
" Usage: `cat somefile | tcp-chat --gen-rsa-stdin`\n"
|
||||||
"--rsa-gen-random [size] Generate random RSA private and public keys.\n"
|
"--rsa-gen-random [size] Generate random RSA private and public keys.\n"
|
||||||
" size: 2048 / 3072 (default) / 4096\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;
|
Return 0;
|
||||||
}
|
}
|
||||||
@@ -50,13 +55,7 @@ int main(const int argc, cstr const* argv){
|
|||||||
printf("program mode is set already\n");
|
printf("program mode is set already\n");
|
||||||
Return 1;
|
Return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = ServerMode;
|
mode = ServerMode;
|
||||||
if(++argi >= argc){
|
|
||||||
printfe("ERROR: no endpoint specified\n");
|
|
||||||
Return 1;
|
|
||||||
}
|
|
||||||
server_endpoint_cstr = argv[argi];
|
|
||||||
}
|
}
|
||||||
else if(arg_is("--config")){
|
else if(arg_is("--config")){
|
||||||
if(++argi >= argc){
|
if(++argi >= argc){
|
||||||
@@ -73,9 +72,9 @@ int main(const int argc, cstr const* argv){
|
|||||||
|
|
||||||
mode = RsaGenStdin;
|
mode = RsaGenStdin;
|
||||||
if(++argi >= argc){
|
if(++argi >= argc){
|
||||||
key_size = RSA_DEFAULT_KEY_SIZE;
|
size_arg = RSA_DEFAULT_KEY_SIZE;
|
||||||
}
|
}
|
||||||
else if(sscanf(argv[argi], "%u", &key_size) != 1){
|
else if(sscanf(argv[argi], "%u", &size_arg) != 1){
|
||||||
printfe("ERROR: no key size specified\n");
|
printfe("ERROR: no key size specified\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,12 +87,40 @@ int main(const int argc, cstr const* argv){
|
|||||||
|
|
||||||
mode = RsaGenRandom;
|
mode = RsaGenRandom;
|
||||||
if(++argi >= argc){
|
if(++argi >= argc){
|
||||||
key_size = RSA_DEFAULT_KEY_SIZE;
|
size_arg = RSA_DEFAULT_KEY_SIZE;
|
||||||
}
|
}
|
||||||
else if(sscanf(argv[argi], "%u", &key_size) != 1){
|
else if(sscanf(argv[argi], "%u", &size_arg) != 1){
|
||||||
printfe("ERROR: no key size specified\n");
|
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 {
|
else {
|
||||||
printfe("ERROR: unknown argument '%s'\n"
|
printfe("ERROR: unknown argument '%s'\n"
|
||||||
"Use '-h' to see list of avaliable arguments\n",
|
"Use '-h' to see list of avaliable arguments\n",
|
||||||
@@ -101,83 +128,33 @@ int main(const int argc, cstr const* argv){
|
|||||||
Return 1;
|
Return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try_fatal_void(network_init());
|
|
||||||
Defer(network_deinit());
|
|
||||||
|
|
||||||
switch(mode){
|
switch(mode){
|
||||||
case ClientMode: {
|
|
||||||
if(!config_path)
|
|
||||||
config_path = _DEFAULT_CONFIG_PATH_CLIENT;
|
|
||||||
|
|
||||||
try_fatal(Client* client, p, Client_createFromConfig(config_path));
|
|
||||||
Defer(Client_free(client));
|
|
||||||
try_fatal_void(Client_run(client));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ServerMode: {
|
|
||||||
if(!config_path)
|
|
||||||
config_path = _DEFAULT_CONFIG_PATH_SERVER;
|
|
||||||
|
|
||||||
try_fatal(Server* server, p, Server_createFromConfig(config_path));
|
|
||||||
Defer(Server_free(server));
|
|
||||||
try_fatal_void(Server_run(server, server_endpoint_cstr));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case RsaGenStdin: {
|
|
||||||
size_t input_max_size = 64*1024;
|
|
||||||
char* input_buf = malloc(input_max_size);
|
|
||||||
Defer(free(input_buf));
|
|
||||||
size_t read_n = fread(input_buf, 1, input_max_size, stdin);
|
|
||||||
if(read_n == 0){
|
|
||||||
printfe("ERROR: no input\n");
|
|
||||||
Return 1;
|
|
||||||
}
|
|
||||||
str input_str = str_construct(input_buf, read_n, false);
|
|
||||||
printfe("generating RSA key pair based on stdin...\n");
|
|
||||||
br_rsa_private_key sk;
|
|
||||||
br_rsa_public_key pk;
|
|
||||||
try_fatal_void(RSA_generateKeyPairFromPassword(key_size, &sk, &pk, input_str));
|
|
||||||
Defer(
|
|
||||||
RSA_destroyPrivateKey(&sk);
|
|
||||||
RSA_destroyPublicKey(&pk);
|
|
||||||
);
|
|
||||||
|
|
||||||
str sk_str = RSA_serializePrivateKey_base64(&sk);
|
|
||||||
printf("rsa_private_key = %s\n", sk_str.data);
|
|
||||||
free(sk_str.data);
|
|
||||||
|
|
||||||
str pk_str = RSA_serializePublicKey_base64(&pk);
|
|
||||||
printf("\nrsa_public_key = %s\n", pk_str.data);
|
|
||||||
free(pk_str.data);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RsaGenRandom: {
|
|
||||||
printfe("generating random RSA key pair...\n");
|
|
||||||
br_rsa_private_key sk;
|
|
||||||
br_rsa_public_key pk;
|
|
||||||
try_fatal_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);
|
|
||||||
free(sk_str.data);
|
|
||||||
|
|
||||||
str pk_str = RSA_serializePublicKey_base64(&pk);
|
|
||||||
printf("\nrsa_public_key = %s\n", pk_str.data);
|
|
||||||
free(pk_str.data);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
printfe("ERROR: invalid program mode %i\n", mode);
|
printfe("ERROR: invalid program mode %i\n", mode);
|
||||||
Return 1;
|
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;
|
Return 0;
|
||||||
15
src/cli/modes/ClientMode.c
Normal file
15
src/cli/modes/ClientMode.c
Normal 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;
|
||||||
|
}
|
||||||
49
src/cli/modes/RandomBytes.c
Normal file
49
src/cli/modes/RandomBytes.c
Normal 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
64
src/cli/modes/RsaGen.c
Normal 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;
|
||||||
|
}
|
||||||
98
src/cli/modes/ServerMode.c
Normal file
98
src/cli/modes/ServerMode.c
Normal 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
19
src/cli/modes/modes.h
Normal 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);
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "client.h"
|
|
||||||
#include "tlibc/string/StringBuilder.h"
|
|
||||||
|
|
||||||
void ClientCredentials_destroy(ClientCredentials* cred){
|
|
||||||
if(!cred)
|
|
||||||
return;
|
|
||||||
free(cred->username.data);
|
|
||||||
free(cred->user_data_key.data);
|
|
||||||
free(cred->token.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Result(void) ClientCredentials_tryConstruct(ClientCredentials* cred,
|
|
||||||
str username, str password)
|
|
||||||
{
|
|
||||||
Deferral(8);
|
|
||||||
|
|
||||||
memset(cred, 0, sizeof(ClientCredentials));
|
|
||||||
bool success = false;
|
|
||||||
Defer(if(!success) ClientCredentials_destroy(cred));
|
|
||||||
|
|
||||||
cred->username = str_copy(username);
|
|
||||||
|
|
||||||
// concat password and username
|
|
||||||
StringBuilder sb = StringBuilder_alloc(username.size + password.size + 1);
|
|
||||||
Defer(StringBuilder_destroy(&sb));
|
|
||||||
StringBuilder_append_str(&sb, password);
|
|
||||||
StringBuilder_append_str(&sb, username);
|
|
||||||
Array(u8) password_and_username = str_castTo_Array(StringBuilder_getStr(&sb));
|
|
||||||
|
|
||||||
// lvl 1 hash - is used as AES key for user data
|
|
||||||
cred->user_data_key = Array_alloc(u8, PASSWORD_HASH_SIZE);
|
|
||||||
hash_password(password_and_username, cred->user_data_key.data, __PASSWORD_HASH_LVL_ITERATIONS);
|
|
||||||
// lvl 2 hash - is used for authentification
|
|
||||||
cred->token = Array_alloc(u8, PASSWORD_HASH_SIZE);
|
|
||||||
hash_password(cred->user_data_key, cred->token.data, __PASSWORD_HASH_LVL_ITERATIONS);
|
|
||||||
|
|
||||||
AESBlockEncryptor_construct(&cred->user_data_aes_enc, cred->user_data_key, AESBlockEncryptor_DEFAULT_CLASS);
|
|
||||||
AESBlockDecryptor_construct(&cred->user_data_aes_dec, cred->user_data_key, AESBlockDecryptor_DEFAULT_CLASS);
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
@@ -1,118 +1,119 @@
|
|||||||
#include "client.h"
|
#include "client_internal.h"
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
#include "requests/requests.h"
|
||||||
|
|
||||||
void ServerConnection_close(ServerConnection* conn){
|
void ServerConnection_close(ServerConnection* self){
|
||||||
if(!conn)
|
if(!self)
|
||||||
return;
|
return;
|
||||||
RSA_destroyPublicKey(&conn->server_pk);
|
RSA_destroyPublicKey(&self->server_pk);
|
||||||
EncryptedSocketTCP_destroy(&conn->sock);
|
EncryptedSocketTCP_destroy(&self->sock);
|
||||||
free(conn->session_key.data);
|
Array_u8_destroy(&self->token);
|
||||||
free(conn);
|
Array_u8_destroy(&self->session_key);
|
||||||
|
MessageBlock_destroy(&self->received_message_block);
|
||||||
|
free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief
|
Result(ServerConnection*) ServerConnection_open(Client* client, cstr server_addr_cstr, cstr server_pk_base64)
|
||||||
/// @param server_link_cstr address:port:public_key
|
{
|
||||||
/// @return
|
|
||||||
Result(void) ServerLink_parse(cstr server_link_cstr, EndpointIPv4* server_end_out, br_rsa_public_key* server_key_out){
|
|
||||||
Deferral(8);
|
|
||||||
str server_link_str = str_from_cstr(server_link_cstr);
|
|
||||||
|
|
||||||
// parse address and port
|
|
||||||
i32 sep_pos = str_seekChar(server_link_str, ':', 0);
|
|
||||||
if(sep_pos == -1){
|
|
||||||
Return RESULT_ERROR_FMT("server link is invalid: %s", server_link_cstr);
|
|
||||||
}
|
|
||||||
*server_end_out = EndpointIPv4_INVALID;
|
|
||||||
try_void(EndpointIPv4_parse(server_link_cstr, server_end_out));
|
|
||||||
if(EndpointIPv4_is_invalid(*server_end_out)){
|
|
||||||
Return RESULT_ERROR_FMT("server address or port is invalid: %s", server_link_cstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse public key
|
|
||||||
sep_pos = str_seekChar(server_link_str, ':', sep_pos + 1);
|
|
||||||
if(sep_pos == -1){
|
|
||||||
Return RESULT_ERROR_FMT("server link is invalid: %s", server_link_cstr);
|
|
||||||
}
|
|
||||||
str server_key_str = str_sliceAfter(server_link_str, sep_pos + 1);
|
|
||||||
char* server_key_cstr = str_copy(server_key_str).data;
|
|
||||||
Defer(free(server_key_cstr));
|
|
||||||
try_void(RSA_parsePublicKey_base64(server_key_cstr, server_key_out));
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(ServerConnection*) ServerConnection_open(ClientCredentials* client_credentials, cstr server_link_cstr){
|
|
||||||
Deferral(16);
|
Deferral(16);
|
||||||
|
|
||||||
ServerConnection* conn = (ServerConnection*)malloc(sizeof(ServerConnection));
|
ServerConnection* conn = (ServerConnection*)malloc(sizeof(ServerConnection));
|
||||||
memset(conn, 0, sizeof(*conn));
|
zeroStruct(conn);
|
||||||
bool success = false;
|
bool success = false;
|
||||||
Defer(if(!success) ServerConnection_close(conn));
|
Defer(if(!success) ServerConnection_close(conn));
|
||||||
|
|
||||||
try_void(ServerLink_parse(server_link_cstr, &conn->server_end, &conn->server_pk));
|
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);
|
RSAEncryptor_construct(&conn->rsa_enc, &conn->server_pk);
|
||||||
|
|
||||||
conn->session_key = Array_alloc_size(AES_SESSION_KEY_SIZE);
|
// lvl 2 hash - is used for authentification
|
||||||
// generate random session key
|
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 };
|
br_hmac_drbg_context key_rng = { .vtable = &br_hmac_drbg_vtable };
|
||||||
rng_init_sha256_seedFromSystem(&key_rng.vtable);
|
rng_init_sha256_seedFromSystem(&key_rng.vtable);
|
||||||
br_hmac_drbg_generate(&key_rng, conn->session_key.data, conn->session_key.size);
|
br_hmac_drbg_generate(&key_rng, conn->session_key.data, conn->session_key.len);
|
||||||
|
|
||||||
// connect to server address
|
// connect to server address
|
||||||
try(Socket _s, i, socket_open_TCP());
|
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_TCP_enableAliveChecks_default(_s));
|
||||||
try_void(socket_connect(_s, conn->server_end));
|
try_void(socket_connect(_s, conn->server_end));
|
||||||
EncryptedSocketTCP_construct(&conn->sock, _s, NETWORK_BUFFER_SIZE, conn->session_key);
|
EncryptedSocketTCP_construct(&conn->sock, _s, NETWORK_BUFFER_SIZE, conn->session_key);
|
||||||
|
|
||||||
// send PacketHeader and ClientHandshake
|
// send ClientHandshake using server public key for encryption
|
||||||
// encryption by server public key
|
PacketHeader req_header;
|
||||||
PacketHeader packet_header = {0};
|
ClientHandshake client_handshake;
|
||||||
ClientHandshake client_handshake = {0};
|
try_void(ClientHandshake_tryConstruct(&client_handshake, &req_header,
|
||||||
try_void(ClientHandshake_tryConstruct(&client_handshake, &packet_header,
|
|
||||||
conn->session_key));
|
conn->session_key));
|
||||||
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &packet_header));
|
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &req_header));
|
||||||
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &client_handshake));
|
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &client_handshake));
|
||||||
|
|
||||||
// receive server response
|
// receive server response
|
||||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &packet_header));
|
PacketHeader res_header;
|
||||||
try_void(PacketHeader_validateMagic(&packet_header));
|
ServerHandshake server_handshake;
|
||||||
|
try_void(recvResponse(&conn->sock, &res_header, &server_handshake,
|
||||||
// handle server response
|
PacketType_ServerHandshake));
|
||||||
switch(packet_header.type){
|
conn->session_id = server_handshake.session_id;
|
||||||
case PacketType_ErrorMessage: {
|
|
||||||
u32 err_msg_size = packet_header.content_size;
|
|
||||||
if(err_msg_size > conn->sock.recv_buf.size)
|
|
||||||
err_msg_size = conn->sock.recv_buf.size;
|
|
||||||
Array(u8) err_buf = Array_alloc_size(err_msg_size + 1);
|
|
||||||
bool err_msg_completed = false;
|
|
||||||
Defer(
|
|
||||||
if(!err_msg_completed)
|
|
||||||
free(err_buf.data);
|
|
||||||
);
|
|
||||||
|
|
||||||
// receive error message
|
MessageBlock_alloc(&conn->received_message_block);
|
||||||
try_void(
|
|
||||||
EncryptedSocketTCP_recv(
|
|
||||||
&conn->sock,
|
|
||||||
Array_sliceTo(err_buf, err_msg_size),
|
|
||||||
SocketRecvFlag_WholeBuffer
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
((u8*)err_buf.data)[err_msg_size] = 0;
|
|
||||||
err_msg_completed = true;
|
|
||||||
Return RESULT_ERROR((char*)err_buf.data, true);
|
|
||||||
}
|
|
||||||
case PacketType_ServerHandshake: {
|
|
||||||
ServerHandshake server_handshake;
|
|
||||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &server_handshake));
|
|
||||||
conn->session_id = server_handshake.session_id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
Return RESULT_ERROR_FMT("unexpected response type: %i", packet_header.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
Return RESULT_VALUE(p, conn);
|
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;
|
||||||
|
}
|
||||||
@@ -1,184 +1,176 @@
|
|||||||
#include "client.h"
|
#include "client/client_internal.h"
|
||||||
#include "term.h"
|
#include "client/requests/requests.h"
|
||||||
#include "tlibc/time.h"
|
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
|
||||||
|
|
||||||
static const str greeting_art = STR(
|
void Client_free(Client* self){
|
||||||
" ^,,^ ╱|\n"
|
if(!self)
|
||||||
" ( •·•) 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"
|
|
||||||
);
|
|
||||||
|
|
||||||
Result(void) Client_createFromConfig(cstr config_path){
|
|
||||||
Deferral(16);
|
|
||||||
|
|
||||||
Client* client = (Client*)malloc(sizeof(Client));
|
|
||||||
memset(client, 0, sizeof(Client));
|
|
||||||
bool success = false;
|
|
||||||
Defer(if(!success) Client_free(client));
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VALUE(p, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client_free(Client* client){
|
|
||||||
if(!client)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ClientCredentials_destroy(&client->cred);
|
str_destroy(self->username);
|
||||||
ServerConnection_close(client->server_connection);
|
Array_u8_destroy(&self->user_data_key);
|
||||||
free(client);
|
ServerConnection_close(self->conn);
|
||||||
|
free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result(void) commandExec(Client* client, str command, bool* stop);
|
Result(Client*) Client_create(str username, str password){
|
||||||
|
|
||||||
static Result(void) askUserNameAndPassword(ClientCredentials* cred){
|
|
||||||
Deferral(8);
|
|
||||||
|
|
||||||
char username_buf[128];
|
|
||||||
str username = str_null;
|
|
||||||
while(true) {
|
|
||||||
printf("username: ");
|
|
||||||
if(fgets(username_buf, sizeof(username_buf), stdin) == NULL){
|
|
||||||
Return RESULT_ERROR("STDIN is closed", false);
|
|
||||||
}
|
|
||||||
username = str_from_cstr(username_buf);
|
|
||||||
str_trim(&username, true);
|
|
||||||
if(username.size < USERNAME_SIZE_MIN || username.size > USERNAME_SIZE_MAX){
|
|
||||||
printf("ERROR: username length (in bytes) must be >= %i and <= %i\n",
|
|
||||||
USERNAME_SIZE_MIN, USERNAME_SIZE_MAX);
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
char password_buf[128];
|
|
||||||
str password = str_null;
|
|
||||||
while(true) {
|
|
||||||
printf("password: ");
|
|
||||||
// TODO: hide password
|
|
||||||
if(fgets(password_buf, sizeof(password_buf), stdin) == NULL){
|
|
||||||
Return RESULT_ERROR("STDIN is closed", false);
|
|
||||||
}
|
|
||||||
password = str_from_cstr(password_buf);
|
|
||||||
str_trim(&password, true);
|
|
||||||
if(password.size < PASSWORD_SIZE_MIN || password.size > PASSWORD_SIZE_MAX){
|
|
||||||
printf("ERROR: password length (in bytes) must be >= %i and <= %i\n",
|
|
||||||
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX);
|
|
||||||
}
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try_void(ClientCredentials_tryConstruct(cred, username, password));
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Client_run(Client* client) {
|
|
||||||
Deferral(16);
|
Deferral(16);
|
||||||
if(!term_init()){
|
|
||||||
Return RESULT_ERROR("can't init terminal", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fputs(greeting_art.data, stdout);
|
Client* self = (Client*)malloc(sizeof(Client));
|
||||||
try_void(askUserNameAndPassword(&client->cred));
|
zeroStruct(self);
|
||||||
|
bool success = false;
|
||||||
|
Defer(if(!success) Client_free(self));
|
||||||
|
|
||||||
Array(char) input_buf = Array_alloc(char, 10000);
|
self->username = str_copy(username);
|
||||||
Defer(free(input_buf.data));
|
|
||||||
str command_input = str_null;
|
// lvl 1 hash - is used as AES key for user data
|
||||||
bool stop = false;
|
self->user_data_key = Array_u8_alloc(PASSWORD_HASH_SIZE);
|
||||||
while(!stop){
|
hash_password(str_castTo_Array_u8(password), str_castTo_Array_u8(username),
|
||||||
sleepMsec(50);
|
self->user_data_key.data, PASSWORD_HASH_LVL_ROUNDS);
|
||||||
fputs("> ", stdout);
|
|
||||||
if(fgets(input_buf.data, input_buf.size, stdin) == NULL){
|
success = true;
|
||||||
Return RESULT_ERROR("STDIN is closed", false);
|
Return RESULT_VALUE(p, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
command_input = str_from_cstr(input_buf.data);
|
Result(void) Client_connect(Client* self, cstr server_addr_cstr, cstr server_pk_base64){
|
||||||
str_trim(&command_input, true);
|
Deferral(8);
|
||||||
if(command_input.size == 0)
|
Client_disconnect(self);
|
||||||
continue;
|
try(self->conn, p,
|
||||||
|
ServerConnection_open(self, server_addr_cstr, server_pk_base64)
|
||||||
ResultVar(void) com_result = commandExec(client, command_input, &stop);
|
);
|
||||||
if(com_result.error){
|
Return RESULT_VOID;
|
||||||
str e_str = Error_toStr(com_result.error);
|
}
|
||||||
printf("%s\n", e_str.data);
|
|
||||||
free(e_str.data);
|
void Client_disconnect(Client* self){
|
||||||
Error_free(com_result.error);
|
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;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define is_alias(LITERAL) str_equals(command, STR(LITERAL))
|
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");
|
||||||
|
|
||||||
static Result(void) commandExec(Client* client, str command, bool* stop){
|
try_void(ServerConnection_requestServerDescription(self->conn, out_str));
|
||||||
Deferral(64);
|
|
||||||
char answer_buf[10000];
|
Return RESULT_VOID;
|
||||||
const u32 answer_buf_size = sizeof(answer_buf);
|
}
|
||||||
if(is_alias("q") || is_alias("quit") || is_alias("exit")){
|
|
||||||
fputs(farewell_art.data, stdout);
|
|
||||||
*stop = true;
|
|
||||||
}
|
|
||||||
else if(is_alias("clear")){
|
|
||||||
term_clear();
|
|
||||||
}
|
|
||||||
else if(is_alias("h") || is_alias("help")){
|
|
||||||
puts(
|
|
||||||
"COMMANDS:\n"
|
|
||||||
"h, help Show this message.\n"
|
|
||||||
"q, quit, exit Close the program.\n"
|
|
||||||
"clear Clear the screen.\n"
|
|
||||||
"j, join Join a server.\n"
|
|
||||||
"c, connect Connect to a server you joined.\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (is_alias("j") || is_alias("join")){
|
|
||||||
ServerConnection_close(client->server_connection);
|
|
||||||
|
|
||||||
puts("Enter server address (ip:port:public_key): ");
|
Result(void) Client_register(Client* self, i64* out_user_id){
|
||||||
if(fgets(answer_buf, answer_buf_size, stdin) == NULL){
|
Deferral(1);
|
||||||
Return RESULT_ERROR("STDIN is closed", false);
|
try_assert(self != NULL);
|
||||||
}
|
try_assert(self->conn != NULL && "didn't connect to a server yet");
|
||||||
str new_server_link = str_from_cstr(answer_buf);
|
|
||||||
str_trim(&new_server_link, true);
|
|
||||||
|
|
||||||
printf("connecting to server...\n");
|
PacketHeader req_head, res_head;
|
||||||
try(client->server_connection, p,
|
RegisterRequest req;
|
||||||
ServerConnection_open(&client->cred, new_server_link.data));
|
RegisterResponse res;
|
||||||
printf("connection established\n");
|
try_void(RegisterRequest_tryConstruct(&req, &req_head, self->username, self->conn->token));
|
||||||
|
try_void(sendRequest(&self->conn->sock, &req_head, &req));
|
||||||
// TODO: request server info
|
try_void(recvResponse(&self->conn->sock, &res_head, &res, PacketType_RegisterResponse));
|
||||||
// show server info
|
self->conn->user_id = res.user_id;
|
||||||
// save server info to user's db
|
*out_user_id = res.user_id;
|
||||||
// try log in
|
|
||||||
// if not registered, request registration and then log in
|
|
||||||
|
|
||||||
// call Client_runIO():
|
|
||||||
// function with infinite loop which sends and receives messages
|
|
||||||
// with navigation across server channels
|
|
||||||
//
|
|
||||||
}
|
|
||||||
else if(is_alias("c") || is_alias("connect")){
|
|
||||||
// TODO: read saved servers from database
|
|
||||||
// show scrollable list of them
|
|
||||||
// select one
|
|
||||||
// try log in
|
|
||||||
// if not registered, ask user if they want to register
|
|
||||||
// regiser and then log in
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("ERROR: unknown command.\n"
|
|
||||||
"Use 'h' to see list of avaliable commands\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "cryptography/AES.h"
|
|
||||||
#include "cryptography/RSA.h"
|
|
||||||
#include "network/encrypted_sockets.h"
|
|
||||||
|
|
||||||
typedef struct Client Client;
|
|
||||||
|
|
||||||
typedef struct ClientCredentials {
|
|
||||||
str username;
|
|
||||||
Array(u8) user_data_key;
|
|
||||||
Array(u8) token;
|
|
||||||
AESBlockEncryptor user_data_aes_enc;
|
|
||||||
AESBlockDecryptor user_data_aes_dec;
|
|
||||||
} ClientCredentials;
|
|
||||||
|
|
||||||
Result(void) ClientCredentials_tryConstruct(ClientCredentials* cred,
|
|
||||||
str username, str password);
|
|
||||||
|
|
||||||
void ClientCredentials_destroy(ClientCredentials* cred);
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct ServerConnection {
|
|
||||||
u64 session_id;
|
|
||||||
EndpointIPv4 server_end;
|
|
||||||
br_rsa_public_key server_pk;
|
|
||||||
RSAEncryptor rsa_enc;
|
|
||||||
Array(u8) session_key;
|
|
||||||
EncryptedSocketTCP sock;
|
|
||||||
} ServerConnection;
|
|
||||||
|
|
||||||
Result(ServerConnection*) ServerConnection_open(ClientCredentials* client_credentials,
|
|
||||||
cstr server_link_cstr);
|
|
||||||
|
|
||||||
void ServerConnection_close(ServerConnection* conn);
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct Client {
|
|
||||||
ClientCredentials cred;
|
|
||||||
ServerConnection* server_connection;
|
|
||||||
} Client;
|
|
||||||
|
|
||||||
Result(void) Client_createFromConfig(cstr config_path);
|
|
||||||
void Client_free(Client* client);
|
|
||||||
Result(void) Client_run(Client* client);
|
|
||||||
40
src/client/client_internal.h
Normal file
40
src/client/client_internal.h
Normal 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);
|
||||||
43
src/client/requests/ErrorMessage.c
Normal file
43
src/client/requests/ErrorMessage.c
Normal 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;
|
||||||
|
}
|
||||||
34
src/client/requests/requests.c
Normal file
34
src/client/requests/requests.c
Normal 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;
|
||||||
|
}
|
||||||
22
src/client/requests/requests.h
Normal file
22
src/client/requests/requests.h
Normal 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))
|
||||||
36
src/config.c
36
src/config.c
@@ -1,36 +0,0 @@
|
|||||||
#include "config.h"
|
|
||||||
|
|
||||||
Result(void) config_findValue(str config_str, str key, str* value, bool throwNotFoundError){
|
|
||||||
u32 line_n = 0;
|
|
||||||
while(config_str.size > 0){
|
|
||||||
line_n++;
|
|
||||||
i32 line_end = str_seekChar(config_str, '\n', 0);
|
|
||||||
if(line_end < 0)
|
|
||||||
line_end = config_str.size - 1;
|
|
||||||
str line = str_sliceBefore(config_str, line_end);
|
|
||||||
config_str = str_sliceAfter(config_str, line_end + 1);
|
|
||||||
|
|
||||||
i32 sep_pos = str_seekChar(line, '=', 1);
|
|
||||||
if(sep_pos < 0){
|
|
||||||
//not a 'key = value' line
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
str line_key = str_sliceBefore(line, sep_pos - 1);
|
|
||||||
str_trim(&line_key, false);
|
|
||||||
if(str_equals(line_key, key)){
|
|
||||||
str line_value = str_sliceAfter(line, sep_pos + 1);
|
|
||||||
str_trim(&line_value, false);
|
|
||||||
*value = line_value;
|
|
||||||
return RESULT_VOID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(throwNotFoundError){
|
|
||||||
char* key_cstr = str_copy(key).data;
|
|
||||||
char* err_msg = sprintf_malloc(key.size + 64, "can't find key '%s'", key_cstr);
|
|
||||||
free(key_cstr);
|
|
||||||
return RESULT_ERROR(err_msg, true);
|
|
||||||
}
|
|
||||||
return RESULT_VOID;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/errors.h"
|
|
||||||
#include "tlibc/string/str.h"
|
|
||||||
|
|
||||||
/// searches for pattern `key = value`
|
|
||||||
Result(void) config_findValue(str config_str, str key, str* value, bool throwNotFoundError);
|
|
||||||
@@ -4,15 +4,21 @@
|
|||||||
// write data from src to array and increment array data pointer
|
// write data from src to array and increment array data pointer
|
||||||
static inline void __Array_writeNext(Array(u8)* dst, u8* src, size_t size){
|
static inline void __Array_writeNext(Array(u8)* dst, u8* src, size_t size){
|
||||||
memcpy(dst->data, src, size);
|
memcpy(dst->data, src, size);
|
||||||
*dst = Array_sliceFrom(*dst, size);
|
*dst = Array_u8_sliceFrom(*dst, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// read data from array to dst and increment array data pointer
|
// read data from array to dst and increment array data pointer
|
||||||
static inline void __Array_readNext(u8* dst, Array(u8)* src, size_t size){
|
static inline void __Array_readNext(u8* dst, Array(u8)* src, size_t size){
|
||||||
memcpy(dst, src->data, size);
|
memcpy(dst, src->data, size);
|
||||||
*src = Array_sliceFrom(*src, size);
|
*src = Array_u8_sliceFrom(*src, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void __calcKeyCheckSum(Array(u8) key, void* dst){
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// AESBlockEncryptor //
|
// AESBlockEncryptor //
|
||||||
@@ -20,35 +26,38 @@ static inline void __Array_readNext(u8* dst, Array(u8)* src, size_t size){
|
|||||||
|
|
||||||
void AESBlockEncryptor_construct(AESBlockEncryptor* ptr,
|
void AESBlockEncryptor_construct(AESBlockEncryptor* ptr,
|
||||||
Array(u8) key, const br_block_cbcenc_class* enc_class)
|
Array(u8) key, const br_block_cbcenc_class* enc_class)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
|
||||||
|
|
||||||
ptr->enc_class = enc_class;
|
ptr->enc_class = enc_class;
|
||||||
ptr->enc_class->init((void*)ptr->enc_keys, key.data, key.size);
|
AESBlockEncryptor_changeKey(ptr, key);
|
||||||
|
|
||||||
ptr->rng_ctx.vtable = &br_hmac_drbg_vtable;
|
ptr->rng_ctx.vtable = &br_hmac_drbg_vtable;
|
||||||
rng_init_sha256_seedFromSystem(&ptr->rng_ctx.vtable);
|
rng_init_sha256_seedFromSystem(&ptr->rng_ctx.vtable);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AESBlockEncryptor_changeKey(AESBlockEncryptor* ptr, Array(u8) key)
|
void AESBlockEncryptor_changeKey(AESBlockEncryptor* ptr, Array(u8) key)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
assert(key.len == 16 || key.len == 24 || key.len == 32);
|
||||||
ptr->enc_class->init((void*)ptr->enc_keys, key.data, key.size);
|
ptr->enc_class->init((void*)ptr->enc_keys, key.data, key.len);
|
||||||
|
__calcKeyCheckSum(key, ptr->key_checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr,
|
Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr,
|
||||||
Array(u8) src, Array(u8) dst)
|
Array(u8) src, Array(u8) dst)
|
||||||
{
|
{
|
||||||
Deferral(4);
|
Deferral(4);
|
||||||
u32 encrypted_size = AESBlockEncryptor_calcDstSize(src.size);
|
u32 encrypted_size = AESBlockEncryptor_calcDstSize(src.len);
|
||||||
try_assert(dst.size >= encrypted_size);
|
try_assert(dst.len >= encrypted_size);
|
||||||
|
|
||||||
// generate random initial vector
|
// generate random initial vector
|
||||||
br_hmac_drbg_generate(&ptr->rng_ctx, ptr->iv, __AES_BLOCK_IV_SIZE);
|
br_hmac_drbg_generate(&ptr->rng_ctx, ptr->iv, __AES_BLOCK_IV_SIZE);
|
||||||
// write IV to the beginning of dst
|
// write IV to the beginning of dst
|
||||||
__Array_writeNext(&dst, ptr->iv, __AES_BLOCK_IV_SIZE);
|
__Array_writeNext(&dst, ptr->iv, __AES_BLOCK_IV_SIZE);
|
||||||
|
|
||||||
const EncryptedBlockHeader header = { .padding_size = 16 - src.size % 16 };
|
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
|
// write header to buffer
|
||||||
memcpy(ptr->buf, &header, sizeof(header));
|
memcpy(ptr->buf, &header, sizeof(header));
|
||||||
// encrypt header
|
// encrypt header
|
||||||
@@ -57,7 +66,7 @@ Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr,
|
|||||||
__Array_writeNext(&dst, ptr->buf, sizeof(header));
|
__Array_writeNext(&dst, ptr->buf, sizeof(header));
|
||||||
|
|
||||||
// encrypt full AESBlockEncryptor buffers
|
// encrypt full AESBlockEncryptor buffers
|
||||||
while(src.size > __AES_BUFFER_SIZE){
|
while(src.len > __AES_BUFFER_SIZE){
|
||||||
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
||||||
ptr->enc_class->run((void*)ptr->enc_keys,
|
ptr->enc_class->run((void*)ptr->enc_keys,
|
||||||
ptr->iv,
|
ptr->iv,
|
||||||
@@ -66,10 +75,10 @@ Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encrypt buffer with remaining data
|
// encrypt buffer with remaining data
|
||||||
if(src.size > 0){
|
if(src.len > 0){
|
||||||
memcpy(ptr->buf, src.data, src.size);
|
memcpy(ptr->buf, src.data, src.len);
|
||||||
u32 src_size_padded = src.size + header.padding_size;
|
memset(ptr->buf + src.len, 0, header.padding_size);
|
||||||
memset(ptr->buf + src.size, 0, header.padding_size);
|
u32 src_size_padded = src.len + header.padding_size;
|
||||||
ptr->enc_class->run((void*)ptr->enc_keys,
|
ptr->enc_class->run((void*)ptr->enc_keys,
|
||||||
ptr->iv,
|
ptr->iv,
|
||||||
ptr->buf, src_size_padded);
|
ptr->buf, src_size_padded);
|
||||||
@@ -87,25 +96,25 @@ Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr,
|
|||||||
void AESBlockDecryptor_construct(AESBlockDecryptor* ptr,
|
void AESBlockDecryptor_construct(AESBlockDecryptor* ptr,
|
||||||
Array(u8) key, const br_block_cbcdec_class* dec_class)
|
Array(u8) key, const br_block_cbcdec_class* dec_class)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
|
||||||
|
|
||||||
ptr->dec_class = dec_class;
|
ptr->dec_class = dec_class;
|
||||||
ptr->dec_class->init((void*)ptr->dec_keys, key.data, key.size);
|
AESBlockDecryptor_changeKey(ptr, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AESBlockDecryptor_changeKey(AESBlockDecryptor* ptr, Array(u8) key)
|
void AESBlockDecryptor_changeKey(AESBlockDecryptor* ptr, Array(u8) key)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
assert(key.len == 16 || key.len == 24 || key.len == 32);
|
||||||
ptr->dec_class->init((void*)ptr->dec_keys, key.data, key.size);
|
ptr->dec_class->init((void*)ptr->dec_keys, key.data, key.len);
|
||||||
|
__calcKeyCheckSum(key, ptr->key_checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr,
|
Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr,
|
||||||
Array(u8) src, Array(u8) dst)
|
Array(u8) src, Array(u8) dst)
|
||||||
{
|
{
|
||||||
Deferral(4);
|
Deferral(4);
|
||||||
try_assert(src.size >= AESBlockEncryptor_calcDstSize(0));
|
u32 overhead_size = AESBlockEncryptor_calcDstSize(0);
|
||||||
try_assert(src.size % 16 == 0 && "src must be array of 16-byte blocks");
|
try_assert(src.len >= overhead_size);
|
||||||
try_assert(dst.size >= src.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
|
// read IV from the beginning of src
|
||||||
__Array_readNext(ptr->iv, &src, __AES_BLOCK_IV_SIZE);
|
__Array_readNext(ptr->iv, &src, __AES_BLOCK_IV_SIZE);
|
||||||
@@ -115,13 +124,19 @@ Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr,
|
|||||||
__Array_readNext((void*)&header, &src, sizeof(header));
|
__Array_readNext((void*)&header, &src, sizeof(header));
|
||||||
// decrypt header
|
// decrypt header
|
||||||
ptr->dec_class->run((void*)ptr->dec_keys, ptr->iv, &header, sizeof(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
|
// size of decrypted data without padding
|
||||||
u32 decrypted_size = src.size - header.padding_size;
|
try_assert(src.len >= header.padding_size && "invalid padding size");
|
||||||
const u32 src_size_padded = src.size;
|
u32 decrypted_size = src.len - header.padding_size;
|
||||||
src.size = decrypted_size;
|
src.len = decrypted_size;
|
||||||
|
|
||||||
// decrypt full buffers
|
// decrypt full buffers
|
||||||
while(src.size > __AES_BUFFER_SIZE){
|
while(src.len > __AES_BUFFER_SIZE){
|
||||||
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
||||||
ptr->dec_class->run((void*)ptr->dec_keys,
|
ptr->dec_class->run((void*)ptr->dec_keys,
|
||||||
ptr->iv,
|
ptr->iv,
|
||||||
@@ -130,12 +145,14 @@ Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// decrypt buffer with remaining data
|
// decrypt buffer with remaining data
|
||||||
if(src.size > 0){
|
if(src.len > 0){
|
||||||
memcpy(ptr->buf, src.data, src.size);
|
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->dec_class->run((void*)ptr->dec_keys,
|
||||||
ptr->iv,
|
ptr->iv,
|
||||||
ptr->buf, src_size_padded);
|
ptr->buf, src_size_padded);
|
||||||
memcpy(dst.data, ptr->buf, src.size);
|
memcpy(dst.data, ptr->buf, src.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
Return RESULT_VALUE(u, decrypted_size);
|
Return RESULT_VALUE(u, decrypted_size);
|
||||||
@@ -149,56 +166,64 @@ Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr,
|
|||||||
void AESStreamEncryptor_construct(AESStreamEncryptor* ptr,
|
void AESStreamEncryptor_construct(AESStreamEncryptor* ptr,
|
||||||
Array(u8) key, const br_block_ctr_class* ctr_class)
|
Array(u8) key, const br_block_ctr_class* ctr_class)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
|
||||||
|
|
||||||
ptr->ctr_class = ctr_class;
|
ptr->ctr_class = ctr_class;
|
||||||
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.size);
|
AESStreamEncryptor_changeKey(ptr, key);
|
||||||
|
ptr->block_counter = 0;
|
||||||
|
|
||||||
br_hmac_drbg_context rng_ctx;
|
br_hmac_drbg_context rng_ctx;
|
||||||
rng_ctx.vtable = &br_hmac_drbg_vtable;
|
rng_ctx.vtable = &br_hmac_drbg_vtable;
|
||||||
rng_init_sha256_seedFromSystem(&rng_ctx.vtable);
|
rng_init_sha256_seedFromSystem(&rng_ctx.vtable);
|
||||||
br_hmac_drbg_generate(&rng_ctx, ptr->iv, __AES_STREAM_IV_SIZE);
|
br_hmac_drbg_generate(&rng_ctx, ptr->iv, __AES_STREAM_IV_SIZE);
|
||||||
|
|
||||||
ptr->block_counter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key)
|
void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
assert(key.len == 16 || key.len == 24 || key.len == 32);
|
||||||
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.size);
|
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.len);
|
||||||
|
__calcKeyCheckSum(key, ptr->key_checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr,
|
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr,
|
||||||
Array(u8) src, Array(u8) dst)
|
Array(u8) src, Array(u8) dst)
|
||||||
{
|
{
|
||||||
Deferral(4);
|
Deferral(4);
|
||||||
u32 encrypted_size = AESStreamEncryptor_calcDstSize(src.size);
|
|
||||||
try_assert(dst.size >= encrypted_size);
|
u32 encrypted_size = src.len;
|
||||||
|
// if it is the beginning of the stream,
|
||||||
// if it is the beginning of the stream, write IV
|
|
||||||
if(ptr->block_counter == 0){
|
if(ptr->block_counter == 0){
|
||||||
|
// write IV generated during initialization
|
||||||
__Array_writeNext(&dst, ptr->iv, __AES_STREAM_IV_SIZE);
|
__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
|
// encrypt full buffers
|
||||||
while(src.size > __AES_BUFFER_SIZE){
|
while(src.len > __AES_BUFFER_SIZE){
|
||||||
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
||||||
ptr->ctr_class->run((void*)ptr->ctr_keys,
|
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
|
||||||
ptr->iv, ptr->block_counter,
|
ptr->iv, ptr->block_counter,
|
||||||
ptr->buf, __AES_BUFFER_SIZE);
|
ptr->buf, __AES_BUFFER_SIZE);
|
||||||
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
|
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt remaining data
|
// encrypt remaining data
|
||||||
if(src.size > 0){
|
if(src.len > 0){
|
||||||
memcpy(ptr->buf, src.data, src.size);
|
memcpy(ptr->buf, src.data, src.len);
|
||||||
ptr->ctr_class->run((void*)ptr->ctr_keys,
|
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
|
||||||
ptr->iv, ptr->block_counter,
|
ptr->iv, ptr->block_counter,
|
||||||
ptr->buf, src.size);
|
ptr->buf, src.len);
|
||||||
memcpy(dst.data, ptr->buf, src.size);
|
memcpy(dst.data, ptr->buf, src.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr->block_counter++;
|
|
||||||
Return RESULT_VALUE(u, encrypted_size);
|
Return RESULT_VALUE(u, encrypted_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,18 +235,16 @@ Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr,
|
|||||||
void AESStreamDecryptor_construct(AESStreamDecryptor* ptr,
|
void AESStreamDecryptor_construct(AESStreamDecryptor* ptr,
|
||||||
Array(u8) key, const br_block_ctr_class* ctr_class)
|
Array(u8) key, const br_block_ctr_class* ctr_class)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
|
||||||
|
|
||||||
ptr->ctr_class = ctr_class;
|
ptr->ctr_class = ctr_class;
|
||||||
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.size);
|
AESStreamDecryptor_changeKey(ptr, key);
|
||||||
|
|
||||||
ptr->block_counter = 0;
|
ptr->block_counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AESStreamDecryptor_changeKey(AESStreamDecryptor* ptr, Array(u8) key)
|
void AESStreamDecryptor_changeKey(AESStreamDecryptor* ptr, Array(u8) key)
|
||||||
{
|
{
|
||||||
assert(key.size == 16 || key.size == 24 || key.size == 32);
|
assert(key.len == 16 || key.len == 24 || key.len == 32);
|
||||||
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.size);
|
ptr->ctr_class->init((void*)ptr->ctr_keys, key.data, key.len);
|
||||||
|
__calcKeyCheckSum(key, ptr->key_checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr,
|
Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr,
|
||||||
@@ -229,32 +252,44 @@ Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr,
|
|||||||
{
|
{
|
||||||
Deferral(4);
|
Deferral(4);
|
||||||
|
|
||||||
// if it is the beginning of the stream, read IV
|
// if it is the beginning of the stream
|
||||||
if(ptr->block_counter == 0){
|
if(ptr->block_counter == 0){
|
||||||
|
// read random IV
|
||||||
__Array_readNext(ptr->iv, &src, __AES_STREAM_IV_SIZE);
|
__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
|
// size without IV
|
||||||
u32 decrypted_size = src.size;
|
u32 decrypted_size = src.len;
|
||||||
try_assert(dst.size >= decrypted_size);
|
try_assert(dst.len >= decrypted_size);
|
||||||
|
|
||||||
// decrypt full buffers
|
// decrypt full buffers
|
||||||
while(src.size > __AES_BUFFER_SIZE){
|
while(src.len > __AES_BUFFER_SIZE){
|
||||||
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
__Array_readNext(ptr->buf, &src, __AES_BUFFER_SIZE);
|
||||||
ptr->ctr_class->run((void*)ptr->ctr_keys,
|
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
|
||||||
ptr->iv, ptr->block_counter,
|
ptr->iv, ptr->block_counter,
|
||||||
ptr->buf, __AES_BUFFER_SIZE);
|
ptr->buf, __AES_BUFFER_SIZE);
|
||||||
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
|
__Array_writeNext(&dst, ptr->buf, __AES_BUFFER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt remaining data
|
// decrypt remaining data
|
||||||
if(src.size > 0){
|
if(src.len > 0){
|
||||||
memcpy(ptr->buf, src.data, src.size);
|
memcpy(ptr->buf, src.data, src.len);
|
||||||
ptr->ctr_class->run((void*)ptr->ctr_keys,
|
ptr->block_counter = ptr->ctr_class->run((void*)ptr->ctr_keys,
|
||||||
ptr->iv, ptr->block_counter,
|
ptr->iv, ptr->block_counter,
|
||||||
ptr->buf, src.size);
|
ptr->buf, src.len);
|
||||||
memcpy(dst.data, ptr->buf, src.size);
|
memcpy(dst.data, ptr->buf, src.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr->block_counter++;
|
|
||||||
Return RESULT_VALUE(u, decrypted_size);
|
Return RESULT_VALUE(u, decrypted_size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/collections/Array.h"
|
#include "tlibc/collections/Array.h"
|
||||||
#include "tlibc/errors.h"
|
#include "tlibc/errors.h"
|
||||||
|
#include "tlibc/magic.h"
|
||||||
#include "bearssl_block.h"
|
#include "bearssl_block.h"
|
||||||
#include "cryptography.h"
|
#include "cryptography.h"
|
||||||
|
|
||||||
@@ -15,8 +16,9 @@
|
|||||||
#define AESStream_DEFAULT_CLASS (&br_aes_big_ctr_vtable)
|
#define AESStream_DEFAULT_CLASS (&br_aes_big_ctr_vtable)
|
||||||
|
|
||||||
|
|
||||||
//TODO: use PKS#7 instead of this garbage
|
#define __AES_BLOCK_KEY_CHECKSUM_SIZE br_sha256_SIZE
|
||||||
typedef struct EncryptedBlockHeader {
|
typedef struct EncryptedBlockHeader {
|
||||||
|
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
|
||||||
u8 padding_size;
|
u8 padding_size;
|
||||||
} ATTRIBUTE_ALIGNED(16) EncryptedBlockHeader;
|
} ATTRIBUTE_ALIGNED(16) EncryptedBlockHeader;
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ typedef struct AESBlockEncryptor {
|
|||||||
u8 enc_keys[sizeof(br_aes_big_cbcenc_keys)];
|
u8 enc_keys[sizeof(br_aes_big_cbcenc_keys)];
|
||||||
u8 buf[__AES_BUFFER_SIZE];
|
u8 buf[__AES_BUFFER_SIZE];
|
||||||
u8 iv[__AES_BLOCK_IV_SIZE];
|
u8 iv[__AES_BLOCK_IV_SIZE];
|
||||||
|
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
|
||||||
br_hmac_drbg_context rng_ctx;
|
br_hmac_drbg_context rng_ctx;
|
||||||
} AESBlockEncryptor;
|
} AESBlockEncryptor;
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ void AESBlockEncryptor_changeKey(AESBlockEncryptor* ptr, Array(u8) key);
|
|||||||
|
|
||||||
/// @brief Encrypts a complete message. For part-by-part encryption use AESStreamEncryptor.
|
/// @brief Encrypts a complete message. For part-by-part encryption use AESStreamEncryptor.
|
||||||
/// @param src array of any size
|
/// @param src array of any size
|
||||||
/// @param dst array of size >= AESBlockEncryptor_calcDstSize(src.size)
|
/// @param dst array of size >= AESBlockEncryptor_calcDstSize(src.len)
|
||||||
/// @return size of encrypted data
|
/// @return size of encrypted data
|
||||||
Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr, Array(u8) src, Array(u8) dst);
|
Result(u32) AESBlockEncryptor_encrypt(AESBlockEncryptor* ptr, Array(u8) src, Array(u8) dst);
|
||||||
|
|
||||||
@@ -60,6 +63,7 @@ typedef struct AESBlockDecryptor {
|
|||||||
u8 dec_keys[sizeof(br_aes_big_cbcdec_keys)];
|
u8 dec_keys[sizeof(br_aes_big_cbcdec_keys)];
|
||||||
u8 buf[__AES_BUFFER_SIZE];
|
u8 buf[__AES_BUFFER_SIZE];
|
||||||
u8 iv[__AES_BLOCK_IV_SIZE];
|
u8 iv[__AES_BLOCK_IV_SIZE];
|
||||||
|
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
|
||||||
} AESBlockDecryptor;
|
} AESBlockDecryptor;
|
||||||
|
|
||||||
/// @param key supported sizes: 16, 24, 32
|
/// @param key supported sizes: 16, 24, 32
|
||||||
@@ -71,7 +75,7 @@ void AESBlockDecryptor_changeKey(AESBlockDecryptor* ptr, Array(u8) key);
|
|||||||
|
|
||||||
/// @brief Decrypts a complete message. For part-by-part decryption use AESStreamEncryptor.
|
/// @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 src array of size at least AESBlockEncryptor_calcDstSize(0). Size must be multiple of 16.
|
||||||
/// @param dst array of size >= src.size
|
/// @param dst array of size >= src.len
|
||||||
/// @return size of decrypted data
|
/// @return size of decrypted data
|
||||||
Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr, Array(u8) src, Array(u8) dst);
|
Result(u32) AESBlockDecryptor_decrypt(AESBlockDecryptor* ptr, Array(u8) src, Array(u8) dst);
|
||||||
|
|
||||||
@@ -86,6 +90,7 @@ typedef struct AESStreamEncryptor {
|
|||||||
u8 ctr_keys[sizeof(br_aes_big_ctr_keys)];
|
u8 ctr_keys[sizeof(br_aes_big_ctr_keys)];
|
||||||
u8 buf[__AES_BUFFER_SIZE];
|
u8 buf[__AES_BUFFER_SIZE];
|
||||||
u8 iv[__AES_STREAM_IV_SIZE];
|
u8 iv[__AES_STREAM_IV_SIZE];
|
||||||
|
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
|
||||||
u32 block_counter;
|
u32 block_counter;
|
||||||
} AESStreamEncryptor;
|
} AESStreamEncryptor;
|
||||||
|
|
||||||
@@ -98,11 +103,11 @@ void AESStreamEncryptor_construct(AESStreamEncryptor* ptr, Array(u8) key, const
|
|||||||
void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key);
|
void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key);
|
||||||
|
|
||||||
/// use this only at the beginning of the stream
|
/// use this only at the beginning of the stream
|
||||||
#define AESStreamEncryptor_calcDstSize(src_size) (src_size + __AES_STREAM_IV_SIZE)
|
#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.
|
/// @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 src array of any size
|
||||||
/// @param dst array of size >= AESStreamEncryptor_calcDstSize(src.size)
|
/// @param dst array of size >= `AESStreamEncryptor_calcDstSize(src.len)` for first block and `src.len `for other blocks
|
||||||
/// @return size of encrypted data
|
/// @return size of encrypted data
|
||||||
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr, Array(u8) src, Array(u8) dst);
|
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr, Array(u8) src, Array(u8) dst);
|
||||||
|
|
||||||
@@ -115,6 +120,7 @@ typedef struct AESStreamDecryptor {
|
|||||||
u8 ctr_keys[sizeof(br_aes_big_ctr_keys)];
|
u8 ctr_keys[sizeof(br_aes_big_ctr_keys)];
|
||||||
u8 buf[__AES_BUFFER_SIZE];
|
u8 buf[__AES_BUFFER_SIZE];
|
||||||
u8 iv[__AES_STREAM_IV_SIZE];
|
u8 iv[__AES_STREAM_IV_SIZE];
|
||||||
|
u8 key_checksum[__AES_BLOCK_KEY_CHECKSUM_SIZE];
|
||||||
u32 block_counter;
|
u32 block_counter;
|
||||||
} AESStreamDecryptor;
|
} AESStreamDecryptor;
|
||||||
|
|
||||||
@@ -128,6 +134,6 @@ void AESStreamDecryptor_changeKey(AESStreamDecryptor* ptr, Array(u8) key);
|
|||||||
|
|
||||||
/// @brief Reads IV from `src`, then decrypts data and writes it to dst
|
/// @brief Reads IV from `src`, then decrypts data and writes it to dst
|
||||||
/// @param src array of any size
|
/// @param src array of any size
|
||||||
/// @param dst array of size >= src.size
|
/// @param dst array of size >= src.len
|
||||||
/// @return size of decrypted data
|
/// @return size of decrypted data
|
||||||
Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr, Array(u8) src, Array(u8) dst);
|
Result(u32) AESStreamDecryptor_decrypt(AESStreamDecryptor* ptr, Array(u8) src, Array(u8) dst);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#include "RSA.h"
|
#include "RSA.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "bearssl_x509.h"
|
|
||||||
#include "bearssl_pem.h"
|
|
||||||
#include "tlibc/base64.h"
|
#include "tlibc/base64.h"
|
||||||
|
|
||||||
// https://crypto.stackexchange.com/questions/3110/impacts-of-not-using-rsa-exponent-of-65537
|
// https://crypto.stackexchange.com/questions/3110/impacts-of-not-using-rsa-exponent-of-65537
|
||||||
@@ -25,7 +23,7 @@ Result(void) RSA_generateKeyPair(u32 key_size,
|
|||||||
|
|
||||||
success = br_rsa_i31_keygen(rng_vtable_ptr, sk, sk_buf, pk, pk_buf, key_size, DEFAULT_PUBLIC_EXPONENT);
|
success = br_rsa_i31_keygen(rng_vtable_ptr, sk, sk_buf, pk, pk_buf, key_size, DEFAULT_PUBLIC_EXPONENT);
|
||||||
if(!success){
|
if(!success){
|
||||||
Return RESULT_ERROR("br_rsa_i31_keygen() failed", false);
|
Return RESULT_ERROR_LITERAL("br_rsa_i31_keygen() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
@@ -46,7 +44,7 @@ Result(void) RSA_generateKeyPairFromPassword(u32 key_size,
|
|||||||
{
|
{
|
||||||
Deferral(4);
|
Deferral(4);
|
||||||
br_hmac_drbg_context password_based_rng = { .vtable = &br_hmac_drbg_vtable };
|
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.size);
|
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));
|
try_void(RSA_generateKeyPair(key_size, sk, pk, &password_based_rng.vtable));
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
@@ -58,7 +56,7 @@ Result(void) RSA_computePublicKey(const br_rsa_private_key* sk, br_rsa_public_ke
|
|||||||
|
|
||||||
size_t modulus_size = compute_modulus(NULL, sk);
|
size_t modulus_size = compute_modulus(NULL, sk);
|
||||||
if (modulus_size == 0) {
|
if (modulus_size == 0) {
|
||||||
Return RESULT_ERROR("compute_modulus", false);
|
Return RESULT_ERROR_LITERAL("compute_modulus");
|
||||||
}
|
}
|
||||||
void* modulus = malloc(modulus_size);
|
void* modulus = malloc(modulus_size);
|
||||||
bool success = false;
|
bool success = false;
|
||||||
@@ -67,12 +65,12 @@ Result(void) RSA_computePublicKey(const br_rsa_private_key* sk, br_rsa_public_ke
|
|||||||
free(modulus)
|
free(modulus)
|
||||||
);
|
);
|
||||||
if (compute_modulus(modulus, sk) != modulus_size) {
|
if (compute_modulus(modulus, sk) != modulus_size) {
|
||||||
Return RESULT_ERROR("compute_modulus", false);
|
Return RESULT_ERROR_LITERAL("compute_modulus");
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 pubexp_little_endian = compute_pubexp(sk);
|
u32 pubexp_little_endian = compute_pubexp(sk);
|
||||||
if (pubexp_little_endian == 0) {
|
if (pubexp_little_endian == 0) {
|
||||||
Return RESULT_ERROR("compute_pubexp", false);
|
Return RESULT_ERROR_LITERAL("compute_pubexp");
|
||||||
}
|
}
|
||||||
u8 pubexp_big_endian[4];
|
u8 pubexp_big_endian[4];
|
||||||
pubexp_big_endian[0] = pubexp_little_endian >> 24;
|
pubexp_big_endian[0] = pubexp_little_endian >> 24;
|
||||||
@@ -115,7 +113,7 @@ Result(void) RSA_parsePublicKey_base64(cstr src, br_rsa_public_key* pk){
|
|||||||
Deferral(4);
|
Deferral(4);
|
||||||
u32 n_bitlen = 0;
|
u32 n_bitlen = 0;
|
||||||
if(sscanf(src, "RSA-Public-%u:", &n_bitlen) != 1){
|
if(sscanf(src, "RSA-Public-%u:", &n_bitlen) != 1){
|
||||||
Return RESULT_ERROR("can't parse key size", false);
|
Return RESULT_ERROR_LITERAL("can't parse key size");
|
||||||
}
|
}
|
||||||
u32 key_buffer_size = BR_RSA_KBUF_PUB_SIZE(n_bitlen);
|
u32 key_buffer_size = BR_RSA_KBUF_PUB_SIZE(n_bitlen);
|
||||||
pk->n = malloc(key_buffer_size);
|
pk->n = malloc(key_buffer_size);
|
||||||
@@ -125,18 +123,18 @@ Result(void) RSA_parsePublicKey_base64(cstr src, br_rsa_public_key* pk){
|
|||||||
str src_str = str_from_cstr(src);
|
str src_str = str_from_cstr(src);
|
||||||
u32 offset = str_seekChar(src_str, ':', 10) + 1;
|
u32 offset = str_seekChar(src_str, ':', 10) + 1;
|
||||||
if(offset == 0){
|
if(offset == 0){
|
||||||
Return RESULT_ERROR("missing ':' before key data", false);
|
Return RESULT_ERROR_LITERAL("missing ':' before key data");
|
||||||
}
|
}
|
||||||
str key_base64_str = src_str;
|
str key_base64_str = src_str;
|
||||||
key_base64_str.data += offset;
|
key_base64_str.data += offset;
|
||||||
key_base64_str.size -= offset;
|
key_base64_str.len -= offset;
|
||||||
u32 decoded_size = base64_decodedSize(key_base64_str.data, key_base64_str.size);
|
u32 decoded_size = base64_decodedSize(key_base64_str.data, key_base64_str.len);
|
||||||
if(decoded_size != key_buffer_size){
|
if(decoded_size != key_buffer_size){
|
||||||
Return RESULT_ERROR_FMT("decoded key size is %u, must be %u", 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.size, pk->n);
|
decoded_size = base64_decode(key_base64_str.data, key_base64_str.len, pk->n);
|
||||||
if(decoded_size != key_buffer_size){
|
if(decoded_size != key_buffer_size){
|
||||||
Return RESULT_ERROR("key decoding failed", false);
|
Return RESULT_ERROR_LITERAL("key decoding failed");
|
||||||
}
|
}
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
@@ -145,7 +143,7 @@ Result(void) RSA_parsePrivateKey_base64(cstr src, br_rsa_private_key* sk){
|
|||||||
Deferral(4);
|
Deferral(4);
|
||||||
u32 n_bitlen = 0;
|
u32 n_bitlen = 0;
|
||||||
if(sscanf(src, "RSA-Private-%u:", &n_bitlen) != 1){
|
if(sscanf(src, "RSA-Private-%u:", &n_bitlen) != 1){
|
||||||
Return RESULT_ERROR("can't parse key size", false);
|
Return RESULT_ERROR_LITERAL("can't parse key size");
|
||||||
}
|
}
|
||||||
sk->n_bitlen = n_bitlen;
|
sk->n_bitlen = n_bitlen;
|
||||||
u32 key_buffer_size = BR_RSA_KBUF_PRIV_SIZE(n_bitlen);
|
u32 key_buffer_size = BR_RSA_KBUF_PRIV_SIZE(n_bitlen);
|
||||||
@@ -159,18 +157,18 @@ Result(void) RSA_parsePrivateKey_base64(cstr src, br_rsa_private_key* sk){
|
|||||||
str src_str = str_from_cstr(src);
|
str src_str = str_from_cstr(src);
|
||||||
u32 offset = str_seekChar(src_str, ':', 10) + 1;
|
u32 offset = str_seekChar(src_str, ':', 10) + 1;
|
||||||
if(offset == 0){
|
if(offset == 0){
|
||||||
Return RESULT_ERROR("missing ':' before key data", false);
|
Return RESULT_ERROR_LITERAL("missing ':' before key data");
|
||||||
}
|
}
|
||||||
str key_base64_str = src_str;
|
str key_base64_str = src_str;
|
||||||
key_base64_str.data += offset;
|
key_base64_str.data += offset;
|
||||||
key_base64_str.size -= offset;
|
key_base64_str.len -= offset;
|
||||||
u32 decoded_size = base64_decodedSize(key_base64_str.data, key_base64_str.size);
|
u32 decoded_size = base64_decodedSize(key_base64_str.data, key_base64_str.len);
|
||||||
if(decoded_size != key_buffer_size){
|
if(decoded_size != key_buffer_size){
|
||||||
Return RESULT_ERROR_FMT("decoded key size is %u, must be %u", 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.size, sk->p);
|
decoded_size = base64_decode(key_base64_str.data, key_base64_str.len, sk->p);
|
||||||
if(decoded_size != key_buffer_size){
|
if(decoded_size != key_buffer_size){
|
||||||
Return RESULT_ERROR("key decoding failed", false);
|
Return RESULT_ERROR_LITERAL("key decoding failed");
|
||||||
}
|
}
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
@@ -189,23 +187,23 @@ void RSAEncryptor_construct(RSAEncryptor* ptr, const br_rsa_public_key* pk){
|
|||||||
Result(u32) RSAEncryptor_encrypt(RSAEncryptor* ptr, Array(u8) src, Array(u8) dst){
|
Result(u32) RSAEncryptor_encrypt(RSAEncryptor* ptr, Array(u8) src, Array(u8) dst){
|
||||||
u32 key_size_bytes = ptr->pk->nlen;
|
u32 key_size_bytes = ptr->pk->nlen;
|
||||||
const u32 max_src_size = RSAEncryptor_calcMaxSrcSize(key_size_bytes * 8, 256);
|
const u32 max_src_size = RSAEncryptor_calcMaxSrcSize(key_size_bytes * 8, 256);
|
||||||
if(src.size > max_src_size){
|
if(src.len > max_src_size){
|
||||||
return RESULT_ERROR_FMT("src.size (%u) must be <= RSAEncryptor_calcMaxSrcSize() (%u)",
|
return RESULT_ERROR_FMT("src.len (%u) must be <= RSAEncryptor_calcMaxSrcSize() (%u)",
|
||||||
src.size, max_src_size);
|
src.len, max_src_size);
|
||||||
}
|
}
|
||||||
if(dst.size < key_size_bytes){
|
if(dst.len < key_size_bytes){
|
||||||
return RESULT_ERROR_FMT("dst.size (%u) must be >= key length in bytes (%u)",
|
return RESULT_ERROR_FMT("dst.len (%u) must be >= key length in bytes (%u)",
|
||||||
dst.size, key_size_bytes);
|
dst.len, key_size_bytes);
|
||||||
}
|
}
|
||||||
size_t sz = br_rsa_i31_oaep_encrypt(
|
size_t sz = br_rsa_i31_oaep_encrypt(
|
||||||
&ptr->rng.vtable, &br_sha256_vtable,
|
&ptr->rng.vtable, &br_sha256_vtable,
|
||||||
NULL, 0,
|
NULL, 0,
|
||||||
ptr->pk,
|
ptr->pk,
|
||||||
dst.data, dst.size,
|
dst.data, dst.len,
|
||||||
src.data, src.size);
|
src.data, src.len);
|
||||||
|
|
||||||
if(sz == 0){
|
if(sz == 0){
|
||||||
return RESULT_ERROR("RSA encryption failed", false);
|
return RESULT_ERROR_LITERAL("RSA encryption failed");
|
||||||
}
|
}
|
||||||
return RESULT_VALUE(u, sz);
|
return RESULT_VALUE(u, sz);
|
||||||
}
|
}
|
||||||
@@ -221,12 +219,12 @@ void RSADecryptor_construct(RSADecryptor* ptr, const br_rsa_private_key* sk){
|
|||||||
|
|
||||||
Result(u32) RSADecryptor_decrypt(RSADecryptor* ptr, Array(u8) buffer){
|
Result(u32) RSADecryptor_decrypt(RSADecryptor* ptr, Array(u8) buffer){
|
||||||
u32 key_size_bits = ptr->sk->n_bitlen;
|
u32 key_size_bits = ptr->sk->n_bitlen;
|
||||||
if(buffer.size != key_size_bits/8){
|
if(buffer.len != key_size_bits/8){
|
||||||
return RESULT_ERROR_FMT("buffer.size (%u) must be == key length in bytes (%u)",
|
return RESULT_ERROR_FMT("buffer.len (%u) must be == key length in bytes (%u)",
|
||||||
buffer.size, key_size_bits/8);
|
buffer.len, key_size_bits/8);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t sz = buffer.size;
|
size_t sz = buffer.len;
|
||||||
size_t r = br_rsa_i31_oaep_decrypt(
|
size_t r = br_rsa_i31_oaep_decrypt(
|
||||||
&br_sha256_vtable,
|
&br_sha256_vtable,
|
||||||
NULL, 0,
|
NULL, 0,
|
||||||
@@ -234,7 +232,7 @@ Result(u32) RSADecryptor_decrypt(RSADecryptor* ptr, Array(u8) buffer){
|
|||||||
buffer.data, &sz);
|
buffer.data, &sz);
|
||||||
|
|
||||||
if(r == 0){
|
if(r == 0){
|
||||||
return RESULT_ERROR("RSA encryption failed", false);
|
return RESULT_ERROR_LITERAL("RSA encryption failed");
|
||||||
}
|
}
|
||||||
return RESULT_VALUE(u, sz);
|
return RESULT_VALUE(u, sz);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/collections/Array.h"
|
#include "tlibc/collections/Array.h"
|
||||||
#include "tlibc/errors.h"
|
#include "tlibc/collections/Array_impl/Array_u8.h"
|
||||||
#include "bearssl_rand.h"
|
#include "bearssl_rand.h"
|
||||||
|
#include "bearssl_hash.h"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
@@ -11,12 +13,11 @@
|
|||||||
|
|
||||||
/// @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 salt some byte array
|
||||||
/// @param out_buffer u8[PASSWORD_HASH_SIZE]
|
/// @param out_buffer u8[PASSWORD_HASH_SIZE]
|
||||||
/// @param iterations number of iterations
|
/// @param rounds number of rounds
|
||||||
void hash_password(Array(u8) password, u8* out_buffer, i32 iterations);
|
void hash_password(Array(u8) password, Array(u8) salt, u8* out_buffer, i32 rounds);
|
||||||
#define PASSWORD_HASH_SIZE 32
|
#define PASSWORD_HASH_LVL_ROUNDS 1e5
|
||||||
|
|
||||||
#define __PASSWORD_HASH_LVL_ITERATIONS 1e5
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// //
|
// //
|
||||||
@@ -27,6 +28,7 @@ void hash_password(Array(u8) password, u8* out_buffer, i32 iterations);
|
|||||||
/// @brief Initialize prng context with sha256 hashing algorithm
|
/// @brief Initialize prng context with sha256 hashing algorithm
|
||||||
/// and seed from system-provided cryptographic random bytes source.
|
/// and seed from system-provided cryptographic random bytes source.
|
||||||
/// @param rng_vtable_ptr pointer to vtable field in prng context. The field must be initialized.
|
/// @param rng_vtable_ptr pointer to vtable field in prng context. The field must be initialized.
|
||||||
|
///
|
||||||
/// EXAMPLE:
|
/// EXAMPLE:
|
||||||
/// ```
|
/// ```
|
||||||
/// br_hmac_drbg_context rng_ctx = { .vtable = &br_hmac_drbg_vtable };
|
/// br_hmac_drbg_context rng_ctx = { .vtable = &br_hmac_drbg_vtable };
|
||||||
@@ -36,6 +38,7 @@ void rng_init_sha256_seedFromSystem(const br_prng_class** rng_vtable_ptr);
|
|||||||
|
|
||||||
/// @brief Initialize prng context with sha256 hashing algorithm and seed from CLOCK_REALTIME.
|
/// @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 rng_vtable_ptr pointer to vtable field in prng context. The field must be initialized.
|
||||||
|
///
|
||||||
/// EXAMPLE:
|
/// EXAMPLE:
|
||||||
/// ```
|
/// ```
|
||||||
/// br_hmac_drbg_context rng_ctx = { .vtable = &br_hmac_drbg_vtable };
|
/// br_hmac_drbg_context rng_ctx = { .vtable = &br_hmac_drbg_vtable };
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#include "cryptography.h"
|
#include "cryptography.h"
|
||||||
#include "bearssl_hash.h"
|
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
|
|
||||||
void hash_password(Array(u8) password, u8* out_buffer, i32 iterations){
|
void hash_password(Array(u8) password, Array(u8) salt, u8* out_buffer, i32 iterations){
|
||||||
assert(PASSWORD_HASH_SIZE == br_sha256_SIZE);;
|
assert(PASSWORD_HASH_SIZE == br_sha256_SIZE);;
|
||||||
memset(out_buffer, 0, br_sha256_SIZE);
|
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_update(&sha256_ctx, salt.data, salt.len);
|
||||||
br_sha256_out(&sha256_ctx, out_buffer);
|
br_sha256_out(&sha256_ctx, out_buffer);
|
||||||
br_sha256_update(&sha256_ctx, out_buffer, PASSWORD_HASH_SIZE);
|
br_sha256_update(&sha256_ctx, out_buffer, PASSWORD_HASH_SIZE);
|
||||||
}
|
}
|
||||||
|
|||||||
312
src/db/idb.c
312
src/db/idb.c
@@ -1,312 +0,0 @@
|
|||||||
#include "idb.h"
|
|
||||||
#include "magic.h"
|
|
||||||
#include "tlibc/filesystem.h"
|
|
||||||
#include "tlibc/collections/HashMap.h"
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
typedef struct TableFileHeader {
|
|
||||||
Magic32 magic;
|
|
||||||
u16 version;
|
|
||||||
bool _dirty_bit;
|
|
||||||
u32 row_size;
|
|
||||||
} ATTRIBUTE_ALIGNED(256) TableFileHeader;
|
|
||||||
|
|
||||||
typedef struct Table {
|
|
||||||
TableFileHeader header;
|
|
||||||
IncrementalDB* db;
|
|
||||||
str name;
|
|
||||||
str table_file_path;
|
|
||||||
str changes_file_path;
|
|
||||||
FILE* table_file;
|
|
||||||
FILE* changes_file;
|
|
||||||
pthread_mutex_t mutex;
|
|
||||||
u64 row_count;
|
|
||||||
} Table;
|
|
||||||
|
|
||||||
typedef struct IncrementalDB {
|
|
||||||
str db_dir;
|
|
||||||
HashMap(Table**) tables_map;
|
|
||||||
pthread_mutex_t mutex;
|
|
||||||
} IncrementalDB;
|
|
||||||
|
|
||||||
static const Magic32 TABLE_FILE_MAGIC = { .bytes = { 'I', 'D', 'B', 't' } };
|
|
||||||
|
|
||||||
|
|
||||||
void Table_close(Table* t){
|
|
||||||
fclose(t->table_file);
|
|
||||||
fclose(t->changes_file);
|
|
||||||
free(t->name.data);
|
|
||||||
free(t->table_file_path.data);
|
|
||||||
free(t->changes_file_path.data);
|
|
||||||
pthread_mutex_destroy(&t->mutex);
|
|
||||||
free(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// element destructor for HashMap(Table*)
|
|
||||||
void TablePtr_destroy(void* t_ptr_ptr){
|
|
||||||
Table_close(*(Table**)t_ptr_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @param name must be null-terminated
|
|
||||||
Result(void) validateTableName(str name){
|
|
||||||
char forbidden_characters[] = { '/', '\\', ':', ';', '?', '"', '\'', '\n', '\r', '\t'};
|
|
||||||
for(u32 i = 0; i < ARRAY_LEN(forbidden_characters); i++) {
|
|
||||||
char c = forbidden_characters[i];
|
|
||||||
if(str_seekChar(name, c, 0) != -1){
|
|
||||||
return RESULT_ERROR_FMT(
|
|
||||||
"Table name '%s' contains forbidden character '%c'",
|
|
||||||
name.data, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Table_readHeader(Table* t){
|
|
||||||
Deferral(4);
|
|
||||||
// seek for start of the file
|
|
||||||
try_void(file_seek(t->table_file, 0, SeekOrigin_Start));
|
|
||||||
// read header
|
|
||||||
try_void(file_readStructsExactly(t->table_file, &t->header, sizeof(t->header), 1));
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Table_writeHeader(Table* t){
|
|
||||||
Deferral(4);
|
|
||||||
// seek for start of the file
|
|
||||||
try_void(file_seek(t->table_file, 0, SeekOrigin_Start));
|
|
||||||
// write header
|
|
||||||
try_void(file_writeStructs(t->table_file, &t->header, sizeof(t->header), 1));
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Table_setDirtyBit(Table* t, bool val){
|
|
||||||
Deferral(4);
|
|
||||||
t->header._dirty_bit = val;
|
|
||||||
try_void(Table_writeHeader(t));
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(bool) Table_getDirtyBit(Table* t){
|
|
||||||
Deferral(4);
|
|
||||||
try_void(Table_readHeader(t));
|
|
||||||
Return RESULT_VALUE(i, t->header._dirty_bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Table_calculateRowCount(Table* t){
|
|
||||||
Deferral(4);
|
|
||||||
try(i64 file_size, i, file_getSize(t->table_file));
|
|
||||||
i64 data_size = file_size - sizeof(t->header);
|
|
||||||
if(data_size % t->header.row_size != 0){
|
|
||||||
//TODO: fix table instead of trowing error
|
|
||||||
Return RESULT_ERROR_FMT(
|
|
||||||
"Table '%s' has invalid size. Last row is incomplete",
|
|
||||||
t->name.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
t->row_count = data_size / t->header.row_size;
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Table_validateHeader(Table* t){
|
|
||||||
Deferral(4);
|
|
||||||
if(t->header.magic.n != TABLE_FILE_MAGIC.n
|
|
||||||
|| t->header.row_size == 0)
|
|
||||||
{
|
|
||||||
Return RESULT_ERROR_FMT(
|
|
||||||
"Table file '%s' has invalid header",
|
|
||||||
t->table_file_path.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: check version
|
|
||||||
|
|
||||||
try(bool dirty_bit, i, Table_getDirtyBit(t));
|
|
||||||
if(dirty_bit){
|
|
||||||
//TODO: handle dirty bit instead of throwing error
|
|
||||||
Return RESULT_ERROR_FMT(
|
|
||||||
"Table file '%s' has dirty bit set",
|
|
||||||
t->table_file_path.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) Table_validateRowSize(Table* t, u32 row_size){
|
|
||||||
if(row_size != t->header.row_size){
|
|
||||||
ResultVar(void) error_result = RESULT_ERROR_FMT(
|
|
||||||
"Requested row size (%u) doesn't match saved row size (%u)",
|
|
||||||
row_size, t->header.row_size);
|
|
||||||
return error_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(IncrementalDB*) idb_open(str db_dir){
|
|
||||||
Deferral(16);
|
|
||||||
IncrementalDB* db = (IncrementalDB*)malloc(sizeof(IncrementalDB));
|
|
||||||
// if object construction fails, destroy incomplete object
|
|
||||||
bool success = false;
|
|
||||||
Defer(if(!success) idb_close(db));
|
|
||||||
|
|
||||||
// value of *db must be set to zero or behavior of idb_close will be undefined
|
|
||||||
memset(db, 0, sizeof(IncrementalDB));
|
|
||||||
db->db_dir = str_copy(db_dir);
|
|
||||||
try_void(dir_create(db->db_dir.data));
|
|
||||||
HashMap_construct(&db->tables_map, Table*, TablePtr_destroy);
|
|
||||||
try_stderrcode(pthread_mutex_init(&db->mutex, NULL));
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VALUE(p, db);
|
|
||||||
}
|
|
||||||
|
|
||||||
void idb_close(IncrementalDB* db){
|
|
||||||
free(db->db_dir.data);
|
|
||||||
HashMap_destroy(&db->tables_map);
|
|
||||||
pthread_mutex_destroy(&db->mutex);
|
|
||||||
free(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(Table*) idb_getOrCreateTable(IncrementalDB* db, str _table_name, u32 row_size){
|
|
||||||
Deferral(16);
|
|
||||||
// db lock
|
|
||||||
try_stderrcode(pthread_mutex_lock(&db->mutex));
|
|
||||||
Defer(pthread_mutex_unlock(&db->mutex));
|
|
||||||
|
|
||||||
Table** tpp = HashMap_tryGetPtr(&db->tables_map, _table_name);
|
|
||||||
if(tpp != NULL){
|
|
||||||
Table* existing_table = *tpp;
|
|
||||||
try_void(Table_validateRowSize(existing_table, row_size));
|
|
||||||
Return RESULT_VALUE(p, existing_table);
|
|
||||||
}
|
|
||||||
|
|
||||||
try_void(validateTableName(_table_name));
|
|
||||||
|
|
||||||
Table* t = (Table*)malloc(sizeof(Table));
|
|
||||||
// if object construction fails, destroy incomplete object
|
|
||||||
bool success = false;
|
|
||||||
Defer(if(!success) Table_close(t));
|
|
||||||
|
|
||||||
// value of *t must be set to zero or behavior of Table_close will be undefined
|
|
||||||
memset(t, 0, sizeof(Table));
|
|
||||||
t->db = db;
|
|
||||||
try_stderrcode(pthread_mutex_init(&t->mutex, NULL));
|
|
||||||
t->name = str_copy(_table_name);
|
|
||||||
t->table_file_path = str_from_cstr(
|
|
||||||
strcat_malloc(db->db_dir.data, path_seps, t->name.data, ".idb-table"));
|
|
||||||
t->changes_file_path = str_from_cstr(
|
|
||||||
strcat_malloc(db->db_dir.data, path_seps, t->name.data, ".idb-changes"));
|
|
||||||
|
|
||||||
bool table_exists = file_exists(t->table_file_path.data);
|
|
||||||
|
|
||||||
// open or create file with table data
|
|
||||||
try(t->table_file, p, file_openOrCreateReadWrite(t->table_file_path.data));
|
|
||||||
// open or create file with backups of updated rows
|
|
||||||
try(t->changes_file, p, file_openOrCreateReadWrite(t->changes_file_path.data));
|
|
||||||
|
|
||||||
if(table_exists){
|
|
||||||
// read table file
|
|
||||||
try_void(Table_readHeader(t));
|
|
||||||
try_void(Table_validateHeader(t));
|
|
||||||
try_void(Table_validateRowSize(t, row_size));
|
|
||||||
try_void(Table_calculateRowCount(t));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// create table file
|
|
||||||
t->header.magic.n = TABLE_FILE_MAGIC.n;
|
|
||||||
t->header.row_size = row_size;
|
|
||||||
t->header.version = IDB_VERSION;
|
|
||||||
try_void(Table_writeHeader(t));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!HashMap_tryPush(&db->tables_map, t->name, &t)){
|
|
||||||
ResultVar(void) error_result = RESULT_ERROR_FMT(
|
|
||||||
"Table '%s' is already open",
|
|
||||||
t->name.data);
|
|
||||||
Return error_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VALUE(p, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) idb_getRows(Table* t, u64 id, void* dst, u64 count){
|
|
||||||
Deferral(8);
|
|
||||||
// table lock
|
|
||||||
try_stderrcode(pthread_mutex_lock(&t->mutex));
|
|
||||||
Defer(pthread_mutex_unlock(&t->mutex));
|
|
||||||
|
|
||||||
if(id + count > t->row_count){
|
|
||||||
Return RESULT_ERROR_FMT(
|
|
||||||
"Can't read " IFWIN("%llu", "%lu") " rows at index " IFWIN("%llu", "%lu")
|
|
||||||
" because table '%s' has only " IFWIN("%llu", "%lu") " rows",
|
|
||||||
count, id, t->name.data, t->row_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
i64 file_pos = sizeof(t->header) + id * t->header.row_size;
|
|
||||||
|
|
||||||
// seek for the row position in file
|
|
||||||
try_void(file_seek(t->table_file, file_pos, SeekOrigin_Start));
|
|
||||||
// read rows from file
|
|
||||||
try_void(file_readStructsExactly(t->table_file, dst, t->header.row_size, count));
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(void) idb_updateRows(Table* t, u64 id, const void* src, u64 count){
|
|
||||||
Deferral(8);
|
|
||||||
// table lock
|
|
||||||
try_stderrcode(pthread_mutex_lock(&t->mutex));
|
|
||||||
Defer(pthread_mutex_unlock(&t->mutex));
|
|
||||||
|
|
||||||
if(id + count >= t->row_count){
|
|
||||||
Return RESULT_ERROR_FMT(
|
|
||||||
"Can't update " IFWIN("%llu", "%lu") " rows at index " IFWIN("%llu", "%lu")
|
|
||||||
" because table '%s' has only " IFWIN("%llu", "%lu") " rows",
|
|
||||||
count, id, t->name.data, t->row_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
try_void(Table_setDirtyBit(t, true));
|
|
||||||
Defer(IGNORE_RESULT Table_setDirtyBit(t, false));
|
|
||||||
|
|
||||||
i64 file_pos = sizeof(t->header) + id * t->header.row_size;
|
|
||||||
|
|
||||||
// TODO: set dirty bit in backup file too
|
|
||||||
// TODO: save old values to the backup file
|
|
||||||
|
|
||||||
// seek for the row position in file
|
|
||||||
try_void(file_seek(t->table_file, file_pos, SeekOrigin_Start));
|
|
||||||
// replace rows in file
|
|
||||||
try_void(file_writeStructs(t->table_file, src, t->header.row_size, count));
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(u64) idb_pushRows(Table* t, const void* src, u64 count){
|
|
||||||
Deferral(8);
|
|
||||||
// table lock
|
|
||||||
try_stderrcode(pthread_mutex_lock(&t->mutex));
|
|
||||||
Defer(pthread_mutex_unlock(&t->mutex));
|
|
||||||
|
|
||||||
try_void(Table_setDirtyBit(t, true));
|
|
||||||
Defer(IGNORE_RESULT Table_setDirtyBit(t, false));
|
|
||||||
|
|
||||||
const u64 new_row_index = t->row_count;
|
|
||||||
|
|
||||||
// seek for end of the file
|
|
||||||
try_void(file_seek(t->table_file, 0, SeekOrigin_End));
|
|
||||||
// write new rows to the file
|
|
||||||
try_void(file_writeStructs(t->table_file, src, t->header.row_size, count));
|
|
||||||
|
|
||||||
t->row_count += count;
|
|
||||||
Return RESULT_VALUE(u, new_row_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(u64) idb_getRowCount(Table* t){
|
|
||||||
Deferral(4);
|
|
||||||
// table lock
|
|
||||||
try_stderrcode(pthread_mutex_lock(&t->mutex));
|
|
||||||
Defer(pthread_mutex_unlock(&t->mutex));
|
|
||||||
u64 count = t->row_count;
|
|
||||||
Return RESULT_VALUE(u, count);
|
|
||||||
}
|
|
||||||
27
src/db/idb.h
27
src/db/idb.h
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "tlibc/errors.h"
|
|
||||||
|
|
||||||
#define IDB_VERSION 1
|
|
||||||
#define IDB_AES_KEY_SIZE 32
|
|
||||||
|
|
||||||
typedef struct IncrementalDB IncrementalDB;
|
|
||||||
typedef struct Table Table;
|
|
||||||
|
|
||||||
|
|
||||||
Result(IncrementalDB*) idb_open(str db_dir);
|
|
||||||
void idb_close(IncrementalDB* db);
|
|
||||||
|
|
||||||
|
|
||||||
Result(Table*) idb_getOrCreateTable(IncrementalDB* db, str _table_name, u32 row_size);
|
|
||||||
|
|
||||||
Result(void) idb_getRows(Table* t, u64 id, void* dst, u64 count);
|
|
||||||
#define idb_getRow(T, ID, DST) idb_getRows(T, ID, DST, 1)
|
|
||||||
|
|
||||||
Result(u64) idb_pushRows(Table* t, const void* src, u64 count);
|
|
||||||
#define idb_pushRow(T, SRC) idb_pushRows(T, SRC, 1)
|
|
||||||
|
|
||||||
Result(void) idb_updateRows(Table* t, u64 id, const void* src, u64 count);
|
|
||||||
#define idb_updateRow(T, ID, SRC) idb_updateRows(T, ID, SRC, 1)
|
|
||||||
|
|
||||||
Result(u64) idb_getRowCount(Table* t);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#define log(context, severity, format, ...) printf("[%s/" severity "]: " format "\n", context ,##__VA_ARGS__)
|
|
||||||
#define logDebug(context, format, ...) log(context, "DEBUG", format ,##__VA_ARGS__)
|
|
||||||
#define logInfo(context, format, ...) log(context, "INFO", format ,##__VA_ARGS__)
|
|
||||||
#define logWarn(context, format, ...) log(context, "WARN", format ,##__VA_ARGS__)
|
|
||||||
#define logError(context, format, ...) log(context, "ERROR", format ,##__VA_ARGS__)
|
|
||||||
12
src/magic.h
12
src/magic.h
@@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/std.h"
|
|
||||||
|
|
||||||
typedef union Magic32 {
|
|
||||||
u32 n;
|
|
||||||
u8 bytes[4];
|
|
||||||
} Magic32;
|
|
||||||
|
|
||||||
typedef union Magic64 {
|
|
||||||
u64 n;
|
|
||||||
u8 bytes[8];
|
|
||||||
} Magic64;
|
|
||||||
@@ -10,16 +10,16 @@ void EncryptedSocketTCP_construct(EncryptedSocketTCP* ptr,
|
|||||||
ptr->sock = sock;
|
ptr->sock = sock;
|
||||||
AESStreamEncryptor_construct(&ptr->enc, aes_key, AESStream_DEFAULT_CLASS);
|
AESStreamEncryptor_construct(&ptr->enc, aes_key, AESStream_DEFAULT_CLASS);
|
||||||
AESStreamDecryptor_construct(&ptr->dec, aes_key, AESStream_DEFAULT_CLASS);
|
AESStreamDecryptor_construct(&ptr->dec, aes_key, AESStream_DEFAULT_CLASS);
|
||||||
ptr->recv_buf = Array_alloc_size(crypto_buffer_size);
|
ptr->recv_buf = Array_u8_alloc(crypto_buffer_size);
|
||||||
ptr->send_buf = Array_alloc_size(crypto_buffer_size);
|
ptr->send_buf = Array_u8_alloc(crypto_buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncryptedSocketTCP_destroy(EncryptedSocketTCP* ptr){
|
void EncryptedSocketTCP_destroy(EncryptedSocketTCP* ptr){
|
||||||
if(!ptr)
|
if(!ptr)
|
||||||
return;
|
return;
|
||||||
socket_close(ptr->sock);
|
socket_close(ptr->sock);
|
||||||
free(ptr->recv_buf.data);
|
Array_u8_destroy(&ptr->recv_buf);
|
||||||
free(ptr->send_buf.data);
|
Array_u8_destroy(&ptr->send_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncryptedSocketTCP_changeKey(EncryptedSocketTCP* ptr, Array(u8) aes_key){
|
void EncryptedSocketTCP_changeKey(EncryptedSocketTCP* ptr, Array(u8) aes_key){
|
||||||
@@ -42,10 +42,11 @@ Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr,
|
|||||||
try_void(
|
try_void(
|
||||||
socket_send(
|
socket_send(
|
||||||
ptr->sock,
|
ptr->sock,
|
||||||
Array_sliceTo(ptr->send_buf, encrypted_size)
|
Array_u8_sliceTo(ptr->send_buf, encrypted_size)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// printf("SEND data_size: %u, enc_size: %u\n", buffer.len, encrypted_size);
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
|
|||||||
{
|
{
|
||||||
Deferral(1);
|
Deferral(1);
|
||||||
|
|
||||||
u32 size_to_receive = buffer.size;
|
u32 size_to_receive = buffer.len;
|
||||||
if(ptr->dec.block_counter == 0){
|
if(ptr->dec.block_counter == 0){
|
||||||
// There is some metadata at the beginning of AES stream
|
// There is some metadata at the beginning of AES stream
|
||||||
size_to_receive = AESStreamEncryptor_calcDstSize(size_to_receive);
|
size_to_receive = AESStreamEncryptor_calcDstSize(size_to_receive);
|
||||||
@@ -62,18 +63,19 @@ Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
|
|||||||
try(i32 received_size, i,
|
try(i32 received_size, i,
|
||||||
socket_recv(
|
socket_recv(
|
||||||
ptr->sock,
|
ptr->sock,
|
||||||
Array_sliceTo(ptr->recv_buf, size_to_receive),
|
Array_u8_sliceTo(ptr->recv_buf, size_to_receive),
|
||||||
flags
|
flags
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
try(u32 decrypted_size, u,
|
try(u32 decrypted_size, u,
|
||||||
AESStreamDecryptor_decrypt(
|
AESStreamDecryptor_decrypt(
|
||||||
&ptr->dec,
|
&ptr->dec,
|
||||||
Array_sliceTo(ptr->recv_buf, received_size),
|
Array_u8_sliceTo(ptr->recv_buf, received_size),
|
||||||
buffer
|
buffer
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// printf("RECV recv_size: %u, dec_size: %u\n", received_size, decrypted_size);
|
||||||
Return RESULT_VALUE(u, decrypted_size);
|
Return RESULT_VALUE(u, decrypted_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +94,11 @@ Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
|
|||||||
try_void(
|
try_void(
|
||||||
socket_send(
|
socket_send(
|
||||||
ptr->sock,
|
ptr->sock,
|
||||||
Array_sliceTo(ptr->send_buf, encrypted_size)
|
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;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,34 +119,36 @@ Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr,
|
|||||||
try(i32 received_size, i,
|
try(i32 received_size, i,
|
||||||
socket_recv(
|
socket_recv(
|
||||||
ptr->sock,
|
ptr->sock,
|
||||||
Array_sliceTo(ptr->recv_buf, size_to_receive),
|
Array_u8_sliceTo(ptr->recv_buf, size_to_receive),
|
||||||
flags
|
flags
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
try(u32 decrypted_size, u,
|
try(u32 decrypted_size, u,
|
||||||
RSADecryptor_decrypt(
|
RSADecryptor_decrypt(
|
||||||
rsa_dec,
|
rsa_dec,
|
||||||
Array_sliceTo(ptr->recv_buf, received_size)
|
Array_u8_sliceTo(ptr->recv_buf, received_size)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if(fill_whole_buffer){
|
if(fill_whole_buffer){
|
||||||
if(decrypted_size != buffer.size){
|
if(decrypted_size != buffer.len){
|
||||||
Return RESULT_ERROR_FMT(
|
Return RESULT_ERROR_FMT(
|
||||||
"SocketRecvFlag_WholeBuffer is set, "
|
"SocketRecvFlag_WholeBuffer is set, "
|
||||||
"but decrypted_size (%u) != buffer.size (%u)",
|
"but decrypted_size (%u) != buffer.len (%u)",
|
||||||
decrypted_size, buffer.size
|
decrypted_size, buffer.len
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(decrypted_size > buffer.size){
|
else if(decrypted_size > buffer.len){
|
||||||
Return RESULT_ERROR_FMT(
|
Return RESULT_ERROR_FMT(
|
||||||
"decrypted_size (%u) > buffer.size (%u)",
|
"decrypted_size (%u) > buffer.len (%u)",
|
||||||
decrypted_size, buffer.size
|
decrypted_size, buffer.len
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(buffer.data, ptr->recv_buf.data, decrypted_size);
|
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);
|
Return RESULT_VALUE(u, decrypted_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,17 +163,16 @@ void EncryptedSocketUDP_construct(EncryptedSocketUDP* ptr,
|
|||||||
ptr->sock = sock;
|
ptr->sock = sock;
|
||||||
AESBlockEncryptor_construct(&ptr->enc, aes_key, AESBlockEncryptor_DEFAULT_CLASS);
|
AESBlockEncryptor_construct(&ptr->enc, aes_key, AESBlockEncryptor_DEFAULT_CLASS);
|
||||||
AESBlockDecryptor_construct(&ptr->dec, aes_key, AESBlockDecryptor_DEFAULT_CLASS);
|
AESBlockDecryptor_construct(&ptr->dec, aes_key, AESBlockDecryptor_DEFAULT_CLASS);
|
||||||
ptr->recv_buf = Array_alloc_size(crypto_buffer_size);
|
ptr->recv_buf = Array_u8_alloc(crypto_buffer_size);
|
||||||
ptr->send_buf = Array_alloc_size(crypto_buffer_size);
|
ptr->send_buf = Array_u8_alloc(crypto_buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncryptedSocketUDP_destroy(EncryptedSocketUDP* ptr){
|
void EncryptedSocketUDP_destroy(EncryptedSocketUDP* ptr){
|
||||||
if(!ptr)
|
if(!ptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
socket_close(ptr->sock);
|
socket_close(ptr->sock);
|
||||||
free(ptr->recv_buf.data);
|
Array_u8_destroy(&ptr->recv_buf);
|
||||||
free(ptr->send_buf.data);
|
Array_u8_destroy(&ptr->send_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncryptedSocketUDP_changeKey(EncryptedSocketUDP* ptr, Array(u8) aes_key){
|
void EncryptedSocketUDP_changeKey(EncryptedSocketUDP* ptr, Array(u8) aes_key){
|
||||||
@@ -191,7 +195,7 @@ Result(void) EncryptedSocketUDP_sendto(EncryptedSocketUDP* ptr,
|
|||||||
try_void(
|
try_void(
|
||||||
socket_sendto(
|
socket_sendto(
|
||||||
ptr->sock,
|
ptr->sock,
|
||||||
Array_sliceTo(ptr->send_buf, encrypted_size),
|
Array_u8_sliceTo(ptr->send_buf, encrypted_size),
|
||||||
remote_end
|
remote_end
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -205,11 +209,11 @@ Result(i32) EncryptedSocketUDP_recvfrom(EncryptedSocketUDP* ptr,
|
|||||||
Deferral(1);
|
Deferral(1);
|
||||||
|
|
||||||
// There is some metadata at the start of each AES block
|
// There is some metadata at the start of each AES block
|
||||||
u32 size_to_receive = AESBlockEncryptor_calcDstSize(buffer.size);
|
u32 size_to_receive = AESBlockEncryptor_calcDstSize(buffer.len);
|
||||||
try(i32 received_size, i,
|
try(i32 received_size, i,
|
||||||
socket_recvfrom(
|
socket_recvfrom(
|
||||||
ptr->sock,
|
ptr->sock,
|
||||||
Array_sliceTo(ptr->recv_buf, size_to_receive),
|
Array_u8_sliceTo(ptr->recv_buf, size_to_receive),
|
||||||
flags,
|
flags,
|
||||||
remote_end
|
remote_end
|
||||||
)
|
)
|
||||||
@@ -217,7 +221,7 @@ Result(i32) EncryptedSocketUDP_recvfrom(EncryptedSocketUDP* ptr,
|
|||||||
try(u32 decrypted_size, u,
|
try(u32 decrypted_size, u,
|
||||||
AESBlockDecryptor_decrypt(
|
AESBlockDecryptor_decrypt(
|
||||||
&ptr->dec,
|
&ptr->dec,
|
||||||
Array_sliceTo(ptr->recv_buf, received_size),
|
Array_u8_sliceTo(ptr->recv_buf, received_size),
|
||||||
buffer
|
buffer
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr,
|
|||||||
|
|
||||||
#define EncryptedSocketTCP_sendStruct(socket, structPtr)\
|
#define EncryptedSocketTCP_sendStruct(socket, structPtr)\
|
||||||
EncryptedSocketTCP_send(socket,\
|
EncryptedSocketTCP_send(socket,\
|
||||||
Array_construct_size(structPtr, sizeof(*structPtr)))
|
struct_castTo_Array_u8(structPtr))
|
||||||
|
|
||||||
Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
|
Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
|
||||||
Array(u8) buffer, SocketRecvFlag flags);
|
Array(u8) buffer, SocketRecvFlag flags);
|
||||||
|
|
||||||
#define EncryptedSocketTCP_recvStruct(socket, structPtr)\
|
#define EncryptedSocketTCP_recvStruct(socket, structPtr)\
|
||||||
EncryptedSocketTCP_recv(socket,\
|
EncryptedSocketTCP_recv(socket,\
|
||||||
Array_construct_size(structPtr, sizeof(*structPtr)),\
|
struct_castTo_Array_u8(structPtr),\
|
||||||
SocketRecvFlag_WholeBuffer)
|
SocketRecvFlag_WholeBuffer)
|
||||||
|
|
||||||
Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
|
Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
|
||||||
@@ -43,14 +43,14 @@ Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
|
|||||||
|
|
||||||
#define EncryptedSocketTCP_sendStructRSA(socket, rsa_enc, structPtr)\
|
#define EncryptedSocketTCP_sendStructRSA(socket, rsa_enc, structPtr)\
|
||||||
EncryptedSocketTCP_sendRSA(socket, rsa_enc,\
|
EncryptedSocketTCP_sendRSA(socket, rsa_enc,\
|
||||||
Array_construct_size(structPtr, sizeof(*structPtr)))
|
struct_castTo_Array_u8(structPtr))
|
||||||
|
|
||||||
Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr,
|
Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr,
|
||||||
RSADecryptor* rsa_dec, Array(u8) buffer, SocketRecvFlag flags);
|
RSADecryptor* rsa_dec, Array(u8) buffer, SocketRecvFlag flags);
|
||||||
|
|
||||||
#define EncryptedSocketTCP_recvStructRSA(socket, rsa_dec, structPtr)\
|
#define EncryptedSocketTCP_recvStructRSA(socket, rsa_dec, structPtr)\
|
||||||
EncryptedSocketTCP_recvRSA(socket, rsa_dec,\
|
EncryptedSocketTCP_recvRSA(socket, rsa_dec,\
|
||||||
Array_construct_size(structPtr, sizeof(*structPtr)),\
|
struct_castTo_Array_u8(structPtr),\
|
||||||
SocketRecvFlag_WholeBuffer)
|
SocketRecvFlag_WholeBuffer)
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/errors.h"
|
#include "tcp-chat.h"
|
||||||
#include "endpoint.h"
|
#include "endpoint.h"
|
||||||
|
|
||||||
#if !defined(KN_USE_WINSOCK)
|
#if !defined(KN_USE_WINSOCK)
|
||||||
@@ -30,10 +30,10 @@
|
|||||||
|
|
||||||
#if KN_USE_WINSOCK
|
#if KN_USE_WINSOCK
|
||||||
#define RESULT_ERROR_SOCKET()\
|
#define RESULT_ERROR_SOCKET()\
|
||||||
RESULT_ERROR(sprintf_malloc(64, "Winsock error %i (look in <winerror.h>)", WSAGetLastError()), true);
|
RESULT_ERROR_CODE_FMT(WINSOCK2, WSAGetLastError(), "Winsock error %i (look in <winerror.h>)", WSAGetLastError());
|
||||||
#else
|
#else
|
||||||
#define RESULT_ERROR_SOCKET()\
|
#define RESULT_ERROR_SOCKET()\
|
||||||
RESULT_ERROR(strerror(errno), false);
|
RESULT_ERROR_ERRNO();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end);
|
struct sockaddr_in EndpointIPv4_toSockaddr(EndpointIPv4 end);
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "network.h"
|
|
||||||
#include "internal.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_FMT("WSAStartup failed with error code 0x%X", result);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
void network_deinit(){
|
|
||||||
#if _WIN32
|
|
||||||
// Deinitialize Winsock
|
|
||||||
(void)WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/errors.h"
|
|
||||||
|
|
||||||
Result(void) network_init();
|
|
||||||
void network_deinit();
|
|
||||||
@@ -60,11 +60,11 @@ Result(void) socket_connect(Socket s, EndpointIPv4 remote_end){
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result(void) socket_send(Socket s, Array(u8) buffer){
|
Result(void) socket_send(Socket s, Array(u8) buffer){
|
||||||
i32 r = send(s, buffer.data, buffer.size, 0);
|
i32 r = send(s, (void*)buffer.data, buffer.len, 0);
|
||||||
if(r < 0){
|
if(r < 0){
|
||||||
return RESULT_ERROR_SOCKET();
|
return RESULT_ERROR_SOCKET();
|
||||||
}
|
}
|
||||||
if((u32)r != buffer.size){
|
if((u32)r != buffer.len){
|
||||||
return RESULT_ERROR_FMT("Socket was unable to send data");
|
return RESULT_ERROR_FMT("Socket was unable to send data");
|
||||||
}
|
}
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
@@ -72,11 +72,11 @@ Result(void) socket_send(Socket s, Array(u8) buffer){
|
|||||||
|
|
||||||
Result(void) socket_sendto(Socket s, Array(u8) buffer, EndpointIPv4 dst){
|
Result(void) socket_sendto(Socket s, Array(u8) buffer, EndpointIPv4 dst){
|
||||||
struct sockaddr_in sockaddr = EndpointIPv4_toSockaddr(dst);
|
struct sockaddr_in sockaddr = EndpointIPv4_toSockaddr(dst);
|
||||||
i32 r = sendto(s, buffer.data, buffer.size, 0, (void*)&sockaddr, sizeof(sockaddr));
|
i32 r = sendto(s, (void*)buffer.data, buffer.len, 0, (void*)&sockaddr, sizeof(sockaddr));
|
||||||
if(r < 0){
|
if(r < 0){
|
||||||
return RESULT_ERROR_SOCKET();
|
return RESULT_ERROR_SOCKET();
|
||||||
}
|
}
|
||||||
if((u32)r != buffer.size){
|
if((u32)r != buffer.len){
|
||||||
return RESULT_ERROR_FMT("Socket was unable to send data");
|
return RESULT_ERROR_FMT("Socket was unable to send data");
|
||||||
}
|
}
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
@@ -92,13 +92,13 @@ static inline int SocketRecvFlags_toStd(SocketRecvFlag flags){
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result(i32) socket_recv(Socket s, Array(u8) buffer, SocketRecvFlag flags){
|
Result(i32) socket_recv(Socket s, Array(u8) buffer, SocketRecvFlag flags){
|
||||||
i32 r = recv(s, buffer.data, buffer.size, SocketRecvFlags_toStd(flags));
|
i32 r = recv(s, (void*)buffer.data, buffer.len, SocketRecvFlags_toStd(flags));
|
||||||
if(r < 0){
|
if(r < 0){
|
||||||
return RESULT_ERROR_SOCKET();
|
return RESULT_ERROR_SOCKET();
|
||||||
}
|
}
|
||||||
if(r == 0 || (flags & SocketRecvFlag_WholeBuffer && (u32)r != buffer.size))
|
if(r == 0 || (flags & SocketRecvFlag_WholeBuffer && (u32)r != buffer.len))
|
||||||
{
|
{
|
||||||
return RESULT_ERROR("Socket closed", false);
|
return RESULT_ERROR_LITERAL("Socket closed");
|
||||||
}
|
}
|
||||||
return RESULT_VALUE(i, r);
|
return RESULT_VALUE(i, r);
|
||||||
}
|
}
|
||||||
@@ -106,14 +106,14 @@ 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){
|
Result(i32) socket_recvfrom(Socket s, Array(u8) buffer, SocketRecvFlag flags, 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);
|
||||||
i32 r = recvfrom(s, buffer.data, buffer.size, SocketRecvFlags_toStd(flags),
|
i32 r = recvfrom(s, (void*)buffer.data, buffer.len, SocketRecvFlags_toStd(flags),
|
||||||
(struct sockaddr*)&remote_addr, (void*)&sockaddr_size);
|
(struct sockaddr*)&remote_addr, (void*)&sockaddr_size);
|
||||||
if(r < 0){
|
if(r < 0){
|
||||||
return RESULT_ERROR_SOCKET();
|
return RESULT_ERROR_SOCKET();
|
||||||
}
|
}
|
||||||
if(r == 0 || (flags & SocketRecvFlag_WholeBuffer && (u32)r != buffer.size))
|
if(r == 0 || (flags & SocketRecvFlag_WholeBuffer && (u32)r != buffer.len))
|
||||||
{
|
{
|
||||||
return RESULT_ERROR("Socket closed", false);
|
return RESULT_ERROR_LITERAL("Socket closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: add IPV6 support (struct sockaddr_in6)
|
//TODO: add IPV6 support (struct sockaddr_in6)
|
||||||
@@ -132,12 +132,12 @@ Result(i32) socket_recvfrom(Socket s, Array(u8) buffer, SocketRecvFlag flags, NU
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result(void) socket_TCP_enableAliveChecks(Socket s,
|
Result(void) socket_TCP_enableAliveChecks(Socket s,
|
||||||
sec_t first_check_time, u32 checks_count, sec_t checks_interval)
|
sec_t first_check_time, u32 check_count, sec_t checks_interval)
|
||||||
{
|
{
|
||||||
#if KN_USE_WINSOCK
|
#if KN_USE_WINSOCK
|
||||||
BOOL opt_SO_KEEPALIVE = 1; // enable keepalives
|
BOOL opt_SO_KEEPALIVE = 1; // enable keepalives
|
||||||
DWORD opt_TCP_KEEPIDLE = first_check_time;
|
DWORD opt_TCP_KEEPIDLE = first_check_time;
|
||||||
DWORD opt_TCP_KEEPCNT = checks_count;
|
DWORD opt_TCP_KEEPCNT = check_count;
|
||||||
DWORD opt_TCP_KEEPINTVL = checks_interval;
|
DWORD opt_TCP_KEEPINTVL = checks_interval;
|
||||||
try_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE);
|
try_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE);
|
||||||
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE);
|
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE);
|
||||||
@@ -145,12 +145,12 @@ Result(void) socket_TCP_enableAliveChecks(Socket s,
|
|||||||
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL);
|
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL);
|
||||||
|
|
||||||
// timeout for connect()
|
// timeout for connect()
|
||||||
DWORD opt_TCP_MAXRT = checks_count * checks_interval;
|
DWORD opt_TCP_MAXRT = check_count * checks_interval;
|
||||||
try_setsockopt(s, IPPROTO_TCP, TCP_MAXRT);
|
try_setsockopt(s, IPPROTO_TCP, TCP_MAXRT);
|
||||||
#else
|
#else
|
||||||
int opt_SO_KEEPALIVE = 1; // enable keepalives
|
int opt_SO_KEEPALIVE = 1; // enable keepalives
|
||||||
int opt_TCP_KEEPIDLE = first_check_time;
|
int opt_TCP_KEEPIDLE = first_check_time;
|
||||||
int opt_TCP_KEEPCNT = checks_count;
|
int opt_TCP_KEEPCNT = check_count;
|
||||||
int opt_TCP_KEEPINTVL = checks_interval;
|
int opt_TCP_KEEPINTVL = checks_interval;
|
||||||
try_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE);
|
try_setsockopt(s, SOL_SOCKET, SO_KEEPALIVE);
|
||||||
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE);
|
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE);
|
||||||
@@ -158,7 +158,7 @@ Result(void) socket_TCP_enableAliveChecks(Socket s,
|
|||||||
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL);
|
try_setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL);
|
||||||
|
|
||||||
// read more in the article
|
// read more in the article
|
||||||
int opt_TCP_USER_TIMEOUT = checks_count * checks_interval * 1000;
|
int opt_TCP_USER_TIMEOUT = check_count * checks_interval * 1000;
|
||||||
try_setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT);
|
try_setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT);
|
||||||
#endif
|
#endif
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Result(i32) socket_recvfrom(Socket s, Array(u8) buffer, SocketRecvFlag flags
|
|||||||
/// Read more: https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/
|
/// Read more: https://blog.cloudflare.com/when-tcp-sockets-refuse-to-die/
|
||||||
/// RU translaton: https://habr.com/ru/articles/700470/
|
/// RU translaton: https://habr.com/ru/articles/700470/
|
||||||
Result(void) socket_TCP_enableAliveChecks(Socket s,
|
Result(void) socket_TCP_enableAliveChecks(Socket s,
|
||||||
sec_t first_check_time, u32 checks_count, sec_t checks_interval);
|
sec_t first_check_time, u32 check_count, sec_t checks_interval);
|
||||||
#define socket_TCP_enableAliveChecks_default(socket) \
|
#define socket_TCP_enableAliveChecks_default(socket) \
|
||||||
socket_TCP_enableAliveChecks(socket, 1, 4, 5)
|
socket_TCP_enableAliveChecks(socket, 1, 4, 5)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const Magic64 PacketHeader_MAGIC = { .bytes = { 't', 'c', 'p', '-', 'c', 'h', 'a
|
|||||||
|
|
||||||
Result(void) PacketHeader_validateMagic(PacketHeader* ptr){
|
Result(void) PacketHeader_validateMagic(PacketHeader* ptr){
|
||||||
if (ptr->magic.n != PacketHeader_MAGIC.n){
|
if (ptr->magic.n != PacketHeader_MAGIC.n){
|
||||||
return RESULT_ERROR("invalid packet magic", false);
|
return RESULT_ERROR_LITERAL("invalid packet magic");
|
||||||
}
|
}
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
@@ -21,14 +21,15 @@ Result(void) PacketHeader_validateType(PacketHeader* ptr, u16 expected_type){
|
|||||||
Result(void) PacketHeader_validateContentSize(PacketHeader* ptr, u64 expected_size){
|
Result(void) PacketHeader_validateContentSize(PacketHeader* ptr, u64 expected_size){
|
||||||
if(ptr->content_size != expected_size){
|
if(ptr->content_size != expected_size){
|
||||||
return RESULT_ERROR_FMT(
|
return RESULT_ERROR_FMT(
|
||||||
"expected message with content_size " IFWIN("%llu", "%lu")
|
"expected message with content_size "FMT_u64
|
||||||
", but received with content_size " IFWIN("%llu", "%lu"),
|
", but received with content_size "FMT_u64,
|
||||||
expected_size, ptr->content_size);
|
expected_size, ptr->content_size);
|
||||||
}
|
}
|
||||||
return RESULT_VOID;
|
return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PacketHeader_construct(PacketHeader* ptr, u8 protocol_version, u16 type, u64 content_size){
|
void PacketHeader_construct(PacketHeader* ptr, u8 protocol_version, u16 type, u64 content_size){
|
||||||
|
zeroStruct(ptr);
|
||||||
ptr->magic.n = PacketHeader_MAGIC.n;
|
ptr->magic.n = PacketHeader_MAGIC.n;
|
||||||
ptr->protocol_version = protocol_version;
|
ptr->protocol_version = protocol_version;
|
||||||
ptr->type = type;
|
ptr->type = type;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/errors.h"
|
#include "tcp-chat.h"
|
||||||
#include "magic.h"
|
#include "tlibc/magic.h"
|
||||||
|
|
||||||
#define AES_SESSION_KEY_SIZE 32
|
#define AES_SESSION_KEY_SIZE 32
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ typedef struct PacketHeader {
|
|||||||
u8 _reserved1;
|
u8 _reserved1;
|
||||||
u16 type;
|
u16 type;
|
||||||
u32 _reserved4;
|
u32 _reserved4;
|
||||||
|
/* size of request/response struct */
|
||||||
u64 content_size;
|
u64 content_size;
|
||||||
} ATTRIBUTE_ALIGNED(64) PacketHeader;
|
} ATTRIBUTE_ALIGNED(64) PacketHeader;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,72 @@
|
|||||||
#include "v1.h"
|
#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) \
|
#define _PacketHeader_construct(T) \
|
||||||
PacketHeader_construct(header, PROTOCOL_VERSION, PacketType_##T, sizeof(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,
|
Result(void) ClientHandshake_tryConstruct(ClientHandshake* ptr, PacketHeader* header,
|
||||||
Array(u8) session_key)
|
Array(u8) session_key)
|
||||||
{
|
{
|
||||||
Deferral(1);
|
Deferral(1);
|
||||||
_PacketHeader_construct(ClientHandshake);
|
_PacketHeader_construct(ClientHandshake);
|
||||||
|
zeroStruct(ptr);
|
||||||
|
|
||||||
try_assert(session_key.size == sizeof(ptr->session_key));
|
try_assert(session_key.len == sizeof(ptr->session_key));
|
||||||
memcpy(ptr->session_key, session_key.data, session_key.size);
|
memcpy(ptr->session_key, session_key.data, session_key.len);
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerHandshake_construct(ServerHandshake* ptr, PacketHeader* header,
|
void ServerHandshake_construct(ServerHandshake* ptr, PacketHeader* header,
|
||||||
u64 session_id)
|
i64 session_id)
|
||||||
{
|
{
|
||||||
_PacketHeader_construct(ServerHandshake);
|
_PacketHeader_construct(ServerHandshake);
|
||||||
|
zeroStruct(ptr);
|
||||||
ptr->session_id = session_id;
|
ptr->session_id = session_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,25 +74,43 @@ void ServerPublicInfoRequest_construct(ServerPublicInfoRequest *ptr, PacketHeade
|
|||||||
ServerPublicInfo property)
|
ServerPublicInfo property)
|
||||||
{
|
{
|
||||||
_PacketHeader_construct(ServerPublicInfoRequest);
|
_PacketHeader_construct(ServerPublicInfoRequest);
|
||||||
|
zeroStruct(ptr);
|
||||||
ptr->property = property;
|
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,
|
Result(void) LoginRequest_tryConstruct(LoginRequest *ptr, PacketHeader* header,
|
||||||
Array(u8) token)
|
str username, Array(u8) token)
|
||||||
{
|
{
|
||||||
Deferral(1);
|
Deferral(1);
|
||||||
_PacketHeader_construct(LoginRequest);
|
_PacketHeader_construct(LoginRequest);
|
||||||
|
zeroStruct(ptr);
|
||||||
|
|
||||||
try_assert(token.size == sizeof(ptr->token));
|
str name_error_str = validateUsername_str(username);
|
||||||
memcpy(ptr->token, token.data, token.size);
|
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;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginResponse_construct(LoginResponse* ptr, PacketHeader* header,
|
void LoginResponse_construct(LoginResponse* ptr, PacketHeader* header,
|
||||||
u64 user_id, u64 landing_channel_id)
|
i64 user_id, i64 landing_channel_id)
|
||||||
{
|
{
|
||||||
_PacketHeader_construct(LoginResponse);
|
_PacketHeader_construct(LoginResponse);
|
||||||
|
zeroStruct(ptr);
|
||||||
|
|
||||||
ptr->user_id = user_id;
|
ptr->user_id = user_id;
|
||||||
ptr->landing_channel_id = landing_channel_id;
|
ptr->landing_channel_id = landing_channel_id;
|
||||||
}
|
}
|
||||||
@@ -54,20 +120,124 @@ Result(void) RegisterRequest_tryConstruct(RegisterRequest *ptr, PacketHeader* he
|
|||||||
{
|
{
|
||||||
Deferral(1);
|
Deferral(1);
|
||||||
_PacketHeader_construct(RegisterRequest);
|
_PacketHeader_construct(RegisterRequest);
|
||||||
|
zeroStruct(ptr);
|
||||||
|
|
||||||
try_assert(username.size >= USERNAME_SIZE_MIN && username.size <= USERNAME_SIZE_MAX);
|
str name_error_str = validateUsername_str(username);
|
||||||
ptr->username_size = username.size;
|
if(name_error_str.data){
|
||||||
memcpy(ptr->username, username.data, username.size);
|
Return RESULT_ERROR(name_error_str, true);
|
||||||
|
}
|
||||||
|
memcpy(ptr->username, username.data, username.len);
|
||||||
|
|
||||||
try_assert(token.size == sizeof(ptr->token));
|
try_assert(token.len == sizeof(ptr->token));
|
||||||
memcpy(ptr->token, token.data, token.size);
|
memcpy(ptr->token, token.data, token.len);
|
||||||
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegisterResponse_construct(RegisterResponse *ptr, PacketHeader* header,
|
void RegisterResponse_construct(RegisterResponse *ptr, PacketHeader* header,
|
||||||
u64 user_id)
|
i64 user_id)
|
||||||
{
|
{
|
||||||
_PacketHeader_construct(RegisterResponse);
|
_PacketHeader_construct(RegisterResponse);
|
||||||
|
zeroStruct(ptr);
|
||||||
ptr->user_id = user_id;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "tlibc/errors.h"
|
#include "tcp-chat.h"
|
||||||
#include "tlibc/string/str.h"
|
#include "tlibc/time.h"
|
||||||
#include "network/tcp-chat-protocol/constant.h"
|
#include "network/tcp-chat-protocol/constant.h"
|
||||||
#include "cryptography/cryptography.h"
|
|
||||||
|
|
||||||
#define PROTOCOL_VERSION 1 /* 1.0.0 */
|
#define PROTOCOL_VERSION 1 /* 1.0.0 */
|
||||||
#define NETWORK_BUFFER_SIZE 65536
|
#define NETWORK_BUFFER_SIZE 65536
|
||||||
|
|
||||||
#define ALIGN_PACKET_STRUCT ATTRIBUTE_ALIGNED(8)
|
#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 {
|
typedef enum PacketType {
|
||||||
PacketType_Invalid,
|
PacketType_Invalid,
|
||||||
@@ -21,12 +32,22 @@ typedef enum PacketType {
|
|||||||
PacketType_LoginResponse,
|
PacketType_LoginResponse,
|
||||||
PacketType_RegisterRequest,
|
PacketType_RegisterRequest,
|
||||||
PacketType_RegisterResponse,
|
PacketType_RegisterResponse,
|
||||||
|
PacketType_SendMessageRequest,
|
||||||
|
PacketType_SendMessageResponse,
|
||||||
|
PacketType_GetMessageBlockRequest,
|
||||||
|
PacketType_GetMessageBlockResponse,
|
||||||
} ATTRIBUTE_PACKED PacketType;
|
} ATTRIBUTE_PACKED PacketType;
|
||||||
|
|
||||||
|
|
||||||
// typedef struct ErrorMessage {
|
#define ERROR_MESSAGE_MAX_SIZE 8192
|
||||||
// /* stream of size header.content_size */
|
|
||||||
// } ErrorMessage;
|
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 {
|
typedef struct ClientHandshake {
|
||||||
@@ -38,11 +59,11 @@ Result(void) ClientHandshake_tryConstruct(ClientHandshake* ptr, PacketHeader* he
|
|||||||
|
|
||||||
|
|
||||||
typedef struct ServerHandshake {
|
typedef struct ServerHandshake {
|
||||||
u64 session_id;
|
i64 session_id;
|
||||||
} ALIGN_PACKET_STRUCT ServerHandshake;
|
} ALIGN_PACKET_STRUCT ServerHandshake;
|
||||||
|
|
||||||
void ServerHandshake_construct(ServerHandshake* ptr, PacketHeader* header,
|
void ServerHandshake_construct(ServerHandshake* ptr, PacketHeader* header,
|
||||||
u64 session_id);
|
i64 session_id);
|
||||||
|
|
||||||
|
|
||||||
typedef enum ServerPublicInfo {
|
typedef enum ServerPublicInfo {
|
||||||
@@ -58,36 +79,35 @@ void ServerPublicInfoRequest_construct(ServerPublicInfoRequest* ptr, PacketHeade
|
|||||||
ServerPublicInfo property);
|
ServerPublicInfo property);
|
||||||
|
|
||||||
|
|
||||||
// typedef struct ServerPublicInfoResponse {
|
typedef struct ServerPublicInfoResponse {
|
||||||
// /* stream of size header.content_size */
|
u32 data_size;
|
||||||
// } ServerPublicInfoResponse;
|
/* stream of size data_size */
|
||||||
|
} ALIGN_PACKET_STRUCT ServerPublicInfoResponse;
|
||||||
|
|
||||||
|
void ServerPublicInfoResponse_construct(ServerPublicInfoResponse* ptr, PacketHeader* header,
|
||||||
|
u32 data_size);
|
||||||
|
|
||||||
|
|
||||||
typedef struct LoginRequest {
|
typedef struct LoginRequest {
|
||||||
|
char username[USERNAME_SIZE_MAX + 1]; // null-terminated
|
||||||
u8 token[PASSWORD_HASH_SIZE];
|
u8 token[PASSWORD_HASH_SIZE];
|
||||||
} ALIGN_PACKET_STRUCT LoginRequest;
|
} ALIGN_PACKET_STRUCT LoginRequest;
|
||||||
|
|
||||||
Result(void) LoginRequest_tryConstruct(LoginRequest* ptr, PacketHeader* header,
|
Result(void) LoginRequest_tryConstruct(LoginRequest* ptr, PacketHeader* header,
|
||||||
Array(u8) token);
|
str username, Array(u8) token);
|
||||||
|
|
||||||
|
|
||||||
typedef struct LoginResponse {
|
typedef struct LoginResponse {
|
||||||
u64 user_id;
|
i64 user_id;
|
||||||
u64 landing_channel_id;
|
i64 landing_channel_id;
|
||||||
} ALIGN_PACKET_STRUCT LoginResponse;
|
} ALIGN_PACKET_STRUCT LoginResponse;
|
||||||
|
|
||||||
void LoginResponse_construct(LoginResponse* ptr, PacketHeader* header,
|
void LoginResponse_construct(LoginResponse* ptr, PacketHeader* header,
|
||||||
u64 user_id, u64 landing_channel_id);
|
i64 user_id, i64 landing_channel_id);
|
||||||
|
|
||||||
|
|
||||||
#define USERNAME_SIZE_MIN 4
|
|
||||||
#define USERNAME_SIZE_MAX 64
|
|
||||||
#define PASSWORD_SIZE_MIN 8
|
|
||||||
#define PASSWORD_SIZE_MAX 32
|
|
||||||
|
|
||||||
typedef struct RegisterRequest {
|
typedef struct RegisterRequest {
|
||||||
u32 username_size;
|
char username[USERNAME_SIZE_MAX + 1]; // null-terminated
|
||||||
char username[USERNAME_SIZE_MAX];
|
|
||||||
u8 token[PASSWORD_HASH_SIZE];
|
u8 token[PASSWORD_HASH_SIZE];
|
||||||
} ALIGN_PACKET_STRUCT RegisterRequest;
|
} ALIGN_PACKET_STRUCT RegisterRequest;
|
||||||
|
|
||||||
@@ -96,10 +116,114 @@ Result(void) RegisterRequest_tryConstruct(RegisterRequest* ptr, PacketHeader* he
|
|||||||
|
|
||||||
|
|
||||||
typedef struct RegisterResponse {
|
typedef struct RegisterResponse {
|
||||||
u64 user_id;
|
i64 user_id;
|
||||||
} ALIGN_PACKET_STRUCT RegisterResponse;
|
} ALIGN_PACKET_STRUCT RegisterResponse;
|
||||||
|
|
||||||
void RegisterResponse_construct(RegisterResponse* ptr, PacketHeader* header,
|
void RegisterResponse_construct(RegisterResponse* ptr, PacketHeader* header,
|
||||||
u64 user_id);
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#include "server.h"
|
#include "server/server_internal.h"
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
|
||||||
|
|
||||||
void ClientConnection_close(ClientConnection* conn){
|
void ClientConnection_close(ClientConnection* conn){
|
||||||
if(!conn)
|
if(!conn)
|
||||||
return;
|
return;
|
||||||
EncryptedSocketTCP_destroy(&conn->sock);
|
EncryptedSocketTCP_destroy(&conn->sock);
|
||||||
free(conn->session_key.data);
|
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);
|
free(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,40 +17,49 @@ Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args)
|
|||||||
Deferral(8);
|
Deferral(8);
|
||||||
|
|
||||||
ClientConnection* conn = (ClientConnection*)malloc(sizeof(ClientConnection));
|
ClientConnection* conn = (ClientConnection*)malloc(sizeof(ClientConnection));
|
||||||
memset(conn, 0, sizeof(*conn));
|
zeroStruct(conn);
|
||||||
bool success = false;
|
bool success = false;
|
||||||
Defer(if(!success) ClientConnection_close(conn));
|
Defer(if(!success) ClientConnection_close(conn));
|
||||||
|
|
||||||
|
conn->server = args->server;
|
||||||
conn->client_end = args->client_end;
|
conn->client_end = args->client_end;
|
||||||
conn->session_id = args->session_id;
|
conn->session_id = args->session_id;
|
||||||
conn->authorized = false;
|
|
||||||
conn->session_key = Array_alloc_size(AES_SESSION_KEY_SIZE);
|
// 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
|
// correct session key will be received from client later
|
||||||
Array_memset(conn->session_key, 0);
|
conn->session_key = Array_u8_alloc(AES_SESSION_KEY_SIZE);
|
||||||
EncryptedSocketTCP_construct(&conn->sock, args->accepted_socket, NETWORK_BUFFER_SIZE, conn->session_key);
|
Array_u8_memset(&conn->session_key, 0);
|
||||||
try_void(socket_TCP_enableAliveChecks_default(args->accepted_socket));
|
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
|
// decrypt the rsa messages using server private key
|
||||||
RSADecryptor rsa_dec;
|
RSADecryptor rsa_dec;
|
||||||
RSADecryptor_construct(&rsa_dec, &args->server->cred.rsa_sk);
|
RSADecryptor_construct(&rsa_dec, &args->server->rsa_sk);
|
||||||
|
|
||||||
// receive PacketHeader
|
// receive PacketHeader
|
||||||
PacketHeader packet_header = {0};
|
PacketHeader packet_header;
|
||||||
try_void(EncryptedSocketTCP_recvStructRSA(&conn->sock, &rsa_dec, &packet_header));
|
try_void(EncryptedSocketTCP_recvStructRSA(&conn->sock, &rsa_dec, &packet_header));
|
||||||
try_void(PacketHeader_validateMagic(&packet_header));
|
try_void(PacketHeader_validateMagic(&packet_header));
|
||||||
try_void(PacketHeader_validateType(&packet_header, PacketType_ClientHandshake));
|
try_void(PacketHeader_validateType(&packet_header, PacketType_ClientHandshake));
|
||||||
try_void(PacketHeader_validateContentSize(&packet_header, sizeof(ClientHandshake)));
|
try_void(PacketHeader_validateContentSize(&packet_header, sizeof(ClientHandshake)));
|
||||||
|
|
||||||
// receive ClientHandshake
|
// receive ClientHandshake
|
||||||
ClientHandshake client_handshake = {0};
|
ClientHandshake client_handshake;
|
||||||
try_void(EncryptedSocketTCP_recvStructRSA(&conn->sock, &rsa_dec, &client_handshake));
|
try_void(EncryptedSocketTCP_recvStructRSA(&conn->sock, &rsa_dec, &client_handshake));
|
||||||
|
|
||||||
// use received session key
|
// use received session key
|
||||||
memcpy(conn->session_key.data, client_handshake.session_key, conn->session_key.size);
|
memcpy(conn->session_key.data, client_handshake.session_key, conn->session_key.len);
|
||||||
EncryptedSocketTCP_changeKey(&conn->sock, conn->session_key);
|
EncryptedSocketTCP_changeKey(&conn->sock, conn->session_key);
|
||||||
|
|
||||||
// send PacketHeader and ServerHandshake over encrypted TCP socket
|
// send ServerHandshake
|
||||||
ServerHandshake server_handshake = {0};
|
ServerHandshake server_handshake;
|
||||||
ServerHandshake_construct(&server_handshake, &packet_header,
|
ServerHandshake_construct(&server_handshake, &packet_header,
|
||||||
conn->session_id);
|
conn->session_id);
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &packet_header));
|
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &packet_header));
|
||||||
@@ -55,4 +67,4 @@ Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args)
|
|||||||
|
|
||||||
success = true;
|
success = true;
|
||||||
Return RESULT_VALUE(p, conn);
|
Return RESULT_VALUE(p, conn);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
#include "server.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Result(void) ServerCredentials_tryConstruct(ServerCredentials* cred,
|
|
||||||
cstr rsa_sk_base64, cstr rsa_pk_base64)
|
|
||||||
{
|
|
||||||
Deferral(4);
|
|
||||||
|
|
||||||
memset(cred, 0, sizeof(*cred));
|
|
||||||
bool success = false;
|
|
||||||
Defer(if(!success) ServerCredentials_destroy(cred));
|
|
||||||
|
|
||||||
try_void(RSA_parsePrivateKey_base64(rsa_sk_base64, &cred->rsa_sk));
|
|
||||||
try_void(RSA_parsePublicKey_base64(rsa_pk_base64, &cred->rsa_pk));
|
|
||||||
|
|
||||||
success = true;
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerCredentials_destroy(ServerCredentials* cred){
|
|
||||||
if(!cred)
|
|
||||||
return;
|
|
||||||
RSA_destroyPrivateKey(&cred->rsa_sk);
|
|
||||||
RSA_destroyPublicKey(&cred->rsa_pk);
|
|
||||||
}
|
|
||||||
110
src/server/db/Channel.c
Normal file
110
src/server/db/Channel.c
Normal 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, ×tamp_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, ×tamp));
|
||||||
|
|
||||||
|
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
50
src/server/db/User.c
Normal 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
153
src/server/db/server_db.c
Normal 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
39
src/server/db/server_db.h
Normal 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);
|
||||||
27
src/server/db/server_db_internal.h
Normal file
27
src/server/db/server_db_internal.h
Normal 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;
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#include "request_handlers.h"
|
|
||||||
|
|
||||||
|
|
||||||
declare_RequestHandler(Login)
|
|
||||||
{
|
|
||||||
Deferral(4);
|
|
||||||
logDebug(log_ctx, "requested %s", req_type_name);
|
|
||||||
|
|
||||||
LoginRequest req = {0};
|
|
||||||
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
|
|
||||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
|
|
||||||
|
|
||||||
//TODO: try authorize client
|
|
||||||
u64 user_id;
|
|
||||||
u64 landing_channel_id;
|
|
||||||
|
|
||||||
LoginResponse res = {0};
|
|
||||||
LoginResponse_construct(&res, res_head, user_id, landing_channel_id);
|
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
|
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, &res));
|
|
||||||
conn->authorized = true;
|
|
||||||
logInfo(log_ctx, "client authorized");
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "request_handlers.h"
|
|
||||||
|
|
||||||
declare_RequestHandler(Register)
|
|
||||||
{
|
|
||||||
Deferral(4);
|
|
||||||
logDebug(log_ctx, "requested %s", req_type_name);
|
|
||||||
|
|
||||||
RegisterRequest req = {0};
|
|
||||||
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
|
|
||||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
|
|
||||||
|
|
||||||
//TODO: try register client
|
|
||||||
u64 user_id;
|
|
||||||
|
|
||||||
RegisterResponse res = {0};
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "request_handlers.h"
|
|
||||||
|
|
||||||
declare_RequestHandler(ServerPublicInfo)
|
|
||||||
{
|
|
||||||
Deferral(4);
|
|
||||||
logDebug(log_ctx, "requested %s", req_type_name);
|
|
||||||
|
|
||||||
ServerPublicInfoRequest req = {0};
|
|
||||||
try_void(PacketHeader_validateContentSize(req_head, sizeof(req)));
|
|
||||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &req));
|
|
||||||
|
|
||||||
//TODO: try find requested info
|
|
||||||
Array(u8) content;
|
|
||||||
|
|
||||||
PacketHeader_construct(res_head,
|
|
||||||
PROTOCOL_VERSION, PacketType_ServerPublicInfoResponse, content.size);
|
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
|
|
||||||
try_void(EncryptedSocketTCP_send(&conn->sock, content));
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
|
||||||
#include "server/server.h"
|
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
|
|
||||||
Result(char*) __sendErrorMessage(ClientConnection* conn, PacketHeader* req_head, PacketHeader* res_head,
|
|
||||||
u32 msg_buf_size, cstr format, va_list argv);
|
|
||||||
Result(char*) sendErrorMessage(ClientConnection* conn, PacketHeader* req_head, PacketHeader* res_head,
|
|
||||||
u32 msg_buf_size, 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);
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#include "request_handlers.h"
|
|
||||||
|
|
||||||
Result(char*) __sendErrorMessage(ClientConnection* conn, PacketHeader* req_head, PacketHeader* res_head,
|
|
||||||
u32 msg_buf_size, cstr format, va_list argv)
|
|
||||||
{
|
|
||||||
Deferral(4);
|
|
||||||
//TODO: limit ErrorMessage size to fit into EncryptedSocketTCP.internal_buffer_size
|
|
||||||
Array(u8) err_buf = Array_alloc(u8, msg_buf_size);
|
|
||||||
bool err_complete = false;
|
|
||||||
Defer(if(!err_complete) free(err_buf.data));
|
|
||||||
vsprintf(err_buf.data, format, argv);
|
|
||||||
err_buf.size = strlen(err_buf.data);
|
|
||||||
|
|
||||||
PacketHeader_construct(res_head,
|
|
||||||
PROTOCOL_VERSION, PacketType_ErrorMessage, err_buf.size);
|
|
||||||
try_void(EncryptedSocketTCP_sendStruct(&conn->sock, res_head));
|
|
||||||
try_void(EncryptedSocketTCP_send(&conn->sock, err_buf));
|
|
||||||
|
|
||||||
err_complete = true;
|
|
||||||
Return RESULT_VALUE(p, err_buf.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result(char*) sendErrorMessage(ClientConnection* conn, PacketHeader* req_head, PacketHeader* res_head,
|
|
||||||
u32 msg_buf_size, cstr format, ...)
|
|
||||||
{
|
|
||||||
va_list argv;
|
|
||||||
va_start(argv, format);
|
|
||||||
ResultVar(char*) err_msg = __sendErrorMessage(conn, req_head, res_head, msg_buf_size, format, argv);
|
|
||||||
va_end(argv);
|
|
||||||
return err_msg;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#include "request_handlers.h"
|
|
||||||
|
|
||||||
declare_RequestHandler(T)
|
|
||||||
{
|
|
||||||
Deferral(4);
|
|
||||||
logDebug(log_ctx, "requested %s", req_type_name);
|
|
||||||
|
|
||||||
Return RESULT_VOID;
|
|
||||||
}
|
|
||||||
57
src/server/responses/GetMessageBlock.c
Normal file
57
src/server/responses/GetMessageBlock.c
Normal 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;
|
||||||
|
}
|
||||||
64
src/server/responses/Login.c
Normal file
64
src/server/responses/Login.c
Normal 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;
|
||||||
|
}
|
||||||
55
src/server/responses/Register.c
Normal file
55
src/server/responses/Register.c
Normal 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;
|
||||||
|
}
|
||||||
57
src/server/responses/SendMessage.c
Normal file
57
src/server/responses/SendMessage.c
Normal 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,
|
||||||
|
×tamp));
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
44
src/server/responses/ServerPublicInfo.c
Normal file
44
src/server/responses/ServerPublicInfo.c
Normal 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;
|
||||||
|
}
|
||||||
30
src/server/responses/responses.h
Normal file
30
src/server/responses/responses.h
Normal 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);
|
||||||
55
src/server/responses/send_error.c
Normal file
55
src/server/responses/send_error.c
Normal 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;
|
||||||
|
}
|
||||||
25
src/server/responses/template
Normal file
25
src/server/responses/template
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,88 +1,144 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include "tlibc/filesystem.h"
|
|
||||||
#include "tlibc/time.h"
|
#include "tlibc/time.h"
|
||||||
#include "db/idb.h"
|
#include "server/server_internal.h"
|
||||||
#include "server.h"
|
#include "server/responses/responses.h"
|
||||||
#include "config.h"
|
#include "tlibtoml.h"
|
||||||
#include "log.h"
|
|
||||||
#include "network/tcp-chat-protocol/v1.h"
|
|
||||||
#include "server/request_handlers/request_handlers.h"
|
|
||||||
|
|
||||||
static void* handleConnection(void* _args);
|
static void* handleConnection(void* _args);
|
||||||
static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_ctx);
|
static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_ctx);
|
||||||
|
|
||||||
void Server_free(Server* server){
|
void Server_free(Server* self){
|
||||||
if(!server)
|
if(!self)
|
||||||
return;
|
return;
|
||||||
free(server->name.data);
|
|
||||||
free(server->description.data);
|
str_destroy(self->name);
|
||||||
ServerCredentials_destroy(&server->cred);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(Server*) Server_createFromConfig(cstr config_path){
|
|
||||||
|
#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);
|
Deferral(16);
|
||||||
cstr log_ctx = "ServerInit";
|
cstr log_ctx = "ServerInit";
|
||||||
logInfo(log_ctx, "parsing config");
|
|
||||||
|
|
||||||
Server* server = (Server*)malloc(sizeof(Server));
|
Server* self = (Server*)malloc(sizeof(Server));
|
||||||
memset(server, 0, sizeof(Server));
|
zeroStruct(self);
|
||||||
bool success = false;
|
bool success = false;
|
||||||
Defer(if(!success) Server_free(server));
|
Defer(if(!success) Server_free(self));
|
||||||
|
|
||||||
// open file
|
|
||||||
try(FILE* config_file, p, file_open(config_path, FO_ReadExisting));
|
|
||||||
Defer(file_close(config_file));
|
|
||||||
// read whole file into Array(char)
|
|
||||||
try(i64 config_file_size, i, file_getSize(config_file));
|
|
||||||
Array(char) config_buf = Array_alloc(char, config_file_size);
|
|
||||||
Defer(free(config_buf.data));
|
|
||||||
try_void(file_readBytesArray(config_file, config_buf));
|
|
||||||
str config_str = Array_castTo_str(config_buf, false);
|
|
||||||
|
|
||||||
// parse name
|
|
||||||
str tmp_str = str_null;
|
|
||||||
try_void(config_findValue(config_str, STR("name"), &tmp_str, true));
|
|
||||||
server->name = str_copy(tmp_str);
|
|
||||||
|
|
||||||
// parse description
|
|
||||||
try_void(config_findValue(config_str, STR("description"), &tmp_str, true));
|
|
||||||
server->description = str_copy(tmp_str);
|
|
||||||
|
|
||||||
// parse rsa_private_key
|
self->logger = logger;
|
||||||
try_void(config_findValue(config_str, STR("rsa_private_key"), &tmp_str, true));
|
self->log_func = log_func;
|
||||||
char* sk_base64_cstr = str_copy(tmp_str).data;
|
|
||||||
Defer(free(sk_base64_cstr));
|
logDebug("parsing config");
|
||||||
|
try(TomlTable* config_top, p, toml_load_str_filename(config_file_content, config_file_name));
|
||||||
|
Defer(TomlTable_free(config_top));
|
||||||
|
|
||||||
// parse rsa_public_key
|
// [server]
|
||||||
try_void(config_findValue(config_str, STR("rsa_public_key"), &tmp_str, true));
|
try(TomlTable* config_server, p, TomlTable_get_table(config_top, STR("server")))
|
||||||
char* pk_base64_cstr = str_copy(tmp_str).data;
|
// name
|
||||||
Defer(free(pk_base64_cstr));
|
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));
|
||||||
|
|
||||||
try_void(ServerCredentials_tryConstruct(&server->cred, sk_base64_cstr, pk_base64_cstr));
|
// 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;
|
success = true;
|
||||||
Return RESULT_VALUE(p, server);
|
Return RESULT_VALUE(p, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result(void) Server_run(Server* server, cstr server_endpoint_cstr){
|
#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);
|
Deferral(16);
|
||||||
cstr log_ctx = "ListenerThread";
|
cstr log_ctx = "ListenerThread";
|
||||||
logInfo(log_ctx, "starting server");
|
logInfo("starting server");
|
||||||
|
|
||||||
EndpointIPv4 server_end;
|
logDebug("initializing main socket");
|
||||||
try_void(EndpointIPv4_parse(server_endpoint_cstr, &server_end));
|
|
||||||
|
|
||||||
logDebug(log_ctx, "initializing main socket");
|
|
||||||
try(Socket main_socket, i, socket_open_TCP());
|
try(Socket main_socket, i, socket_open_TCP());
|
||||||
try_void(socket_bind(main_socket, server_end));
|
try_void(socket_bind(main_socket, server->local_end));
|
||||||
try_void(socket_listen(main_socket, 512));
|
try_void(socket_listen(main_socket, 512));
|
||||||
logInfo(log_ctx, "server is listening at %s", server_endpoint_cstr);
|
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);
|
||||||
|
|
||||||
u64 session_id = 1;
|
i64 session_id = 1;
|
||||||
while(true){
|
while(true){
|
||||||
ConnectionHandlerArgs* args = (ConnectionHandlerArgs*)malloc(sizeof(ConnectionHandlerArgs));
|
ConnectionHandlerArgs* args = (ConnectionHandlerArgs*)malloc(sizeof(ConnectionHandlerArgs));
|
||||||
args->server = server;
|
args->server = server;
|
||||||
try(args->accepted_socket, i,
|
try(args->accepted_socket_tcp, i,
|
||||||
socket_accept(main_socket, &args->client_end));
|
socket_accept(main_socket, &args->client_end));
|
||||||
args->session_id = session_id++;
|
args->session_id = session_id++;
|
||||||
pthread_t conn_thread = {0};
|
pthread_t conn_thread = {0};
|
||||||
@@ -97,35 +153,46 @@ Result(void) Server_run(Server* server, cstr server_endpoint_cstr){
|
|||||||
|
|
||||||
static void* handleConnection(void* _args){
|
static void* handleConnection(void* _args){
|
||||||
ConnectionHandlerArgs* args = (ConnectionHandlerArgs*)_args;
|
ConnectionHandlerArgs* args = (ConnectionHandlerArgs*)_args;
|
||||||
|
Server* server = args->server;
|
||||||
char log_ctx[64];
|
char log_ctx[64];
|
||||||
sprintf(log_ctx, "Session-" IFWIN("%llx", "%lx"), args->session_id);
|
sprintf(log_ctx, "Session-"FMT_x64, args->session_id);
|
||||||
|
|
||||||
ResultVar(void) r = try_handleConnection(args, log_ctx);
|
ResultVar(void) r = try_handleConnection(args, log_ctx);
|
||||||
if(r.error){
|
if(r.error){
|
||||||
str error_s = Error_toStr(r.error);
|
Error_addCallPos(r.error, ErrorCallPos_here());
|
||||||
logError(log_ctx, "%s", error_s.data);
|
str e_str = Error_toStr(r.error);
|
||||||
free(error_s.data);
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_ctx){
|
static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_ctx){
|
||||||
Deferral(16);
|
Deferral(16);
|
||||||
Defer(free(args));
|
|
||||||
|
|
||||||
|
Server* server = args->server;
|
||||||
|
logInfo("a client is trying to connect");
|
||||||
ClientConnection* conn = NULL;
|
ClientConnection* conn = NULL;
|
||||||
Defer(
|
|
||||||
ClientConnection_close(conn);
|
|
||||||
logInfo(log_ctx, "session closed");
|
|
||||||
);
|
|
||||||
// establish encrypted connection
|
// establish encrypted connection
|
||||||
try(conn, p, ClientConnection_accept(args));
|
try(conn, p, ClientConnection_accept(args));
|
||||||
logInfo(log_ctx, "session accepted");
|
Defer(ClientConnection_close(conn));
|
||||||
|
logInfo("session accepted");
|
||||||
|
|
||||||
// handle requests
|
// handle requests
|
||||||
PacketHeader req_head = {0};
|
PacketHeader req_head;
|
||||||
PacketHeader res_head = {0};
|
PacketHeader res_head;
|
||||||
while(true){
|
while(true){
|
||||||
sleepMsec(20);
|
sleepMsec(20);
|
||||||
//TODO: implement some additional check if socket is dead or not
|
//TODO: implement some additional check if socket is dead or not
|
||||||
@@ -136,21 +203,20 @@ static Result(void) try_handleConnection(ConnectionHandlerArgs* args, cstr log_c
|
|||||||
switch(req_head.type){
|
switch(req_head.type){
|
||||||
// send error message and close connection
|
// send error message and close connection
|
||||||
default:
|
default:
|
||||||
try(char* err_msg, p,
|
try_void(sendErrorMessage_f(log_ctx, conn, &res_head,
|
||||||
sendErrorMessage(
|
LogSeverity_Error,
|
||||||
conn, &req_head, &res_head,
|
"Received unexpected packet of type %u",
|
||||||
128, "Received unexpected packet of type %u",
|
req_head.type));
|
||||||
req_head.type
|
|
||||||
)
|
|
||||||
);
|
|
||||||
logWarn(log_ctx, "%s", err_msg);
|
|
||||||
Return RESULT_VOID;
|
Return RESULT_VOID;
|
||||||
|
|
||||||
// unauthorized requests
|
// unauthorized requests
|
||||||
case_handleRequest(ServerPublicInfo);
|
case_handleRequest(ServerPublicInfo);
|
||||||
case_handleRequest(Login);
|
case_handleRequest(Login);
|
||||||
case_handleRequest(Register);
|
case_handleRequest(Register);
|
||||||
|
|
||||||
// authorized requests
|
// authorized requests
|
||||||
|
case_handleRequest(SendMessage);
|
||||||
|
case_handleRequest(GetMessageBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "cryptography/AES.h"
|
|
||||||
#include "cryptography/RSA.h"
|
|
||||||
#include "network/encrypted_sockets.h"
|
|
||||||
|
|
||||||
typedef struct Server Server;
|
|
||||||
|
|
||||||
typedef struct ServerCredentials {
|
|
||||||
br_rsa_private_key rsa_sk;
|
|
||||||
br_rsa_public_key rsa_pk;
|
|
||||||
} ServerCredentials;
|
|
||||||
|
|
||||||
Result(void) ServerCredentials_tryConstruct(ServerCredentials* cred,
|
|
||||||
cstr rsa_sk_base64, cstr rsa_pk_base64);
|
|
||||||
|
|
||||||
void ServerCredentials_destroy(ServerCredentials* cred);
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct ClientConnection {
|
|
||||||
u64 session_id;
|
|
||||||
EndpointIPv4 client_end;
|
|
||||||
Array(u8) session_key;
|
|
||||||
EncryptedSocketTCP sock;
|
|
||||||
bool authorized;
|
|
||||||
} ClientConnection;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct ConnectionHandlerArgs {
|
|
||||||
Server* server;
|
|
||||||
Socket accepted_socket;
|
|
||||||
EndpointIPv4 client_end;
|
|
||||||
u64 session_id;
|
|
||||||
} ConnectionHandlerArgs;
|
|
||||||
|
|
||||||
Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args);
|
|
||||||
|
|
||||||
void ClientConnection_close(ClientConnection* conn);
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct Server {
|
|
||||||
str name;
|
|
||||||
str description;
|
|
||||||
ServerCredentials cred;
|
|
||||||
} Server;
|
|
||||||
|
|
||||||
Result(Server*) Server_createFromConfig(cstr config_path);
|
|
||||||
void Server_free(Server* server);
|
|
||||||
Result(void) Server_run(Server* server, cstr server_endpoint_cstr);
|
|
||||||
60
src/server/server_internal.h
Normal file
60
src/server/server_internal.h
Normal 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
29
src/tcp-chat.c
Normal 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
|
||||||
|
}
|
||||||
95
src/term.c
95
src/term.c
@@ -1,95 +0,0 @@
|
|||||||
#include "term.h"
|
|
||||||
#include <unistd.h>
|
|
||||||
#include IFWIN("windows.h", "sys/ioctl.h")
|
|
||||||
|
|
||||||
bool term_init(){
|
|
||||||
#if defined(_WIN64) || defined(_WIN32)
|
|
||||||
DWORD mode=0;
|
|
||||||
HANDLE h;
|
|
||||||
|
|
||||||
// configure stdout
|
|
||||||
h = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
||||||
if(h == INVALID_HANDLE_VALUE)
|
|
||||||
return false;
|
|
||||||
GetConsoleMode(h, &mode);
|
|
||||||
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
||||||
mode |= ENABLE_PROCESSED_OUTPUT;
|
|
||||||
SetConsoleMode(h, mode);
|
|
||||||
|
|
||||||
// configure stdin
|
|
||||||
h = GetStdHandle(STD_INPUT_HANDLE);
|
|
||||||
if(h == INVALID_HANDLE_VALUE)
|
|
||||||
return false;
|
|
||||||
GetConsoleMode(h, &mode);
|
|
||||||
mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
|
|
||||||
mode |= ENABLE_PROCESSED_INPUT;
|
|
||||||
SetConsoleMode(h, mode);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getenv_int(const char* var_name){
|
|
||||||
char* str=getenv(var_name);
|
|
||||||
if(str==NULL)
|
|
||||||
return -1;
|
|
||||||
return strtol(str, NULL, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool term_getSize(TerminalSize* out) {
|
|
||||||
#if defined(_WIN64) || defined(_WIN32)
|
|
||||||
// helps when STD_OUT is redirected to a file
|
|
||||||
HANDLE hConsoleErr = GetStdHandle(STD_ERROR_HANDLE);
|
|
||||||
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
|
|
||||||
if(!GetConsoleScreenBufferInfo(hConsoleErr, &consoleInfo))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
out->cols = consoleInfo.srWindow.Right - consoleInfo.srWindow.Left + 1;
|
|
||||||
out->rows = consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1;
|
|
||||||
#else
|
|
||||||
struct winsize ws = {0};
|
|
||||||
// try to get terminal size from stdin, stdout, stderr
|
|
||||||
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)==0 ||
|
|
||||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)==0 ||
|
|
||||||
ioctl(STDERR_FILENO, TIOCGWINSZ, &ws)==0 ){
|
|
||||||
out->cols=ws.ws_col;
|
|
||||||
out->rows=ws.ws_row;
|
|
||||||
}
|
|
||||||
// try to get size from environtent variables
|
|
||||||
else {
|
|
||||||
out->cols=getenv_int("COLUMNS");
|
|
||||||
out->rows=getenv_int("LINES");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return out->cols > 0 && out->rows > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Most of escape sequences can be found there
|
|
||||||
https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
|
|
||||||
*/
|
|
||||||
|
|
||||||
void term_resetCursor() {
|
|
||||||
printf("\e[H");
|
|
||||||
}
|
|
||||||
|
|
||||||
void term_resetColors() {
|
|
||||||
printf("\e[0m");
|
|
||||||
}
|
|
||||||
|
|
||||||
void term_clear() {
|
|
||||||
printf("\e[0m\e[H\e[2J");
|
|
||||||
}
|
|
||||||
|
|
||||||
void term_cursorMove(u16 row, u16 column) {
|
|
||||||
printf("\e[%u;%uH",row,column);
|
|
||||||
}
|
|
||||||
|
|
||||||
void term_cursorHide() {
|
|
||||||
printf("\e[?25l");
|
|
||||||
}
|
|
||||||
|
|
||||||
void term_cursorShow() {
|
|
||||||
printf("\e[?25h");
|
|
||||||
}
|
|
||||||
16
src/term.h
16
src/term.h
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "tlibc/std.h"
|
|
||||||
|
|
||||||
typedef struct TerminalSize {
|
|
||||||
i16 cols;
|
|
||||||
i16 rows;
|
|
||||||
} TerminalSize;
|
|
||||||
|
|
||||||
bool term_init();
|
|
||||||
bool term_getSize(TerminalSize* out);
|
|
||||||
void term_resetCursor();
|
|
||||||
void term_resetColors();
|
|
||||||
void term_clear();
|
|
||||||
void term_cursorMove(u16 row, u16 column);
|
|
||||||
void term_cursorHide();
|
|
||||||
void term_cursorShow();
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
exe_path="$OUTDIR/$EXEC_FILE"
|
|
||||||
myprint "${BLUE}stripping symbols from ${WHITE}$exe_path"
|
|
||||||
strip -s "$exe_path"
|
|
||||||
22
tcp-chat-server.toml.default
Normal file
22
tcp-chat-server.toml.default
Normal 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>'
|
||||||
Reference in New Issue
Block a user