commit 57bfe61c13b3ed9e78070b1be8e15fb98ef3ba0d Author: DcruBro Date: Sun Mar 29 17:18:23 2026 +0200 Copied TCP impl from other project, basic Block implementation, randomx pow, signing via secp256k1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..252937e --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +CMakeCache.txt +cmake_install.cmake +Makefile +svrtest +main +CMakeFiles/ +.vscode/ +build/ +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6c41ac2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,128 @@ +cmake_minimum_required(VERSION 3.16) + +project(miniboinc + VERSION 0.1.0 + LANGUAGES C CXX +) + +set(CMAKE_C_STANDARD 23) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +find_package(Threads REQUIRED) + +# OpenSSL +find_package(OpenSSL QUIET) +if(NOT OpenSSL_FOUND) + if(APPLE) + execute_process( + COMMAND brew --prefix openssl@3 + OUTPUT_VARIABLE HOMEBREW_OPENSSL_PREFIX + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(HOMEBREW_OPENSSL_PREFIX) + set(OPENSSL_ROOT_DIR "${HOMEBREW_OPENSSL_PREFIX}") + endif() + endif() + find_package(OpenSSL REQUIRED) +endif() + +# secp256k1 (Bitcoin Core library) +find_package(PkgConfig QUIET) +if(PkgConfig_FOUND) + pkg_check_modules(SECP256K1 QUIET IMPORTED_TARGET libsecp256k1) +endif() + +if(NOT SECP256K1_FOUND) + find_path(SECP256K1_INCLUDE_DIR NAMES secp256k1.h) + find_library(SECP256K1_LIBRARY NAMES secp256k1) + if(NOT SECP256K1_INCLUDE_DIR OR NOT SECP256K1_LIBRARY) + message(FATAL_ERROR "secp256k1 not found. Install with: brew install secp256k1") + endif() +endif() + +# RandomX +set(RANDOMX_ROOT "" CACHE PATH "Root directory of a RandomX installation") +set(RANDOMX_USING_FETCHCONTENT OFF) +if(PkgConfig_FOUND) + pkg_check_modules(RANDOMX QUIET IMPORTED_TARGET randomx) +endif() + +if(NOT RANDOMX_FOUND) + find_path(RANDOMX_INCLUDE_DIR + NAMES randomx.h + HINTS ${RANDOMX_ROOT} + PATH_SUFFIXES include + ) + find_library(RANDOMX_LIBRARY + NAMES randomx + HINTS ${RANDOMX_ROOT} + PATH_SUFFIXES lib + ) + + if(NOT RANDOMX_INCLUDE_DIR OR NOT RANDOMX_LIBRARY) + include(FetchContent) + FetchContent_Declare( + randomx_src + GIT_REPOSITORY https://github.com/tevador/RandomX.git + GIT_TAG master + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(randomx_src) + set(RANDOMX_USING_FETCHCONTENT ON) + set(RANDOMX_INCLUDE_DIR ${randomx_src_SOURCE_DIR}/src) + endif() +endif() + +# --------------------------------------------------------- +# Output directories +# --------------------------------------------------------- +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +foreach(OUTPUTCONFIG DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG_UPPER) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/bin) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG_UPPER} ${CMAKE_BINARY_DIR}/lib) +endforeach() + +# Node +file(GLOB_RECURSE NODE_SRC CONFIGURE_DEPENDS src/*.c) +add_executable(node ${NODE_SRC}) +target_link_libraries(node PRIVATE + ${CMAKE_THREAD_LIBS_INIT} + OpenSSL::SSL + OpenSSL::Crypto +) + +if(SECP256K1_FOUND) + target_link_libraries(node PRIVATE PkgConfig::SECP256K1) +else() + target_include_directories(node PRIVATE ${SECP256K1_INCLUDE_DIR}) + target_link_libraries(node PRIVATE ${SECP256K1_LIBRARY}) +endif() + +if(RANDOMX_FOUND) + target_link_libraries(node PRIVATE PkgConfig::RANDOMX) +elseif(RANDOMX_USING_FETCHCONTENT) + target_link_libraries(node PRIVATE randomx) + target_include_directories(node PRIVATE ${RANDOMX_INCLUDE_DIR}) +else() + target_include_directories(node PRIVATE ${RANDOMX_INCLUDE_DIR}) + target_link_libraries(node PRIVATE ${RANDOMX_LIBRARY}) +endif() + +target_include_directories(node PRIVATE + ${PROJECT_SOURCE_DIR}/include +) +target_compile_options(node PRIVATE + -Wall + -Wextra + -Wpedantic + -g +) +target_compile_definitions(node PRIVATE) +set_target_properties(node PROPERTIES OUTPUT_NAME "minicoin_node") diff --git a/README.md b/README.md new file mode 100644 index 0000000..79505a9 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# MiniCoin + +Basic blockchain currency implementation in C, for educational purposes. Not intended for production use. + +cmake: +```bash +cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" +``` \ No newline at end of file diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..11ae860 --- /dev/null +++ b/TODO.txt @@ -0,0 +1 @@ +Eventually move the Proof of Work to RandomX. Keep the SHA-256 for Block IDs, TX IDs, etc. But nonce and mining should be RandomX. This will make it more resistant to ASICs and more accessible for CPU mining. \ No newline at end of file diff --git a/include/balance_sheet.h b/include/balance_sheet.h new file mode 100644 index 0000000..1608287 --- /dev/null +++ b/include/balance_sheet.h @@ -0,0 +1,11 @@ +#ifndef BALANCE_SHEET_H +#define BALANCE_SHEET_H + +#include + +typedef struct { + uint8_t address[32]; // For now just the SHA-256 of the public key; allows representation in different encodings (base58, bech32, etc) without changing the underlying data structure + uint64_t balance; +} balance_sheet_entry_t; + +#endif \ No newline at end of file diff --git a/include/block/block.h b/include/block/block.h new file mode 100644 index 0000000..79bbd3c --- /dev/null +++ b/include/block/block.h @@ -0,0 +1,36 @@ +#ifndef BLOCK_H +#define BLOCK_H + +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + uint8_t version; + uint32_t blockNumber; + uint8_t prevHash[32]; + uint8_t merkleRoot[32]; + uint64_t timestamp; + uint32_t difficultyTarget; // Encoding: [1 byte exponent][3 byte coefficient]; Target = coefficient * 256^(exponent-3) + uint64_t nonce; // Higher nonce for RandomX +} block_header_t; + +typedef struct { + block_header_t header; + DynArr* transactions; // Array of signed_transaction_t +} block_t; + +block_t* Block_Create(); +void Block_CalculateHash(const block_t* block, uint8_t* outHash); +void Block_CalculateRandomXHash(const block_t* block, uint8_t* outHash); +void Block_AddTransaction(block_t* block, signed_transaction_t* tx); +void Block_RemoveTransaction(block_t* block, uint8_t* txHash); +bool Block_HasValidProofOfWork(const block_t* block); +bool Block_AllTransactionsValid(const block_t* block); +void Block_Destroy(block_t* block); + +#endif diff --git a/include/block/chain.h b/include/block/chain.h new file mode 100644 index 0000000..3adecf9 --- /dev/null +++ b/include/block/chain.h @@ -0,0 +1,20 @@ +#ifndef CHAIN_H +#define CHAIN_H + +#include +#include +#include + +typedef struct { + DynArr* blocks; + size_t size; +} blockchain_t; + +blockchain_t* Chain_Create(); +void Chain_Destroy(blockchain_t* chain); +bool Chain_AddBlock(blockchain_t* chain, block_t* block); +block_t* Chain_GetBlock(blockchain_t* chain, size_t index); +size_t Chain_Size(blockchain_t* chain); +bool Chain_IsValid(blockchain_t* chain); + +#endif diff --git a/include/block/transaction.h b/include/block/transaction.h new file mode 100644 index 0000000..69746c2 --- /dev/null +++ b/include/block/transaction.h @@ -0,0 +1,48 @@ +#ifndef TRANSACTION_H +#define TRANSACTION_H + +#include +#include +#include + +// Special sender/recipient address marker for coinbase logic: 32 bytes of 0xFF. +static inline bool Address_IsCoinbase(const uint8_t address[32]) { + if (!address) { + return false; + } + + for (size_t i = 0; i < 32; ++i) { + if (address[i] != 0xFF) { + return false; + } + } + + return true; +} + +// 178 bytes total for v1 +typedef struct { + uint8_t version; + uint8_t senderAddress[32]; + uint8_t recipientAddress[32]; + uint64_t amount; + uint64_t fee; // Rewarded to the miner; can be zero, but the miner may choose to ignore transactions with very low fees + uint8_t compressedPublicKey[33]; + // Timestamp is dictated by the block +} transaction_t; + +typedef struct { + uint8_t txHash[32]; + uint8_t signature[64]; // Signature of the hash +} transaction_sig_t; + +typedef struct { + transaction_t transaction; + transaction_sig_t signature; +} signed_transaction_t; + +void Transaction_CalculateHash(const signed_transaction_t* tx, uint8_t* outHash); +void Transaction_Sign(signed_transaction_t* tx, const uint8_t* privateKey); +bool Transaction_Verify(const signed_transaction_t* tx); + +#endif diff --git a/include/crypto/crypto.h b/include/crypto/crypto.h new file mode 100644 index 0000000..2657061 --- /dev/null +++ b/include/crypto/crypto.h @@ -0,0 +1,13 @@ +#ifndef CRYPTO_H +#define CRYPTO_H + +#include +#include +#include +#include +#include + +bool Crypto_VerifySignature(const uint8_t* data, size_t len, const uint8_t* signature, const uint8_t* publicKey); +void Crypto_SignData(const uint8_t* data, size_t len, const uint8_t* privateKey, uint8_t* outSignature); + +#endif diff --git a/include/dynarr.h b/include/dynarr.h new file mode 100644 index 0000000..cb6c20b --- /dev/null +++ b/include/dynarr.h @@ -0,0 +1,60 @@ +#ifndef DYNARR_H +#define DYNARR_H + +#include +#include +#include + +#define DYNARR_MAX_CAPACITY ((size_t)0x7FFFFFFF) + +typedef struct { + size_t size; + size_t elemSize; + size_t capacity; + void* data; +} DynArr; + +// Do not use; Use DYNARR_CREATE macro instead. +DynArr* DynArr_create(size_t elemSize, size_t capacity); + +// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead. +void DynArr_reserve(DynArr* p, size_t n); + +// Push data into a new block at the end of the array +void* DynArr_push_back(DynArr* p, void* value); + +// Remove the last block in the array. +void DynArr_pop_back(DynArr* p); + +// Remove first block from array. +void DynArr_pop_front(DynArr* p); + +// Remove index from array. This moves all blocks after the index block. +void DynArr_remove(DynArr* p, size_t index); + +// Erase the array. This will not free unused blocks. +void DynArr_erase(DynArr* p); + +// Prune and free unused blocks. If pruning to zero, ensure to reserve after. +void DynArr_prune(DynArr* p); + +// Get a pointer to a block by index +void* DynArr_at(DynArr* p, size_t index); + +// Get the index by block pointer +size_t DynArr_at_ptr(DynArr* p, void* ptr); + +// Get size +size_t DynArr_size(DynArr* p); + +// Get element size +size_t DynArr_elemSize(DynArr* p); + +// Get capacity +size_t DynArr_capacity(DynArr* p); + +void DynArr_destroy(DynArr* p); + +#define DYNARR_CREATE(T, initialCapacity) DynArr_create(sizeof(T), initialCapacity) + +#endif diff --git a/include/dynset.h b/include/dynset.h new file mode 100644 index 0000000..016b386 --- /dev/null +++ b/include/dynset.h @@ -0,0 +1,20 @@ +#ifndef DYNSET_H +#define DYNSET_H + +#include + +// Dynamic Set structure - basically DynArr with uniqueness enforced +typedef struct { + DynArr* arr; +} DynSet; + +// Function prototypes +DynSet* DynSet_Create(size_t elemSize); +void DynSet_Destroy(DynSet* set); +int DynSet_Insert(DynSet* set, const void* element); +int DynSet_Contains(DynSet* set, const void* element); +size_t DynSet_Size(DynSet* set); +void* DynSet_Get(DynSet* set, size_t index); +void DynSet_Remove(DynSet* set, const void* element); + +#endif diff --git a/include/numgen.h b/include/numgen.h new file mode 100644 index 0000000..ad3207e --- /dev/null +++ b/include/numgen.h @@ -0,0 +1,14 @@ +#ifndef NUMGEN_H +#define NUMGEN_H + +#include +#include +#include +#include + +unsigned char random_byte(void); +uint16_t random_two_byte(void); +uint32_t random_four_byte(void); +uint64_t random_eight_byte(void); + +#endif diff --git a/include/packettype.h b/include/packettype.h new file mode 100644 index 0000000..58aeb81 --- /dev/null +++ b/include/packettype.h @@ -0,0 +1,18 @@ +#ifndef PACKETTYPE_H +#define PACKETTYPE_H + +typedef enum { + PACKET_TYPE_NONE = 0, + PACKET_TYPE_HELLO = 1, + PACKET_TYPE_TASK_ASSIGN = 2, + PACKET_TYPE_TASK_RESULT = 3, + PACKET_TYPE_STATUS_UPDATE = 4, + PACKET_TYPE_CLIENT_CAPABILITIES = 5, + PACKET_TYPE_TASK_REQUEST = 6, + PACKET_TYPE_MISSING_INFO = 7, + PACKET_TYPE_ACKNOWLEDGE = 8, + PACKET_TYPE_TASK_REJECT = 9, + PACKET_TYPE_TASK_NONE_AVAILABLE = 10 +} PacketType; + +#endif diff --git a/include/randomx/librx_wrapper.h b/include/randomx/librx_wrapper.h new file mode 100644 index 0000000..0af8262 --- /dev/null +++ b/include/randomx/librx_wrapper.h @@ -0,0 +1,21 @@ +#ifndef LIBRX_WRAPPER_H +#define LIBRX_WRAPPER_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool RandomX_Init(const char* key); +void RandomX_Destroy(); +void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/tcpd/tcpclient.h b/include/tcpd/tcpclient.h new file mode 100644 index 0000000..f1c6d98 --- /dev/null +++ b/include/tcpd/tcpclient.h @@ -0,0 +1,29 @@ +#ifndef TCPCLIENT_H +#define TCPCLIENT_H + +#include +#include +#include +#include +#include +#include +#include + +#define MTU 1500 + +struct TcpClient { + int clientFd; + struct sockaddr_in clientAddr; + uint32_t clientId; + + unsigned char dataBuf[MTU]; + ssize_t dataBufLen; + void (*on_data)(struct TcpClient* client); + void (*on_disconnect)(struct TcpClient* client); + + pthread_t clientThread; +}; + +typedef struct TcpClient TcpClient; + +#endif diff --git a/include/tcpd/tcpserver.h b/include/tcpd/tcpserver.h new file mode 100644 index 0000000..332f867 --- /dev/null +++ b/include/tcpd/tcpserver.h @@ -0,0 +1,56 @@ +#ifndef TCPSERVER_H +#define TCPSERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef struct { + int sockFd; + struct sockaddr_in addr; + int opt; + + // Called before the client thread runs + void (*on_connect)(TcpClient* client); + // Called when data is received + void (*on_data)(TcpClient* client); + // Called before the socket and client thread are killed; Do NOT free client manually + void (*on_disconnect)(TcpClient* client); + + // max clients + size_t clients; + TcpClient** clientsArrPtr; + + pthread_t svrThread; +} TcpServer; + +struct tcpclient_thread_args { + TcpClient* clientPtr; + TcpServer* serverPtr; +}; + +typedef struct tcpclient_thread_args tcpclient_thread_args; + +TcpServer* TcpServer_Create(); +void TcpServer_Destroy(TcpServer* ptr); + +void TcpServer_Init(TcpServer* ptr, unsigned short port, const char* addr); +void TcpServer_Start(TcpServer* ptr, int maxcons); +void TcpServer_Stop(TcpServer* ptr); +void TcpServer_Send(TcpServer* ptr, TcpClient* cli, void* data, size_t len); +void Generic_SendSocket(int sock, void* data, size_t len); +void TcpServer_Disconnect(TcpServer* ptr, TcpClient* cli); +void TcpServer_KillClient(TcpServer* ptr, TcpClient* cli); + +size_t Generic_FindClientInArrayByPtr(TcpClient** arr, TcpClient* ptr, size_t len); + +#endif diff --git a/src/block/block.c b/src/block/block.c new file mode 100644 index 0000000..30fd7fc --- /dev/null +++ b/src/block/block.c @@ -0,0 +1,163 @@ +#include +#include + +block_t* Block_Create() { + block_t* block = (block_t*)malloc(sizeof(block_t)); + if (!block) { + return NULL; + } + memset(&block->header, 0, sizeof(block_header_t)); + block->transactions = DYNARR_CREATE(signed_transaction_t, 1); + if (!block->transactions) { + free(block); + return NULL; + } + return block; +} + +void Block_CalculateHash(const block_t* block, uint8_t* outHash) { + if (!block || !outHash || !block->transactions || DynArr_size(block->transactions) <= 0) { + return; + } + + // Merkle root TODO + + // Flatten the block header and transactions into a single buffer for hashing (assume that txs are verified - usually on receive) + uint8_t buffer[sizeof(block_header_t) + (DynArr_size(block->transactions) * DynArr_elemSize(block->transactions))]; + memcpy(buffer, &block->header, sizeof(block_header_t)); + for (size_t i = 0; i < DynArr_size(block->transactions); i++) { + void* txPtr = (char*)DynArr_at(block->transactions, i); + memcpy(buffer + sizeof(block_header_t) + (i * DynArr_elemSize(block->transactions)), txPtr, DynArr_elemSize(block->transactions)); + } + + SHA256((const unsigned char*)buffer, sizeof(buffer), outHash); + SHA256(outHash, 32, outHash); // Double-Hash +} + +void Block_CalculateRandomXHash(const block_t* block, uint8_t* outHash) { + if (!block || !outHash || !block->transactions || DynArr_size(block->transactions) <= 0) { + return; + } + + // Merkle root TODO + + // Flatten the block header and transactions into a single buffer for hashing (assume that txs are verified - usually on receive) + uint8_t buffer[sizeof(block_header_t) + (DynArr_size(block->transactions) * DynArr_elemSize(block->transactions))]; + memcpy(buffer, &block->header, sizeof(block_header_t)); + for (size_t i = 0; i < DynArr_size(block->transactions); i++) { + void* txPtr = (char*)DynArr_at(block->transactions, i); + memcpy(buffer + sizeof(block_header_t) + (i * DynArr_elemSize(block->transactions)), txPtr, DynArr_elemSize(block->transactions)); + } + + RandomX_CalculateHash(buffer, sizeof(buffer), outHash); +} + +void Block_AddTransaction(block_t* block, signed_transaction_t* tx) { + if (!block || !tx || !block->transactions) { + return; + } + + DynArr_push_back(block->transactions, tx); +} + +void Block_RemoveTransaction(block_t* block, uint8_t* txHash) { + if (!block || !txHash || !block->transactions) { + return; + } + + for (size_t i = 0; i < DynArr_size(block->transactions); i++) { + signed_transaction_t* currentTx = (signed_transaction_t*)DynArr_at(block->transactions, i); + if (memcmp(currentTx->signature.txHash, txHash, 32) == 0) { + DynArr_remove(block->transactions, i); + return; + } + } +} + +static int Uint256_CompareBE(const uint8_t a[32], const uint8_t b[32]) { + for (int i = 0; i < 32; ++i) { + if (a[i] < b[i]) return -1; + if (a[i] > b[i]) return 1; + } + return 0; +} + +static bool DecodeCompactTarget(uint32_t nBits, uint8_t target[32]) { + memset(target, 0, 32); + + uint32_t exponent = nBits >> 24; + uint32_t mantissa = nBits & 0x007fffff; // ignore sign bit for now + bool negative = (nBits & 0x00800000) != 0; + + if (negative || mantissa == 0) { + return false; + } + + // Compute: target = mantissa * 256^(exponent - 3) + if (exponent <= 3) { + mantissa >>= 8 * (3 - exponent); + + target[29] = (mantissa >> 16) & 0xff; + target[30] = (mantissa >> 8) & 0xff; + target[31] = mantissa & 0xff; + } else { + uint32_t byte_index = exponent - 3; // number of zero-bytes appended on right + + if (byte_index > 29) { + return false; // overflow 256 bits + } + + target[32 - byte_index - 3] = (mantissa >> 16) & 0xff; + target[32 - byte_index - 2] = (mantissa >> 8) & 0xff; + target[32 - byte_index - 1] = mantissa & 0xff; + } + + return true; +} + +bool Block_HasValidProofOfWork(const block_t* block) { + if (!block) { + return false; + } + + uint8_t target[32]; + if (!DecodeCompactTarget(block->header.difficultyTarget, target)) { + return false; + } + + uint8_t hash[32]; + Block_CalculateRandomXHash(block, hash); + + return Uint256_CompareBE(hash, target) <= 0; +} + +bool Block_AllTransactionsValid(const block_t* block) { + if (!block || !block->transactions) { + return false; + } + + bool hasCoinbase = false; + + for (size_t i = 0; i < DynArr_size(block->transactions); i++) { + signed_transaction_t* tx = (signed_transaction_t*)DynArr_at(block->transactions, i); + if (!Transaction_Verify(tx)) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + if (hasCoinbase) { + return false; // More than one coinbase transaction + } + + hasCoinbase = true; + } + } + + return true && hasCoinbase && DynArr_size(block->transactions) > 0; // Every block must have at least one transaction (the coinbase) +} + +void Block_Destroy(block_t* block) { + if (!block) return; + DynArr_destroy(block->transactions); + free(block); +} diff --git a/src/block/chain.c b/src/block/chain.c new file mode 100644 index 0000000..e511b6c --- /dev/null +++ b/src/block/chain.c @@ -0,0 +1,53 @@ +#include + +blockchain_t* Chain_Create() { + blockchain_t* ptr = (blockchain_t*)malloc(sizeof(blockchain_t)); + if (!ptr) { + return NULL; + } + + ptr->blocks = DYNARR_CREATE(block_t, 1); + ptr->size = 0; + + return ptr; +} + +void Chain_Destroy(blockchain_t* chain) { + if (chain) { + if (chain->blocks) { + DynArr_destroy(chain->blocks); + } + free(chain); + } +} + +bool Chain_AddBlock(blockchain_t* chain, block_t* block) { + if (chain && block && chain->blocks) { + DynArr_push_back(chain->blocks, block); + return true; + } + + return false; +} + +block_t* Chain_GetBlock(blockchain_t* chain, size_t index) { + if (chain) { + return DynArr_at(chain->blocks, index); + } + return NULL; +} + +size_t Chain_Size(blockchain_t* chain) { + if (chain) { + return DynArr_size(chain->blocks); + } + return 0; +} + +bool Chain_IsValid(blockchain_t* chain) { + if (!chain || !chain->blocks) { + return false; + } + // Add validation logic here + return true; +} diff --git a/src/block/transaction.c b/src/block/transaction.c new file mode 100644 index 0000000..2894ff4 --- /dev/null +++ b/src/block/transaction.c @@ -0,0 +1,76 @@ +#include +#include + +void Transaction_CalculateHash(const signed_transaction_t* tx, uint8_t* outHash) { + if (!tx || !outHash) { + return; + } + + uint8_t buffer[sizeof(transaction_t)]; + memcpy(buffer, &tx->transaction, sizeof(transaction_t)); + + SHA256(buffer, sizeof(buffer), outHash); + SHA256(outHash, 32, outHash); // Double-Hash +} + +void Transaction_Sign(signed_transaction_t* tx, const uint8_t* privateKey) { + if (!tx || !privateKey) { + return; + } + + Transaction_CalculateHash(tx, tx->signature.txHash); + Crypto_SignData( + (const uint8_t*)&tx->transaction, + sizeof(transaction_t), + privateKey, + tx->signature.signature + ); +} + +bool Transaction_Verify(const signed_transaction_t* tx) { + if (!tx) { + return false; + } + + if (Address_IsCoinbase(tx->transaction.senderAddress)) { + // Coinbase transactions are valid if the signature is correct for the block (handled in Block_Verify) + return true; + } + + uint8_t computeAddress[32]; + SHA256(tx->transaction.compressedPublicKey, 33, computeAddress); // Address is hash of public key + if (memcmp(computeAddress, tx->transaction.senderAddress, 32) != 0) { + return false; // Sender address does not match public key + } + + if (tx->transaction.amount == 0) { + return false; // Zero-amount transactions are not valid + } + + if (tx->transaction.fee > tx->transaction.amount) { + return false; // Fee cannot exceed amount + } + + if (tx->transaction.version != 1) { + return false; // Unsupported version + } + + if (Address_IsCoinbase(tx->transaction.recipientAddress)) { + return false; // Cannot send to coinbase address + } + + uint8_t txHash[32]; + Transaction_CalculateHash(tx, txHash); + + if (memcmp(txHash, tx->signature.txHash, 32) != 0) { + return false; // Hash does not match signature hash + } + + // If all checks pass, verify the signature + return Crypto_VerifySignature( + (const uint8_t*)&tx->transaction, + sizeof(transaction_t), + tx->signature.signature, + tx->transaction.compressedPublicKey + ); +} diff --git a/src/crypto/crypto.c b/src/crypto/crypto.c new file mode 100644 index 0000000..700b5a7 --- /dev/null +++ b/src/crypto/crypto.c @@ -0,0 +1,77 @@ +#include +#include + +static bool crypto_hash_to_32(const uint8_t* data, size_t len, uint8_t out32[32]) { + if (!data || !out32) { + return false; + } + return SHA256(data, len, out32) != NULL; +} + +bool Crypto_VerifySignature(const uint8_t* data, size_t len, const uint8_t* signature, const uint8_t* publicKey) { + if (!data || !signature || !publicKey) { + return false; + } + + uint8_t digest[32]; + if (!crypto_hash_to_32(data, len, digest)) { + return false; + } + + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + if (!ctx) { + return false; + } + + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature sig; + + const int pub_ok = secp256k1_ec_pubkey_parse(ctx, &pubkey, publicKey, 33); + const int sig_ok = secp256k1_ecdsa_signature_parse_compact(ctx, &sig, signature); + const int verified = (pub_ok && sig_ok) ? secp256k1_ecdsa_verify(ctx, &sig, digest, &pubkey) : 0; + + secp256k1_context_destroy(ctx); + return verified == 1; +} + +void Crypto_SignData(const uint8_t* data, size_t len, const uint8_t* privateKey, uint8_t* outSignature) { + if (!data || !privateKey || !outSignature) { + return; + } + + uint8_t digest[32]; + if (!crypto_hash_to_32(data, len, digest)) { + memset(outSignature, 0, 64); + return; + } + + secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + if (!ctx) { + memset(outSignature, 0, 64); + return; + } + + if (!secp256k1_ec_seckey_verify(ctx, privateKey)) { + memset(outSignature, 0, 64); + secp256k1_context_destroy(ctx); + return; + } + + secp256k1_ecdsa_signature sig; + const int sign_ok = secp256k1_ecdsa_sign( + ctx, + &sig, + digest, + privateKey, + secp256k1_nonce_function_default, + NULL + ); + if (!sign_ok) { + memset(outSignature, 0, 64); + secp256k1_context_destroy(ctx); + return; + } + + secp256k1_ecdsa_signature_serialize_compact(ctx, outSignature, &sig); + secp256k1_context_destroy(ctx); +} diff --git a/src/dynarr.c b/src/dynarr.c new file mode 100644 index 0000000..5055d70 --- /dev/null +++ b/src/dynarr.c @@ -0,0 +1,175 @@ +#include + +DynArr* DynArr_create(size_t elemSize, size_t capacity) { + DynArr* p = (DynArr*)malloc(sizeof(DynArr)); + if (!p) return NULL; + + p->elemSize = elemSize; + p->capacity = capacity; + p->data = malloc(elemSize * capacity); + if (!p->data) { + free(p); + return NULL; + } + p->size = 0; + + return p; +} + +// Reserve n blocks in arary; New size will be n, NOT size + n; Reserving less memory that current will fail, use prune instead. +void DynArr_reserve(DynArr* p, size_t n) { + if (n <= p->capacity) { + printf("reserve ignored; attempted to reserve less or equal to current capacity\n"); + return; + } + + if (n > DYNARR_MAX_CAPACITY) { + printf("reserve ignored; attempted to reserve over 32 bits\n"); + return; + } + + void* new_data = realloc(p->data, n * p->elemSize); + if (!new_data) { + printf("reserve failed\n"); + exit(1); + } + p->data = new_data; + p->capacity = n; +} + +// Push data into a new block at the end of the array; If value is NULL, the new block will be zeroed. +void* DynArr_push_back(DynArr* p, void* value) { + //if (value == NULL) { + // printf("push_back ignored; value is null"); + // return NULL; + //} + + if (p->size >= p->capacity) { + size_t new_cap = (p->capacity == 0) ? 1 : p->capacity * 2; + + if (new_cap < p->capacity || new_cap > DYNARR_MAX_CAPACITY) { + printf("push_back ignored; capacity overflow\n"); + return NULL; + } + + void* new_data = realloc(p->data, new_cap * p->elemSize); + if (!new_data) { + printf("push failed\n"); + exit(1); + } + p->capacity = new_cap; + p->data = new_data; + } + + void* dst = (void*)((char*)p->data + (p->size * p->elemSize)); + + if (value == NULL) { + memset(dst, 0, p->elemSize); // Handle NULL value. + } else { + memcpy((char*)dst, value, p->elemSize); + } + + p->size++; + + return dst; +} + +// Remove the last block in the array. +void DynArr_pop_back(DynArr* p) { + if (p->size == 0) { + printf("pop_back ignored; size is 0\n"); + return; + } + + p->size--; // Will automatically overwrite that memory naturally +} + +// Remove first block from array. +void DynArr_pop_front(DynArr* p) { + if (p->size == 0) { + printf("pop_front ignored; size is 0\n"); + return; + } + + memmove( + (char*)p->data, + (char*)p->data + p->elemSize, + (p->size - 1) * p->elemSize + ); + + p->size--; +} + +// Remove index from array. This moves all blocks after the index block. +void DynArr_remove(DynArr* p, size_t index) { + if (index >= p->size) return; + + memmove( + (char*)p->data + (index * p->elemSize), + (char*)p->data + (index + 1) * p->elemSize, + (p->size - index - 1) * p->elemSize + ); + + p->size--; +} + +// Erase the array. This will not free unused blocks. +void DynArr_erase(DynArr* p) { + p->size = 0; +} + +// Prune and free unused blocks. If pruning to zero, ensure to reserve after. +void DynArr_prune(DynArr* p) { + void* new_data = realloc(p->data, (p->size == 0 ? 1 : p->size) * p->elemSize); + if (!new_data) { + printf("pruning failed\n"); + exit(1); + } + + p->data = new_data; + p->capacity = p->size; +} + +// Get a pointer to a block by index +void* DynArr_at(DynArr* p, size_t index) { + if (index >= p->size) return NULL; + return (char*)p->data + (index * p->elemSize); +} + +// Get the index by block pointer +size_t DynArr_at_ptr(DynArr* p, void* ptr) { + if (!p || !ptr) { + printf("invalid pointer\n"); + exit(1); + } + + for (size_t i = 0; i < p->size; i++) { + if ((void*)(((char*)p->data) + (i * p->elemSize)) == ptr) { + return i; + } + } + + // If for some reason the array has 2^64 elements in it, then fuck it, I guess we'll just crash, I don't care. + return -1; +} + +// Get size +size_t DynArr_size(DynArr* p) { + return p->size; +} + +// Get element size +size_t DynArr_elemSize(DynArr* p) { + return p->elemSize; +} + +// Get capacity +size_t DynArr_capacity(DynArr* p) { + return p->capacity; +} + +void DynArr_destroy(DynArr* p) { + if (!p) return; + free(p->data); + free(p); +} diff --git a/src/dynset.c b/src/dynset.c new file mode 100644 index 0000000..f54705a --- /dev/null +++ b/src/dynset.c @@ -0,0 +1,58 @@ +#include + +DynSet* DynSet_Create(size_t elemSize) { + DynSet* set = (DynSet*)malloc(sizeof(DynSet)); + if (!set) { + return NULL; + } + set->arr = DynArr_create(elemSize, 1); + if (!set->arr) { + free(set); + return NULL; + } + return set; +} + +void DynSet_Destroy(DynSet* set) { + if (set) { + DynArr_destroy(set->arr); + free(set); + } +} + +int DynSet_Insert(DynSet* set, const void* element) { + if (DynSet_Contains(set, element)) { + return 0; // Element already exists + } + return DynArr_push_back(set->arr, element) != NULL; +} + +int DynSet_Contains(DynSet* set, const void* element) { + size_t size = DynArr_size(set->arr); + for (size_t i = 0; i < size; i++) { + void* current = DynArr_at(set->arr, i); + if (memcmp(current, element, set->arr->elemSize) == 0) { + return 1; // Found + } + } + return 0; // Not found +} + +size_t DynSet_Size(DynSet* set) { + return DynArr_size(set->arr); +} + +void* DynSet_Get(DynSet* set, size_t index) { + return DynArr_at(set->arr, index); +} + +void DynSet_Remove(DynSet* set, const void* element) { + size_t size = DynArr_size(set->arr); + for (size_t i = 0; i < size; i++) { + void* current = DynArr_at(set->arr, i); + if (memcmp(current, element, set->arr->elemSize) == 0) { + DynArr_remove(set->arr, i); + return; + } + } +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..de8845a --- /dev/null +++ b/src/main.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +void handle_sigint(int sig) { + printf("Caught signal %d, exiting...\n", sig); + RandomX_Destroy(); + exit(0); +} + +static double MonotonicSeconds(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + ((double)ts.tv_nsec / 1000000000.0); +} + +static double MeasureRandomXHashrate(void) { + uint8_t input[80] = {0}; + uint8_t outHash[32]; + uint64_t counter = 0; + + const double start = MonotonicSeconds(); + double now = start; + do { + memcpy(input, &counter, sizeof(counter)); + RandomX_CalculateHash(input, sizeof(input), outHash); + counter++; + now = MonotonicSeconds(); + } while ((now - start) < 0.75); // short local benchmark window + + const double elapsed = now - start; + if (elapsed <= 0.0 || counter == 0) { + return 0.0; + } + + return (double)counter / elapsed; +} + +static uint32_t CompactTargetForExpectedHashes(double expectedHashes) { + if (expectedHashes < 1.0) { + expectedHashes = 1.0; + } + + // For exponent 0x1f: target = mantissa * 2^(8*(0x1f-3)) = mantissa * 2^224 + // So expected hashes ~= 2^256 / target = 2^32 / mantissa. + double mantissaF = 4294967296.0 / expectedHashes; + if (mantissaF < 1.0) { + mantissaF = 1.0; + } + if (mantissaF > 8388607.0) { + mantissaF = 8388607.0; // 0x007fffff + } + + const uint32_t mantissa = (uint32_t)mantissaF; + return (0x1fU << 24) | (mantissa & 0x007fffffU); +} + +static bool GenerateKeypair( + const secp256k1_context* ctx, + uint8_t outPrivateKey[32], + uint8_t outCompressedPublicKey[33] +) { + if (!ctx || !outPrivateKey || !outCompressedPublicKey) { + return false; + } + + secp256k1_pubkey pubkey; + for (size_t i = 0; i < 1024; ++i) { + arc4random_buf(outPrivateKey, 32); + if (!secp256k1_ec_seckey_verify(ctx, outPrivateKey)) { + continue; + } + + if (!secp256k1_ec_pubkey_create(ctx, &pubkey, outPrivateKey)) { + continue; + } + + size_t serializedLen = 33; + if (!secp256k1_ec_pubkey_serialize( + ctx, + outCompressedPublicKey, + &serializedLen, + &pubkey, + SECP256K1_EC_COMPRESSED + )) { + continue; + } + + return serializedLen == 33; + } + + return false; +} + +static bool MineBlock(block_t* block) { + if (!block) { + return false; + } + + for (uint64_t nonce = 0;; ++nonce) { + block->header.nonce = nonce; + if (Block_HasValidProofOfWork(block)) { + return true; + } + + if (nonce == UINT64_MAX) { + return false; + } + } +} + +int main(void) { + signal(SIGINT, handle_sigint); + + // Init RandomX + if (!RandomX_Init("minicoin")) { // TODO: Use a key that is not hardcoded; E.g. hash of the last block, every thousand blocks, etc. + fprintf(stderr, "failed to initialize RandomX\n"); + return 1; + } + + blockchain_t* chain = Chain_Create(); + if (!chain) { + fprintf(stderr, "failed to create chain\n"); + return 1; + } + + secp256k1_context* secpCtx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + if (!secpCtx) { + fprintf(stderr, "failed to create secp256k1 context\n"); + Chain_Destroy(chain); + return 1; + } + + uint8_t senderPrivateKey[32]; + uint8_t receiverPrivateKey[32]; + uint8_t senderCompressedPublicKey[33]; + uint8_t receiverCompressedPublicKey[33]; + + if (!GenerateKeypair(secpCtx, senderPrivateKey, senderCompressedPublicKey) || + !GenerateKeypair(secpCtx, receiverPrivateKey, receiverCompressedPublicKey)) { + fprintf(stderr, "failed to generate keypairs\n"); + secp256k1_context_destroy(secpCtx); + Chain_Destroy(chain); + return 1; + } + + signed_transaction_t tx; + memset(&tx, 0, sizeof(tx)); + tx.transaction.version = 1; + tx.transaction.amount = 100; + tx.transaction.fee = 1; + + SHA256(senderCompressedPublicKey, 33, tx.transaction.senderAddress); + SHA256(receiverCompressedPublicKey, 33, tx.transaction.recipientAddress); + memcpy(tx.transaction.compressedPublicKey, senderCompressedPublicKey, 33); + + Transaction_Sign(&tx, senderPrivateKey); + if (!Transaction_Verify(&tx)) { + fprintf(stderr, "signed transaction did not verify\n"); + secp256k1_context_destroy(secpCtx); + Chain_Destroy(chain); + RandomX_Destroy(); + return 1; + } + + block_t* block = Block_Create(); + if (!block) { + fprintf(stderr, "failed to create block\n"); + secp256k1_context_destroy(secpCtx); + Chain_Destroy(chain); + RandomX_Destroy(); + return 1; + } + + block->header.version = 1; + block->header.blockNumber = (uint32_t)Chain_Size(chain); + memset(block->header.prevHash, 0, sizeof(block->header.prevHash)); + memset(block->header.merkleRoot, 0, sizeof(block->header.merkleRoot)); + block->header.timestamp = (uint64_t)time(NULL); + + const double hps = MeasureRandomXHashrate(); + const double targetSeconds = 60.0; + const double expectedHashes = (hps > 0.0) ? (hps * targetSeconds) : 65536.0; + block->header.difficultyTarget = CompactTargetForExpectedHashes(expectedHashes); + block->header.nonce = 0; + + printf("RandomX benchmark: %.2f H/s, target %.0fs, nBits=0x%08x\n", + hps, + targetSeconds, + block->header.difficultyTarget); + + Block_AddTransaction(block, &tx); + printf("Added transaction to block: sender %02x... -> recipient %02x..., amount %lu, fee %lu\n", + tx.transaction.senderAddress[0], tx.transaction.senderAddress[31], + tx.transaction.recipientAddress[0], tx.transaction.recipientAddress[31], + tx.transaction.amount, tx.transaction.fee); + + if (!MineBlock(block)) { + fprintf(stderr, "failed to mine block within nonce range\n"); + Block_Destroy(block); + secp256k1_context_destroy(secpCtx); + Chain_Destroy(chain); + RandomX_Destroy(); + return 1; + } + + if (!Chain_AddBlock(chain, block)) { + fprintf(stderr, "failed to append block to chain\n"); + Block_Destroy(block); + secp256k1_context_destroy(secpCtx); + Chain_Destroy(chain); + RandomX_Destroy(); + return 1; + } + + printf("Mined block %u with nonce %llu and chain size %zu\n", + block->header.blockNumber, + (unsigned long long)block->header.nonce, + Chain_Size(chain)); + + printf("Block hash (SHA256): "); + uint8_t blockHash[32]; + Block_CalculateHash(block, blockHash); + for (size_t i = 0; i < 32; ++i) { + printf("%02x", blockHash[i]); + } + printf("\nBlock hash (RandomX): "); + uint8_t randomXHash[32]; + Block_CalculateRandomXHash(block, randomXHash); + for (size_t i = 0; i < 32; ++i) { + printf("%02x", randomXHash[i]); + } + printf("\n"); + + // Chain currently stores a copy of block_t that references the same tx array pointer, + // so we do not destroy `block` here to avoid invalidating chain data. + secp256k1_context_destroy(secpCtx); + + Chain_Destroy(chain); + return 0; +} diff --git a/src/numgen.c b/src/numgen.c new file mode 100644 index 0000000..e8b07a5 --- /dev/null +++ b/src/numgen.c @@ -0,0 +1,41 @@ +#include + +unsigned char random_byte(void) { + return (unsigned char)(rand() % 256); +} + +uint16_t random_two_byte(void) { + uint16_t x; + unsigned char bytes[2]; + for (unsigned char i = 0; i < 2; i++) { + bytes[i] = random_byte(); + } + + memcpy(&x, bytes, sizeof(x)); + + return x; +} + +uint32_t random_four_byte(void) { + uint32_t x; + unsigned char bytes[4]; + for (unsigned char i = 0; i < 4; i++) { + bytes[i] = random_byte(); + } + + memcpy(&x, bytes, sizeof(x)); + + return x; +} + +uint64_t random_eight_byte(void) { + uint64_t x; + unsigned char bytes[8]; + for (unsigned char i = 0; i < 8; i++) { + bytes[i] = random_byte(); + } + + memcpy(&x, bytes, sizeof(x)); + + return x; +} diff --git a/src/randomx/librx_wrapper.c b/src/randomx/librx_wrapper.c new file mode 100644 index 0000000..b8d9e76 --- /dev/null +++ b/src/randomx/librx_wrapper.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +static randomx_cache* rxCache = NULL; +static randomx_dataset* rxDataset = NULL; +static randomx_vm* rxVm = NULL; + +bool RandomX_Init(const char* key) { + if (!key || rxCache || rxVm) { + return false; + } + + const randomx_flags baseFlags = randomx_get_flags() | RANDOMX_FLAG_JIT; + randomx_flags vmFlags = baseFlags | RANDOMX_FLAG_FULL_MEM; + + rxCache = randomx_alloc_cache(baseFlags); + if (!rxCache) { + return false; + } + + randomx_init_cache(rxCache, key, strlen(key)); + + // Prefer full-memory mode. If dataset allocation fails, fall back to light mode. + rxDataset = randomx_alloc_dataset(vmFlags); + if (rxDataset) { + const unsigned long datasetItems = randomx_dataset_item_count(); + randomx_init_dataset(rxDataset, rxCache, 0, datasetItems); + rxVm = randomx_create_vm(vmFlags, NULL, rxDataset); + if (rxVm) { + printf("RandomX initialized in full-memory mode\n"); + return true; + } + + randomx_release_dataset(rxDataset); + rxDataset = NULL; + } + + vmFlags = baseFlags; + rxVm = randomx_create_vm(vmFlags, rxCache, NULL); + if (!rxVm) { + randomx_release_cache(rxCache); + rxCache = NULL; + return false; + } + + printf("RandomX initialized in light mode\n"); + return true; +} + +void RandomX_Destroy() { + if (rxVm) { + randomx_destroy_vm(rxVm); + rxVm = NULL; + } + if (rxDataset) { + randomx_release_dataset(rxDataset); + rxDataset = NULL; + } + if (rxCache) { + randomx_release_cache(rxCache); + rxCache = NULL; + } +} + +void RandomX_CalculateHash(const uint8_t* input, size_t inputLen, uint8_t* output) { + if (!rxVm || !input || !output) { + return; + } + randomx_calculate_hash(rxVm, input, inputLen, output); +} diff --git a/src/tcpd/tcpserver.c b/src/tcpd/tcpserver.c new file mode 100644 index 0000000..297b5f8 --- /dev/null +++ b/src/tcpd/tcpserver.c @@ -0,0 +1,317 @@ +#include + +TcpServer* TcpServer_Create() { + TcpServer* svr = (TcpServer*)malloc(sizeof(TcpServer)); + + if (!svr) { + perror("tcpserver - creation failure"); + exit(1); + } + + svr->sockFd = -1; + svr->svrThread = 0; + svr->on_connect = NULL; + svr->on_data = NULL; + svr->on_disconnect = NULL; + + svr->clients = 0; + svr->clientsArrPtr = NULL; + + return svr; +} + +void TcpServer_Destroy(TcpServer* ptr) { + if (ptr) { + if (ptr->clientsArrPtr) { + for (size_t i = 0; i < ptr->clients; i++) { + if (ptr->clientsArrPtr[i]) { + free(ptr->clientsArrPtr[i]); + } + } + + free(ptr->clientsArrPtr); + } + + close(ptr->sockFd); + free(ptr); + } +} + +void TcpServer_Init(TcpServer* ptr, unsigned short port, const char* addr) { + if (ptr) { + // Create socket + ptr->sockFd = socket(AF_INET, SOCK_STREAM, 0); + if (ptr->sockFd < 0) { + perror("tcpserver - socket"); + exit(EXIT_FAILURE); + } + + // Allow quick port resue + ptr->opt = 1; + setsockopt(ptr->sockFd, SOL_SOCKET, SO_REUSEADDR, &ptr->opt, sizeof(int)); + + // Fill address structure + memset(&ptr->addr, 0, sizeof(ptr->addr)); + ptr->addr.sin_family = AF_INET; + ptr->addr.sin_port = htons(port); + inet_pton(AF_INET, addr, &ptr->addr.sin_addr); + + // Bind + if (bind(ptr->sockFd, (struct sockaddr*)&ptr->addr, sizeof(ptr->addr)) < 0) { + perror("tcpserver - bind"); + close(ptr->sockFd); + exit(EXIT_FAILURE); + } + } +} + +// Do not call outside of func. +void* TcpServer_clientthreadprocess(void* ptr) { + if (!ptr) { + perror("Client ptr is null!\n"); + return NULL; + } + + tcpclient_thread_args* args = (tcpclient_thread_args*)ptr; + + TcpClient* cli = args->clientPtr; + TcpServer* svr = args->serverPtr; + + if (args) { + free(args); + } + + while (1) { + memset(cli->dataBuf, 0, MTU); // Reset buffer + ssize_t n = recv(cli->clientFd, cli->dataBuf, MTU, 0); + cli->dataBufLen = n; + + if (n == 0) { + break; // Client disconnected + } else if (n > 0) { + if (cli->on_data) { + cli->on_data(cli); + } + } + + pthread_testcancel(); // Check for thread death + } + + cli->on_disconnect(cli); + + // Close on exit + close(cli->clientFd); + + // Destroy + TcpClient** arr = svr->clientsArrPtr; + size_t idx = Generic_FindClientInArrayByPtr(arr, cli, svr->clients); + if (idx != SIZE_MAX) { + if (arr[idx]) { + free(arr[idx]); + arr[idx] = NULL; + } + } else { + perror("tcpserver (client thread) - something already freed the client!"); + } + + //free(ptr); + + return NULL; +} + +// Do not call outside of func. +void* TcpServer_threadprocess(void* ptr) { + if (!ptr) { + perror("Client ptr is null!\n"); + return NULL; + } + + TcpServer* svr = (TcpServer*)ptr; + while (1) { + TcpClient tempclient; + socklen_t clientsize = sizeof(tempclient.clientAddr); + int client = accept(svr->sockFd, (struct sockaddr*)&tempclient.clientAddr, &clientsize); + if (client >= 0) { + tempclient.clientFd = client; + tempclient.on_data = svr->on_data; + tempclient.on_disconnect = svr->on_disconnect; + + // I'm lazy, so I'm just copying the data for now (I should probably make this a better way) + TcpClient* heapCli = (TcpClient*)malloc(sizeof(TcpClient)); + if (!heapCli) { + perror("tcpserver - client failed to allocate"); + exit(EXIT_FAILURE); // Wtf just happened??? + } + + heapCli->clientAddr = tempclient.clientAddr; + heapCli->clientFd = tempclient.clientFd; + heapCli->on_data = tempclient.on_data; + heapCli->on_disconnect = tempclient.on_disconnect; + heapCli->clientId = random_four_byte(); + heapCli->dataBufLen = 0; + + size_t i; + for (i = 0; i < svr->clients; i++) { + if (svr->clientsArrPtr[i] == NULL) { + // Make use of that space + svr->clientsArrPtr[i] = heapCli; // We have now transfered the ownership :) + break; + } + } + + if (i == svr->clients) { + // Not found + // RST; Thread doesn't exist yet + struct linger so_linger; + so_linger.l_onoff = 1; + so_linger.l_linger = 0; + setsockopt(heapCli->clientFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); + close(heapCli->clientFd); + + free(heapCli); + heapCli = NULL; + //svr->clientsArrPtr[i] = NULL; + + continue; + } + + tcpclient_thread_args* arg = (tcpclient_thread_args*)malloc(sizeof(tcpclient_thread_args)); + arg->clientPtr = heapCli; + arg->serverPtr = svr; + + if (svr->on_connect) { + svr->on_connect(heapCli); + } + + pthread_create(&heapCli->clientThread, NULL, TcpServer_clientthreadprocess, arg); + pthread_detach(heapCli->clientThread); // May not work :( + } + + pthread_testcancel(); // Check for thread death + } + + return NULL; +} + +void TcpServer_Start(TcpServer* ptr, int maxcons) { + if (ptr) { + if (listen(ptr->sockFd, maxcons) < 0) { + perror("tcpserver - listen"); + close(ptr->sockFd); + exit(EXIT_FAILURE); + } + + ptr->clients = maxcons; + ptr->clientsArrPtr = (TcpClient**)malloc(sizeof(TcpClient*) * maxcons); + + if (!ptr->clientsArrPtr) { + perror("tcpserver - allocation of client space fatally errored"); + exit(EXIT_FAILURE); + } + + // Fucking null out everything + for (int i = 0; i < maxcons; i++) { + ptr->clientsArrPtr[i] = NULL; + } + } + + // Spawn server thread + pthread_create(&ptr->svrThread, NULL, TcpServer_threadprocess, ptr); +} + +void TcpServer_Stop(TcpServer* ptr) { + if (ptr && ptr->svrThread != 0) { + // Stop server + pthread_cancel(ptr->svrThread); + pthread_join(ptr->svrThread, NULL); + + // Disconnect clients + for (size_t i = 0; i < ptr->clients; i++) { + TcpClient* cliPtr = ptr->clientsArrPtr[i]; + if (cliPtr) { + close(cliPtr->clientFd); + pthread_cancel(cliPtr->clientThread); + } + } + + ptr->svrThread = 0; + } +} + +void TcpServer_Send(TcpServer* ptr, TcpClient* cli, void* data, size_t len) { + if (ptr && cli && data && len > 0) { + size_t sent = 0; + while (sent < len) { + // Ensure that all data is sent. TCP can split sends. + ssize_t n = send(cli->clientFd, (unsigned char*)data + sent, len - sent, 0); + if (n < 0) { + perror("tcpserver - send error"); + break; + } + sent += n; + } + } +} + +void Generic_SendSocket(int sock, void* data, size_t len) { + if (sock > 0 && data && len > 0) { + size_t sent = 0; + while (sent < len) { + ssize_t n = send(sock, (unsigned char*)data + sent, len - sent, 0); + if (n < 0) { + perror("generic - send socket error"); + break; + } + sent += n; + } + } +} + +void TcpServer_Disconnect(TcpServer* ptr, TcpClient* cli) { + if (ptr && cli) { + close(cli->clientFd); + pthread_cancel(cli->clientThread); + + size_t idx = Generic_FindClientInArrayByPtr(ptr->clientsArrPtr, cli, ptr->clients); + if (idx != SIZE_MAX) { + if (ptr->clientsArrPtr[idx]) { + free(ptr->clientsArrPtr[idx]); + } + ptr->clientsArrPtr[idx] = NULL; + } else { + perror("tcpserver - didn't find client to disconnect in array!"); + } + } +} + +void TcpServer_KillClient(TcpServer* ptr, TcpClient* cli) { + if (ptr && cli) { + // RST the connection + struct linger so_linger; + so_linger.l_onoff = 1; + so_linger.l_linger = 0; + setsockopt(cli->clientFd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); + close(cli->clientFd); + pthread_cancel(cli->clientThread); + + size_t idx = Generic_FindClientInArrayByPtr(ptr->clientsArrPtr, cli, ptr->clients); + if (idx != SIZE_MAX) { + if (ptr->clientsArrPtr[idx]) { + free(ptr->clientsArrPtr[idx]); + } + ptr->clientsArrPtr[idx] = NULL; + } else { + perror("tcpserver - didn't find client to kill in array!"); + } + } +} + +size_t Generic_FindClientInArrayByPtr(TcpClient** arr, TcpClient* ptr, size_t len) { + for (size_t i = 0; i < len; i++) { + if (arr[i] == ptr) { + return i; + } + } + + return SIZE_MAX; // Returns max unsigned, likely improbable to be correct +}