From 7099a86ca74fa637f26af38674f80fb8efd5f6fa Mon Sep 17 00:00:00 2001 From: Trumeet Date: Tue, 26 Jul 2022 17:36:20 -0700 Subject: refactor(libacron/acronc/helloworld): move to separate directories The corresponding CMakeLists.txt files are still rough. --- client/helloworld/.gitignore | 82 +++++++++ client/helloworld/CMakeLists.txt | 50 ++++++ client/helloworld/main.c | 370 +++++++++++++++++++++++++++++++++++++++ client/helloworld/net.c | 181 +++++++++++++++++++ client/helloworld/net.h | 45 +++++ 5 files changed, 728 insertions(+) create mode 100644 client/helloworld/.gitignore create mode 100644 client/helloworld/CMakeLists.txt create mode 100644 client/helloworld/main.c create mode 100644 client/helloworld/net.c create mode 100644 client/helloworld/net.h (limited to 'client/helloworld') diff --git a/client/helloworld/.gitignore b/client/helloworld/.gitignore new file mode 100644 index 0000000..6741ba2 --- /dev/null +++ b/client/helloworld/.gitignore @@ -0,0 +1,82 @@ +cmake-build-debug/ +cmake-build-release/ +json-c/ + +# https://raw.githubusercontent.com/github/gitignore/main/Global/JetBrains.gitignore +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/client/helloworld/CMakeLists.txt b/client/helloworld/CMakeLists.txt new file mode 100644 index 0000000..f36e020 --- /dev/null +++ b/client/helloworld/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.22) +project(libac C) + +set(CMAKE_C_STANDARD 11) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug) +endif() + +add_definitions(-D_POSIX_C_SOURCE=200809L) +IF(CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions(-DDEBUG) +ENDIF(CMAKE_BUILD_TYPE MATCHES Debug) + +if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + set(CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_FLAGS_DEBUG} -g3 -O0 -fsanitize=address") + set(CMAKE_EXE_LINKER_FLAGS_DEBUG + "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address") + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} -fvisibility=hidden") +elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") + # TODO: MSVC ASAN + set(CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_FLAGS_DEBUG} /DEBUG /Z7 /Od") + set(CMAKE_EXE_LINKER_FLAGS_DEBUG + "${CMAKE_EXE_LINKER_FLAGS_DEBUG}") +endif() + +add_subdirectory(../libacron ${CMAKE_BINARY_DIR}/libac) + +# helloworld +if(WIN32) + set(APPS_DEPS ws2_32) +endif() +set(APPS_DEPS ${APPS_DEPS} ac) +add_executable(helloworld + main.c + net.c + net.h + ) +target_link_libraries(helloworld ${APPS_DEPS}) + +install(TARGETS helloworld + EXPORT ${PROJECT_NAME}-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/libac +) diff --git a/client/helloworld/main.c b/client/helloworld/main.c new file mode 100644 index 0000000..c112fcb --- /dev/null +++ b/client/helloworld/main.c @@ -0,0 +1,370 @@ +/* + * Created by yuuta on 7/20/22. + */ + +#include "libac.h" +#include "net.h" + +#include + +#ifndef WIN32 + +#include +#include +#include + +#endif + +#ifdef __STDC_NO_THREADS__ +# ifdef WIN32 +#define THREAD_WIN32 +#include +static HANDLE mtx_conn; + +int lock(void) { \ +DWORD _dwlck = WaitForSingleObject(mtx_conn, 0); \ +if (_dwlck == WAIT_FAILED) { _dwlck = GetLastError(); } \ +if (_dwlck) { fprintf(stderr, "Cannot lock the mutex: %d.\n", _dwlck); return _dwlck; } \ +return 0; \ +} + +int unlock(void) { \ +if (!ReleaseMutex(mtx_conn)) { \ +int r = GetLastError(); \ +fprintf(stderr, "Cannot release the mutex: %d.\n", r); \ +return r; \ +} \ +return 0; \ +} + +# else +#error "Either C11 threading or Win32 API is required for concurrency." +# endif +#else + +#define THREAD_C11 + +#include + +static mtx_t mtx_conn; + +int lock(void) { + \ +if (mtx_lock(&mtx_conn) != thrd_success) { + \ +fprintf(stderr, "Cannot lock the mutex.\n"); \ +return 1; \ + + } \ +return 0; \ + +} + +int unlock(void) { + \ +if (mtx_unlock(&mtx_conn) != thrd_success) { + \ +fprintf(stderr, "Cannot release the mutex.\n"); \ +return 1; \ + + } \ +return 0; \ + +} + +#endif + +static const char *world_name(const enum ac_world world) { + switch (world) { + case overworld: + return "overworld"; + case nether: + return "nether"; + case end: + return "end"; + default: + return "unknown world"; + } +} + +static void handle_event(const ac_event_t *event) { + switch (event->type) { + case AC_EVENT_LAGGING: { + ac_event_lagging_t *o = (ac_event_lagging_t *) event; + printf("Server lagging: running %ld milliseconds (%ld ticks) behind.\n", + o->ms, + o->ticks); + break; + } + case AC_EVENT_ENTITY_DEATH: { + ac_event_entity_death_t *o = (ac_event_entity_death_t *) event; + printf("Entity '%s' died at %s(%.2f, %.2f, %.2f): %s.\n", + o->entity.name, + world_name(o->entity.world), + o->entity.pos.x, + o->entity.pos.y, + o->entity.pos.z, + o->message); + break; + } + case AC_EVENT_PLAYER_MESSAGE: { + ac_event_player_message_t *o = (ac_event_player_message_t *) event; + printf("Player '%s' said at %s(%.2f, %.2f, %.2f): %s.\n", + o->player.name, + world_name(o->player.world), + o->player.pos.x, + o->player.pos.y, + o->player.pos.z, + o->text); + break; + } + case AC_EVENT_PLAYER_DISCONNECT: { + ac_event_player_disconnect_t *o = (ac_event_player_disconnect_t *) event; + printf("Player '%s' disconnected at %s(%.2f, %.2f, %.2f): %s.\n", + o->player.name, + world_name(o->player.world), + o->player.pos.x, + o->player.pos.y, + o->player.pos.z, + o->reason); + break; + } + case AC_EVENT_PLAYER_JOIN: { + ac_event_player_join_t *o = (ac_event_player_join_t *) event; + printf("Player '%s' joined at %s(%.2f, %.2f, %.2f).\n", + o->player.name, + world_name(o->player.world), + o->player.pos.x, + o->player.pos.y, + o->player.pos.z); + break; + } + default: { + printf("Received an unrecognized event of type '%u'.\n", + event->type); + } + } +} + +static void handle_response(const ac_response_t *response) { + switch (response->type) { + case AC_RESPONSE_OK: { + ac_response_ok_t *o = (ac_response_ok_t *) response; + printf("Request %d OK.\n", + o->id); + break; + } + case AC_RESPONSE_ERROR: { + ac_response_error_t *o = (ac_response_error_t *) response; + printf("Request %d failed: %s (%d).\n", + o->id, + o->message, + o->code); + break; + } + case AC_RESPONSE_CMD_OUT: { + ac_response_cmd_out_t *o = (ac_response_cmd_out_t *) response; + printf("Request %d output by %s: %s.\n", + o->id, + o->sender, + o->out); + break; + } + case AC_RESPONSE_CMD_RESULT: { + ac_response_cmd_result_t *o = (ac_response_cmd_result_t *) response; + printf("Request %d is done: %s (%d).\n", + o->id, + o->success ? "Success" : "Failed", + o->result); + break; + } + default: { + printf("Received an unrecognized response of type '%u'.\n", + response->type); + } + } +} + +#ifndef WIN32 + +static void intr(int signum) {} + +#endif + +static int on_send(const void *sock, + const void *buffer, + const size_t len) { + int r; + printf("Writing %lu bytes.\n", len); + if ((r = net_send(sock, buffer, len))) { + fprintf(stderr, "Cannot write %lu bytes to server: %s (%d).\n", + len, + net_strerror(r), + r); + } + return r; +} + +static int say(void *connection) { + int r; + ac_config_t conf = { + .name = "helloworld" + }; + ac_request_cmd_t req = { + .type = AC_REQUEST_CMD, + .id = 100, + .config = &conf, + .cmd = "say Hi" + }; + if ((r = ac_request(connection, (ac_request_t *) &req))) { + return r; + } + return 0; +} + +int main(int argc, char **argv) { + int r; + if ((r = net_init())) { + return r; + } +#if defined(THREAD_WIN32) + if (!(mtx_conn = CreateMutex(NULL, TRUE, NULL))) { + r = GetLastError(); + fprintf(stderr, "Cannot create mutex: %d.\n", r); + return r; + } +#elif defined(THREAD_C11) + if (mtx_init(&mtx_conn, mtx_plain) != thrd_success) { + fprintf(stderr, "Cannot create mutex.\n"); + return 1; + } +#endif + +#ifdef WIN32 + fprintf(stderr, "Warning: ^C handler on Windows is not yet available.\n"); +#else + struct sigaction sa; + sa.sa_handler = intr; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGINT, &sa, NULL) || + sigaction(SIGTERM, &sa, NULL)) { + const int e = errno; + fprintf(stderr, "Cannot set SIGINT or SIGTERM handlers: %s (%d).\n", + strerror(e), + e); + return e; + } +#endif + libac_config_t config = { +#ifdef DEBUG + .out = stderr, +#else + .out = NULL, +#endif + .tok = NULL + }; + if ((r = ac_init(&config))) return r; + void *connection; + SOCKET sock; + ac_connection_parameters_t parameters = { + .host = "localhost", + .port = 25575, + .id = "1", + .token = "123", + .version = 0, + .sock = NULL, + .on_send = on_send + }; + if ((r = net_connect(parameters.host, parameters.port, &sock))) { + fprintf(stderr, "Cannot connect to server: %s (%d).\n", + net_strerror(r), + r); + ac_free(); + net_free(); + return r; + } + parameters.sock = &sock; + if ((r = ac_connect(parameters, &connection))) { + net_close(&sock); + ac_free(); + net_free(); + return r; + } + ac_obj_t *obj; + int nr; + uint8_t buffer[1000U]; + size_t bytes; + enum ac_connection_state state; + printf("Waiting until the connection is established.\n"); + bool ready = false; + while (1) { + if ((nr = net_read(&sock, buffer, sizeof(buffer), &bytes, 10))) { + if (nr == NET_TIMEOUT) { + printf("Receive timeout.\n"); + continue; + } else { + goto end; + } + } + if ((r = lock())) { goto end; } + if ((r = ac_receive(connection, buffer, bytes, &obj))) { + if ((r = unlock())) { goto end; } + goto end; + } + if (!ready) { + /* Wait until ready. */ + if ((r = ac_get_state(connection, &state))) { + if ((r = unlock())) { goto end; } + goto end; + } + switch (state) { + case AC_STATE_INIT: + continue; + case AC_STATE_READY: + printf("Connection is established.\n"); + ready = true; + say(connection); + if ((r = unlock())) { goto end; } + continue; + default: + fprintf(stderr, "Unexpected state.\n"); + if ((r = unlock())) { goto end; } + goto end; + } + } + if ((r = unlock())) { goto end; } + + if (!obj) { + continue; + } + if (AC_IS_EVENT(obj->type)) { + handle_event((ac_event_t *) obj); + } else if (AC_IS_RESPONSE(obj->type)) { + handle_response((ac_response_t *) obj); + } + ac_object_free(obj); + } + end: + if (nr) { + if (nr == NET_CLOSED) { + fprintf(stderr, "Connection is closed while reading from the server.\n"); + } else { + fprintf(stderr, "Cannot receive from server: %s (%d).\n", + net_strerror(nr), + nr); + } + } + if ((r = lock())) { goto end; } + ac_disconnect(connection, false); + if ((r = unlock())) { goto end; } + net_close(&sock); + ac_free(); + net_free(); +#if defined(THREAD_WIN32) + CloseHandle(mtx_conn); +#elif defined(THREAD_C11) + mtx_destroy(&mtx_conn); +#endif + return r; +} \ No newline at end of file diff --git a/client/helloworld/net.c b/client/helloworld/net.c new file mode 100644 index 0000000..83192e0 --- /dev/null +++ b/client/helloworld/net.c @@ -0,0 +1,181 @@ +/* + * Created by yuuta on 7/22/22. + * + * A simple cross-platform (Unix and Windows) implementation of a socket client. + */ + +#include "net.h" + +#include +#include +#include +#include + +#ifdef WIN32 +#include /* For DWORD */ +#include +#include + +#define errno_sock WSAGetLastError() + +#else + +#include +#include +#include +#include + +#define closesocket(socket) close(socket) +#define errno_sock errno + +#endif + +int net_connect(const char *host, + const uint16_t port, + SOCKET *sock) { + int r; + struct addrinfo *res; + char service[6]; + + snprintf(service, 6, "%u", port); + if ((r = getaddrinfo(host, service, NULL, &res))) { + return r; + } + + SOCKET fd; + if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) <= 0) { + const int e = errno_sock; + freeaddrinfo(res); + return e; + } + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + const int e = errno_sock; + closesocket(fd); + freeaddrinfo(res); + return e; + } + freeaddrinfo(res); + *sock = fd; + return 0; +} + +int net_read(const SOCKET *sock, + void *buffer, + const size_t length, + size_t *read, + const unsigned int timeout) { + +#ifdef WIN32 + DWORD to = timeout * 1000; + if (setsockopt(*sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&to, sizeof to)) { +#else + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (setsockopt(*sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv)) { +#endif + return errno_sock; + } + + int bytes; + if ((bytes = (int) recv(*sock, buffer, length, 0)) <= 0) { + if (bytes < 0) { + const int e = errno_sock; +#ifdef WIN32 + if (e == WSAETIMEDOUT) { +#else + if (e == EAGAIN) { +#endif + return NET_TIMEOUT; + } + return e; + } + return NET_CLOSED; + } + *read = bytes; + return 0; +} + +int net_send(const SOCKET *sock, + const void *buffer, + size_t len) { + size_t pos; + const uint8_t *ptr = buffer; + int retval; + + for (pos = 0U; pos < len; pos += retval) { + size_t n = len - pos; + retval = (int) send(*sock, &ptr[pos], (int) n, 0); + if (retval <= 0) { + return errno_sock; + } + } + return 0; +} + +int net_init(void) { +#ifdef WIN32 + int r; + WORD wVersionRequested; + WSADATA wsaData; + wVersionRequested = MAKEWORD(2, 2); + if ((r = WSAStartup(wVersionRequested, &wsaData))) { + fprintf(stderr, "WSAStartup failed with error: %d\n", r); + return r; + } + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { + fprintf(stderr, "Could not find a usable version of Winsock.dll\n"); + WSACleanup(); + return 1; + } +#endif + return 0; +} + +void net_close(const SOCKET *sock) { + closesocket(*sock); +} + +#ifdef WIN32 +static __declspec( thread ) LPTSTR err_buf = NULL; +#else +static _Thread_local char err_buf[1024]; +#endif + +void net_free(void) { +#ifdef WIN32 + WSACleanup(); + if (err_buf) { + LocalFree(err_buf); + err_buf = NULL; + } +#endif +} + +char *net_strerror(int errnum) { +#ifdef WIN32 + if (err_buf) { + LocalFree(err_buf); + err_buf = NULL; + } + FormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errnum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &err_buf, + 0, + NULL); + if (!err_buf) { + return "Unknown error"; + } +#else + int r; + if ((r = strerror_r(errnum, err_buf, sizeof(err_buf) / sizeof(char)))) { + snprintf(err_buf, 1024, "%d (%d)", errnum, r); + } +#endif + return err_buf; +} \ No newline at end of file diff --git a/client/helloworld/net.h b/client/helloworld/net.h new file mode 100644 index 0000000..1a49709 --- /dev/null +++ b/client/helloworld/net.h @@ -0,0 +1,45 @@ +/* + * Created by yuuta on 7/23/22. + */ + +#ifndef ACRONC_NET_H +#define ACRONC_NET_H + +#include +#include + +#ifdef WIN32 +#include + +#else + +#define SOCKET int + +#endif + +#define NET_CLOSED -2 +#define NET_TIMEOUT -1 + +int net_connect(const char *host, + uint16_t port, + SOCKET *sock); + +int net_read(const SOCKET *sock, + void *buffer, + size_t length, + size_t *read, + unsigned int timeout); + +int net_send(const SOCKET *sock, + const void *buffer, + size_t len); + +void net_close(const SOCKET *sock); + +int net_init(void); + +void net_free(void); + +char *net_strerror(int errnum); + +#endif /* ACRONC_NET_H */ -- cgit v1.2.3