diff options
author | turret <turret@duck.com> | 2024-03-30 16:04:45 -0500 |
---|---|---|
committer | turret <turret@duck.com> | 2024-03-30 16:04:45 -0500 |
commit | 80a67b7d20393a29aa5d2cb92197f3381be7fd96 (patch) | |
tree | f0c313da5ef509e79e5067972edd91976513ce0e | |
parent | f01745a2ee84f11b8cc54e37c5f7f596184ab785 (diff) | |
download | discord-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.h | 4 | ||||
-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.c | 87 | ||||
-rw-r--r-- | net/net.c | 203 | ||||
-rw-r--r-- | net/ws.c | 67 | ||||
-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.c | 383 | ||||
-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. */ @@ -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); |