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; | ||||
| } | ||||
							
								
								
									
										150
									
								
								libdc/src/api.c
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								libdc/src/api.c
									
									
									
									
									
								
							| @ -2,7 +2,8 @@ | ||||
| #include <dc/refable.h> | ||||
| #include "internal.h" | ||||
| 
 | ||||
| #define DISCORD_URL "https://discordapp.com/api/v6"
 | ||||
| #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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user