web sockets are hard
This commit is contained in:
parent
c3f4f5a180
commit
91bdd23a72
@ -12,6 +12,7 @@ SET(SOURCES
|
||||
"include/dc/api.h"
|
||||
"include/dc/apisync.h"
|
||||
"include/dc/channel.h"
|
||||
"include/dc/gateway.h"
|
||||
"include/dc/guild.h"
|
||||
"include/dc/loop.h"
|
||||
"include/dc/message.h"
|
||||
@ -22,10 +23,13 @@ SET(SOURCES
|
||||
"src/api-auth.c"
|
||||
"src/api-channel.c"
|
||||
"src/api-friends.c"
|
||||
"src/api-user.c"
|
||||
"src/apisync.c"
|
||||
"src/channel.c"
|
||||
"src/gateway.c"
|
||||
"src/guild.c"
|
||||
"src/loop.c"
|
||||
"src/masking.c"
|
||||
"src/message.c"
|
||||
"src/refable.c"
|
||||
"src/util.c"
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <dc/account.h>
|
||||
#include <dc/guild.h>
|
||||
#include <dc/channel.h>
|
||||
#include <dc/gateway.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
@ -35,6 +36,11 @@ json_t *dc_api_call_sync(dc_api_t api, char const *token,
|
||||
char const *verb, char const *method,
|
||||
json_t *j);
|
||||
|
||||
/**
|
||||
* Establish a GATEWAY to the discord servers.
|
||||
*/
|
||||
dc_gateway_t dc_api_establish_gateway(dc_api_t api, dc_account_t login);
|
||||
|
||||
/**
|
||||
* Authenticate a given user account. The user account should have
|
||||
* email, and password set. If the auth succeeds the account will have
|
||||
@ -44,6 +50,13 @@ json_t *dc_api_call_sync(dc_api_t api, char const *token,
|
||||
*/
|
||||
bool dc_api_authenticate(dc_api_t api, dc_account_t account);
|
||||
|
||||
/**
|
||||
* Log the account in. Will first call dc_api_authenticate(), then
|
||||
* dc_api_get_userinfo(), then dc_api_get_friends(). If
|
||||
* any of these steps fail, it returns false.
|
||||
*/
|
||||
bool dc_api_login(dc_api_t api, dc_account_t account);
|
||||
|
||||
/**
|
||||
* Inverse of dc_api_authenticate(). Logs the given user out, destroying the
|
||||
* login token in the process.
|
||||
|
56
libdc/include/dc/gateway.h
Normal file
56
libdc/include/dc/gateway.h
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef DC_GATEWAY_H
|
||||
#define DC_GATEWAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <dc/account.h>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
struct dc_gateway_;
|
||||
typedef struct dc_gateway_ *dc_gateway_t;
|
||||
|
||||
typedef enum {
|
||||
GATEWAY_OPCODE_HEARTBEAT = 1,
|
||||
GATEWAY_OPCODE_IDENTIFY = 2,
|
||||
GATEWAY_OPCODE_HELLO = 10,
|
||||
} dc_gateway_opcode_t;
|
||||
|
||||
dc_gateway_t dc_gateway_new(void);
|
||||
|
||||
void dc_gateway_set_login(dc_gateway_t gw, dc_account_t login);
|
||||
|
||||
/**
|
||||
* Set all required CURL handles. The object will delete the easy handle
|
||||
* and make sure it is removed from the multi handle upon unref. Do not
|
||||
* free the multi handle before you remove the gateway.
|
||||
*/
|
||||
void dc_gateway_set_curl(dc_gateway_t gw, CURLM *multi, CURL *easy);
|
||||
|
||||
CURL *dc_gateway_curl(dc_gateway_t gw);
|
||||
|
||||
/**
|
||||
* Returns a CURL slist that lasts as long as the handle itself lasts
|
||||
*/
|
||||
struct curl_slist * dc_gateway_slist(dc_gateway_t gw);
|
||||
|
||||
/**
|
||||
* To be used as a WRITEFUNCTION for a curl handle. Don't forget to set the
|
||||
* gateway handle as a WRITEDATA too, otherwise this will have no effect.
|
||||
*/
|
||||
size_t dc_gateway_writefunc(char *ptr, size_t sz, size_t nmemb, void *data);
|
||||
|
||||
/**
|
||||
* Process the queue of data that came from the websocket.
|
||||
*/
|
||||
void dc_gateway_process(dc_gateway_t gw);
|
||||
|
||||
/**
|
||||
* utility function to make a websocket frame
|
||||
*/
|
||||
uint8_t *
|
||||
dc_gateway_makeframe(uint8_t const *d, size_t data_len,
|
||||
uint8_t type, size_t *outlen);
|
||||
|
||||
#endif
|
@ -2,6 +2,7 @@
|
||||
#define DC_LOOP_H
|
||||
|
||||
#include <dc/api.h>
|
||||
#include <dc/gateway.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <curl/curl.h>
|
||||
@ -39,6 +40,11 @@ struct event_base *dc_loop_event_base(dc_loop_t l);
|
||||
*/
|
||||
void dc_loop_add_api(dc_loop_t loop, dc_api_t api);
|
||||
|
||||
/**
|
||||
* Add a gateway to be handled with the rest.
|
||||
*/
|
||||
void dc_loop_add_gateway(dc_loop_t loop, dc_gateway_t gw);
|
||||
|
||||
/**
|
||||
* Loop once, and process one message in the queues of the event
|
||||
* base, and one message from the queue of the CURL multi events.
|
||||
|
@ -28,6 +28,29 @@ bool dc_api_logout(dc_api_t api, dc_account_t account)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dc_api_login(dc_api_t api, dc_account_t account)
|
||||
{
|
||||
if (!dc_api_authenticate(api, account)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!dc_api_get_userinfo(api, account, account)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!dc_api_get_friends(api, account)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
cleanup:
|
||||
|
||||
dc_account_set_token(account, NULL);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dc_api_authenticate(dc_api_t api, dc_account_t account)
|
||||
{
|
||||
json_t *j = json_object(), *reply = NULL, *token = NULL;
|
||||
|
93
libdc/src/api-user.c
Normal file
93
libdc/src/api-user.c
Normal file
@ -0,0 +1,93 @@
|
||||
#include <dc/api.h>
|
||||
#include "internal.h"
|
||||
|
||||
bool dc_api_get_userinfo(dc_api_t api, dc_account_t login,
|
||||
dc_account_t user)
|
||||
{
|
||||
char *url = NULL;
|
||||
json_t *reply = NULL, *val = NULL;
|
||||
bool ret = false;
|
||||
|
||||
return_if_true(api == NULL, false);
|
||||
return_if_true(login == NULL, false);
|
||||
return_if_true(user == NULL, false);
|
||||
|
||||
if (user == login) {
|
||||
url = strdup("users/@me");
|
||||
} else {
|
||||
asprintf(&url, "users/%s", dc_account_id(user));
|
||||
}
|
||||
|
||||
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
|
||||
goto_if_true(reply == NULL, cleanup);
|
||||
|
||||
val = json_object_get(reply, "username");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_account_set_username(user, json_string_value(val));
|
||||
|
||||
val = json_object_get(reply, "discriminator");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_account_set_discriminator(user, json_string_value(val));
|
||||
|
||||
val = json_object_get(reply, "id");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_account_set_id(user, json_string_value(val));
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
|
||||
free(url);
|
||||
json_decref(reply);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out)
|
||||
{
|
||||
char const *url = "users/@me/guilds";
|
||||
json_t *reply = NULL, *c = NULL, *val = NULL;
|
||||
size_t i = 0;
|
||||
bool ret = false;
|
||||
GPtrArray *guilds = g_ptr_array_new_with_free_func(
|
||||
(GDestroyNotify)dc_unref
|
||||
);
|
||||
|
||||
return_if_true(api == NULL, false);
|
||||
return_if_true(login == NULL, false);
|
||||
|
||||
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
|
||||
goto_if_true(reply == NULL, cleanup);
|
||||
|
||||
goto_if_true(!json_is_array(reply), cleanup);
|
||||
|
||||
json_array_foreach(reply, i, c) {
|
||||
dc_guild_t g = dc_guild_new();
|
||||
|
||||
val = json_object_get(c, "id");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_guild_set_id(g, json_string_value(val));
|
||||
|
||||
val = json_object_get(c, "name");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_guild_set_name(g, json_string_value(val));
|
||||
|
||||
g_ptr_array_add(guilds, g);
|
||||
}
|
||||
|
||||
*out = guilds;
|
||||
guilds = NULL;
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
|
||||
json_decref(reply);
|
||||
|
||||
if (guilds) {
|
||||
g_ptr_array_unref(guilds);
|
||||
guilds = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
148
libdc/src/api.c
148
libdc/src/api.c
@ -3,6 +3,7 @@
|
||||
#include "internal.h"
|
||||
|
||||
#define DISCORD_URL "https://discordapp.com/api/v6"
|
||||
#define DISCORD_GATEWAY "https://gateway.discord.gg/?encoding=json&v=6"
|
||||
|
||||
#define DISCORD_USERAGENT "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0"
|
||||
|
||||
@ -276,93 +277,88 @@ bool dc_api_error(json_t *j, int *code, char const **message)
|
||||
return error;
|
||||
}
|
||||
|
||||
bool dc_api_get_userinfo(dc_api_t api, dc_account_t login,
|
||||
dc_account_t user)
|
||||
static size_t stall_connection(char *buffer, size_t size, size_t nitems,
|
||||
void *userdata)
|
||||
{
|
||||
char *url = NULL;
|
||||
json_t *reply = NULL, *val = NULL;
|
||||
bool ret = false;
|
||||
CURL *easy = (CURL *)userdata;
|
||||
|
||||
return_if_true(api == NULL, false);
|
||||
return_if_true(login == NULL, false);
|
||||
return_if_true(user == NULL, false);
|
||||
|
||||
if (user == login) {
|
||||
url = strdup("users/@me");
|
||||
} else {
|
||||
asprintf(&url, "users/%s", dc_account_id(user));
|
||||
if (strncmp(buffer, "\r\n", size) == 0) {
|
||||
curl_easy_setopt(easy, CURLOPT_CONNECT_ONLY, 1);
|
||||
//curl_easy_pause(easy, CURLPAUSE_ALL);
|
||||
curl_easy_setopt(easy, CURLOPT_FORBID_REUSE, 1);
|
||||
}
|
||||
|
||||
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
|
||||
goto_if_true(reply == NULL, cleanup);
|
||||
return size * nitems;
|
||||
}
|
||||
|
||||
val = json_object_get(reply, "username");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_account_set_username(user, json_string_value(val));
|
||||
dc_gateway_t dc_api_establish_gateway(dc_api_t api, dc_account_t login)
|
||||
{
|
||||
return_if_true(api == NULL, NULL);
|
||||
return_if_true(api->curl == NULL, NULL);
|
||||
return_if_true(login == NULL || !dc_account_has_token(login), NULL);
|
||||
|
||||
val = json_object_get(reply, "discriminator");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_account_set_discriminator(user, json_string_value(val));
|
||||
CURL *c = NULL;
|
||||
struct curl_slist *list = NULL;
|
||||
/* BE THE BROKEN OR THE BREAKER
|
||||
*/
|
||||
dc_gateway_t gw = NULL;
|
||||
dc_gateway_t ret = NULL;
|
||||
|
||||
val = json_object_get(reply, "id");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_account_set_id(user, json_string_value(val));
|
||||
c = curl_easy_init();
|
||||
goto_if_true(c == NULL, cleanup);
|
||||
|
||||
ret = true;
|
||||
gw = dc_gateway_new();
|
||||
goto_if_true(gw == NULL, cleanup);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_URL, DISCORD_GATEWAY);
|
||||
|
||||
list = dc_gateway_slist(gw);
|
||||
curl_slist_append(list, "Content-Type: application/json");
|
||||
curl_slist_append(list, "Accept: application/json");
|
||||
curl_slist_append(list, "User-Agent: " DISCORD_USERAGENT);
|
||||
curl_slist_append(list, "Pragma: no-cache");
|
||||
curl_slist_append(list, "Cache-Control: no-cache");
|
||||
curl_slist_append(list, "Sec-WebSocket-Key: cbYK1Jm6cpk3Rua");
|
||||
curl_slist_append(list, "Sec-WebSocket-Version: 13");
|
||||
curl_slist_append(list, "Upgrade: websocket");
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, stall_connection);
|
||||
curl_easy_setopt(c, CURLOPT_HEADERDATA, c);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_HTTPHEADER, list);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_TCP_KEEPIDLE, 120L);
|
||||
curl_easy_setopt(c, CURLOPT_TCP_KEEPINTVL, 60L);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_FORBID_REUSE, 1L);
|
||||
curl_easy_setopt(c, CURLOPT_FRESH_CONNECT, 1L);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, dc_gateway_writefunc);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, gw);
|
||||
|
||||
dc_gateway_set_login(gw, login);
|
||||
dc_gateway_set_curl(gw, api->curl, c);
|
||||
|
||||
if (curl_multi_add_handle(api->curl, c) != CURLM_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
c = NULL;
|
||||
|
||||
ret = gw;
|
||||
gw = NULL;
|
||||
|
||||
cleanup:
|
||||
|
||||
free(url);
|
||||
json_decref(reply);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool dc_api_get_userguilds(dc_api_t api, dc_account_t login, GPtrArray **out)
|
||||
{
|
||||
char const *url = "users/@me/guilds";
|
||||
json_t *reply = NULL, *c = NULL, *val = NULL;
|
||||
size_t i = 0;
|
||||
bool ret = false;
|
||||
GPtrArray *guilds = g_ptr_array_new_with_free_func(
|
||||
(GDestroyNotify)dc_unref
|
||||
);
|
||||
|
||||
return_if_true(api == NULL, false);
|
||||
return_if_true(login == NULL, false);
|
||||
|
||||
reply = dc_api_call_sync(api, "GET", dc_account_token(login), url, NULL);
|
||||
goto_if_true(reply == NULL, cleanup);
|
||||
|
||||
goto_if_true(!json_is_array(reply), cleanup);
|
||||
|
||||
json_array_foreach(reply, i, c) {
|
||||
dc_guild_t g = dc_guild_new();
|
||||
|
||||
val = json_object_get(c, "id");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_guild_set_id(g, json_string_value(val));
|
||||
|
||||
val = json_object_get(c, "name");
|
||||
goto_if_true(val == NULL || !json_is_string(val), cleanup);
|
||||
dc_guild_set_name(g, json_string_value(val));
|
||||
|
||||
g_ptr_array_add(guilds, g);
|
||||
}
|
||||
|
||||
*out = guilds;
|
||||
guilds = NULL;
|
||||
|
||||
ret = true;
|
||||
|
||||
cleanup:
|
||||
|
||||
json_decref(reply);
|
||||
|
||||
if (guilds) {
|
||||
g_ptr_array_unref(guilds);
|
||||
guilds = NULL;
|
||||
}
|
||||
if (c != NULL) {
|
||||
curl_easy_cleanup(c);
|
||||
}
|
||||
|
||||
dc_unref(gw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
295
libdc/src/gateway.c
Normal file
295
libdc/src/gateway.c
Normal file
@ -0,0 +1,295 @@
|
||||
#include <dc/gateway.h>
|
||||
#include "internal.h"
|
||||
#include <jansson.h>
|
||||
|
||||
struct dc_gateway_
|
||||
{
|
||||
dc_refable_t ref;
|
||||
|
||||
GPtrArray *ops;
|
||||
GPtrArray *out;
|
||||
GByteArray *buffer;
|
||||
|
||||
CURLM *multi;
|
||||
CURL *easy;
|
||||
struct curl_slist *slist;
|
||||
|
||||
dc_account_t login;
|
||||
|
||||
uint64_t heartbeat_interval;
|
||||
time_t last_heartbeat;
|
||||
};
|
||||
|
||||
static void dc_gateway_free(dc_gateway_t g)
|
||||
{
|
||||
return_if_true(g == NULL,);
|
||||
|
||||
if (g->buffer != NULL) {
|
||||
g_byte_array_unref(g->buffer);
|
||||
g->buffer = NULL;
|
||||
}
|
||||
|
||||
if (g->ops != NULL) {
|
||||
g_ptr_array_unref(g->ops);
|
||||
g->ops = NULL;
|
||||
}
|
||||
|
||||
if (g->out != NULL) {
|
||||
g_ptr_array_unref(g->out);
|
||||
g->out = NULL;
|
||||
}
|
||||
|
||||
if (g->easy != NULL) {
|
||||
curl_multi_remove_handle(g->multi, g->easy);
|
||||
curl_easy_cleanup(g->easy);
|
||||
g->easy = NULL;
|
||||
}
|
||||
|
||||
if (g->slist != NULL) {
|
||||
curl_slist_free_all(g->slist);
|
||||
g->slist = NULL;
|
||||
}
|
||||
|
||||
dc_unref(g->login);
|
||||
|
||||
free(g);
|
||||
}
|
||||
|
||||
dc_gateway_t dc_gateway_new(void)
|
||||
{
|
||||
dc_gateway_t g = calloc(1, sizeof(struct dc_gateway_));
|
||||
return_if_true(g == NULL, NULL);
|
||||
|
||||
g->ref.cleanup = (dc_cleanup_t)dc_gateway_free;
|
||||
|
||||
g->buffer = g_byte_array_new();
|
||||
goto_if_true(g->buffer == NULL, error);
|
||||
|
||||
g->ops = g_ptr_array_new_with_free_func((GDestroyNotify)json_decref);
|
||||
goto_if_true(g->ops == NULL, error);
|
||||
|
||||
g->out = g_ptr_array_new_with_free_func((GDestroyNotify)json_decref);
|
||||
goto_if_true(g->out == NULL, error);
|
||||
|
||||
g->slist = curl_slist_append(NULL, "");
|
||||
goto_if_true(g->slist == NULL, error);
|
||||
|
||||
return dc_ref(g);
|
||||
|
||||
error:
|
||||
|
||||
dc_gateway_free(g);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dc_gateway_set_login(dc_gateway_t gw, dc_account_t login)
|
||||
{
|
||||
return_if_true(gw == NULL,);
|
||||
gw->login = dc_ref(login);
|
||||
}
|
||||
|
||||
void dc_gateway_set_curl(dc_gateway_t gw, CURLM *multi, CURL *easy)
|
||||
{
|
||||
return_if_true(gw == NULL,);
|
||||
gw->multi = multi;
|
||||
gw->easy = easy;
|
||||
}
|
||||
|
||||
CURL *dc_gateway_curl(dc_gateway_t gw)
|
||||
{
|
||||
return_if_true(gw == NULL, NULL);
|
||||
return gw->easy;
|
||||
}
|
||||
|
||||
struct curl_slist * dc_gateway_slist(dc_gateway_t gw)
|
||||
{
|
||||
return_if_true(gw == NULL, NULL);
|
||||
return gw->slist;
|
||||
}
|
||||
|
||||
size_t dc_gateway_writefunc(char *ptr, size_t sz, size_t nmemb, void *data)
|
||||
{
|
||||
dc_gateway_t g = (dc_gateway_t)data;
|
||||
json_t *j = NULL;
|
||||
size_t i = 0;
|
||||
|
||||
FILE *f = fopen("websocket.txt", "a+");
|
||||
fprintf(f, ">> ");
|
||||
fwrite(ptr, sz, nmemb, f);
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
|
||||
for (i = 0; *ptr != '{' && i < (sz *nmemb); ptr++, i++)
|
||||
;
|
||||
|
||||
if (i < (sz * nmemb)) {
|
||||
j = json_loadb(ptr, (sz*nmemb) - i, JSON_DISABLE_EOF_CHECK, NULL);
|
||||
if (j != NULL) {
|
||||
g_ptr_array_add(g->ops, j);
|
||||
}
|
||||
}
|
||||
|
||||
return sz * nmemb;
|
||||
}
|
||||
|
||||
static json_t *dc_gateway_answer(dc_gateway_t gw)
|
||||
{
|
||||
json_t *j = NULL;
|
||||
char const *token = NULL;
|
||||
|
||||
j = json_object();
|
||||
return_if_true(j == NULL, NULL);
|
||||
|
||||
token = dc_account_token(gw->login);
|
||||
if (token != NULL) {
|
||||
json_object_set_new(j, "token", json_string(token));
|
||||
}
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
static void dc_gateway_queue(dc_gateway_t gw, int code, json_t *d)
|
||||
{
|
||||
json_t *j = NULL;
|
||||
|
||||
j = json_object();
|
||||
return_if_true(j == NULL,);
|
||||
|
||||
if (d == NULL) {
|
||||
d = dc_gateway_answer(gw);
|
||||
}
|
||||
|
||||
json_object_set_new(j, "t", json_null());
|
||||
json_object_set_new(j, "s", json_null());
|
||||
json_object_set_new(j, "d", d);
|
||||
json_object_set_new(j, "op", json_integer(code));
|
||||
|
||||
g_ptr_array_add(gw->out, j);
|
||||
}
|
||||
|
||||
static void dc_gateway_queue_heartbeat(dc_gateway_t gw)
|
||||
{
|
||||
dc_gateway_queue(gw, GATEWAY_OPCODE_HEARTBEAT, NULL);
|
||||
gw->last_heartbeat = time(NULL);
|
||||
}
|
||||
|
||||
static bool dc_gateway_handle_hello(dc_gateway_t gw, json_t *d)
|
||||
{
|
||||
json_t *val = NULL;
|
||||
|
||||
val = json_object_get(d, "heartbeat_interval");
|
||||
return_if_true(val == NULL || !json_is_integer(val), false);
|
||||
|
||||
/* send an identify first
|
||||
*/
|
||||
dc_gateway_queue(gw, GATEWAY_OPCODE_IDENTIFY, NULL);
|
||||
|
||||
gw->heartbeat_interval = json_integer_value(val);
|
||||
dc_gateway_queue_heartbeat(gw);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dc_gateway_handle_op(dc_gateway_t gw, json_t *j)
|
||||
{
|
||||
json_t *val = NULL;
|
||||
dc_gateway_opcode_t op = 0;
|
||||
|
||||
val = json_object_get(j, "op");
|
||||
return_if_true(val == NULL || !json_is_integer(val), false);
|
||||
op = (dc_gateway_opcode_t)json_integer_value(val);
|
||||
|
||||
val = json_object_get(j, "d");
|
||||
return_if_true(val == NULL || !json_is_object(val), false);
|
||||
|
||||
switch (op) {
|
||||
case GATEWAY_OPCODE_HELLO: dc_gateway_handle_hello(gw, val); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void dc_gateway_process_read(dc_gateway_t gw)
|
||||
{
|
||||
char buf[100] = {0};
|
||||
size_t read = 0;
|
||||
int ret = 0;
|
||||
FILE *f = NULL;
|
||||
json_t *j = NULL;
|
||||
size_t where = 0;
|
||||
|
||||
ret = curl_easy_recv(gw->easy, &buf, sizeof(buf), &read);
|
||||
return_if_true(ret != CURLE_OK,);
|
||||
|
||||
g_byte_array_append(gw->buffer, (uint8_t const*)buf, read);
|
||||
|
||||
f = fmemopen(gw->buffer->data, gw->buffer->len, "r");
|
||||
return_if_true(f == NULL,);
|
||||
|
||||
j = json_loadf(f, JSON_DISABLE_EOF_CHECK, NULL);
|
||||
where = ftell(f);
|
||||
|
||||
fclose(f);
|
||||
f = NULL;
|
||||
|
||||
if (j != NULL) {
|
||||
g_ptr_array_add(gw->ops, j);
|
||||
g_byte_array_remove_range(gw->buffer, 0, where);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void dc_gateway_process_in(dc_gateway_t gw)
|
||||
{
|
||||
while (gw->ops->len > 0) {
|
||||
json_t *j = g_ptr_array_index(gw->ops, 0);
|
||||
dc_gateway_handle_op(gw, j);
|
||||
g_ptr_array_remove_index(gw->ops, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dc_gateway_process_out(dc_gateway_t gw)
|
||||
{
|
||||
char *str = NULL;
|
||||
size_t slen = 0, outlen = 0, sent = 0;
|
||||
uint8_t *mask = NULL;
|
||||
|
||||
while (gw->out->len > 0) {
|
||||
json_t *j = g_ptr_array_index(gw->out, 0);
|
||||
|
||||
str = json_dumps(j, JSON_COMPACT);
|
||||
|
||||
if (str != NULL) {
|
||||
slen = strlen(str);
|
||||
mask = dc_gateway_makeframe((uint8_t const *)str, slen, 0, &outlen);
|
||||
curl_easy_send(gw->easy, mask, outlen, &sent);
|
||||
|
||||
FILE *f = fopen("websocket.txt", "a+");
|
||||
fprintf(f, "<< %s\n", str);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
free(str);
|
||||
free(mask);
|
||||
|
||||
g_ptr_array_remove_index(gw->out, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void dc_gateway_process(dc_gateway_t gw)
|
||||
{
|
||||
time_t diff = 0;
|
||||
|
||||
if (gw->heartbeat_interval > 0) {
|
||||
diff = time(NULL) - gw->last_heartbeat;
|
||||
if (diff >= (gw->heartbeat_interval / 1000)) {
|
||||
dc_gateway_queue_heartbeat(gw);
|
||||
}
|
||||
}
|
||||
|
||||
//dc_gateway_process_read(gw);
|
||||
dc_gateway_process_in(gw);
|
||||
dc_gateway_process_out(gw);
|
||||
}
|
@ -15,6 +15,7 @@ struct dc_loop_
|
||||
bool multi_owner;
|
||||
|
||||
GPtrArray *apis;
|
||||
GPtrArray *gateways;
|
||||
};
|
||||
|
||||
static void dc_loop_free(dc_loop_t p)
|
||||
@ -42,6 +43,11 @@ static void dc_loop_free(dc_loop_t p)
|
||||
p->apis = NULL;
|
||||
}
|
||||
|
||||
if (p->gateways != NULL) {
|
||||
g_ptr_array_unref(p->gateways);
|
||||
p->gateways = NULL;
|
||||
}
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
@ -63,6 +69,18 @@ mcurl_handler(CURL *easy, curl_socket_t s, int what, void *userp, void *socketp)
|
||||
struct event *event = (struct event *)socketp;
|
||||
dc_loop_t loop = (dc_loop_t)userp;
|
||||
|
||||
for (size_t i = 0; i < loop->gateways->len; i++) {
|
||||
dc_gateway_t gw = g_ptr_array_index(loop->gateways, i);
|
||||
|
||||
if (dc_gateway_curl(gw) == easy) {
|
||||
printf("gateway event: %s: %s%s\n",
|
||||
(what == CURL_POLL_REMOVE ? "remove" : "add"),
|
||||
((what & CURL_POLL_IN) ? "r" : ""),
|
||||
((what & CURL_POLL_OUT) ? "w": "")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (what == CURL_POLL_REMOVE) {
|
||||
if (event != NULL) {
|
||||
event_del(event);
|
||||
@ -148,9 +166,12 @@ dc_loop_t dc_loop_new_full(struct event_base *base, CURLM *multi)
|
||||
ptr->multi_owner = true;
|
||||
}
|
||||
|
||||
ptr->apis = g_ptr_array_new();
|
||||
ptr->apis = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref);
|
||||
goto_if_true(ptr->apis == NULL, fail);
|
||||
|
||||
ptr->gateways = g_ptr_array_new_with_free_func((GDestroyNotify)dc_unref);
|
||||
goto_if_true(ptr->gateways == NULL, fail);
|
||||
|
||||
ptr->timer = evtimer_new(ptr->base, timer_handler, ptr);
|
||||
goto_if_true(ptr->timer == NULL, fail);
|
||||
|
||||
@ -188,7 +209,14 @@ void dc_loop_add_api(dc_loop_t l, dc_api_t a)
|
||||
dc_api_set_event_base(p, l->base);
|
||||
dc_api_set_curl_multi(p, l->multi);
|
||||
|
||||
g_ptr_array_add(l->apis, p);
|
||||
g_ptr_array_add(l->apis, dc_ref(p));
|
||||
}
|
||||
|
||||
void dc_loop_add_gateway(dc_loop_t l, dc_gateway_t gw)
|
||||
{
|
||||
return_if_true(l == NULL || gw == NULL,);
|
||||
|
||||
g_ptr_array_add(l->gateways, dc_ref(gw));
|
||||
}
|
||||
|
||||
void dc_loop_abort(dc_loop_t l)
|
||||
@ -225,5 +253,10 @@ bool dc_loop_once(dc_loop_t l)
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < l->gateways->len; i++) {
|
||||
dc_gateway_t gw = g_ptr_array_index(l->gateways, i);
|
||||
dc_gateway_process(gw);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
63
libdc/src/masking.c
Normal file
63
libdc/src/masking.c
Normal file
@ -0,0 +1,63 @@
|
||||
#include <dc/gateway.h>
|
||||
#include "internal.h"
|
||||
|
||||
static uint8_t *
|
||||
websocket_mask(uint8_t key[4], uint8_t const *pload, size_t psize)
|
||||
{
|
||||
size_t i = 0;
|
||||
uint8_t *ret = calloc(psize, sizeof(char));
|
||||
|
||||
for (i = 0; i < psize; i++) {
|
||||
ret[i] = pload[i] ^ key[i % 4];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t *
|
||||
dc_gateway_makeframe(uint8_t const *d, size_t data_len,
|
||||
uint8_t type, size_t *outlen)
|
||||
{
|
||||
uint8_t *data = NULL;
|
||||
uint8_t *full_data;
|
||||
uint32_t len_size = 1;
|
||||
uint8_t mkey[4] = { 0x12, 0x34, 0x56, 0x78 };
|
||||
|
||||
data = websocket_mask(mkey, d, data_len);
|
||||
|
||||
if (data_len > 125) {
|
||||
if (data_len <= UINT16_MAX) {
|
||||
len_size += 2;
|
||||
} else {
|
||||
len_size += 8;
|
||||
}
|
||||
}
|
||||
|
||||
full_data = calloc(1 + data_len + len_size + 4, sizeof(uint8_t));
|
||||
|
||||
if (type == 0) {
|
||||
type = 129;
|
||||
}
|
||||
|
||||
full_data[0] = type;
|
||||
|
||||
if (data_len <= 125) {
|
||||
full_data[1] = data_len | 0x80;
|
||||
} else if (data_len <= G_MAXUINT16) {
|
||||
uint16_t be_len = GUINT16_TO_BE(data_len);
|
||||
full_data[1] = 126 | 0x80;
|
||||
memmove(full_data + 2, &be_len, 2);
|
||||
} else {
|
||||
guint64 be_len = GUINT64_TO_BE(data_len);
|
||||
full_data[1] = 127 | 0x80;
|
||||
memmove(full_data + 2, &be_len, 8);
|
||||
}
|
||||
|
||||
memmove(full_data + (1 + len_size), &mkey, 4);
|
||||
memmove(full_data + (1 + len_size + 4), data, data_len);
|
||||
|
||||
*outlen = 1 + data_len + len_size + 4;
|
||||
free(data);
|
||||
|
||||
return full_data;
|
||||
}
|
@ -47,6 +47,7 @@ extern GHashTable *accounts;
|
||||
extern dc_account_t current_account;
|
||||
|
||||
extern dc_api_t api;
|
||||
extern dc_loop_t loop;
|
||||
|
||||
extern char *ncdc_private_dir;
|
||||
extern void *config;
|
||||
|
@ -7,6 +7,7 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
|
||||
char *arg = NULL;
|
||||
bool ret = false;
|
||||
dc_account_t acc = NULL;
|
||||
dc_gateway_t gw = NULL;
|
||||
|
||||
goto_if_true(ac <= 1, cleanup);
|
||||
|
||||
@ -29,22 +30,22 @@ bool ncdc_cmd_login(ncdc_mainwindow_t n, size_t ac, wchar_t **av)
|
||||
}
|
||||
}
|
||||
|
||||
if (!dc_api_authenticate(api, acc)) {
|
||||
if (!dc_api_login(api, acc)) {
|
||||
LOG(n, L"login: %ls: authentication failed; wrong password?", av[1]);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!dc_api_get_userinfo(api, acc, acc)) {
|
||||
LOG(n, L"login: %ls: failed to get basic user information", av[1]);
|
||||
gw = dc_api_establish_gateway(api, acc);
|
||||
if (gw == NULL) {
|
||||
LOG(n, L"login: %ls: failed to establish gateway", av[1]);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!dc_api_get_friends(api, acc)) {
|
||||
LOG(n, L"login: %ls: failed to load friends", av[1]);
|
||||
goto cleanup;
|
||||
}
|
||||
dc_loop_add_gateway(loop, gw);
|
||||
|
||||
dc_unref(current_account);
|
||||
current_account = dc_ref(acc);
|
||||
|
||||
LOG(n, L"login: %ls: authentication successful", av[1]);
|
||||
ret = true;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user