aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrumeet <yuuta@yuuta.moe>2022-07-23 11:03:20 -0700
committerTrumeet <yuuta@yuuta.moe>2022-07-23 11:03:20 -0700
commit6c49e2e93b23defd51d4b342baad5a9c3f843ea1 (patch)
treeccf1c90934571fe9472c85ea86ee1c5112ce3e65
parent4337729c2fa77871e3b10cbe329f286c01ddc01f (diff)
downloadacron-6c49e2e93b23defd51d4b342baad5a9c3f843ea1.tar
acron-6c49e2e93b23defd51d4b342baad5a9c3f843ea1.tar.gz
acron-6c49e2e93b23defd51d4b342baad5a9c3f843ea1.tar.bz2
acron-6c49e2e93b23defd51d4b342baad5a9c3f843ea1.zip
feat(libacron/acronc): delegate socket IO to the client
API:CHANGE Signed-off-by: Trumeet <yuuta@yuuta.moe>
-rw-r--r--client/libacron/CMakeLists.txt7
-rw-r--r--client/libacron/README.md69
-rw-r--r--client/libacron/acronc/main.c142
-rw-r--r--client/libacron/acronc/net.c181
-rw-r--r--client/libacron/acronc/net.h45
-rw-r--r--client/libacron/include/net.h51
-rw-r--r--client/libacron/net.c210
-rw-r--r--client/libacron/private/connection.h6
-rw-r--r--client/libacron/private/win32.h3
9 files changed, 499 insertions, 215 deletions
diff --git a/client/libacron/CMakeLists.txt b/client/libacron/CMakeLists.txt
index 82ecc24..223b259 100644
--- a/client/libacron/CMakeLists.txt
+++ b/client/libacron/CMakeLists.txt
@@ -91,7 +91,12 @@ target_link_libraries(ac-static ${LIBAC_DEPS})
add_executable(acronc
acronc/main.c
- )
+ acronc/net.c
+ acronc/net.h)
+if(WIN32)
+ set(ACRONC_DEPS ws2_32)
+endif()
+set(ACRONC_DEPS ${ACRON_DEPS} ac)
target_link_libraries(acronc ac)
target_include_directories(acronc PUBLIC "${PROJECT_BINARY_DIR}" include/)
diff --git a/client/libacron/README.md b/client/libacron/README.md
index e5ed81b..09e36dc 100644
--- a/client/libacron/README.md
+++ b/client/libacron/README.md
@@ -164,6 +164,22 @@ pass this pointer as-is when making connections or disconnecting.
Every thread can have multiple connections simultaneously.
+In addition, the client is required to provide its own socket IO implementations due to:
+
+1. Cross-platform is easier.
+2. It is easier for users to supply their own async IO mechanism.
+
+The client needs to establish a socket connection before calling ac_connect. It should also
+supply the pointer to its socket identifier (anything is accepted: libac only needs a pointer) to
+`ac_connection_parameters`. A typical type is `* int` (pointer to socket fd).
+
+The client also needs to supply its own `on_send` function. libac will call that function when it
+needs to write anything to the socket. When calling that function, the socket pointer will be passed
+as-is.
+
+No worries, `acronc/net.c` provides a ready-to-use example of cross-platform (Unix and Windows) blocking
+socket client. See `acronc/main.c` for more details.
+
```c
void *connection = NULL;
int r;
@@ -172,40 +188,79 @@ ac_connection_parameters_t args = {
.port = 25575,
.version = 0,
.id = "1",
- .token = "123"
+ .token = "123",
+ .on_send = /* TODO: on_send callback function */,
+ .socket = NULL
};
+int socket = /* TODO: Connect to the socket. */;
+args.socket = &socket;
if ((r = ac_connect(args, &connection))) {
return r;
}
/* Listen or make requests. */
```
-Then, the program can listen for responses or events:
+Upon `ac_connect`, libac only does a little to initialize the connection (e. g. write initial
+handshake request to the socket). The handshake is not yet completed, and the client cannot send
+requests yet. To display the state, libac provides a function called `ac_get_state()` which outputs
+the current state to the connection (`AC_STATE_INIT`, `AC_STATE_READY` or `AC_STATE_CLOSED`).
+A common usage is to loop `ac_receive` (see below) infinitely after `ac_connect` until the state
+changes from `AC_STATE_INIT` to `AC_STATE_READY`, where `ac_request` is allowd and valid data will
+return from `ac_receive`.
+
+Note: if the state changed to `AC_STATE_CLOSED` within `ac_receive`, this is likely due to an abnormal
+termination of the connection. In this case, `ac_receive` will return `AC_E_NET`.
+
+Then, the program can listen for responses or events.
+The client should do its own magic to receive from socket and
+supply `ac_receive` with a buffer and size.
```c
ac_obj_t *obj = NULL;
-while (!interrupted || !(r = ac_receive(connection, &obj))) {
+uint8_t buffer[1000U];
+size_t bytes;
+/* TODO: recv() */
+while (!(r = ac_receive(connection, buffer, bytes, &obj))) {
/* The obj is now referencing to a library allocated event or response. */
/* Do things with the event or response. */
ac_obj_free(obj);
}
```
-The program can make requests using `ac_request()`. ~~This function can
-be called from any thread (not specific to the listening one), but any thread
-calling this function must also call `ac_init()` first.~~ **On the roadmap.**
+The program can make requests using `ac_request()`:
+
+```c
+ac_config_t conf = {
+ .name = "acronc"
+};
+ac_request_cmd_t req = {
+ .type = AC_REQUEST_CMD,
+ .id = 100,
+ .config = &conf,
+ .cmd = "say Hi"
+};
+ac_request(connection, (ac_request_t *) &req));
+```
Finally, when done, call `ac_disconnect()`. It will close the socket and free the
`connection` parameter. The `connection` pointer after the method call is undefined.
+The client must manually close its socket after calling `ac_disconnect()`.
> **Notes**:
>
-> Only `ws://` is supported at this time. `wss://` support is
+> Only unencrypted WebSocket is supported at this time. `wss://` support is
> on the roadmap.
Read more: `acronc/main.c` example client.
+#### Connection Threading
+
+libac reads stores some state in the connection object upon each function call. Therefore,
+it is the caller's responsibility to guard the whole connection with a mutex. libac does not
+provide that because this may limit the flexibility. After locking, it is safe to call the
+connection-related functions from any threads.
+
## Roadmap
* Make unit tests
diff --git a/client/libacron/acronc/main.c b/client/libacron/acronc/main.c
index f76b172..43802ad 100644
--- a/client/libacron/acronc/main.c
+++ b/client/libacron/acronc/main.c
@@ -3,15 +3,16 @@
*/
#include "libac.h"
+#include "net.h"
#include <stdio.h>
-#ifdef WIN32
-#include <winsock2.h>
-#include <windef.h>
-#else
+
+#ifndef WIN32
+
#include <signal.h>
#include <errno.h>
#include <string.h>
+
#endif
static const char *world_name(const enum ac_world world) {
@@ -126,11 +127,47 @@ static void handle_response(const ac_response_t *response) {
}
#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 = "acronc"
+ };
+ 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;
+ }
#ifdef WIN32
fprintf(stderr, "Warning: ^C handler on Windows is not yet available.\n");
#else
@@ -139,7 +176,7 @@ int main(int argc, char **argv) {
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) ||
- sigaction(SIGTERM, &sa, NULL)) {
+ sigaction(SIGTERM, &sa, NULL)) {
const int e = errno;
fprintf(stderr, "Cannot set SIGINT or SIGTERM handlers: %s (%d).\n",
strerror(e),
@@ -147,20 +184,6 @@ int main(int argc, char **argv) {
return e;
}
#endif
-#ifdef WIN32
- 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
libac_config_t config = {
#ifdef DEBUG
.out = stderr,
@@ -171,33 +194,69 @@ int main(int argc, char **argv) {
};
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
+ .version = 0,
+ .sock = NULL,
+ .on_send = on_send
};
- if ((r = ac_connect(parameters, &connection))) {
+ 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;
}
- ac_obj_t *obj;
- ac_config_t conf = {
- .name = "acronc"
- };
- 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))) {
- ac_disconnect(connection);
+ parameters.sock = &sock;
+ if ((r = ac_connect(parameters, &connection))) {
+ net_close(&sock);
ac_free();
+ net_free();
return r;
}
- while (!(r = ac_receive(connection, &obj, 10))) {
+ 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 = ac_receive(connection, buffer, bytes, &obj))) {
+ goto end;
+ }
+ if (!ready) {
+ /* Wait until ready. */
+ if ((r = ac_get_state(connection, &state))) {
+ goto end;
+ }
+ switch (state) {
+ case AC_STATE_INIT:
+ continue;
+ case AC_STATE_READY:
+ printf("Connection is established.\n");
+ ready = true;
+ say(connection);
+ continue;
+ default:
+ fprintf(stderr, "Unexpected state.\n");
+ goto end;
+ }
+ }
+
if (!obj) {
continue;
}
@@ -208,10 +267,19 @@ int main(int argc, char **argv) {
}
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);
+ }
+ }
ac_disconnect(connection);
+ net_close(&sock);
ac_free();
-#ifdef WIN32
- WSACleanup();
-#endif
+ net_free();
return r;
} \ No newline at end of file
diff --git a/client/libacron/acronc/net.c b/client/libacron/acronc/net.c
new file mode 100644
index 0000000..83192e0
--- /dev/null
+++ b/client/libacron/acronc/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 <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <windows.h> /* For DWORD */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#define errno_sock WSAGetLastError()
+
+#else
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#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/libacron/acronc/net.h b/client/libacron/acronc/net.h
new file mode 100644
index 0000000..1a49709
--- /dev/null
+++ b/client/libacron/acronc/net.h
@@ -0,0 +1,45 @@
+/*
+ * Created by yuuta on 7/23/22.
+ */
+
+#ifndef ACRONC_NET_H
+#define ACRONC_NET_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+
+#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 */
diff --git a/client/libacron/include/net.h b/client/libacron/include/net.h
index 5cf8f67..03be12f 100644
--- a/client/libacron/include/net.h
+++ b/client/libacron/include/net.h
@@ -10,6 +10,8 @@
#include "events.h"
#include "requests.h"
+#include <stddef.h>
+
/* Connection */
typedef struct ac_connection_parameters {
@@ -18,8 +20,38 @@ typedef struct ac_connection_parameters {
unsigned int version;
char *id;
char *token;
+ /**
+ * An established socket using the host and port parameters above.
+ * The type of the value is opaque to libac, and it will pass
+ * the value as-is to the on_send function pointer parameter.
+ *
+ * The user must keep the socket to disconnect manuall after
+ * calling ac_disconnect().
+ */
+ void *sock;
+
+ /**
+ * Called when ac_request needs to write to the socket.
+ * This function is called on the same thread calling ac_*.
+ * Any functions related to the connection may call on_send.
+ * These functions do not have internal locking, and concurrent
+ * accesses to the same connection object is a racing condition.
+ * Applications are required to do their own locking to guard
+ * the connection.
+ * @param sock The same sock passed in.
+ * @param buffer The buffer of data.
+ * @param len The length to the buffer.
+ * @return 0 on success. Otherwise error.
+ */
+ int (*on_send)(const void *sock, const void *buffer, const size_t len);
} ac_connection_parameters_t;
+enum ac_connection_state {
+ AC_STATE_INIT,
+ AC_STATE_READY,
+ AC_STATE_CLOSED
+};
+
/**
* Connect to the server.
* @param parameters Connection parameters. Untouched.
@@ -31,6 +63,7 @@ LIBAC_EXPORT int ac_connect(ac_connection_parameters_t parameters, void **out);
/**
* Disconnect the connection.
+ * The user must manually disconnect the socket passed in during ac_connect.
* @param connection A non-NULL and connected connection passed as-is from ac_connect.
* @return AC_E_OK or an error code. When failed, connection is undefined. When succeeds, connection is freed and invalid.
* MT-Unsafe
@@ -40,10 +73,24 @@ LIBAC_EXPORT int ac_disconnect(void *connection);
/**
* Blocks the current thread until a new response or event arrives.
* @param connection A non-NULL and connected connection passed as-is from ac_connect.
+ * @param buffer Buffer of data read from the socket.
+ * @param len Length to buffer.
* @param response Output response of either an event or a response. May be NULL even if it succeeds.
- * @param timeout Read timeout in seconds. Set to 0 to wait infinitely.
* @return AC_E_OK or an error code. When failed, *response is NULL.
+ * Notes: if the state is changed to CLOSED after receiving, this function will return AC_E_NET.
+ */
+LIBAC_EXPORT int ac_receive(void *connection,
+ const void *buffer,
+ size_t len,
+ ac_obj_t **response);
+
+/**
+ * Get the current state of the connection.
+ * @param connection A non-NULL and connected connection passed as-is from ac_connect.
+ * @param out Output state.
+ * @return AC_E_OK or an error code. When failed, *out is untouched.
*/
-LIBAC_EXPORT int ac_receive(void *connection, ac_obj_t **response, long timeout);
+LIBAC_EXPORT int ac_get_state(void *connection,
+ enum ac_connection_state *out);
#endif /* LIBAC_NET_H */
diff --git a/client/libacron/net.c b/client/libacron/net.c
index bbfb465..1af752c 100644
--- a/client/libacron/net.c
+++ b/client/libacron/net.c
@@ -2,7 +2,6 @@
* Created by yuuta on 7/13/22.
*/
-#include "win32.h"
#include "config.h"
#include "net.h"
#include "connection.h"
@@ -15,24 +14,10 @@
#include <stdio.h>
#include <string.h>
-#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
-#ifdef WIN32
-#include <windows.h> /* For DWORD */
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#define errno_sock WSAGetLastError()
-#else
-#define errno_sock errno
-#include <sys/socket.h>
-#include <netdb.h>
-#include <unistd.h>
-#include <sys/time.h>
-#endif
-
static void conn_free(struct ac_connection *conn) {
if (conn->url) free(conn->url);
if (conn->parameters.id) free(conn->parameters.id);
@@ -41,33 +26,12 @@ static void conn_free(struct ac_connection *conn) {
free(conn);
}
-static void on_open_handler(struct wic_inst *inst) {
- struct ac_connection *conn = wic_get_app(inst);
- conn->established = true;
-}
-
static void on_send_handler(struct wic_inst *inst,
const void *data,
size_t size,
enum wic_buffer type) {
struct ac_connection *conn = wic_get_app(inst);
- size_t pos;
- const uint8_t *ptr = data;
- int retval;
-
- for (pos = 0U; pos < size; pos += retval) {
- size_t n = size - pos;
- LOGDV("write(%d, %p[%u, %u])", conn->fd, data, pos, n);
- retval = (int) send(conn->fd, &ptr[pos], (int) n, 0);
- if (retval <= 0) {
- /* There's no way to abort the process. */
- int e = errno_sock;
- LOGEV("Cannot write to socket: %s (%d).",
- strerror2(errno),
- e);
- break;
- }
- }
+ conn->parameters.on_send(conn->parameters.sock, data, size);
free((void *) data);
}
@@ -128,7 +92,9 @@ int ac_connect(ac_connection_parameters_t parameters, void **out) {
if (!parameters.id ||
!parameters.host ||
- !parameters.token) {
+ !parameters.token ||
+ !parameters.sock ||
+ !parameters.on_send) {
LOGE("Required parameters are missing.");
return AC_E_INVALID_REQUEST;
}
@@ -140,6 +106,9 @@ int ac_connect(ac_connection_parameters_t parameters, void **out) {
}
/* Do not use memcpy() here because we want to keep track of copied and not-copied strings. */
conn->parameters.port = parameters.port;
+ conn->parameters.sock = parameters.sock;
+ conn->parameters.version = parameters.version;
+ conn->parameters.on_send = parameters.on_send;
if ((r = strdup2(parameters.token, &conn->parameters.token)) ||
(r = strdup2(parameters.host, &conn->parameters.host)) ||
(r = strdup2(parameters.id, &conn->parameters.id))) {
@@ -183,7 +152,6 @@ int ac_connect(ac_connection_parameters_t parameters, void **out) {
arg.rx_max = sizeof(rx);
arg.on_send = on_send_handler;
- arg.on_open = on_open_handler;
arg.on_message = on_message_handler;
arg.on_buffer = on_buffer_handler;
@@ -197,82 +165,12 @@ int ac_connect(ac_connection_parameters_t parameters, void **out) {
return AC_E_INTERNAL;
}
- struct addrinfo *res;
- char service[6];
-
- const enum wic_schema schema = wic_get_url_schema(inst);
- const uint16_t port = wic_get_url_port(inst);
- snprintf(service, 6, "%u", port);
- const char *host = wic_get_url_hostname(inst);
-
- switch (schema) {
- case WIC_SCHEMA_WS:
- break;
- case WIC_SCHEMA_WSS:
- LOGE("WSS is not supported yet.");
- conn_free(conn);
- return AC_E_INVALID_REQUEST;
- default:
- LOGE("Unsupported protocol. The URL must be ws://");
- conn_free(conn);
- return AC_E_INVALID_REQUEST;
- }
-
- if ((r = getaddrinfo(host, service, NULL, &res))) {
- /* gai_strerror() seems to cause an linkage error. */
-#ifdef WIN32
- LOGEV("Resolve host: %d.", r);
-#else
- LOGEV("Resolve host: %s.", gai_strerror(r));
-#endif
- r = AC_E_NET;
- conn_free(conn);
- return r;
- }
-
- SOCKET fd;
- if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) <= 0) {
- const int e = errno_sock;
- LOGEV("Cannot create socket: %s (%d).",
- strerror2(e),
- e);
- freeaddrinfo(res);
- conn_free(conn);
- return AC_E_NET;
- }
- if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
- const int e = errno_sock;
- LOGEV("Cannot connect to socket: %s (%d).",
- strerror2(e),
- e);
- closesocket(fd);
- freeaddrinfo(res);
- conn_free(conn);
- return AC_E_NET;
- }
- freeaddrinfo(res);
-
- conn->fd = fd;
if (wic_start(inst) != WIC_STATUS_SUCCESS) {
LOGE("Cannot start the WIC client.");
- closesocket(fd);
conn_free(conn);
return AC_E_NET;
}
- /* Wait until established (ready to send). */
- while (!conn->established) {
- ac_obj_t *obj = NULL;
- r = ac_receive(conn, &obj, 0);
- if (obj) {
- LOGW("Received an object before connection is established. Dropping.");
- ac_object_free(obj);
- }
- if (r) {
- ac_disconnect(conn);
- return r;
- }
- }
*out = conn;
return AC_E_OK;
}
@@ -280,70 +178,27 @@ int ac_connect(ac_connection_parameters_t parameters, void **out) {
int ac_disconnect(void *connection) {
AC_CHECK_INIT;
struct ac_connection *conn = connection;
- if (!conn->fd) {
- LOGE("Trying to disconnect an already disconnected connection.");
- return AC_E_INVALID_REQUEST;
- }
LOGD("Disconnecting...");
wic_close(&conn->inst);
- closesocket(conn->fd);
conn_free(conn);
return AC_E_OK;
}
-int ac_receive(void *connection, ac_obj_t **response, long timeout) {
+int ac_receive(void *connection,
+ const void *buffer,
+ const size_t len,
+ ac_obj_t **response) {
AC_CHECK_INIT;
struct ac_connection *conn = connection;
struct wic_inst *inst = &conn->inst;
- static uint8_t buffer[1000U];
- int bytes;
+ const uint8_t *ptr = buffer;
size_t retval, pos;
-#ifdef WIN32
- DWORD to = timeout * 1000;
- if (setsockopt(conn->fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&to, sizeof to)) {
-#else
- struct timeval tv;
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
- if (setsockopt(conn->fd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv)) {
-#endif
- const int e = errno_sock;
- LOGEV("Failed to call setsockopt(3): %s (%d).",
- strerror2(e),
- e);
- return AC_E_INTERNAL;
- }
-
- if ((bytes = (int) recv(conn->fd, buffer, sizeof(buffer), 0)) <= 0) {
- LOGDV("recv(%d) = %d", conn->fd, bytes);
- if (bytes < 0) {
- const int e = errno_sock;
-#ifdef WIN32
- if (e == WSAETIMEDOUT) {
-#else
- if (e == EAGAIN) {
-#endif
- /* Timeout */
- *response = NULL;
- return AC_E_OK;
- }
- LOGEV("Failed to receive from socket : %s (%d).",
- strerror2(e),
- e);
- } else {
- LOGI("Peer abnormally shutdown.");
- }
- wic_close_with_reason(inst, WIC_CLOSE_ABNORMAL_2, NULL, 0);
- return AC_E_NET;
- }
- LOGDV("recv(%d) = %d", conn->fd, bytes);
-
/* In case the deserializer does not run at all. */
memset(&conn->result, 0, sizeof(struct ac_result));
- for (pos = 0U; pos < bytes; pos += retval) {
- retval = wic_parse(inst, &buffer[pos], bytes - pos);
+ for (pos = 0U; pos < len; pos += retval) {
+ retval = wic_parse(inst, &ptr[pos], len - pos);
if (wic_get_state(inst) == WIC_STATE_CLOSED) {
LOGE("Connection closed.");
return AC_E_NET;
@@ -390,6 +245,17 @@ int ac_request(void *connection, const ac_request_t *request) {
LOGE("Cannot serialize JSON.");
return AC_E_INTERNAL;
}
+ /**
+ * Access:
+ * * inst->role (ro, safe)
+ * * inst->state (rw, unsafe)
+ * * inst->utf8_tx (rw, safe)
+ * * inst->frag (rw, safe)
+ * * inst->on_buffer (ro, unsafe)
+ * * inst->on_sand (ro, unsafe)
+ * * conn->fd (ro, unsafe)
+ * * write(ro, unsafe)
+ */
if (wic_send_text(inst, true, str, len) != WIC_STATUS_SUCCESS) {
LOGE("Cannot send.");
json_object_put(obj);
@@ -398,3 +264,29 @@ int ac_request(void *connection, const ac_request_t *request) {
json_object_put(obj);
return AC_E_OK;
}
+
+int ac_get_state(void *connection, enum ac_connection_state *out) {
+ AC_CHECK_INIT;
+ struct ac_connection *conn = connection;
+ switch (wic_get_state(&conn->inst)) {
+ case WIC_STATE_CLOSED: {
+ *out = AC_STATE_CLOSED;
+ break;
+ }
+ case WIC_STATE_PARSE_HANDSHAKE:
+ case WIC_STATE_INIT: {
+ *out = AC_STATE_INIT;
+ break;
+ }
+ case WIC_STATE_OPEN:
+ case WIC_STATE_READY: {
+ *out = AC_STATE_READY;
+ break;
+ }
+ default: {
+ *out = AC_STATE_INIT;
+ break;
+ }
+ }
+ return AC_E_OK;
+}
diff --git a/client/libacron/private/connection.h b/client/libacron/private/connection.h
index a76fada..a2bb62f 100644
--- a/client/libacron/private/connection.h
+++ b/client/libacron/private/connection.h
@@ -10,10 +10,6 @@
#include "libac.h"
#include <stdbool.h>
-#ifdef WIN32
-#include <winsock2.h>
-#endif
-
/**
* Used to transfer deserialization result from receive handler to ac_receive().
*/
@@ -30,8 +26,6 @@ struct ac_connection {
ac_connection_parameters_t parameters;
struct wic_inst inst;
char *url;
- SOCKET fd;
- bool established;
/* Result from message handler.
* Message handling will happen at the same thread as ac_receive (lib wic). */
struct ac_result result;
diff --git a/client/libacron/private/win32.h b/client/libacron/private/win32.h
index 07cdca8..a907b28 100644
--- a/client/libacron/private/win32.h
+++ b/client/libacron/private/win32.h
@@ -7,9 +7,6 @@
#ifdef WIN32
#define _Thread_local __declspec( thread )
-#else
-#define SOCKET int
-#define closesocket(socket) close(socket)
#endif
#endif /* LIBAC_WIN32_H */