diff --git a/code/logic/CMakeLists.txt b/code/logic/CMakeLists.txt index 2d911f5..14fbbef 100644 --- a/code/logic/CMakeLists.txt +++ b/code/logic/CMakeLists.txt @@ -15,6 +15,7 @@ set(TEST_CODE soap.c stream.c keyboard.c + network.c ) # Create the library target @@ -23,6 +24,11 @@ add_library(fossil-io STATIC ${TEST_CODE} ${HEADER_FILES}) # Link the math library target_link_libraries(fossil-io PUBLIC m) +# Link to Winsock library only on Windows +if(WIN32) + target_link_libraries(fossil-io PUBLIC ws2_32) +endif() + # Set the library to be installed install(TARGETS fossil-io ARCHIVE DESTINATION lib diff --git a/code/logic/fossil/io/framework.h b/code/logic/fossil/io/framework.h index 02bfc3f..f0b0efd 100644 --- a/code/logic/fossil/io/framework.h +++ b/code/logic/fossil/io/framework.h @@ -16,6 +16,7 @@ // Include the necessary headers #include "keyboard.h" +#include "network.h" #include "output.h" #include "input.h" #include "error.h" diff --git a/code/logic/fossil/io/network.h b/code/logic/fossil/io/network.h new file mode 100644 index 0000000..2c4ecf9 --- /dev/null +++ b/code/logic/fossil/io/network.h @@ -0,0 +1,196 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#ifndef FOSSIL_IO_NETWORK_H +#define FOSSIL_IO_NETWORK_H + +#include + +#ifdef _WIN32 + #include + #include + typedef SOCKET fossil_io_socket_t; + #define FOSSIL_IO_INVALID_SOCKET INVALID_SOCKET +#else + #include + #include + #include + #include + #include + #define closesocket close + typedef int fossil_io_socket_t; + #define FOSSIL_IO_INVALID_SOCKET (-1) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Initialize the network stack (needed for Windows). + * Returns 0 on success, non-zero on failure. + */ +int fossil_io_network_create(void); + +/** + * Clean up network stack (needed for Windows). + */ +void fossil_io_network_destroy(void); + +/** + * Create a new TCP socket. + * Returns a valid socket on success or FOSSIL_IO_INVALID_SOCKET on failure. + */ +fossil_io_socket_t fossil_io_network_create_socket(void); + +/** + * Bind a socket to a specific port (IPv4/IPv6). + * Returns 0 on success, -1 on failure. + */ +int fossil_io_network_bind(fossil_io_socket_t sock, const char *ip, uint16_t port); + +/** + * Listen for incoming connections. + * Returns 0 on success, -1 on failure. + */ +int fossil_io_network_listen(fossil_io_socket_t sock, int backlog); + +/** + * Accept a new connection. + * Returns a valid socket on success, or FOSSIL_IO_INVALID_SOCKET on failure. + */ +fossil_io_socket_t fossil_io_network_accept(fossil_io_socket_t sock, char *client_ip, uint16_t *client_port); + +/** + * Connect to a remote server. + * Returns 0 on success, -1 on failure. + */ +int fossil_io_network_connect(fossil_io_socket_t sock, const char *ip, uint16_t port); + +/** + * Send data over a socket. + * Returns the number of bytes sent, or -1 on failure. + */ +int fossil_io_network_send(fossil_io_socket_t sock, const void *data, size_t len); + +/** + * Receive data from a socket. + * Returns the number of bytes received, or -1 on failure. + */ +int fossil_io_network_receive(fossil_io_socket_t sock, void *buffer, size_t len); + +/** + * Close a socket. + */ +void fossil_io_network_close(fossil_io_socket_t sock); + +#ifdef __cplusplus +} +/** + * C++ wrapper for the output functions. + */ +namespace fossil { + /** + * Namespace for input/output operations. + */ + namespace io { + /** + * Class for network operations. + */ + class Network { + public: + /** + * Initialize the network stack (needed for Windows). + * Returns 0 on success, non-zero on failure. + */ + static int init(void) { + return fossil_io_network_create(); + } + + /** + * Clean up network stack (needed for Windows). + */ + static void cleanup(void) { + fossil_io_network_destroy(); + } + + /** + * Create a new TCP socket. + * Returns a valid socket on success or FOSSIL_IO_INVALID_SOCKET on failure. + */ + static fossil_io_socket_t create_socket(void) { + return fossil_io_network_create_socket(); + } + + /** + * Bind a socket to a specific port (IPv4/IPv6). + * Returns 0 on success, -1 on failure. + */ + static int bind(fossil_io_socket_t sock, const char *ip, uint16_t port) { + return fossil_io_network_bind(sock, ip, port); + } + + /** + * Listen for incoming connections. + * Returns 0 on success, -1 on failure. + */ + static int listen(fossil_io_socket_t sock, int backlog) { + return fossil_io_network_listen(sock, backlog); + } + + /** + * Accept a new connection. + * Returns a valid socket on success, or FOSSIL_IO_INVALID_SOCKET on failure. + */ + static fossil_io_socket_t accept(fossil_io_socket_t sock, char *client_ip, uint16_t *client_port) { + return fossil_io_network_accept(sock, client_ip, client_port); + } + + /** + * Connect to a remote server. + * Returns 0 on success, -1 on failure. + */ + static int connect(fossil_io_socket_t sock, const char *ip, uint16_t port) { + return fossil_io_network_connect(sock, ip, port); + } + + /** + * Send data over a socket. + * Returns the number of bytes sent, or -1 on failure. + */ + static int send(fossil_io_socket_t sock, const void *data, size_t len) { + return fossil_io_network_send(sock, data, len); + } + + /** + * Receive data from a socket. + * Returns the number of bytes received, or -1 on failure. + */ + static int receive(fossil_io_socket_t sock, void *buffer, size_t len) { + return fossil_io_network_receive(sock, buffer, len); + } + + /** + * Close a socket. + */ + static void close(fossil_io_socket_t sock) { + fossil_io_network_close(sock); + } + + }; + } +} + +#endif + +#endif /* FOSSIL_IO_FRAMEWORK_H */ diff --git a/code/logic/meson.build b/code/logic/meson.build index 8b27dbd..1a99b92 100644 --- a/code/logic/meson.build +++ b/code/logic/meson.build @@ -1,10 +1,17 @@ dir = include_directories('.') cc = meson.get_compiler('c') +# Check if the host system is Windows +if host_machine.system() == 'windows' + winsock_dep = cc.find_library('ws2_32', required: true) +else + winsock_dep = [] +endif + fossil_io_lib = library('fossil-io', - files('parser.c', 'input.c', 'output.c', 'error.c', 'soap.c', 'stream.c', 'keyboard.c'), + files('parser.c', 'input.c', 'output.c', 'error.c', 'soap.c', 'stream.c', 'keyboard.c', 'network.c'), install: true, - dependencies: cc.find_library('m', required : false), + dependencies: [cc.find_library('m', required: false), winsock_dep], include_directories: dir) fossil_io_dep = declare_dependency( diff --git a/code/logic/network.c b/code/logic/network.c new file mode 100644 index 0000000..a8b058d --- /dev/null +++ b/code/logic/network.c @@ -0,0 +1,151 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#include "fossil/io/network.h" +#include +#include +#include // For POSIX error handling +#include // For exit() + +#ifdef _WIN32 + static WSADATA wsa; +#endif + +int fossil_io_network_create(void) { +#ifdef _WIN32 + return WSAStartup(MAKEWORD(2, 2), &wsa); +#else + return 0; // No initialization needed on Unix-like systems +#endif +} + +void fossil_io_network_destroy(void) { +#ifdef _WIN32 + WSACleanup(); +#endif +} + +fossil_io_socket_t fossil_io_network_create_socket(void) { + fossil_io_socket_t sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == FOSSIL_IO_INVALID_SOCKET) { +#ifdef _WIN32 + fprintf(stderr, "Socket creation failed with error: %d\n", WSAGetLastError()); +#else + perror("Socket creation failed"); +#endif + } + return sock; +} + +int fossil_io_network_bind(fossil_io_socket_t sock, const char *ip, uint16_t port) { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip ? inet_addr(ip) : INADDR_ANY; + + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { +#ifdef _WIN32 + fprintf(stderr, "Bind failed with error: %d\n", WSAGetLastError()); +#else + perror("Bind failed"); +#endif + return -1; + } + return 0; +} + +int fossil_io_network_listen(fossil_io_socket_t sock, int backlog) { + if (listen(sock, backlog) == -1) { +#ifdef _WIN32 + fprintf(stderr, "Listen failed with error: %d\n", WSAGetLastError()); +#else + perror("Listen failed"); +#endif + return -1; + } + return 0; +} + +fossil_io_socket_t fossil_io_network_accept(fossil_io_socket_t sock, char *client_ip, uint16_t *client_port) { + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + fossil_io_socket_t client_sock = accept(sock, (struct sockaddr*)&client_addr, &addr_len); + + if (client_sock == FOSSIL_IO_INVALID_SOCKET) { +#ifdef _WIN32 + fprintf(stderr, "Accept failed with error: %d\n", WSAGetLastError()); +#else + perror("Accept failed"); +#endif + } else if (client_ip) { + strcpy(client_ip, inet_ntoa(client_addr.sin_addr)); + if (client_port) { + *client_port = ntohs(client_addr.sin_port); + } + } + + return client_sock; +} + +int fossil_io_network_connect(fossil_io_socket_t sock, const char *ip, uint16_t port) { + struct sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr.s_addr = inet_addr(ip); + + if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { +#ifdef _WIN32 + fprintf(stderr, "Connect failed with error: %d\n", WSAGetLastError()); +#else + perror("Connect failed"); +#endif + return -1; + } + return 0; +} + +int fossil_io_network_send(fossil_io_socket_t sock, const void *data, size_t len) { + int bytes_sent = send(sock, data, (int)len, 0); + if (bytes_sent == -1) { +#ifdef _WIN32 + fprintf(stderr, "Send failed with error: %d\n", WSAGetLastError()); +#else + perror("Send failed"); +#endif + } + return bytes_sent; +} + +int fossil_io_network_receive(fossil_io_socket_t sock, void *buffer, size_t len) { + int bytes_received = recv(sock, buffer, (int)len, 0); + if (bytes_received == -1) { +#ifdef _WIN32 + fprintf(stderr, "Receive failed with error: %d\n", WSAGetLastError()); +#else + perror("Receive failed"); +#endif + } + return bytes_received; +} + +void fossil_io_network_close(fossil_io_socket_t sock) { +#ifdef _WIN32 + if (closesocket(sock) == SOCKET_ERROR) { + fprintf(stderr, "Close socket failed with error: %d\n", WSAGetLastError()); + } +#else + if (close(sock) == -1) { + perror("Close socket failed"); + } +#endif +} diff --git a/code/tests/cases/test_network.c b/code/tests/cases/test_network.c new file mode 100644 index 0000000..ea4405d --- /dev/null +++ b/code/tests/cases/test_network.c @@ -0,0 +1,103 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#include + +#include "fossil/io/framework.h" + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Utilites +// * * * * * * * * * * * * * * * * * * * * * * * * +// Setup steps for things like test fixtures and +// mock objects are set here. +// * * * * * * * * * * * * * * * * * * * * * * * * + +// Define the test suite and add test cases +FOSSIL_TEST_SUITE(c_network_suite); + +// Setup function for the test suite +FOSSIL_SETUP(c_network_suite) { + // Setup code here +} + +// Teardown function for the test suite +FOSSIL_TEARDOWN(c_network_suite) { + // Teardown code here +} + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Cases +// * * * * * * * * * * * * * * * * * * * * * * * * +// The test cases below are provided as samples, inspired +// by the Meson build system's approach of using test cases +// as samples for library usage. +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_CASE(c_test_network_init) { + int result = fossil_io_network_create(); + ASSUME_ITS_EQUAL_I32(0, result); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(c_test_network_create_socket) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil_io_network_close(sock); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(c_test_network_bind) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + int result = fossil_io_network_bind(sock, "127.0.0.1", 8080); + ASSUME_ITS_EQUAL_I32(0, result); + fossil_io_network_close(sock); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(c_test_network_listen) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil_io_network_bind(sock, "127.0.0.1", 8080); + int result = fossil_io_network_listen(sock, 5); + ASSUME_ITS_EQUAL_I32(0, result); + fossil_io_network_close(sock); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(c_test_network_close) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil_io_network_close(sock); + // No direct way to test close, but we assume no errors if it reaches here + fossil_io_network_destroy(); +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Pool +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_GROUP(c_network_tests) { + FOSSIL_TEST_ADD(c_network_suite, c_test_network_init); + FOSSIL_TEST_ADD(c_network_suite, c_test_network_create_socket); + FOSSIL_TEST_ADD(c_network_suite, c_test_network_bind); + FOSSIL_TEST_ADD(c_network_suite, c_test_network_listen); + FOSSIL_TEST_ADD(c_network_suite, c_test_network_close); + + FOSSIL_TEST_REGISTER(c_network_suite); +} diff --git a/code/tests/cases/test_network.cpp b/code/tests/cases/test_network.cpp new file mode 100644 index 0000000..90fb930 --- /dev/null +++ b/code/tests/cases/test_network.cpp @@ -0,0 +1,152 @@ +/* + * ----------------------------------------------------------------------------- + * Project: Fossil Logic + * + * This file is part of the Fossil Logic project, which aims to develop high- + * performance, cross-platform applications and libraries. The code contained + * herein is subject to the terms and conditions defined in the project license. + * + * Author: Michael Gene Brockus (Dreamer) + * + * Copyright (C) 2024 Fossil Logic. All rights reserved. + * ----------------------------------------------------------------------------- + */ +#include + +#include "fossil/io/framework.h" + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Utilites +// * * * * * * * * * * * * * * * * * * * * * * * * +// Setup steps for things like test fixtures and +// mock objects are set here. +// * * * * * * * * * * * * * * * * * * * * * * * * + +// Define the test suite and add test cases +FOSSIL_TEST_SUITE(cpp_network_suite); + +// Setup function for the test suite +FOSSIL_SETUP(cpp_network_suite) { + // Setup code here +} + +// Teardown function for the test suite +FOSSIL_TEARDOWN(cpp_network_suite) { + // Teardown code here +} + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Cases +// * * * * * * * * * * * * * * * * * * * * * * * * +// The test cases below are provided as samples, inspired +// by the Meson build system's approach of using test cases +// as samples for library usage. +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_CASE(cpp_test_network_init) { + int result = fossil_io_network_create(); + ASSUME_ITS_EQUAL_I32(0, result); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(cpp_test_network_create_socket) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil_io_network_close(sock); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(cpp_test_network_bind) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + int result = fossil_io_network_bind(sock, "127.0.0.1", 8080); + ASSUME_ITS_EQUAL_I32(0, result); + fossil_io_network_close(sock); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(cpp_test_network_listen) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil_io_network_bind(sock, "127.0.0.1", 8080); + int result = fossil_io_network_listen(sock, 5); + ASSUME_ITS_EQUAL_I32(0, result); + fossil_io_network_close(sock); + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(cpp_test_network_close) { + fossil_io_network_create(); + fossil_io_socket_t sock = fossil_io_network_create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil_io_network_close(sock); + // No direct way to test close, but we assume no errors if it reaches here + fossil_io_network_destroy(); +} + +FOSSIL_TEST_CASE(cpp_test_network_class_init) { + int result = fossil::io::Network::init(); + ASSUME_ITS_EQUAL_I32(0, result); + fossil::io::Network::cleanup(); +} + +FOSSIL_TEST_CASE(cpp_test_network_class_create_socket) { + fossil::io::Network::init(); + fossil_io_socket_t sock = fossil::io::Network::create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil::io::Network::close(sock); + fossil::io::Network::cleanup(); +} + +FOSSIL_TEST_CASE(cpp_test_network_class_bind) { + fossil::io::Network::init(); + fossil_io_socket_t sock = fossil::io::Network::create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + int result = fossil::io::Network::bind(sock, "127.0.0.1", 8080); + ASSUME_ITS_EQUAL_I32(0, result); + fossil::io::Network::close(sock); + fossil::io::Network::cleanup(); +} + +FOSSIL_TEST_CASE(cpp_test_network_class_listen) { + fossil::io::Network::init(); + fossil_io_socket_t sock = fossil::io::Network::create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil::io::Network::bind(sock, "127.0.0.1", 8080); + int result = fossil::io::Network::listen(sock, 5); + ASSUME_ITS_EQUAL_I32(0, result); + fossil::io::Network::close(sock); + fossil::io::Network::cleanup(); +} + +FOSSIL_TEST_CASE(cpp_test_network_class_close) { + fossil::io::Network::init(); + fossil_io_socket_t sock = fossil::io::Network::create_socket(); + ASSUME_NOT_EQUAL_I32(FOSSIL_IO_INVALID_SOCKET, sock); + fossil::io::Network::close(sock); + // No direct way to test close, but we assume no errors if it reaches here + fossil::io::Network::cleanup(); +} + +// * * * * * * * * * * * * * * * * * * * * * * * * +// * Fossil Logic Test Pool +// * * * * * * * * * * * * * * * * * * * * * * * * + +FOSSIL_TEST_GROUP(cpp_network_tests) { + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_init); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_create_socket); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_bind); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_listen); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_close); + + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_class_init); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_class_create_socket); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_class_bind); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_class_listen); + FOSSIL_TEST_ADD(cpp_network_suite, cpp_test_network_class_close); + + FOSSIL_TEST_REGISTER(cpp_network_suite); +} diff --git a/code/tests/meson.build b/code/tests/meson.build index f201f61..5a09e0a 100644 --- a/code/tests/meson.build +++ b/code/tests/meson.build @@ -2,7 +2,7 @@ if get_option('with_test').enabled() run_command(['python3', 'tools' / 'generate-runner.py'], check: true) test_c = ['unit_runner.c'] - test_cases = ['parser', 'stream', 'soap', 'input', 'error', 'keyboard'] + test_cases = ['parser', 'stream', 'soap', 'input', 'error', 'keyboard', 'network'] foreach cases : test_cases test_c += ['cases' / 'test_' + cases + '.c']