Compare commits
No commits in common. "85c0736c8d138a2c092206076dff987a73821a40" and "9942d94c94fc538dcd53fae226dd4a5c0073ef99" have entirely different histories.
85c0736c8d
...
9942d94c94
29
README.md
29
README.md
@ -1,29 +0,0 @@
|
||||
# tcp-chat
|
||||
|
||||
## Build
|
||||
1. Clone the 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).
|
||||
3. Build executable
|
||||
```
|
||||
cd tcp-chat
|
||||
cbuild build_exec
|
||||
```
|
||||
|
||||
## Usage
|
||||
**Client:**
|
||||
```sh
|
||||
cd bin
|
||||
./tcp-chat
|
||||
```
|
||||
**Server:**
|
||||
1. ```sh
|
||||
cp tcp-chat-server.config.default bin/tcp-chat-server.config
|
||||
```
|
||||
2. Edit config
|
||||
3. ```sh
|
||||
cd bin
|
||||
./tcp-chat -l
|
||||
```
|
||||
2
dependencies/tlibc
vendored
2
dependencies/tlibc
vendored
@ -1 +1 @@
|
||||
Subproject commit 425794361bc52240bb50001becbb5fb6e9ebcf20
|
||||
Subproject commit ae0fa95d6aee2a7427c3e3575d74af1ed360e6e5
|
||||
@ -1,5 +1,5 @@
|
||||
#include "client.h"
|
||||
#include "requests/requests.h"
|
||||
#include "network/tcp-chat-protocol/v1.h"
|
||||
|
||||
void ServerConnection_close(ServerConnection* conn){
|
||||
if(!conn)
|
||||
@ -7,8 +7,6 @@ void ServerConnection_close(ServerConnection* conn){
|
||||
RSA_destroyPublicKey(&conn->server_pk);
|
||||
EncryptedSocketTCP_destroy(&conn->sock);
|
||||
free(conn->session_key.data);
|
||||
free(conn->name.data);
|
||||
free(conn->description.data);
|
||||
free(conn);
|
||||
}
|
||||
|
||||
@ -66,38 +64,52 @@ Result(ServerConnection*) ServerConnection_open(cstr server_link_cstr){
|
||||
try_void(socket_connect(_s, conn->server_end));
|
||||
EncryptedSocketTCP_construct(&conn->sock, _s, NETWORK_BUFFER_SIZE, conn->session_key);
|
||||
|
||||
// send ClientHandshake using server public key for encryption
|
||||
PacketHeader req_header;
|
||||
// send PacketHeader and ClientHandshake
|
||||
// encryption by server public key
|
||||
PacketHeader packet_header;
|
||||
ClientHandshake client_handshake;
|
||||
try_void(ClientHandshake_tryConstruct(&client_handshake, &req_header,
|
||||
try_void(ClientHandshake_tryConstruct(&client_handshake, &packet_header,
|
||||
conn->session_key));
|
||||
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &req_header));
|
||||
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &packet_header));
|
||||
try_void(EncryptedSocketTCP_sendStructRSA(&conn->sock, &conn->rsa_enc, &client_handshake));
|
||||
|
||||
// receive server response
|
||||
PacketHeader res_header;
|
||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &packet_header));
|
||||
try_void(PacketHeader_validateMagic(&packet_header));
|
||||
|
||||
// handle server response
|
||||
switch(packet_header.type){
|
||||
case PacketType_ErrorMessage: {
|
||||
ErrorMessage err_msg;
|
||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &err_msg));
|
||||
if(err_msg.msg_size > conn->sock.recv_buf.size)
|
||||
err_msg.msg_size = conn->sock.recv_buf.size;
|
||||
Array(u8) err_buf = Array_alloc_size(err_msg.msg_size + 1);
|
||||
bool err_msg_completed = false;
|
||||
Defer(if(!err_msg_completed) free(err_buf.data));
|
||||
|
||||
// receive message content
|
||||
try_void(
|
||||
EncryptedSocketTCP_recv(
|
||||
&conn->sock,
|
||||
Array_sliceTo(err_buf, err_msg.msg_size),
|
||||
SocketRecvFlag_WholeBuffer
|
||||
)
|
||||
);
|
||||
|
||||
((u8*)err_buf.data)[err_msg.msg_size] = 0;
|
||||
err_msg_completed = true;
|
||||
Return RESULT_ERROR((char*)err_buf.data, true);
|
||||
}
|
||||
case PacketType_ServerHandshake: {
|
||||
ServerHandshake server_handshake;
|
||||
try_void(recvResponse(&conn->sock, &res_header, &server_handshake,
|
||||
PacketType_ServerHandshake));
|
||||
try_void(EncryptedSocketTCP_recvStruct(&conn->sock, &server_handshake));
|
||||
conn->session_id = server_handshake.session_id;
|
||||
|
||||
// get server name
|
||||
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, &conn->name));
|
||||
|
||||
// get server description
|
||||
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, &conn->description));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Return RESULT_ERROR_FMT("unexpected response type: %i", packet_header.type);
|
||||
}
|
||||
|
||||
success = true;
|
||||
Return RESULT_VALUE(p, conn);
|
||||
|
||||
@ -157,18 +157,24 @@ static Result(void) commandExec(Client* client, str command, bool* stop){
|
||||
ServerConnection_open(new_server_link.data));
|
||||
printf("connection established\n");
|
||||
|
||||
// TODO: show server info
|
||||
// TODO: save server info to user's db
|
||||
// TODO: ask in loop: log in / register
|
||||
// TODO: request server info
|
||||
// show server info
|
||||
// save server info to user's db
|
||||
// try log in
|
||||
// if not registered, request registration and then log in
|
||||
|
||||
//TODO: call Client_runIO():
|
||||
// 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
|
||||
// TODO: show scrollable list of servers, get selected one
|
||||
// TODO: ask in loop: log in / register
|
||||
// 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"
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
#include "cryptography/AES.h"
|
||||
#include "cryptography/RSA.h"
|
||||
#include "network/encrypted_sockets.h"
|
||||
#include "tlibc/string/str.h"
|
||||
|
||||
typedef struct Client Client;
|
||||
|
||||
@ -21,17 +20,16 @@ void ClientCredentials_destroy(ClientCredentials* cred);
|
||||
|
||||
|
||||
typedef struct ServerConnection {
|
||||
u64 session_id;
|
||||
EndpointIPv4 server_end;
|
||||
br_rsa_public_key server_pk;
|
||||
RSAEncryptor rsa_enc;
|
||||
u64 session_id;
|
||||
Array(u8) session_key;
|
||||
EncryptedSocketTCP sock;
|
||||
str name;
|
||||
str description;
|
||||
} ServerConnection;
|
||||
|
||||
Result(ServerConnection*) ServerConnection_open(cstr server_link_cstr);
|
||||
|
||||
void ServerConnection_close(ServerConnection* conn);
|
||||
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
#include "requests.h"
|
||||
|
||||
Result(void) recvStr(EncryptedSocketTCP* sock, u32 size, str* out_s){
|
||||
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(s),
|
||||
SocketRecvFlag_WholeBuffer
|
||||
)
|
||||
);
|
||||
|
||||
s.data[s.size] = 0;
|
||||
*out_s = 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.size)
|
||||
res.msg_size = sock->recv_buf.size;
|
||||
|
||||
str err_msg;
|
||||
try_void(recvStr(sock, res.msg_size, &err_msg));
|
||||
*out_err_msg = err_msg;
|
||||
|
||||
Return RESULT_VOID;
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
#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.data, true);
|
||||
}
|
||||
|
||||
try_void(PacketHeader_validateType(res_header, res_type));
|
||||
try_void(PacketHeader_validateContentSize(res_header, res.size));
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#include "network/tcp-chat-protocol/v1.h"
|
||||
#include "client/client.h"
|
||||
|
||||
|
||||
Result(void) recvErrorMessage(EncryptedSocketTCP* sock, PacketHeader* res_header,
|
||||
str* out_err_msg);
|
||||
|
||||
Result(void) recvStr(EncryptedSocketTCP* sock, u32 size, str* out_s);
|
||||
|
||||
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(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(req_ptr))
|
||||
@ -172,14 +172,13 @@ Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr,
|
||||
Array(u8) src, Array(u8) dst)
|
||||
{
|
||||
Deferral(4);
|
||||
u32 encrypted_size = AESStreamEncryptor_calcDstSize(src.size);
|
||||
try_assert(dst.size >= encrypted_size);
|
||||
|
||||
u32 encrypted_size = src.size;
|
||||
// if it is the beginning of the stream, write IV
|
||||
if(ptr->block_counter == 0){
|
||||
__Array_writeNext(&dst, ptr->iv, __AES_STREAM_IV_SIZE);
|
||||
encrypted_size = AESStreamEncryptor_calcDstSize(encrypted_size);
|
||||
}
|
||||
try_assert(dst.size >= encrypted_size);
|
||||
|
||||
// encrypt full buffers
|
||||
while(src.size > __AES_BUFFER_SIZE){
|
||||
|
||||
@ -101,7 +101,7 @@ void AESStreamEncryptor_changeKey(AESStreamEncryptor* ptr, Array(u8) key);
|
||||
|
||||
/// @brief If ptr->block_counter == 0, writes random IV to `dst`. After that writes encrypted data to dst.
|
||||
/// @param src array of any size
|
||||
/// @param dst array of size >= `AESStreamEncryptor_calcDstSize(src.size)` for first block and `src.size `for other blocks
|
||||
/// @param dst array of size >= AESStreamEncryptor_calcDstSize(src.size)
|
||||
/// @return size of encrypted data
|
||||
Result(u32) AESStreamEncryptor_encrypt(AESStreamEncryptor* ptr, Array(u8) src, Array(u8) dst);
|
||||
|
||||
|
||||
10
src/db/idb.c
10
src/db/idb.c
@ -39,8 +39,6 @@ static const Magic32 TABLE_FILE_MAGIC = { .bytes = { 'I', 'D', 'B', 't' } };
|
||||
|
||||
|
||||
void Table_close(Table* t){
|
||||
if(t == NULL)
|
||||
return;
|
||||
fclose(t->table_file);
|
||||
fclose(t->changes_file);
|
||||
free(t->name.data);
|
||||
@ -166,9 +164,10 @@ static Result(void) Table_validateEncryption(Table* t){
|
||||
|
||||
static Result(void) Table_validateRowSize(Table* t, u32 row_size){
|
||||
if(row_size != t->header.row_size){
|
||||
return RESULT_ERROR_FMT(
|
||||
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;
|
||||
@ -199,8 +198,6 @@ Result(IncrementalDB*) idb_open(str db_dir, NULLABLE(Array(u8) aes_key)){
|
||||
}
|
||||
|
||||
void idb_close(IncrementalDB* db){
|
||||
if(db == NULL)
|
||||
return;
|
||||
free(db->db_dir.data);
|
||||
free(db->aes_key.data);
|
||||
HashMap_destroy(&db->tables_map);
|
||||
@ -271,9 +268,10 @@ Result(Table*) idb_getOrCreateTable(IncrementalDB* db, str table_name, u32 row_s
|
||||
}
|
||||
|
||||
if(!HashMap_tryPush(&db->tables_map, t->name, &t)){
|
||||
Return RESULT_ERROR_FMT(
|
||||
ResultVar(void) error_result = RESULT_ERROR_FMT(
|
||||
"Table '%s' is already open",
|
||||
t->name.data);
|
||||
Return error_result;
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
@ -46,7 +46,6 @@ Result(void) EncryptedSocketTCP_send(EncryptedSocketTCP* ptr,
|
||||
)
|
||||
);
|
||||
|
||||
// printf("SEND data_size: %u, enc_size: %u\n", buffer.size, encrypted_size);
|
||||
Return RESULT_VOID;
|
||||
}
|
||||
|
||||
@ -75,7 +74,6 @@ Result(u32) EncryptedSocketTCP_recv(EncryptedSocketTCP* ptr,
|
||||
)
|
||||
);
|
||||
|
||||
// printf("RECV recv_size: %u, dec_size: %u\n", received_size, decrypted_size);
|
||||
Return RESULT_VALUE(u, decrypted_size);
|
||||
}
|
||||
|
||||
@ -98,7 +96,6 @@ Result(void) EncryptedSocketTCP_sendRSA(EncryptedSocketTCP* ptr,
|
||||
)
|
||||
);
|
||||
|
||||
// printf("SEND-RSA data_size: %u, enc_size: %u\n", buffer.size, encrypted_size);
|
||||
Return RESULT_VOID;
|
||||
}
|
||||
|
||||
@ -147,8 +144,6 @@ Result(u32) EncryptedSocketTCP_recvRSA(EncryptedSocketTCP* ptr,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -170,6 +165,7 @@ void EncryptedSocketUDP_construct(EncryptedSocketUDP* ptr,
|
||||
void EncryptedSocketUDP_destroy(EncryptedSocketUDP* ptr){
|
||||
if(!ptr)
|
||||
return;
|
||||
|
||||
socket_close(ptr->sock);
|
||||
free(ptr->recv_buf.data);
|
||||
free(ptr->send_buf.data);
|
||||
|
||||
@ -40,7 +40,7 @@ typedef enum PacketType {
|
||||
typedef struct ErrorMessage {
|
||||
u32 msg_size; // <= ERROR_MESSAGE_MAX_SIZE
|
||||
/* stream of size msg_size */
|
||||
} ALIGN_PACKET_STRUCT ErrorMessage;
|
||||
} ErrorMessage;
|
||||
|
||||
void ErrorMessage_construct(ErrorMessage* ptr, PacketHeader* header,
|
||||
u32 msg_size);
|
||||
@ -78,7 +78,7 @@ void ServerPublicInfoRequest_construct(ServerPublicInfoRequest* ptr, PacketHeade
|
||||
typedef struct ServerPublicInfoResponse {
|
||||
u32 data_size;
|
||||
/* stream of size data_size */
|
||||
} ALIGN_PACKET_STRUCT ServerPublicInfoResponse;
|
||||
} ServerPublicInfoResponse;
|
||||
|
||||
void ServerPublicInfoResponse_construct(ServerPublicInfoResponse* ptr, PacketHeader* header,
|
||||
u32 data_size);
|
||||
|
||||
@ -46,7 +46,7 @@ Result(ClientConnection*) ClientConnection_accept(ConnectionHandlerArgs* args)
|
||||
memcpy(conn->session_key.data, client_handshake.session_key, conn->session_key.size);
|
||||
EncryptedSocketTCP_changeKey(&conn->sock, conn->session_key);
|
||||
|
||||
// send ServerHandshake
|
||||
// send PacketHeader and ServerHandshake over encrypted TCP socket
|
||||
ServerHandshake server_handshake;
|
||||
ServerHandshake_construct(&server_handshake, &packet_header,
|
||||
conn->session_id);
|
||||
|
||||
@ -23,7 +23,7 @@ declare_RequestHandler(ServerPublicInfo)
|
||||
content = str_castTo_Array(server->name);
|
||||
break;
|
||||
case ServerPublicInfo_Description:
|
||||
content = str_castTo_Array(server->description);
|
||||
content = str_castTo_Array(server->name);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -78,7 +78,7 @@ Result(Server*) Server_createFromConfig(cstr config_path){
|
||||
|
||||
try_void(ServerCredentials_tryConstruct(&server->cred, sk_base64_cstr, pk_base64_cstr));
|
||||
|
||||
// parse db_aes_key
|
||||
// parse db_key
|
||||
try_void(config_findValue(config_str, STR("db_aes_key"), &tmp_str, true));
|
||||
Array(u8) db_aes_key = Array_alloc_size(base64_decodedSize(tmp_str.data, tmp_str.size));
|
||||
base64_decode(tmp_str.data, tmp_str.size, db_aes_key.data);
|
||||
@ -96,7 +96,7 @@ Result(Server*) Server_createFromConfig(cstr config_path){
|
||||
// load whole table to list
|
||||
try_void(idb_getRows(server->db_users_table, 0, server->users_cache_list.data, users_count));
|
||||
// build name-id map
|
||||
for(u64 id = 0; id < users_count; id++){
|
||||
for(u64 id; id < users_count; id++){
|
||||
User* u = &List_index(server->users_cache_list, User, id);
|
||||
str key = str_construct(u->name, u->name_len, true);
|
||||
if(!HashMap_tryPush(&server->users_name_id_map, key, &id)){
|
||||
@ -145,10 +145,9 @@ static void* handleConnection(void* _args){
|
||||
|
||||
ResultVar(void) r = try_handleConnection(args, log_ctx);
|
||||
if(r.error){
|
||||
str e_str = Error_toStr(r.error);
|
||||
logError(log_ctx, "%s", e_str.data);
|
||||
free(e_str.data);
|
||||
Error_free(r.error);
|
||||
str error_s = Error_toStr(r.error);
|
||||
logError(log_ctx, "%s", error_s.data);
|
||||
free(error_s.data);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
name = test server
|
||||
description = Lorem ipsum labuba aboba
|
||||
landing_channel_id = 0
|
||||
local_address = 127.0.0.1:9988
|
||||
db_dir = server-db
|
||||
db_aes_key = <generate with './tcp-chat --random-bytes-base64 32'>
|
||||
|
||||
rsa_private_key = <generate with './tcp-chat --rsa-gen-random'>
|
||||
rsa_public_key =
|
||||
Loading…
Reference in New Issue
Block a user