#include "ssb.rpc.h" #include "mem.h" #include "ssb.h" #include "ssb.db.h" #include "util.js.h" #include #include #include #include static void _tf_ssb_rpc_gossip_ping(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { char buffer[256]; snprintf(buffer, sizeof(buffer), "%" PRId64, (int64_t)time(NULL)); tf_ssb_connection_rpc_send( connection, flags, -request_number, (const uint8_t*)buffer, strlen(buffer), NULL, NULL, NULL); } static void _tf_ssb_rpc_blobs_get(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_connection_get_context(connection); JSValue ids = JS_GetPropertyStr(context, args, "args"); int length = tf_util_get_length(context, ids); bool success = false; for (int i = 0; i < length; i++) { JSValue arg = JS_GetPropertyUint32(context, ids, i); const char* id = NULL; if (JS_IsString(arg)) { id = JS_ToCString(context, arg); } else { JSValue key = JS_GetPropertyStr(context, arg, "key"); id = JS_ToCString(context, key); JS_FreeValue(context, key); } uint8_t* blob = NULL; size_t size = 0; const int k_send_max = 8192; if (tf_ssb_db_blob_get(ssb, id, &blob, &size)) { printf("sending %s (%zd)\n", id, size); for (size_t offset = 0; offset < size; offset += k_send_max) { tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -request_number, blob + offset, offset + k_send_max <= size ? k_send_max : (size - offset), NULL, NULL, NULL); } success = true; tf_free(blob); } JS_FreeCString(context, id); JS_FreeValue(context, arg); } JS_FreeValue(context, ids); tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error | k_ssb_rpc_flag_stream, -request_number, (const uint8_t*)(success ? "true" : "false"), strlen(success ? "true" : "false"), NULL, NULL, NULL); } static void _tf_ssb_rpc_blobs_has(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_connection_get_context(connection); JSValue ids = JS_GetPropertyStr(context, args, "args"); JSValue id = JS_GetPropertyUint32(context, ids, 0); const char* id_str = JS_ToCString(context, id); bool has = tf_ssb_db_blob_has(ssb, id_str); JS_FreeCString(context, id_str); JS_FreeValue(context, id); JS_FreeValue(context, ids); tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error, -request_number, (const uint8_t*)(has ? "true" : "false"), strlen(has ? "true" : "false"), NULL, NULL, NULL); } static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id, void* user_data) { tf_ssb_connection_t* connection = user_data; tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection); JSContext* context = tf_ssb_get_context(ssb); JSValue message = JS_NewObject(context); JS_SetPropertyStr(context, message, id, JS_NewInt64(context, -1)); JSValue json = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t size = 0; const char* message_str = JS_ToCStringLen(context, &size, json); tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, -blob_wants->request_number, (const uint8_t*)message_str, size, NULL, NULL, NULL); JS_FreeCString(context, message_str); JS_FreeValue(context, json); JS_FreeValue(context, message); } static void _tf_ssb_rpc_request_more_blobs(tf_ssb_connection_t* connection) { JSContext* context = tf_ssb_connection_get_context(connection); tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); sqlite3* db = tf_ssb_get_db(ssb); sqlite3_stmt* statement; if (sqlite3_prepare(db, "SELECT id FROM blob_wants WHERE id > ? ORDER BY id LIMIT 32", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_bind_text(statement, 1, blob_wants->last_id, -1, NULL) == SQLITE_OK) { while (sqlite3_step(statement) == SQLITE_ROW) { const char* blob = (const char*)sqlite3_column_text(statement, 0); JSValue message = JS_NewObject(context); JS_SetPropertyStr(context, message, blob, JS_NewInt32(context, -1)); JSValue json = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t size = 0; const char* message_str = JS_ToCStringLen(context, &size, json); tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, -blob_wants->request_number, (const uint8_t*)message_str, size, NULL, NULL, NULL); JS_FreeCString(context, message_str); JS_FreeValue(context, json); JS_FreeValue(context, message); snprintf(blob_wants->last_id, sizeof(blob_wants->last_id), "%s", blob); blob_wants->wants_sent++; } } sqlite3_finalize(statement); } } static void _tf_ssb_rpc_blobs_createWants(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); tf_ssb_add_blob_want_added_callback(ssb, _tf_ssb_rpc_blob_wants_added_callback, NULL, connection); blob_wants->request_number = request_number; _tf_ssb_rpc_request_more_blobs(connection); } typedef struct tunnel_t { tf_ssb_connection_t* connection; int32_t request_number; } tunnel_t; void _tf_ssb_rpc_tunnel_callback(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tunnel_t* tun = user_data; tf_ssb_connection_rpc_send(tun->connection, flags, tun->request_number, message, size, NULL, NULL, NULL); } void _tf_ssb_rpc_tunnel_cleanup(tf_ssb_t* ssb, void* user_data) { tf_free(user_data); } static void _tf_ssb_rpc_tunnel_connect(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_connection_get_context(connection); JSValue arg_array = JS_GetPropertyStr(context, args, "args"); JSValue arg = JS_GetPropertyUint32(context, arg_array, 0); JSValue origin = JS_GetPropertyStr(context, arg, "origin"); JSValue portal = JS_GetPropertyStr(context, arg, "portal"); JSValue target = JS_GetPropertyStr(context, arg, "target"); if (JS_IsUndefined(origin) && !JS_IsUndefined(portal) && !JS_IsUndefined(target)) { const char* portal_str = JS_ToCString(context, portal); const char* target_str = JS_ToCString(context, target); tf_ssb_connection_t* target_connection = tf_ssb_connection_get(ssb, target_str); int32_t tunnel_request_number = tf_ssb_connection_next_request_number(target_connection); JSValue message = JS_NewObject(context); JSValue name = JS_NewArray(context); JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "tunnel")); JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "connect")); JS_SetPropertyStr(context, message, "name", name); JSValue arg_obj = JS_NewObject(context); char origin_str[k_id_base64_len] = ""; tf_ssb_connection_get_id(connection, origin_str, sizeof(origin_str)); JS_SetPropertyStr(context, arg_obj, "origin", JS_NewString(context, origin_str)); JS_SetPropertyStr(context, arg_obj, "portal", JS_NewString(context, portal_str)); JS_SetPropertyStr(context, arg_obj, "target", JS_NewString(context, target_str)); JSValue arg_array = JS_NewArray(context); JS_SetPropertyUint32(context, arg_array, 0, arg_obj); JS_SetPropertyStr(context, message, "args", arg_array); JS_SetPropertyStr(context, message, "type", JS_NewString(context, "duplex")); JSValue message_val = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t size; const char* message_str = JS_ToCStringLen(context, &size, message_val); tf_ssb_connection_rpc_send( target_connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, tunnel_request_number, (const uint8_t*)message_str, size, NULL, NULL, NULL); JS_FreeCString(context, message_str); tunnel_t* data0 = tf_malloc(sizeof(tunnel_t)); *data0 = (tunnel_t) { .connection = target_connection, .request_number = tunnel_request_number, }; tunnel_t* data1 = tf_malloc(sizeof(tunnel_t)); *data1 = (tunnel_t) { .connection = connection, .request_number = -request_number, }; tf_ssb_connection_add_request(connection, -request_number, _tf_ssb_rpc_tunnel_callback, _tf_ssb_rpc_tunnel_cleanup, data0, connection); tf_ssb_connection_add_request(target_connection, tunnel_request_number, _tf_ssb_rpc_tunnel_callback, _tf_ssb_rpc_tunnel_cleanup, data1, target_connection); JS_FreeValue(context, message_val); JS_FreeValue(context, message); JS_FreeCString(context, portal_str); JS_FreeCString(context, target_str); } else if (!JS_IsUndefined(origin) && !JS_IsUndefined(portal) && !JS_IsUndefined(target)) { const char* origin_str = JS_ToCString(context, origin); const char* portal_str = JS_ToCString(context, portal); const char* target_str = JS_ToCString(context, target); tf_ssb_connection_tunnel_create(ssb, portal_str, -request_number, origin_str); JS_FreeCString(context, origin_str); JS_FreeCString(context, portal_str); JS_FreeCString(context, target_str); } JS_FreeValue(context, origin); JS_FreeValue(context, portal); JS_FreeValue(context, target); JS_FreeValue(context, arg); JS_FreeValue(context, arg_array); } static bool _get_global_setting_bool(tf_ssb_t* ssb, const char* name, bool default_value) { bool result = default_value; JSContext* context = tf_ssb_get_context(ssb); sqlite3* db = tf_ssb_get_db(ssb); sqlite3_stmt* statement; if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_step(statement) == SQLITE_ROW) { JSValue value = JS_ParseJSON(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0), NULL); JSValue property = JS_GetPropertyStr(context, value, name); if (JS_IsBool(property)) { result = JS_ToBool(context, property); } JS_FreeValue(context, property); JS_FreeValue(context, value); } sqlite3_finalize(statement); } return result; } static void _tf_ssb_rpc_tunnel_is_room(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_get_context(ssb); JSValue response = JS_FALSE; if (_get_global_setting_bool(ssb, "room", true)) { response = JS_NewObject(context); JS_SetPropertyStr(context, response, "name", JS_NewString(context, "tilde friends tunnel")); JS_SetPropertyStr(context, response, "membership", JS_FALSE); JSValue features = JS_NewArray(context); JS_SetPropertyUint32(context, features, 0, JS_NewString(context, "tunnel")); JS_SetPropertyUint32(context, features, 1, JS_NewString(context, "room1")); JS_SetPropertyStr(context, response, "features", features); } JSValue message_val = JS_JSONStringify(context, response, JS_NULL, JS_NULL); size_t json_size = 0; const char* message_str = JS_ToCStringLen(context, &json_size, message_val); tf_ssb_connection_rpc_send( connection, flags | k_ssb_rpc_flag_end_error, -request_number, (const uint8_t*)message_str, json_size, NULL, NULL, NULL); JS_FreeCString(context, message_str); JS_FreeValue(context, message_val); JS_FreeValue(context, response); } typedef struct _blobs_get_t { char id[k_blob_id_len]; size_t received; size_t expected_size; char buffer[]; } blobs_get_t; static void _tf_ssb_rpc_connection_blobs_get_callback(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_get_context(ssb); blobs_get_t* get = user_data; if ((flags & k_ssb_rpc_mask_type) == k_ssb_rpc_flag_binary && size > 0 && get->received + size <= get->expected_size) { memcpy(get->buffer + get->received, message, size); get->received += size; } else if ((flags & k_ssb_rpc_mask_type) == k_ssb_rpc_flag_json) { bool stored = false; if (JS_ToBool(context, args)) { char id[256]; stored = tf_ssb_db_blob_store(ssb, (uint8_t*)get->buffer, get->expected_size, id, sizeof(id), NULL); } tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream | k_ssb_rpc_flag_end_error, -request_number, (const uint8_t*)(stored ? "true" : "false"), strlen(stored ? "true" : "false"), NULL, NULL, NULL); } } static void _tf_ssb_rpc_connection_blobs_get_cleanup(tf_ssb_t* ssb, void* user_data) { tf_free(user_data); } static void _tf_ssb_rpc_connection_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size) { blobs_get_t* get = tf_malloc(sizeof(blobs_get_t) + size); *get = (blobs_get_t) { .expected_size = size }; snprintf(get->id, sizeof(get->id), "%s", blob_id); memset(get->buffer, 0, size); tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_get_context(ssb); JSValue message = JS_NewObject(context); JSValue name = JS_NewArray(context); JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "blobs")); JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "get")); JS_SetPropertyStr(context, message, "name", name); JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source")); JSValue args = JS_NewArray(context); JS_SetPropertyUint32(context, args, 0, JS_NewString(context, blob_id)); JS_SetPropertyStr(context, message, "args", args); JSValue message_val = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t message_size; const char* message_str = JS_ToCStringLen(context, &message_size, message_val); tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, tf_ssb_connection_next_request_number(connection), (const uint8_t*)message_str, message_size, _tf_ssb_rpc_connection_blobs_get_callback, _tf_ssb_rpc_connection_blobs_get_cleanup, get); JS_FreeCString(context, message_str); JS_FreeValue(context, message); } static void _tf_ssb_rpc_connection_blobs_createWants_callback(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data) { tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection); if (!blob_wants) { return; } tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection); JSContext* context = tf_ssb_connection_get_context(connection); JSValue name = JS_GetPropertyStr(context, args, "name"); if (!JS_IsUndefined(name)) { /* { name: "Error" } */ JS_FreeValue(context, name); return; } JSPropertyEnum* ptab; uint32_t plen; JS_GetOwnPropertyNames(context, &ptab, &plen, args, JS_GPN_STRING_MASK); for (uint32_t i = 0; i < plen; ++i) { JSValue key = JS_AtomToString(context, ptab[i].atom); JSPropertyDescriptor desc; JSValue key_value = JS_NULL; if (JS_GetOwnProperty(context, &desc, args, ptab[i].atom) == 1) { key_value = desc.value; JS_FreeValue(context, desc.setter); JS_FreeValue(context, desc.getter); } const char* blob_id = JS_ToCString(context, key); int64_t size = 0; JS_ToInt64(context, &size, key_value); if (--blob_wants->wants_sent == 0) { _tf_ssb_rpc_request_more_blobs(connection); } if (size < 0) { size_t blob_size = 0; if (tf_ssb_db_blob_get(ssb, blob_id, NULL, &blob_size)) { JSValue message = JS_NewObject(context); JS_SetPropertyStr(context, message, blob_id, JS_NewInt64(context, blob_size)); JSValue message_val = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t message_size; const char* message_str = JS_ToCStringLen(context, &message_size, message_val); tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, -blob_wants->request_number, (const uint8_t*)message_str, message_size, NULL, NULL, NULL); JS_FreeCString(context, message_str); JS_FreeValue(context, message); } else if (size == -1LL) { JSValue message = JS_NewObject(context); JS_SetPropertyStr(context, message, blob_id, JS_NewInt64(context, -2)); JSValue message_val = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t message_size; const char* message_str = JS_ToCStringLen(context, &message_size, message_val); tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, -blob_wants->request_number, (const uint8_t*)message_str, message_size, NULL, NULL, NULL); JS_FreeCString(context, message_str); JS_FreeValue(context, message); } } else { _tf_ssb_rpc_connection_blobs_get(connection, blob_id, size); } JS_FreeCString(context, blob_id); JS_FreeValue(context, key); JS_FreeValue(context, key_value); } for (uint32_t i = 0; i < plen; ++i) { JS_FreeAtom(context, ptab[i].atom); } js_free(context, ptab); } static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data) { if (change == k_tf_ssb_change_connect) { JSContext* context = tf_ssb_get_context(ssb); JSValue message = JS_NewObject(context); JSValue name = JS_NewArray(context); JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "blobs")); JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "createWants")); JS_SetPropertyStr(context, message, "name", name); JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source")); JS_SetPropertyStr(context, message, "args", JS_NewArray(context)); JSValue message_val = JS_JSONStringify(context, message, JS_NULL, JS_NULL); size_t size; const char* message_str = JS_ToCStringLen(context, &size, message_val); tf_ssb_connection_rpc_send( connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, tf_ssb_connection_next_request_number(connection), (const uint8_t*)message_str, size, _tf_ssb_rpc_connection_blobs_createWants_callback, NULL, NULL); JS_FreeCString(context, message_str); JS_FreeValue(context, message_val); JS_FreeValue(context, message); } else if (change == k_tf_ssb_change_remove) { tf_ssb_remove_blob_want_added_callback(ssb, _tf_ssb_rpc_blob_wants_added_callback, connection); } } void tf_ssb_rpc_register(tf_ssb_t* ssb) { tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, NULL, NULL); tf_ssb_add_rpc_callback(ssb, (const char*[]) { "gossip", "ping", NULL }, _tf_ssb_rpc_gossip_ping, NULL, NULL); tf_ssb_add_rpc_callback(ssb, (const char*[]) { "blobs", "get", NULL }, _tf_ssb_rpc_blobs_get, NULL, NULL); tf_ssb_add_rpc_callback(ssb, (const char*[]) { "blobs", "has", NULL }, _tf_ssb_rpc_blobs_has, NULL, NULL); tf_ssb_add_rpc_callback(ssb, (const char*[]) { "blobs", "createWants", NULL }, _tf_ssb_rpc_blobs_createWants, NULL, NULL); tf_ssb_add_rpc_callback(ssb, (const char*[]) { "tunnel", "connect", NULL }, _tf_ssb_rpc_tunnel_connect, NULL, NULL); tf_ssb_add_rpc_callback(ssb, (const char*[]) { "tunnel", "isRoom", NULL }, _tf_ssb_rpc_tunnel_is_room, NULL, NULL); }