#include "ssb.connections.h" #include "ssb.h" #include #include #include #include #if !defined(_countof) #define _countof(a) ((int)(sizeof((a)) / sizeof(*(a)))) #endif typedef struct _tf_ssb_connections_t { tf_ssb_t* ssb; sqlite3* db; uv_timer_t timer; } tf_ssb_connections_t; static void _tf_ssb_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data) { tf_ssb_connections_t* connections = user_data; switch (change) { case k_tf_ssb_change_create: { char key[ID_BASE64_LEN]; if (tf_ssb_connection_get_host(connection) && *tf_ssb_connection_get_host(connection) && tf_ssb_connection_get_port(connection) && tf_ssb_connection_get_id(connection, key, sizeof(key))) { tf_ssb_connections_store(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key); tf_ssb_connections_set_attempted(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key); } } break; case k_tf_ssb_change_connect: { char key[ID_BASE64_LEN]; if (tf_ssb_connection_get_id(connection, key, sizeof(key))) { tf_ssb_connections_set_succeeded(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key); } } break; case k_tf_ssb_change_remove: break; } } static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size) { bool result = false; sqlite3_stmt* statement; if (sqlite3_prepare(connections->db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > $1) ORDER BY last_attempt LIMIT 1", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) { snprintf(host, host_size, "%s", sqlite3_column_text(statement, 0)); *port = sqlite3_column_int(statement, 1); snprintf(key, key_size, "%s", sqlite3_column_text(statement, 2)); result = true; } sqlite3_finalize(statement); } else { printf("prepare: %s\n", sqlite3_errmsg(connections->db)); } return result; } static void _tf_ssb_connections_timer(uv_timer_t* timer) { tf_ssb_connections_t* connections = timer->data; tf_ssb_connection_t* active[4]; int count = tf_ssb_get_connections(connections->ssb, active, _countof(active)); if (count < _countof(active)) { char host[256]; int port; char key[ID_BASE64_LEN]; if (_tf_ssb_connections_get_next_connection(connections, host, sizeof(host), &port, key, sizeof(key))) { uint8_t key_bin[ID_BIN_LEN]; if (tf_ssb_id_str_to_bin(key_bin, key)) { tf_ssb_connect(connections->ssb, host, port, key_bin); } } } } tf_ssb_connections_t* tf_ssb_connections_create(tf_ssb_t* ssb) { tf_ssb_connections_t* connections = malloc(sizeof(tf_ssb_connections_t)); memset(connections, 0, sizeof(*connections)); connections->ssb = ssb; connections->db = tf_ssb_get_db(ssb); tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_connections_changed_callback, NULL, connections); uv_loop_t* loop = tf_ssb_get_loop(ssb); connections->timer.data = connections; uv_timer_init(loop, &connections->timer); uv_timer_start(&connections->timer, _tf_ssb_connections_timer, 2000, 2000); uv_unref((uv_handle_t*)&connections->timer); return connections; } static void _tf_ssb_connections_on_handle_close(uv_handle_t* handle) { tf_ssb_connections_t* connections = handle->data; handle->data = NULL; free(connections); } void tf_ssb_connections_destroy(tf_ssb_connections_t* connections) { uv_close((uv_handle_t*)&connections->timer, _tf_ssb_connections_on_handle_close); } void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key) { sqlite3_stmt* statement; if (sqlite3_prepare(connections->db, "INSERT INTO connections (host, port, key) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, port) == SQLITE_OK && sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK) { int r = sqlite3_step(statement); if (r != SQLITE_DONE) { printf("tf_ssb_connections_store: %d, %s.\n", r, sqlite3_errmsg(connections->db)); } } sqlite3_finalize(statement); } } void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key) { sqlite3_stmt* statement; if (sqlite3_prepare(connections->db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, port) == SQLITE_OK && sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK) { if (sqlite3_step(statement) != SQLITE_DONE) { printf("tf_ssb_connections_set_attempted: %s.\n", sqlite3_errmsg(connections->db)); } } sqlite3_finalize(statement); } } void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key) { sqlite3_stmt* statement; if (sqlite3_prepare(connections->db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK && sqlite3_bind_int(statement, 2, port) == SQLITE_OK && sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK) { if (sqlite3_step(statement) != SQLITE_DONE) { printf("tf_ssb_connections_set_succeeded: %s.\n", sqlite3_errmsg(connections->db)); } } sqlite3_finalize(statement); } }