tildefriends/src/ssb.rpc.c

281 lines
9.7 KiB
C

#include "ssb.rpc.h"
#include "mem.h"
#include "ssb.h"
#include "ssb.db.h"
#include "util.js.h"
#include <sqlite3.h>
#include <inttypes.h>
#include <string.h>
#include <time.h>
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);
}
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);
}
void tf_ssb_rpc_register(tf_ssb_t* ssb)
{
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*[]) { "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);
}