From 8b07bf593e54dd876e30d0cb1c7c7226d0d1b1e2 Mon Sep 17 00:00:00 2001 From: Trumeet Date: Tue, 26 Jul 2022 17:17:09 -0700 Subject: feat(acronc): add acronc(1), the Acron cli Still in early development, Windows support is incomplete. Signed-off-by: Trumeet --- client/libacron/CMakeLists.txt | 25 ++- client/libacron/README.md | 3 + client/libacron/apps/acronc/README.md | 11 ++ client/libacron/apps/acronc/async_dns.c | 65 +++++++ client/libacron/apps/acronc/client.c | 114 ++++++++++++ client/libacron/apps/acronc/client.h | 12 ++ client/libacron/apps/acronc/common.h | 8 + client/libacron/apps/acronc/config.c | 100 +++++++++++ client/libacron/apps/acronc/config.h | 12 ++ client/libacron/apps/acronc/handler.h | 55 ++++++ client/libacron/apps/acronc/handler_signal.c | 40 +++++ client/libacron/apps/acronc/handler_socket.c | 255 +++++++++++++++++++++++++++ client/libacron/apps/acronc/handler_stdin.c | 85 +++++++++ client/libacron/apps/acronc/helpers.c | 58 ++++++ client/libacron/apps/acronc/helpers.h | 24 +++ client/libacron/apps/acronc/log.c | 61 +++++++ client/libacron/apps/acronc/log.h | 52 ++++++ client/libacron/apps/acronc/main.c | 105 +++++++++++ 18 files changed, 1079 insertions(+), 6 deletions(-) create mode 100644 client/libacron/apps/acronc/README.md create mode 100644 client/libacron/apps/acronc/async_dns.c create mode 100644 client/libacron/apps/acronc/client.c create mode 100644 client/libacron/apps/acronc/client.h create mode 100644 client/libacron/apps/acronc/common.h create mode 100644 client/libacron/apps/acronc/config.c create mode 100644 client/libacron/apps/acronc/config.h create mode 100644 client/libacron/apps/acronc/handler.h create mode 100644 client/libacron/apps/acronc/handler_signal.c create mode 100644 client/libacron/apps/acronc/handler_socket.c create mode 100644 client/libacron/apps/acronc/handler_stdin.c create mode 100644 client/libacron/apps/acronc/helpers.c create mode 100644 client/libacron/apps/acronc/helpers.h create mode 100644 client/libacron/apps/acronc/log.c create mode 100644 client/libacron/apps/acronc/log.h create mode 100644 client/libacron/apps/acronc/main.c diff --git a/client/libacron/CMakeLists.txt b/client/libacron/CMakeLists.txt index b64a299..57ce521 100644 --- a/client/libacron/CMakeLists.txt +++ b/client/libacron/CMakeLists.txt @@ -89,19 +89,32 @@ target_link_libraries(ac ${LIBAC_DEPS}) target_include_directories(ac-static PUBLIC ${LIBAC_INCLUDES}) target_link_libraries(ac-static ${LIBAC_DEPS}) +# Apps +if(WIN32) + set(APPS_DEPS ws2_32) +endif() +set(APPS_DEPS ${APPS_DEPS} ac) + +# apps/helloworld add_executable(helloworld apps/helloworld/main.c apps/helloworld/net.c apps/helloworld/net.h ) -if(WIN32) - set(ACRONC_DEPS ws2_32) -endif() -set(ACRONC_DEPS ${ACRON_DEPS} ac) -target_link_libraries(helloworld ${ACRONC_DEPS}) +target_link_libraries(helloworld ${APPS_DEPS}) target_include_directories(helloworld PUBLIC "${PROJECT_BINARY_DIR}" include/) -install(TARGETS ac ac-static helloworld +# apps/acronc +add_executable(acronc + apps/acronc/main.c + apps/acronc/common.h + apps/acronc/log.h + apps/acronc/log.c + apps/acronc/handler.h apps/acronc/handler_signal.c apps/acronc/config.c apps/acronc/config.h apps/acronc/client.c apps/acronc/client.h apps/acronc/handler_stdin.c apps/acronc/helpers.c apps/acronc/helpers.h apps/acronc/handler_socket.c apps/acronc/async_dns.c) +target_link_libraries(acronc ${APPS_DEPS} uv) +target_include_directories(acronc PUBLIC "${PROJECT_BINARY_DIR}" include/) + +install(TARGETS ac ac-static helloworld acronc EXPORT ${PROJECT_NAME}-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/client/libacron/README.md b/client/libacron/README.md index 4514e21..418e07b 100644 --- a/client/libacron/README.md +++ b/client/libacron/README.md @@ -3,6 +3,9 @@ A client library written in C, based on [json-c](https://github.com/json-c/json-c) and [wic](https://github.com/cjhdev/wic). +This document is for client developers. For users who want a remote console to their Minecraft +server, they should consult [acronc(1)](apps/acronc), a ready-to-use Acron cli. + ## Building Requirements: diff --git a/client/libacron/apps/acronc/README.md b/client/libacron/apps/acronc/README.md new file mode 100644 index 0000000..87c9db0 --- /dev/null +++ b/client/libacron/apps/acronc/README.md @@ -0,0 +1,11 @@ +# acronc(1) + +Acron client using libac and libuv, written in C. + +Current status: In development. + +Windows is currently not supported yet. + +## License + +GPL-2.0-only \ No newline at end of file diff --git a/client/libacron/apps/acronc/async_dns.c b/client/libacron/apps/acronc/async_dns.c new file mode 100644 index 0000000..2e221cf --- /dev/null +++ b/client/libacron/apps/acronc/async_dns.c @@ -0,0 +1,65 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "handler.h" +#include "log.h" + +#include + +static uv_getaddrinfo_t resolv; + +static struct addrinfo *ai = NULL; +static struct addrinfo *ai_current = NULL; +static void (*cb)(int status, + const struct addrinfo *, + void (*on_connect_result)(bool)) = NULL; + +static void on_conn(bool res) { + if (res) { + uv_freeaddrinfo(ai); + ai = NULL; + ai_current = NULL; + cb = NULL; + return; + } + ai_current = ai_current->ai_next; + if (!ai_current) { + /* No more result available. */ + cb(1, NULL, NULL); + uv_freeaddrinfo(ai); + ai = NULL; + ai_current = NULL; + cb = NULL; + return; + } + cb(0, ai_current, &on_conn); +} + +static void on_resolv(uv_getaddrinfo_t *req, int status, struct addrinfo *res) { + LOGDV("on_resolv(req = %p): status = %d, res = %p", + req, + status, + res); + if (status) { + LOGEV("Cannot resolve host: %s", uv_strerror(status)); + cb(status, NULL, NULL); + return; + } + ai = res; + ai_current = res; + cb(0, ai_current, &on_conn); +} + +int a_dns(const char *host, + uint16_t port, + void (*c)(int status, + const struct addrinfo *, + void (*on_connect_result)(bool))) { + int r; + char service[6]; + snprintf(service, 6, "%u", port); + cb = c; + if ((r = uv_getaddrinfo(loop, &resolv, on_resolv, host, service, NULL))) return r; + return 0; +} diff --git a/client/libacron/apps/acronc/client.c b/client/libacron/apps/acronc/client.c new file mode 100644 index 0000000..5553d4c --- /dev/null +++ b/client/libacron/apps/acronc/client.c @@ -0,0 +1,114 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "client.h" +#include "helpers.h" + +#include +#include + +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); + } + } +} + +int handle_object(ac_obj_t *obj) { + if (AC_IS_RESPONSE(obj->type)) handle_response((ac_response_t *) obj); + else handle_event((ac_event_t *) obj); + ac_object_free(obj); + return 0; +} \ No newline at end of file diff --git a/client/libacron/apps/acronc/client.h b/client/libacron/apps/acronc/client.h new file mode 100644 index 0000000..438d7fd --- /dev/null +++ b/client/libacron/apps/acronc/client.h @@ -0,0 +1,12 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#ifndef ACRONC_CLIENT_H +#define ACRONC_CLIENT_H + +#include + +int handle_object(ac_obj_t *obj); + +#endif /* ACRONC_CLIENT_H */ diff --git a/client/libacron/apps/acronc/common.h b/client/libacron/apps/acronc/common.h new file mode 100644 index 0000000..0ca3b92 --- /dev/null +++ b/client/libacron/apps/acronc/common.h @@ -0,0 +1,8 @@ +/* + * Created by yuuta on 7/23/22. + */ + +#ifndef ACRONC_COMMON_H +#define ACRONC_COMMON_H + +#endif /* ACRONC_COMMON_H */ diff --git a/client/libacron/apps/acronc/config.c b/client/libacron/apps/acronc/config.c new file mode 100644 index 0000000..046d931 --- /dev/null +++ b/client/libacron/apps/acronc/config.c @@ -0,0 +1,100 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +int config_parse(const int argc, const char **argv, ac_connection_parameters_t *params) { + if (argc <= 1) { + fprintf(stderr, "Usage: %s [-t token] [-p port] @\n" + "Please refer to acronc(1) for more details.\n", + argv[0] ? "acronc" : argv[0]); + return 64; + } + bool svr_set = false; + for (int i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') { + if (svr_set) { + fprintf(stderr, "Unexpected value: %s\n", arg); + return 64; + } + const char *id = strtok((char *) arg, "@"); + if (!id) { + fprintf(stderr, "Malformed argument: should be ID@server.\n"); + return 64; + } + const char *server = strtok(NULL, ""); + if (!server) { + fprintf(stderr, "Malformed argument: should be ID@server.\n"); + return 64; + } + params->host = (char *) server; + params->id = (char *) id; + svr_set = true; + continue; + } + char a = arg[1]; + char **value_ptr; + char *port_str = NULL; + switch (a) { + case 't': { + value_ptr = ¶ms->token; + goto read_value; + } + case 'p': { + value_ptr = &port_str; + goto read_value; + } + default: { + fprintf(stderr, "Unexpected switch -%c.\n", a); + return 64; + } + } + read_value: + { + if (i == (argc - 1)) { + fprintf(stderr, "-%c requires an argument.\n", a); + return 64; + } + *value_ptr = (char *) argv[++i]; + } + if (port_str) { + errno = 0; + char *endptr; + unsigned long val = strtol(port_str, &endptr, 0); + if (errno) { + const int e = errno; + fprintf(stderr, "Cannot parse port: %s", strerror(e)); + return e; + } + if (endptr == port_str) { + fprintf(stderr, "Illegal port.\n"); + return 64; + } + if (val > UINT16_MAX) { + fprintf(stderr, "Illegal port.\n"); + return 64; + } + params->port = (uint16_t) val; + port_str = NULL; + } + } + if (!params->host || !params->id) { + fprintf(stderr, "Usage: acronc ID@server[:port]\n"); + return 64; + } + char tok[1024]; + if (!params->token) { + printf("Token: "); + scanf("%1023[^\n]", tok); + params->token = tok; + } + return 0; +} \ No newline at end of file diff --git a/client/libacron/apps/acronc/config.h b/client/libacron/apps/acronc/config.h new file mode 100644 index 0000000..ee51f34 --- /dev/null +++ b/client/libacron/apps/acronc/config.h @@ -0,0 +1,12 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#ifndef ACRONC_CONFIG_H +#define ACRONC_CONFIG_H + +#include + +int config_parse(int argc, const char **argv, ac_connection_parameters_t *params); + +#endif /* ACRONC_CONFIG_H */ diff --git a/client/libacron/apps/acronc/handler.h b/client/libacron/apps/acronc/handler.h new file mode 100644 index 0000000..1a2ccfe --- /dev/null +++ b/client/libacron/apps/acronc/handler.h @@ -0,0 +1,55 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#ifndef ACRONC_HANDLER_H +#define ACRONC_HANDLER_H + +#include +#include + +enum exit_reason { + /* Exit due to a signal. Socket is working. */ + EXIT_SIGNAL, + /* Exit due to an EOF from stdin. Socket is working. */ + EXIT_STDIN_EOF, + /* Exit due to an EOF from socket. Socket is closed. */ + EXIT_SOCKET_EOF, + /* Exit due to a socket failure. Socket is closed. */ + EXIT_FAILURE_SOCKET, + /* Exit due to a failure. Socket is working. */ + EXIT_FAILURE +}; + +struct uv_obj { + void *obj; + bool running; +}; + +extern uv_loop_t *loop; + +extern void (*on_exit)(enum exit_reason reason); + +int h_signal(void); + +int h_stdin(int (*on_input)(ac_request_t *req), + void (*on_close)(void)); + +int h_socket(ac_connection_parameters_t *p, + const struct addrinfo *ai, + void (*on_connect_result)(bool), + int (*on_ready)(void), + int (*on_received)(ac_obj_t *obj), + void (*on_closed)(void)); + +int sock_request(ac_request_t *req); + +int sock_ext(bool trigger_callback); + +int a_dns(const char *host, + uint16_t port, + void (*on_resolv)(int status, + const struct addrinfo *, + void (*on_connect_result)(bool))); + +#endif /* ACRONC_HANDLER_H */ diff --git a/client/libacron/apps/acronc/handler_signal.c b/client/libacron/apps/acronc/handler_signal.c new file mode 100644 index 0000000..1d0ba7e --- /dev/null +++ b/client/libacron/apps/acronc/handler_signal.c @@ -0,0 +1,40 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "handler.h" +#include "log.h" + +static uv_signal_t sigint; +static struct uv_obj obj_sigint = { + .obj = &sigint, + .running = false +}; +static uv_signal_t sigterm; +static struct uv_obj obj_sigterm = { + .obj = &sigterm, + .running = false +}; + +static void on_signal(uv_signal_t *handle, int signum) { + LOGDV("on_signal(handle = %p): %d", + handle, + signum); + uv_signal_stop(&sigint); + obj_sigint.running = false; + uv_signal_stop(&sigterm); + obj_sigterm.running = false; + on_exit(EXIT_SIGNAL); +} + +int h_signal(void) { + int r; + if ((r = uv_signal_init(loop, &sigint))) return r; + if ((r = uv_signal_start(&sigint, on_signal, SIGINT))) return r; + + obj_sigint.running = true; + if ((r = uv_signal_init(loop, &sigterm))) return r; + if ((r = uv_signal_start(&sigterm, on_signal, SIGTERM))) return r; + obj_sigterm.running = true; + return 0; +} diff --git a/client/libacron/apps/acronc/handler_socket.c b/client/libacron/apps/acronc/handler_socket.c new file mode 100644 index 0000000..ff8eefb --- /dev/null +++ b/client/libacron/apps/acronc/handler_socket.c @@ -0,0 +1,255 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "handler.h" +#include "log.h" +#include "helpers.h" + +#include +#include +#include + +static void (*cb_conn)(bool); + +static int (*cb_ready)(void); + +static int (*cb_recv)(ac_obj_t *obj); + +static void (*cb_close)(void); + +static ac_connection_parameters_t *params; + +static uv_tcp_t sock; +static uv_write_t write; + +static uv_connect_t conn; + +static bool ready = false; +static void *ac_conn = NULL; + +#define RUNNING !uv_is_closing((uv_handle_t *) &sock) + +static void on_close(uv_handle_t *handle) { + LOGDV("on_close(handle = %p)", + handle); + if (handle->data) + cb_close(); +} + +static void ex2(bool force, bool cb) { + LOGDV("Exiting socket handlers: Force WebSocket close: %s; Socket still running: %s.", + force ? "true" : "false", + RUNNING ? "true" : "false"); + if (ac_conn) { + ac_disconnect(ac_conn, + RUNNING ? force : true /* If sock is not running, always force */); + ac_conn = NULL; + } + if (RUNNING) { + sock.data = cb ? &ex2 /* Any non-NULL value */ : NULL; + uv_close((uv_handle_t *) &sock, on_close); + } else { + if (cb) { + cb_close(); + } + } +} + +static void ex(bool force) { + ex2(force, true); +} + +int sock_ext(bool trigger_callback) { + ex2(false, trigger_callback); + return 0; +} + +static void on_alloc(uv_handle_t *handle, size_t size, uv_buf_t *buf) { + LOGDV("on_alloc(handle = %p): size = %lu", + handle, + size); + void *b = malloc(size); + if (!b) { + LOGEV("Cannot allocate memory of %u bytes: %s.", + size, + strerror(errno)); + /* Socket is still working now. */ + ex(false); + *buf = uv_buf_init(NULL, 0); /* Just in case? */ + return; + } + *buf = uv_buf_init(b, size); +} + +static void on_read(uv_stream_t *tcp, ssize_t nread, const uv_buf_t *buf) { + LOGDV("on_read(stream = %p): nread = %ld, buf = %p, buf->base = %p, buf->len = %lu", + tcp, + nread, + buf, buf->base, buf->len); + if (!nread) { + /* EAGAIN */ + if (buf->base) free(buf->base); + return; + } + if (nread == UV_EOF) { + LOGW("Received EOF from server."); + free(buf->base); + /* The socket * should * be closed already? */ + ex(true); + return; + } + if (nread < 0) { + LOGEV("Encountered a failure while reading from the socket: ", uv_strerror(nread)); + if (buf->base) free(buf->base); + /* Docs: The callee is responsible for stopping/closing the stream when an error happens */ + ex(true); /* Force close libac connection here. */ + return; + } + int r; + ac_obj_t *obj = NULL; + if ((r = ac_receive(ac_conn, buf->base, nread, &obj))) { + LOGEV("Cannot parse the response (%d).", r); + /* libac error. Socket is working. */ + ex(false); + return; + } + + if (!ready) { + enum ac_connection_state state; + if ((r = ac_get_state(ac_conn, &state))) { + LOGEV("Cannot get state (%d).", r); + /* libac error. Socket is working. */ + ex(false); + return; + } + if (state == AC_STATE_READY) { + ready = true; + if ((cb_ready())) { + /* acronc error. Socket is working. */ + ex(false); + return; + } + } + } + if (obj) { + LOGDV("Got object: %p", obj); + /* uv_async_send is unreliable, and missed messages will cause memory leak. */ + if (cb_recv(obj)) { + /* acronc error. Socket is working. */ + ex(false); + } + } + + free(buf->base); +} + +static void on_write(uv_write_t *req, int status) { + LOGDV("on_write(req = %p): %d", + req, + status); + if (status) { + LOGEV("Cannot write to socket: %s", uv_strerror(status)); + /* Socket may be closed? Anyway writing again will definitely be problematic, but closing may not. */ + ex(true); + } +} + +static int on_send(const void *s, + const void *buf, + const size_t len) { + LOGDV("on_send(s = %p): len = %u", + s, + len); + const uv_stream_t *stream = s; + uv_buf_t buffer[] = { + {.base = (char *) buf, .len = len} + }; + int r; + if ((r = uv_write(&write, (uv_stream_t *) stream, buffer, 1, on_write))) { + LOGEV("Cannot write to socket: %s", uv_strerror(r)); + /* Socket may be closed? Anyway writing again will definitely be problematic, but closing may not. */ + ex(true); + } + return 0; +} + +static void on_retry_closed(uv_handle_t *handle) { + cb_conn(false); +} + +static void on_connect(uv_connect_t *req, int status) { + LOGDV("on_connect(req = %p): status = %d", + req, + status); + if (status) { + LOGEV("Cannot connect to the server: %s", uv_strerror(status)); + /* Close it for the next retry. */ + if (RUNNING) { + LOGD("Closing socket before retry."); + uv_close((uv_handle_t *) &sock, on_retry_closed); + } else { + cb_conn(false); + } + return; + } + LOGI("Connected."); + uv_stream_t *stream = req->handle; + int r; + params->sock = stream; + if ((r = ac_connect(*params, &ac_conn))) { + LOGEV("Cannot initialize connection: %d.", r); + cb_conn(false); + return; + } + if ((r = uv_read_start(stream, on_alloc, on_read))) { + LOGEV("Cannot read socket: %s", uv_strerror(r)); + cb_conn(false); + return; + } + cb_conn(true); +} + +int h_socket(ac_connection_parameters_t *p, + const struct addrinfo *ai, + void (*on_connect_result)(bool), + int (*on_ready)(void), + int (*on_received)(ac_obj_t *obj), + void (*on_closed)(void)) { + cb_conn = on_connect_result; + cb_ready = on_ready; + cb_recv = on_received; + cb_close = on_closed; + params = p; + params->on_send = on_send; + + struct sockaddr *sa = ai->ai_addr; + LOGIV("Connecting to %s...", ntop(sa)); + int r; + if ((r = uv_tcp_init(loop, &sock))) { + LOGEV("Cannot initialize the socket: %s", uv_strerror(r)); + return r; + } + if ((r = uv_tcp_connect(&conn, &sock, sa, on_connect))) { + LOGEV("Cannot initialize the connection: %s", uv_strerror(r)); + return r; + } + return 0; +} + +int sock_request(ac_request_t *req) { + if (!ac_conn) { + /* Just in case. */ + LOGE("sock_request() called on a closed socket."); + /* Ignore it. */ + return 0; + } + int r = ac_request(ac_conn, req); + if (r) { + LOGEV("Cannot send request: %d.", r); + /* libac error. Socket is working. */ + ex(false); + } + return r; +} + diff --git a/client/libacron/apps/acronc/handler_stdin.c b/client/libacron/apps/acronc/handler_stdin.c new file mode 100644 index 0000000..3ac307d --- /dev/null +++ b/client/libacron/apps/acronc/handler_stdin.c @@ -0,0 +1,85 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "handler.h" +#include "log.h" + +#include +#include + +static unsigned int id = 0; + +static uv_tty_t tty; + +static int (*cb_recv)(ac_request_t *req); + +static void (*cb_close)(void); + +static void on_close(uv_handle_t *handle) { + cb_close(); +} + +static void on_alloc(uv_handle_t *handle, size_t size, uv_buf_t *buf) { + LOGDV("on_alloc(handle = %p): size = %lu", + handle, + size); + void *b = malloc(size); + if (!b) { + LOGEV("Cannot allocate memory of %u bytes: %s.", + size, + strerror(errno)); + /* Stream is still working now. */ + uv_close(handle, on_close); + *buf = uv_buf_init(NULL, 0); /* Just in case? */ + return; + } + *buf = uv_buf_init(b, size); +} + +static void on_stdin(uv_stream_t *t, ssize_t nread, const uv_buf_t *buf) { + LOGDV("on_stdin(stream = %p): nread = %ld, buf = %p, buf->base = %p, buf->len = %lu", + t, + nread, + buf, buf->base, buf->len); + if (!nread) { + if (buf->base) free(buf->base); + return; + } + if (nread == UV_EOF) { + LOGI("Exit."); + free(buf->base); + + cb_close(); + return; + } + if (nread < 0) { + LOGEV("Encountered a failure while reading stdin: ", uv_strerror(nread)); + if (buf->base) free(buf->base); + uv_close((uv_handle_t *) t, on_close); + return; + } + buf->base[nread - 1] = '\0'; /* Remove junk and tailing \n */ + if ((++ id) > INT_MAX) { + id = 0; + } + ac_request_cmd_t req = { + .type = AC_REQUEST_CMD, + .id = (int) id, + .cmd = buf->base + }; + if (cb_recv((ac_request_t *) &req)) { + uv_close((uv_handle_t *) t, on_close); + } + free(buf->base); +} + +int h_stdin(int (*on_input)(ac_request_t *req), + void (*on_close)(void)) { + cb_recv = on_input; + cb_close = on_close; + int r; + if ((r = uv_tty_init(loop, &tty, 0, 0))) return r; + if ((r = uv_read_start((uv_stream_t *) &tty, on_alloc, on_stdin))) return r; + return 0; +} \ No newline at end of file diff --git a/client/libacron/apps/acronc/helpers.c b/client/libacron/apps/acronc/helpers.c new file mode 100644 index 0000000..9e141d4 --- /dev/null +++ b/client/libacron/apps/acronc/helpers.c @@ -0,0 +1,58 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#include "helpers.h" + +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include + +#define errno_sock WSAGetLastError() + +#else + +#include +#include + +#define errno_sock errno + +#endif + +const char *ntop(const struct sockaddr *sa) { + static char buf[INET6_ADDRSTRLEN]; + const char *retval; + switch (sa->sa_family) { + case AF_INET: { + retval = inet_ntop(AF_INET, &((struct sockaddr_in *) sa)->sin_addr, buf, sizeof(buf)); + break; + } + case AF_INET6: { + retval = inet_ntop(AF_INET6, &((struct sockaddr_in6 *) sa)->sin6_addr, buf, sizeof(buf)); + break; + } + default: { + return "Unknown address family"; + } + } + if (retval) return retval; + return strerror(errno_sock); +} + +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"; + } +} diff --git a/client/libacron/apps/acronc/helpers.h b/client/libacron/apps/acronc/helpers.h new file mode 100644 index 0000000..e983439 --- /dev/null +++ b/client/libacron/apps/acronc/helpers.h @@ -0,0 +1,24 @@ +/* + * Created by yuuta on 7/24/22. + */ + +#ifndef ACRONC_HELPERS_H +#define ACRONC_HELPERS_H + +#include + +#ifdef WIN32 +#include +#include + +#else + +#include + +#endif + +const char *ntop(const struct sockaddr *sa); + +const char *world_name(enum ac_world world); + +#endif /* ACRONC_HELPERS_H */ diff --git a/client/libacron/apps/acronc/log.c b/client/libacron/apps/acronc/log.c new file mode 100644 index 0000000..08f48b6 --- /dev/null +++ b/client/libacron/apps/acronc/log.c @@ -0,0 +1,61 @@ +/* + * Created by yuuta on 1/1/22. + */ + +#define _GNU_SOURCE + +#include "log.h" +#include "config.h" + +#include +#include +#include + +#ifdef __linux__ +#include +#include +#include +#endif + +void g_log(enum log_level level, + const char *file, + int line, + const char *format, + ...) { + FILE *stream = stderr; + switch (level) { + case log_fetal: + fprintf(stream, "F"); + break; + case log_error: + fprintf(stream, "E"); + break; + case log_warn: + fprintf(stream, "W"); + break; + case log_info: + fprintf(stream, "I"); + break; + case log_debug: +#ifdef DEBUG + fprintf(stream, "D"); + break; +#else + return; +#endif + default: + fprintf(stderr, "Unknown log level: %d.\n", level); + assert(0); + } + int tid = -1; +#ifdef __linux__ + tid = (int) syscall(__NR_gettid); +#endif + fprintf(stream, "[%d %s:%d]: ", + tid, file, line); + va_list list; + va_start(list, format); + vfprintf(stream, format, list); + va_end(list); + fprintf(stream, "\n"); +} diff --git a/client/libacron/apps/acronc/log.h b/client/libacron/apps/acronc/log.h new file mode 100644 index 0000000..7bc9532 --- /dev/null +++ b/client/libacron/apps/acronc/log.h @@ -0,0 +1,52 @@ +/* + * Created by yuuta on 1/1/22. + */ + +#ifndef LOG_H +#define LOG_H + +enum log_level { + log_fetal = 1, + log_error = 2, + log_warn = 3, + log_info = 4, + log_debug = 5 +}; + +void g_log(enum log_level level, + const char *file, + int line, + const char *format, + ...); + +#define LOGF(X) g_log(log_fetal, __FUNCTION__, __LINE__, X) + +#define LOGFV(X, ...) g_log(log_fetal, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGE(X) g_log(log_error, __FUNCTION__, __LINE__, X) + +#define LOGEV(X, ...) g_log(log_error, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGW(X) g_log(log_warn, __FUNCTION__, __LINE__, X) + +#define LOGWV(X, ...) g_log(log_warn, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#define LOGI(X) g_log(log_info, __FUNCTION__, __LINE__, X) + +#define LOGIV(X, ...) g_log(log_info, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#ifdef DEBUG + +#define LOGD(X) g_log(log_debug, __FUNCTION__, __LINE__, X) + +#define LOGDV(X, ...) g_log(log_debug, __FUNCTION__, __LINE__, X, __VA_ARGS__) + +#else + +#define LOGD(X) + +#define LOGDV(X, ...) + +#endif + +#endif /* LOG_H */ diff --git a/client/libacron/apps/acronc/main.c b/client/libacron/apps/acronc/main.c new file mode 100644 index 0000000..6e18ed2 --- /dev/null +++ b/client/libacron/apps/acronc/main.c @@ -0,0 +1,105 @@ +/* + * Created by yuuta on 7/23/22. + */ + +#include "log.h" +#include "handler.h" +#include "config.h" +#include "helpers.h" +#include "client.h" + +#include +#include +#include +#include +#include +#include + +static uv_loop_t lop; +uv_loop_t *loop = &lop; + +static void on_close(uv_handle_t *handle); + +static ac_connection_parameters_t params = { + .sock = NULL, + .version = 0, + .port = 25575, + .host = NULL, + .id = NULL, + .token = NULL, + .on_send = NULL, +}; + +static void cleanup(void) { + LOGD("cleanup()"); + /* sock must be closed before cleanup(). */ + uv_loop_close(loop); + ac_free(); + uv_tty_reset_mode(); +} + +static void on_stdin_closed(void) { + sock_ext(true); +} + +static void on_sock_closed(void) { + uv_stop(loop); +} + +static int on_input(ac_request_t *req) { + /* The socket will close itself upon errors. So do the input stream. */ + return sock_request(req); +} + +static int on_sock_ready(void) { + return h_stdin(on_input, on_stdin_closed); +} + +static void on_resolv(int status, const struct addrinfo *ai, void (*on_connected)(bool)) { + if (status) { + uv_stop(loop); + return; + } + + if (h_socket(¶ms, + ai, + on_connected, + on_sock_ready, + handle_object, + on_sock_closed)) { + /* Mark as true to clean up resources and prevent next retry. */ + on_connected(true); + uv_stop(loop); + } +} + +int main(int argc, const char **argv) { + int r; + + if ((r = config_parse(argc, argv, ¶ms))) return r; + atexit(cleanup); + libac_config_t config = { +#ifdef DEBUG + .out = NULL, +#else + .out = NULL, +#endif + .tok = NULL + }; + if ((r = ac_init(&config))) errx(r, "Cannot initialize Acron library."); + + if ((r = uv_loop_init(loop))) goto uviniterr; + if ((r = h_signal())) goto uviniterr; + if ((r = a_dns(params.host, params.port, on_resolv))) goto uviniterr; + + if ((r = uv_run(loop, UV_RUN_DEFAULT))) { + if (r == 1) { + /* Seems to return 1 if uv_stop is called. */ + return 0; + } + errx(-r, "Cannot run: %s", uv_strerror(r)); + } + return 0; + uviniterr: + errx(-r, "Cannot initialize: %s", uv_strerror(r)); +} \ No newline at end of file -- cgit v1.2.3