aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorturret <turret@duck.com>2024-03-30 16:04:45 -0500
committerturret <turret@duck.com>2024-03-30 16:04:45 -0500
commit80a67b7d20393a29aa5d2cb92197f3381be7fd96 (patch)
treef0c313da5ef509e79e5067972edd91976513ce0e
parentf01745a2ee84f11b8cc54e37c5f7f596184ab785 (diff)
downloaddiscord-bot-skeleton-80a67b7d20393a29aa5d2cb92197f3381be7fd96.tar.gz
discord-bot-skeleton-80a67b7d20393a29aa5d2cb92197f3381be7fd96.tar.bz2
discord-bot-skeleton-80a67b7d20393a29aa5d2cb92197f3381be7fd96.zip
*: directory changes
since this project is a skeleton and not meant to clutter up the code that will actually consume the bot, i've opted to consolidate the majority of files under a single directory and minimise extra files *: move code to util/ *: move include files to include/dbs/ net: consolidate net functions into single file config: remove config
-rw-r--r--include/config.h4
-rw-r--r--include/dbs/api.h (renamed from include/api.h)9
-rw-r--r--include/dbs/init.h (renamed from include/init.h)0
-rw-r--r--include/dbs/log.h (renamed from include/log.h)0
-rw-r--r--include/dbs/subsys.h (renamed from include/subsys.h)5
-rw-r--r--include/dbs/util.h (renamed from include/util.h)0
-rw-r--r--net/api.c87
-rw-r--r--net/net.c203
-rw-r--r--net/ws.c67
-rw-r--r--util/init.c (renamed from init/init.c)56
-rw-r--r--util/log.c (renamed from init/log.c)5
-rw-r--r--util/net.c383
-rw-r--r--util/subsys.c (renamed from init/subsys.c)7
13 files changed, 419 insertions, 407 deletions
diff --git a/include/config.h b/include/config.h
deleted file mode 100644
index 4254edc..0000000
--- a/include/config.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#define VERSION "0.0.1"
-#define NAME_SHORTHAND "TCDBF"
-#define NAME "turret.'s C Discord Bot Framework"
-
diff --git a/include/api.h b/include/dbs/api.h
index a70d447..d421f08 100644
--- a/include/api.h
+++ b/include/dbs/api.h
@@ -1,6 +1,5 @@
#ifndef __API_H
#define __API_H
-#define _Nullable
typedef enum {
HTTP_GET,
@@ -10,15 +9,11 @@ typedef enum {
HTTP_PATCH
} HTTPMethod;
-#ifndef __API_INTERNAL
-
int http_request(HTTPMethod method, char *url,
- struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz);
+ struct curl_slist *headers, char *writebuf, size_t bufsiz);
int api_request(HTTPMethod method, char * url,
- struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz);
-
-#endif
+ struct curl_slist *headers, char *writebuf, size_t bufsiz);
#define http_get(...) http_request(HTTP_GET, __VA_ARGS__)
#define http_post(...) http_request(HTTP_POST, __VA_ARGS__)
diff --git a/include/init.h b/include/dbs/init.h
index 79e60f1..79e60f1 100644
--- a/include/init.h
+++ b/include/dbs/init.h
diff --git a/include/log.h b/include/dbs/log.h
index 30fec81..30fec81 100644
--- a/include/log.h
+++ b/include/dbs/log.h
diff --git a/include/subsys.h b/include/dbs/subsys.h
index 1515293..1885a80 100644
--- a/include/subsys.h
+++ b/include/dbs/subsys.h
@@ -3,5 +3,10 @@
int __impl_start_subsystem(char *name, int (*fn)(void));
#define start_subsystem(fn) __impl_start_subsystem(#fn, fn)
+#define declare_subsystem(fn) \
+ void subsys_start_##fn(void) { \
+ start_subsystem(fn); \
+ } \
+ l5_initcall(subsys_start_##fn)
#endif
diff --git a/include/util.h b/include/dbs/util.h
index bad4a33..bad4a33 100644
--- a/include/util.h
+++ b/include/dbs/util.h
diff --git a/net/api.c b/net/api.c
deleted file mode 100644
index be36866..0000000
--- a/net/api.c
+++ /dev/null
@@ -1,87 +0,0 @@
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <curl/curl.h>
-
-#define __API_INTERNAL
-#include <api.h>
-#include <log.h>
-
-extern char *token;
-
-int http_request(HTTPMethod method, char *url,
- struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz)
-{
- int inputpipe[2];
- int outputpipe[2];
-
- if(pipe(inputpipe) < 0)
- return -(errno << 8);
- if(pipe(outputpipe) < 0)
- return -(errno << 8);
-
- if(writebuf && bufsiz > 0)
- write(inputpipe[1], writebuf, bufsiz);
- close(inputpipe[1]);
-
- FILE *input_read = fdopen(inputpipe[0], "r");
- FILE *output_write = fdopen(outputpipe[1], "w");
-
- int ret = outputpipe[0];
-
- CURL *job = curl_easy_init();
- if(job == NULL)
- panic("api: curl_easy_init failed");
-
- curl_easy_setopt(job, CURLOPT_URL, url);
- curl_easy_setopt(job, CURLOPT_READDATA, input_read);
- curl_easy_setopt(job, CURLOPT_WRITEDATA, output_write);
- char *requestmethod = "GET";
- switch(method) {
- case HTTP_PATCH:
- requestmethod = "PATCH";
- break;
- case HTTP_DELETE:
- requestmethod = "DELETE";
- break;
- case HTTP_PUT:
- requestmethod = "PUT";
- break;
- case HTTP_POST:
- requestmethod = "POST";
- break;
- case HTTP_GET: /* fallthrough */
- default:
- break;
- }
- curl_easy_setopt(job, CURLOPT_CUSTOMREQUEST, requestmethod);
- if(headers)
- curl_easy_setopt(job, CURLOPT_HTTPHEADER, headers);
- CURLcode res = curl_easy_perform(job);
-
- if(res > 0) {
- close(outputpipe[0]);
- ret = -res;
- }
-
- curl_easy_cleanup(job);
- fclose(input_read);
- fclose(output_write);
- return ret;
-}
-
-int api_request(HTTPMethod method, char *url,
- struct curl_slist *_Nullable headers, char *writebuf, size_t bufsiz)
-{
- char *new_url = calloc((strlen("https://discord.com/api") + strlen(url) + 1),
- sizeof(char));
- strcpy(new_url, "https://discord.com/api");
- strcat(new_url, url);
- struct curl_slist *headers_auth = curl_slist_append(headers, token);
- int ret = http_request(method, new_url, headers_auth, writebuf, bufsiz);
- free(new_url);
- curl_slist_free_all(headers_auth);
- return ret;
-}
diff --git a/net/net.c b/net/net.c
deleted file mode 100644
index ee3b043..0000000
--- a/net/net.c
+++ /dev/null
@@ -1,203 +0,0 @@
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <pthread.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/signalfd.h>
-#include <unistd.h>
-
-#include <cJSON.h>
-#include <curl/curl.h>
-
-#include <api.h>
-#include <init.h>
-#include <log.h>
-#include <subsys.h>
-
-extern void ws_handle_event(cJSON *event);
-extern void ws_send_heartbeat();
-CURL *ws_handle;
-char *gateway_url;
-
-int net_subsystem(void)
-{
- if(!gateway_url)
- panic("net: gateway url invalid");
-
- /* Initialise CURL */
- ws_handle = curl_easy_init();
-
- curl_easy_setopt(ws_handle, CURLOPT_URL, gateway_url);
- curl_easy_setopt(ws_handle, CURLOPT_CONNECT_ONLY, 2L);
-
- CURLcode ret = curl_easy_perform(ws_handle);
-
- if(ret > 0) {
- panic("net: cannot open websocket: %s", curl_easy_strerror(ret));
- }
-
- int ws_sockfd;
- if((ret = curl_easy_getinfo(ws_handle,
- CURLINFO_ACTIVESOCKET, &ws_sockfd)) != CURLE_OK)
- panic("net: curl cannot get active socket: "
- "%s", curl_easy_strerror(ret));
-
-
- /* Block ALRM */
- sigset_t *set = malloc(sizeof(sigset_t));
- sigemptyset(set);
- sigaddset(set, SIGALRM);
- sigprocmask(SIG_BLOCK, set, NULL);
- int alrmfd = signalfd(-1, set, 0);
- free(set);
-
- /* Prepare poll */
- struct pollfd pollarray[2] = {
- {
- .fd = ws_sockfd,
- .events = POLLIN,
- .revents = POLLIN
- },
- {
- .fd = alrmfd,
- .events = POLLIN,
- .revents = 0
- }
- };
-
- struct pollfd *sockpoll = &(pollarray[0]);
- struct pollfd *alrmpoll = &(pollarray[1]);
-
- /* Misc. variables */
- char *inbuf = malloc(1<<16 * sizeof(char));
- size_t rlen;
- const struct curl_ws_frame *meta;
-
- errno = 0;
- do {
- if((sockpoll->revents & POLLIN) == POLLIN) {
- ret = curl_ws_recv(ws_handle, inbuf, 1<<16, &rlen, &meta);
- /* sometimes only SSL information gets sent through, so no actual
- data is received. curl uses NONBLOCK internally so it lets us
- know if there is no more data remaining */
- if(ret == CURLE_AGAIN)
- continue;
- if(ret != CURLE_OK) {
- print(LOG_ERR "net: encountered error while reading socket: "
- "%s", curl_easy_strerror(ret));
- break;
- }
-
- /* TODO: partial frames */
- if((meta->offset | meta->bytesleft) > 0) {
- print(LOG_ERR "net: dropped partial frame");
- continue;
- }
-
- cJSON *event = cJSON_ParseWithLength(inbuf, rlen);
- if(!event) {
- print(LOG_ERR "net: dropped malformed frame");
- continue;
- }
- ws_handle_event(event);
- cJSON_Delete(event);
- } else if((sockpoll->revents &
- (POLLRDHUP | POLLERR | POLLHUP | POLLNVAL)) > 0) {
- break;
- }
-
- if((alrmpoll->revents & POLLIN) == POLLIN) {
- struct signalfd_siginfo siginfo;
- read(alrmfd, &siginfo, sizeof(struct signalfd_siginfo));
- ws_send_heartbeat();
- }
- } while(poll(pollarray, 2, -1) >= 0);
-
- if(errno > 0) {
- print(LOG_ERR "net: poll: %s", strerror(errno));
- }
-
- free(inbuf);
-
- curl_easy_cleanup(ws_handle);
-
- panic("net: websocket closed unexpectedly");
-
- return 0;
-}
-
-void net_get_gateway_url()
-{
- /* determine if websockets are supported */
- curl_version_info_data *curl_version =
- curl_version_info(CURLVERSION_NOW);
- const char * const* curl_protocols = curl_version->protocols;
- int wss_supported = 0;
- for(int i = 0; curl_protocols[i]; ++i) {
- if(strcmp(curl_protocols[i], "wss") == 0) {
- wss_supported = 1;
- break;
- }
- }
-
- if(!wss_supported)
- panic("net: wss not supported by libcurl");
-
- /* fetch preferred url from discord */
- int fd = api_get("/gateway/bot", NULL, NULL, 0);
- if(fd < 0) {
- print(LOG_ERR "net: cannot get gateway url: %s", curl_easy_strerror(-fd));
- goto assume;
- }
-
- char buf[512];
- int buf_length = read(fd, buf, 512);
- close(fd);
-
- cJSON *gateway_info = cJSON_ParseWithLength(buf, buf_length);
- cJSON *gateway_url_json =
- cJSON_GetObjectItemCaseSensitive(gateway_info, "url");
- if(!cJSON_IsString(gateway_url_json) ||
- gateway_url_json->valuestring == NULL) {
-
- cJSON *gateway_message =
- cJSON_GetObjectItemCaseSensitive(gateway_info, "message");
-
- if(cJSON_IsString(gateway_message)) {
- print(LOG_ERR "net: cannot get gateway url from api: "
- "%s: assuming url", cJSON_GetStringValue(gateway_message));
- } else {
- print(LOG_ERR "net: cannot get gateway url from api "
- "(unknown error): assuming url");
- }
- cJSON_Delete(gateway_info);
- goto assume;
- }
-
- /* curl requires websocket secure URLs to begin with WSS instead
- of wss, so we fix up the received url for curl */
- gateway_url = calloc(strlen(gateway_url_json->valuestring) + 1,
- sizeof(char));
- strcpy(gateway_url, gateway_url_json->valuestring);
- gateway_url[0] = 'W';
- gateway_url[1] = 'S';
- gateway_url[2] = 'S';
-
- cJSON_Delete(gateway_info);
- return;
-
-assume:
- gateway_url = calloc(strlen("WSS://gateway.discord.gg") + 1,
- sizeof(char));
- strcpy(gateway_url, "WSS://gateway.discord.gg");
- return;
-}
-l1_initcall(net_get_gateway_url);
-
-void net_initcall()
-{
- start_subsystem(net_subsystem);
-}
-l2_initcall(net_initcall);
diff --git a/net/ws.c b/net/ws.c
deleted file mode 100644
index 3a8d512..0000000
--- a/net/ws.c
+++ /dev/null
@@ -1,67 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/time.h>
-#include <unistd.h>
-
-#include <cJSON.h>
-#include <curl/curl.h>
-
-#include <log.h>
-
-extern CURL *ws_handle;
-long last_sequence = -1;
-struct timeval heartbeat_time;
-
-void ws_send_heartbeat()
-{
- char buf[128] = "{\"op\":1,\"d\":null}";
- if(last_sequence > 0)
- snprintf(buf, 128, "{\"op\":1,\"d\":%ld}", last_sequence);
- size_t sent;
- curl_ws_send(ws_handle, buf, strnlen(buf, 128), &sent, 0, CURLWS_TEXT);
-
- /* if we receive a heartbeat request from discord, we need to fix
- the itimer so we don't send another one before the desired
- heartbeat interval. if our itimer is off more than 2 seconds
- then we fix it up and reset it */
- struct itimerval itimer;
- getitimer(ITIMER_REAL, &itimer);
- if(itimer.it_value.tv_sec < heartbeat_time.tv_sec - 2) {
- itimer.it_value = heartbeat_time;
- setitimer(ITIMER_REAL, &itimer, NULL);
- }
-}
-
-void ws_handle_event(cJSON *event)
-{
- int op = cJSON_GetObjectItem(event, "op")->valueint;
- cJSON *data = cJSON_GetObjectItem(event, "d");
- switch(op) {
- case 1: /* Heartbeat request */
- ws_send_heartbeat();
- break;
- case 10: ; /* Hello */
- int heartbeat_wait = cJSON_GetObjectItem(data,
- "heartbeat_interval")->valueint;
- float jitter = (float)rand() / (RAND_MAX * 1.0f);
-
- heartbeat_time.tv_sec = heartbeat_wait / 1000;
- heartbeat_time.tv_usec = (heartbeat_wait % 1000) * 1000;
- struct timeval jitter_time = {
- .tv_sec = heartbeat_time.tv_sec * jitter,
- .tv_usec = heartbeat_time.tv_usec * jitter,
- };
- struct itimerval new_itimer = {
- .it_interval = heartbeat_time,
- .it_value = jitter_time
- };
- setitimer(ITIMER_REAL, &new_itimer, NULL);
- break;
- case 11: /* Heartbeat ACK */
- break;
- default:
- print(LOG_ERR "ws: received unknown WS opcode %d", op);
- break;
- }
-}
diff --git a/init/init.c b/util/init.c
index 126442e..77cd4eb 100644
--- a/init/init.c
+++ b/util/init.c
@@ -10,16 +10,14 @@
#include <curl/curl.h>
-#include <config.h>
-#include <init.h>
-#include <log.h>
-#include <util.h>
+#include <dbs/init.h>
+#include <dbs/log.h>
+#include <dbs/util.h>
extern int subsystem_handle_term(int pid);
extern int subsystem_count;
int mainpid = 0;
long stack_size = 8192 * 512;
-char *token;
/* For some reason, I get SIGSEGV'd when running because a random-ass
byte was inserted where it isnt supposed to be. Added a safety byte
@@ -139,34 +137,26 @@ int main(void)
doenv(".env");
/* find directory of self and use env from there if it exists */
- char *buf = calloc(PATH_MAX, sizeof(char));
- ssize_t self_size = readlink("/proc/self/exe", buf, PATH_MAX);
- if(self_size + strlen(".env") + 1 > PATH_MAX)
- goto skip_self;
-
- char *lastslash = strrchr(buf, '/');
- *lastslash = '\0';
-
- char *cwd = get_current_dir_name();
- int cwd_is_exec_dir = strcmp(buf, cwd) == 0;
- free(cwd);
- if(cwd_is_exec_dir)
- goto skip_self;
-
- strcat(buf, "/.env");
- doenv(buf);
+ {
+ char *buf = calloc(PATH_MAX, sizeof(char));
+ ssize_t self_size = readlink("/proc/self/exe", buf, PATH_MAX);
+ if(self_size + strlen(".env") + 1 > PATH_MAX)
+ goto skip_self;
+
+ char *lastslash = strrchr(buf, '/');
+ *lastslash = '\0';
+
+ char *cwd = get_current_dir_name();
+ int cwd_is_exec_dir = strcmp(buf, cwd) == 0;
+ free(cwd);
+ if(cwd_is_exec_dir)
+ goto skip_self;
+
+ strcat(buf, "/.env");
+ doenv(buf);
skip_self:
- free(buf);
-
- /* fetch token */
- char *token_base = getenv("TOKEN");
- if(!token_base)
- panic("init: cannot find TOKEN in env");
-
- token = calloc(strlen(token_base) + strlen("Authorization: Bot ") + 1,
- sizeof(char));
- strcpy(token, "Authorization: Bot ");
- strcat(token, token_base);
+ free(buf);
+ }
/* init curl */
if(curl_global_init(CURL_GLOBAL_DEFAULT))
@@ -175,7 +165,7 @@ skip_self:
/* init random seed */
srand(time(NULL));
- /* Rest of the program.. */
+ /* Perform initcalls */
do_initcalls();
/* Reaper. Much like init. */
diff --git a/init/log.c b/util/log.c
index f39467c..afbc2e3 100644
--- a/init/log.c
+++ b/util/log.c
@@ -8,8 +8,9 @@
#include <sys/syscall.h>
#include <sys/time.h>
#include <unistd.h>
-#include <log.h>
-#include <util.h>
+
+#include <dbs/log.h>
+#include <dbs/util.h>
extern int subsystem_change_mode(int pid, char mode);
extern char *subsystem_get_name(int pid);
diff --git a/util/net.c b/util/net.c
new file mode 100644
index 0000000..98f371a
--- /dev/null
+++ b/util/net.c
@@ -0,0 +1,383 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+
+#include <cJSON.h>
+#include <curl/curl.h>
+
+#include <dbs/api.h>
+#include <dbs/init.h>
+#include <dbs/log.h>
+#include <dbs/subsys.h>
+
+/* functions */
+int http_request(HTTPMethod method, char *url,
+ struct curl_slist *headers, char *writebuf, size_t bufsiz);
+int api_request(HTTPMethod method, char *url,
+ struct curl_slist *headers, char *writebuf, size_t bufsiz);
+static void setup_token_header();
+
+static void ws_send_heartbeat();
+static void ws_handle_event(cJSON *event);
+
+int net_subsystem();
+void net_get_gateway_url();
+
+/* variables */
+static CURL *ws_handle;
+static char *gateway_url;
+static char *token_header;
+
+static long last_sequence = -1;
+static struct timeval heartbeat_time;
+
+int http_request(HTTPMethod method, char *url,
+ struct curl_slist *headers, char *writebuf, size_t bufsiz)
+{
+ int inputpipe[2];
+ int outputpipe[2];
+
+ if(pipe(inputpipe) < 0)
+ return -(errno << 8);
+ if(pipe(outputpipe) < 0)
+ return -(errno << 8);
+
+ if(writebuf && bufsiz > 0)
+ write(inputpipe[1], writebuf, bufsiz);
+ close(inputpipe[1]);
+
+ FILE *input_read = fdopen(inputpipe[0], "r");
+ FILE *output_write = fdopen(outputpipe[1], "w");
+
+ int ret = outputpipe[0];
+
+ CURL *job = curl_easy_init();
+ if(job == NULL)
+ panic("api: curl_easy_init failed");
+
+ curl_easy_setopt(job, CURLOPT_URL, url);
+ curl_easy_setopt(job, CURLOPT_READDATA, input_read);
+ curl_easy_setopt(job, CURLOPT_WRITEDATA, output_write);
+ char *requestmethod = "GET";
+ switch(method) {
+ case HTTP_PATCH:
+ requestmethod = "PATCH";
+ break;
+ case HTTP_DELETE:
+ requestmethod = "DELETE";
+ break;
+ case HTTP_PUT:
+ requestmethod = "PUT";
+ break;
+ case HTTP_POST:
+ requestmethod = "POST";
+ break;
+ case HTTP_GET: /* fallthrough */
+ default:
+ break;
+ }
+ curl_easy_setopt(job, CURLOPT_CUSTOMREQUEST, requestmethod);
+ if(headers)
+ curl_easy_setopt(job, CURLOPT_HTTPHEADER, headers);
+ CURLcode res = curl_easy_perform(job);
+
+ if(res > 0) {
+ close(outputpipe[0]);
+ ret = -res;
+ }
+
+ curl_easy_cleanup(job);
+ fclose(input_read);
+ fclose(output_write);
+ return ret;
+}
+
+static void setup_token_header()
+{
+ if(token_header != NULL)
+ return;
+ char *token = getenv("TOKEN");
+ if(!token)
+ panic("api: cannot find TOKEN in env");
+ token_header = calloc(strlen(token) + strlen("Authorization: Bot ") + 1, sizeof(char));
+ strcpy(token_header, "Authorization: Bot ");
+ strcat(token_header, token);
+}
+l1_initcall(setup_token_header);
+
+int api_request(HTTPMethod method, char *url,
+ struct curl_slist *headers, char *writebuf, size_t bufsiz)
+{
+ char *new_url = calloc((strlen("https://discord.com/api") + strlen(url) + 1),
+ sizeof(char));
+ strcpy(new_url, "https://discord.com/api");
+ strcat(new_url, url);
+ if(token_header == NULL)
+ setup_token_header();
+ struct curl_slist *headers_auth = curl_slist_append(headers, token_header);
+ int ret = http_request(method, new_url, headers_auth, writebuf, bufsiz);
+ free(new_url);
+ curl_slist_free_all(headers_auth);
+ return ret;
+}
+
+static void ws_send_heartbeat()
+{
+ char buf[128] = "{\"op\":1,\"d\":null}";
+ if(last_sequence > 0)
+ snprintf(buf, 128, "{\"op\":1,\"d\":%ld}", last_sequence);
+ size_t sent;
+ curl_ws_send(ws_handle, buf, strnlen(buf, 128), &sent, 0, CURLWS_TEXT);
+
+ /* if we receive a heartbeat request from discord, we need to fix
+ the itimer so we don't send another one before the desired
+ heartbeat interval. if our itimer is off more than 2 seconds
+ then we fix it up and reset it */
+ struct itimerval itimer;
+ getitimer(ITIMER_REAL, &itimer);
+ if(itimer.it_value.tv_sec < heartbeat_time.tv_sec - 2) {
+ itimer.it_value = heartbeat_time;
+ setitimer(ITIMER_REAL, &itimer, NULL);
+ }
+}
+
+static void ws_handle_event(cJSON *event)
+{
+ int op = cJSON_GetObjectItem(event, "op")->valueint;
+ cJSON *data = cJSON_GetObjectItem(event, "d");
+ switch(op) {
+ case 0: /* Event dispatch */
+ break;
+ case 1: /* Heartbeat request */
+ ws_send_heartbeat();
+ break;
+ case 9: /* Invalid Session */
+ if(!cJSON_IsTrue(data)) {
+ /* discord sets data to true if we can reconnect,
+ but in this statement it is false, so we just die */
+ /* note: discord closes the websocket after sending this,
+ so we let our ws code accept and handle the error */
+ break;
+ }
+ /* FALLTHROUGH */
+ case 7: /* Reconnect */
+ /* TODO */
+ panic("ws: cannot reconnect to ws after failure");
+ break;
+ case 10: ; /* Hello */
+ int heartbeat_wait = cJSON_GetObjectItem(data,
+ "heartbeat_interval")->valueint;
+ float jitter = (float)rand() / (RAND_MAX * 1.0f);
+
+ heartbeat_time.tv_sec = heartbeat_wait / 1000;
+ heartbeat_time.tv_usec = (heartbeat_wait % 1000) * 1000;
+ struct timeval jitter_time = {
+ .tv_sec = heartbeat_time.tv_sec * jitter,
+ .tv_usec = heartbeat_time.tv_usec * jitter,
+ };
+ struct itimerval new_itimer = {
+ .it_interval = heartbeat_time,
+ .it_value = jitter_time
+ };
+ setitimer(ITIMER_REAL, &new_itimer, NULL);
+ break;
+ case 11: /* Heartbeat ACK */
+ print(LOG_DEBUG "ws: heartbeat ACK");
+ break;
+ default:
+ print(LOG_ERR "ws: received unknown WS opcode %d", op);
+ break;
+ }
+}
+
+int net_subsystem(void)
+{
+ if(!gateway_url)
+ panic("net: gateway url invalid");
+
+ /* Initialise CURL */
+ ws_handle = curl_easy_init();
+
+ curl_easy_setopt(ws_handle, CURLOPT_URL, gateway_url);
+ curl_easy_setopt(ws_handle, CURLOPT_CONNECT_ONLY, 2L);
+
+ CURLcode ret = curl_easy_perform(ws_handle);
+
+ if(ret > 0) {
+ panic("net: cannot open websocket: %s", curl_easy_strerror(ret));
+ }
+
+ int ws_sockfd;
+ if((ret = curl_easy_getinfo(ws_handle,
+ CURLINFO_ACTIVESOCKET, &ws_sockfd)) != CURLE_OK)
+ panic("net: curl cannot get active socket: "
+ "%s", curl_easy_strerror(ret));
+
+
+ /* Block ALRM */
+ sigset_t *set = malloc(sizeof(sigset_t));
+ sigemptyset(set);
+ sigaddset(set, SIGALRM);
+ sigprocmask(SIG_BLOCK, set, NULL);
+ int alrmfd = signalfd(-1, set, 0);
+ free(set);
+
+ /* Prepare poll */
+ struct pollfd pollarray[2] = {
+ {
+ .fd = ws_sockfd,
+ .events = POLLIN,
+ .revents = POLLIN
+ },
+ {
+ .fd = alrmfd,
+ .events = POLLIN,
+ .revents = 0
+ }
+ };
+
+ struct pollfd *sockpoll = &(pollarray[0]);
+ struct pollfd *alrmpoll = &(pollarray[1]);
+
+ /* Misc. variables */
+ char *inbuf = malloc(1<<16 * sizeof(char));
+ size_t rlen;
+ const struct curl_ws_frame *meta;
+
+ errno = 0;
+ do {
+ if((sockpoll->revents & POLLIN) == POLLIN) {
+ ret = curl_ws_recv(ws_handle, inbuf, 1<<16, &rlen, &meta);
+ /* sometimes only SSL information gets sent through, so no actual
+ data is received. curl uses NONBLOCK internally so it lets us
+ know if there is no more data remaining */
+ if(ret == CURLE_AGAIN)
+ goto sockpoll_continue;
+ if(ret != CURLE_OK) {
+ print(LOG_ERR "net: encountered error while reading socket: "
+ "%s", curl_easy_strerror(ret));
+ break;
+ }
+
+ /* TODO: partial frames */
+ if((meta->offset | meta->bytesleft) > 0) {
+ print(LOG_ERR "net: dropped partial frame");
+ goto sockpoll_continue;
+ }
+
+ switch(meta->flags) {
+ case(CURLWS_PING):
+ curl_ws_send(ws_handle, NULL, 0, NULL, 0, CURLWS_PONG);
+ goto sockpoll_continue;
+ case(CURLWS_CLOSE):
+ default:
+ break;
+ }
+
+ cJSON *event = cJSON_ParseWithLength(inbuf, rlen);
+ if(!event) {
+ print(LOG_ERR "net: dropped malformed frame");
+ goto sockpoll_continue;
+ }
+ ws_handle_event(event);
+ cJSON_Delete(event);
+ } else if((sockpoll->revents &
+ (POLLRDHUP | POLLERR | POLLHUP | POLLNVAL)) > 0) {
+ break;
+ }
+sockpoll_continue:
+
+ if((alrmpoll->revents & POLLIN) == POLLIN) {
+ struct signalfd_siginfo siginfo;
+ read(alrmfd, &siginfo, sizeof(struct signalfd_siginfo));
+ ws_send_heartbeat();
+ }
+ } while(poll(pollarray, 2, -1) >= 0);
+
+ if(errno > 0) {
+ print(LOG_ERR "net: poll: %s", strerror(errno));
+ }
+
+ free(inbuf);
+
+ curl_easy_cleanup(ws_handle);
+
+ panic("net: websocket closed unexpectedly");
+
+ return 0;
+} /* net_subsystem */
+declare_subsystem(net_subsystem);
+
+void net_get_gateway_url()
+{
+ /* determine if websockets are supported */
+ curl_version_info_data *curl_version =
+ curl_version_info(CURLVERSION_NOW);
+ const char * const* curl_protocols = curl_version->protocols;
+ int wss_supported = 0;
+ for(int i = 0; curl_protocols[i]; ++i) {
+ if(strcmp(curl_protocols[i], "wss") == 0) {
+ wss_supported = 1;
+ break;
+ }
+ }
+
+ if(!wss_supported)
+ panic("net: wss not supported by libcurl");
+
+ /* fetch preferred url from discord */
+ int fd = api_get("/gateway/bot", NULL, NULL, 0);
+ if(fd < 0) {
+ print(LOG_ERR "net: cannot get gateway url: %s", curl_easy_strerror(-fd));
+ goto assume;
+ }
+
+ char buf[512];
+ int buf_length = read(fd, buf, 512);
+ close(fd);
+
+ cJSON *gateway_info = cJSON_ParseWithLength(buf, buf_length);
+ cJSON *gateway_url_json =
+ cJSON_GetObjectItemCaseSensitive(gateway_info, "url");
+ if(!cJSON_IsString(gateway_url_json) ||
+ gateway_url_json->valuestring == NULL) {
+
+ cJSON *gateway_message =
+ cJSON_GetObjectItemCaseSensitive(gateway_info, "message");
+
+ if(cJSON_IsString(gateway_message)) {
+ print(LOG_ERR "net: cannot get gateway url from api: "
+ "%s: assuming url", cJSON_GetStringValue(gateway_message));
+ } else {
+ print(LOG_ERR "net: cannot get gateway url from api "
+ "(unknown error): assuming url");
+ }
+ cJSON_Delete(gateway_info);
+ goto assume;
+ }
+
+ /* curl requires websocket secure URLs to begin with WSS instead
+ of wss, so we fix up the received url for curl */
+ gateway_url = calloc(strlen(gateway_url_json->valuestring) + 1,
+ sizeof(char));
+ strcpy(gateway_url, gateway_url_json->valuestring);
+ gateway_url[0] = 'W';
+ gateway_url[1] = 'S';
+ gateway_url[2] = 'S';
+
+ cJSON_Delete(gateway_info);
+ return;
+
+assume:
+ gateway_url = calloc(strlen("WSS://gateway.discord.gg") + 1,
+ sizeof(char));
+ strcpy(gateway_url, "WSS://gateway.discord.gg");
+ return;
+}
+l1_initcall(net_get_gateway_url);
diff --git a/init/subsys.c b/util/subsys.c
index 704678f..a2dc057 100644
--- a/init/subsys.c
+++ b/util/subsys.c
@@ -9,9 +9,8 @@
#include <sys/mman.h>
#include <unistd.h>
-#include <config.h>
-#include <log.h>
-#include <util.h>
+#include <dbs/log.h>
+#include <dbs/util.h>
#define MAX_SUBSYSTEMS 32
#define MAX_RESPAWN 3
@@ -36,7 +35,7 @@ static int __subsystem_entry(struct subsystem_info *info)
what we are looking at from a glance in a ps view or htop or
whatever. */
char *name = malloc(16 * sizeof(char));
- snprintf(name, 16, NAME_SHORTHAND ": %s", info->fn_name);
+ snprintf(name, 16, "DBS: %s", info->fn_name);
name[15] = '\0';
prctl(PR_SET_NAME, name);
free(name);