32 Commits

Author SHA1 Message Date
88d8e60511 Some minor paranoia to appease valgrind. 2024-03-12 21:44:20 -04:00
439f07162e Disable Haiku automation tests until I find a way to automate a browser on Haiku. 2024-03-09 08:44:06 -05:00
efe2b6cbd9 A make target to run prettier. 2024-03-08 21:43:08 -05:00
0aa1ed9464 Fix a failure requesting more blobs. 2024-03-08 21:38:31 -05:00
cb94ed6a2a Some plumbing to expose the actual bound SHS port so that I can make a dynamic room app. 2024-03-07 21:03:14 -05:00
cf187ee46b Reorder things so that we only zipalign -z during a dist build. To slow for make all. 2024-03-07 20:42:08 -05:00
3e71fc20fd Prettier. 2024-03-06 21:14:09 -05:00
f3601321f7 That's all the doxygen warnings. #27 2024-03-06 21:13:16 -05:00
540059368c 11 make docs warnings left, but I'm out of time for tonight. 2024-03-06 20:57:38 -05:00
7ce89123f7 85 make docs warnings remain. 2024-03-06 12:46:27 -05:00
e3c7c86212 All but the two biggest .h files have docs. 2024-03-06 12:31:17 -05:00
794804e27f A few more .h file docs. 2024-03-05 21:17:20 -05:00
6d89c1da6e Format. 2024-03-05 20:49:30 -05:00
d059554464 Some workarounds for Haiku. uv_fs_scandir can't tell if a dirent is a file. setrlimit doesn't do anything productive for us. 2024-03-05 20:49:16 -05:00
3a392d4a9f More .h docs. 2024-03-05 12:47:58 -05:00
e3071b372a Poking at TCP binds from Haiku. 2024-03-04 21:51:27 -05:00
18bd279b0c Some progress on .h docs, and add a preliminary CONTRIBUTING.md. 2024-03-04 12:23:00 -05:00
5b93db7463 A buncha muncha cruncha .h docs. Also add vim temporary files to .gitignore. 2024-03-03 18:12:44 -05:00
5b7e5eb91b Give fts a better chance of working with jsonb messages.content. 2024-03-03 18:55:58 +00:00
78ca383e3c http.h docs. 2024-03-03 12:35:10 -05:00
c1eed9ada3 Fixed a leak in ssb.getServerIdentity(). 2024-03-03 12:20:03 -05:00
8d6feb5394 Set the root of private messages correct so that other clients show them. 2024-03-03 12:09:03 -05:00
42994f8977 Make the SSB network key configurable by command-line argument. 2024-03-02 15:01:09 -05:00
f0a871e1f8 More docs. 2024-03-01 21:18:12 -05:00
a710c30572 Fix apps for jsonb. 2024-02-29 19:26:56 -05:00
c991763b00 tests.h and tlscontext.js.h docs. 2024-02-28 21:18:59 -05:00
72dae14f87 Android NDK update. 2024-02-28 21:04:22 -05:00
5800340762 Fix up some more jsonb references. 2024-02-28 20:41:27 -05:00
c5f5adcac6 Missed some more jsonb messages.content use issues. 2024-02-28 20:31:25 -05:00
591642efb3 Convert messages.content to JSONB. This is a very disruptive change. 2024-02-28 20:01:52 -05:00
6182ffa1d4 Docs for tls.h and trace.h. 2024-02-28 19:12:41 -05:00
402a898d96 Let's start working on 0.0.17. 2024-02-28 18:47:21 -05:00
57 changed files with 2264 additions and 156 deletions

8
.gitignore vendored
View File

@ -1,8 +1,10 @@
**/node_modules
.keys
.zsign_cache/
db.*
deps/ios_toolchain/
deps/openssl/
dist/
.keys
**/node_modules
out
*.swo
*.swp
.zsign_cache/

37
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,37 @@
# Contributing to Tilde Friends
Thank you for your interest in Tilde Friends.
Above all, Tilde Friends aims to be a fun, safe place to play. When that is at
odds with the course of development, we will work through it with respectful
communication.
## How can I contribute?
The nature of Tilde Friends makes for a wide range of ways to contribute
- Just use it. Really, just kicking the tires will probably shake out issues
in useful ways at this point.
- Report and comment on bugs: https://dev.tildefriends.net/issues.
- Make apps. You don't need my permission to make and share apps with Tilde
Friends. I hope that an ecosystem of good apps grows outside of this
repository. If you want to recreate better versions of the stock apps, just
do it. If you make a better ssb app or whatever and drop me a line however
is most convenient for you, I will probably take a look and consider
replacing the stock one with it.
- Write about it. Docs in the git repository, blog posts, private messages to
me with ideas...really there is no wrong answer. Just make some noise, and
I'll do my best to incorporate or otherwise link your feedback and make the
most of it.
- Write C code in the git repository. I'm really striving for it to be the
case that other people don't really need to meddle in there, but if you can
help out, I will gladly review your pull requests via
https://dev.tildefriends.net/pulls.
## Best practices
- The C code is formatted with clang-format. Run `make format`.
- The rest is formatted with prettier. Run `npm run prettier`.
- We strive to have code compile on all platforms with no warnings and run with
no sanitizer issues.
- There are tests. Run `out/debug/tildefriends test`.

View File

@ -3,9 +3,9 @@
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
VERSION_CODE := 16
VERSION_NUMBER := 0.0.16
VERSION_NAME := Now with 38% more process.
VERSION_CODE := 17
VERSION_NUMBER := 0.0.17-wip
VERSION_NAME := Please enjoy responsibly.
PROJECT = tildefriends
BUILD_DIR ?= out
@ -55,7 +55,7 @@ CFLAGS += \
ANDROID_BUILD_TOOLS := $(ANDROID_SDK)/build-tools/34.0.0
ANDROID_PLATFORM := $(ANDROID_SDK)/platforms/android-34
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.1.10909125
ANDROID_NDK ?= $(ANDROID_SDK)/ndk/26.2.11394342
ANDROID_MIN_SDK_VERSION := 24
ANDROID_TARGET_SDK_VERSION := 34
@ -726,7 +726,7 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
@cp out/apk/classes.dex out/apk-arm-$(BUILD_TYPE)/
@cd out/apk-arm-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q $(RAW_FILES)
@rm -f $@ && $(ANDROID_BUILD_TOOLS)/zipalign -z 4 $@.zip $@
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/apk/TildeFriends-x86-%.unsigned.apk:
@mkdir -p $(dir $@) out/apk-x86-$(BUILD_TYPE)/lib/x86_64/ out/apk-x86-$(BUILD_TYPE)/lib/x86/
@ -739,13 +739,18 @@ out/apk/TildeFriends-x86-%.unsigned.apk:
@cp out/apk/classes.dex out/apk-x86-$(BUILD_TYPE)/
@cd out/apk-x86-$(BUILD_TYPE) && zip -u ../../$@.zip -q -9 -r . && cd ../../
@zip -u $@.zip -q $(RAW_FILES)
@rm -f $@ && $(ANDROID_BUILD_TOOLS)/zipalign -z 4 $@.zip $@
@$(ANDROID_BUILD_TOOLS)/zipalign -f 4 $@.zip $@
out/%.apk: out/apk/%.unsigned.apk
@echo "[apksigner] $(notdir $@)"
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $<
release-apk: out/TildeFriends-arm-release.apk out/TildeFriends-x86-release.apk
out/%.zopfli.apk: out/%.apk
@echo "[zopfli] $(notdir $@)"
$(ANDROID_BUILD_TOOLS)/zipalign -f -z 4 $< $@.zopfli
@$(ANDROID_BUILD_TOOLS)/apksigner sign --ks .keys/android.jks --ks-key-alias androidKey --ks-pass pass:android --key-pass pass:android --min-sdk-version $(ANDROID_MIN_SDK_VERSION) --out $@ $@.zopfli
release-apk: out/TildeFriends-arm-release.zopfli.apk out/TildeFriends-x86-release.zopfli.apk
.PHONY: release-apk
releaseapkgo: out/TildeFriends-arm-release.apk
@ -859,9 +864,9 @@ dist: release-apk iosrelease-ipa
--exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
@echo "[cp] TildeFriends-x86-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-x86-release.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@cp out/TildeFriends-x86-release.zopfli.apk dist/TildeFriends-x86-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-arm-$(VERSION_NUMBER).apk"
@cp out/TildeFriends-arm-release.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
@cp out/TildeFriends-arm-release.zopfli.apk dist/TildeFriends-arm-$(VERSION_NUMBER).apk
@echo "[cp] TildeFriends-$(VERSION_NUMBER).ipa"
@cp out/tildefriends-release.ipa dist/TildeFriends-$(VERSION_NUMBER).ipa
.PHONY: dist
@ -877,6 +882,10 @@ format:
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
.PHONY: format
prettier:
@npm run prettier
.PHONY: prettier
docs:
@doxygen
.PHONY: docs

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "💻",
"previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256"
"previous": "&icsPplXHgmpkbNWyo/WTvRdT/A8BXxW4lJixOtP4ueQ=.sha256"
}

View File

@ -28,10 +28,10 @@ async function fetch_shared_apps() {
await ssb.sqlAsync(
`
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts('"application/tildefriends"')
JOIN messages ON messages.rowid = messages_fts.rowid
ORDER BY timestamp
ORDER BY messages.timestamp
`,
[],
function (row) {

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📝",
"previous": "&2hdIDbBrAg63T2X1MzdGSF7yiqHvlnfF0PnInQLp0DA=.sha256"
"previous": "&b//KqE4Vx6kOSBRODK1p/8wjOLKZJ+CBB5IkaBt5YsM=.sha256"
}

View File

@ -1,4 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "👟"
"emoji": "👟",
"previous": "&zhV2BKLLZ6aG3HsVyRTs9ESLxE2lb0e7TDE7PobnyNY=.sha256"
}

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🐌",
"previous": "&DUxMMCJcuhm6S9jg/eKgEyWodkITu6Tg9g5I5wgLWFU=.sha256"
"previous": "&Xs1X5TzLCk6KVr+5IDc80JAHYxJyoD10cXKBUYpFqWQ=.sha256"
}

View File

@ -107,7 +107,7 @@ class TfElement extends LitElement {
let abouts = await tfrpc.rpc.query(
`
SELECT
messages.*
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM
messages,
json_each(?1) AS following
@ -118,7 +118,7 @@ class TfElement extends LitElement {
json_extract(messages.content, '$.type') = 'about'
UNION
SELECT
messages.*
messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM
messages,
json_each(?2) AS following
@ -158,7 +158,7 @@ class TfElement extends LitElement {
async fetch_new_message(id) {
let messages = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.id = ?
@ -221,7 +221,7 @@ class TfElement extends LitElement {
this.tags = await tfrpc.rpc.query(
`
WITH
recent AS (SELECT id, content FROM messages
recent AS (SELECT id, json(content) AS content FROM messages
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
ORDER BY timestamp DESC LIMIT 1024),
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag

View File

@ -262,7 +262,7 @@ class TfComposeElement extends LitElement {
try {
let rows = await tfrpc.rpc.query(
`
SELECT messages.content FROM messages_fts(?)
SELECT json(messages.content) FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
WHERE messages.content LIKE ?
ORDER BY timestamp DESC LIMIT 10

View File

@ -494,7 +494,7 @@ ${JSON.stringify(mention, null, 2)}</pre
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${this.message.content.root || this.message.id}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}
@ -681,7 +681,7 @@ ${JSON.stringify(content, null, 2)}</pre
<tf-compose
whoami=${this.whoami}
.users=${this.users}
root=${this.message.content.root || this.message.id}
root=${content.root || this.message.id}
branch=${this.message.id}
.drafts=${this.drafts}
@tf-discard=${this.discard_reply}

View File

@ -29,7 +29,7 @@ class TfTabMentionsElement extends LitElement {
console.log('Loading...', this.whoami);
let results = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value

View File

@ -33,12 +33,12 @@ class TfTabNewsFeedElement extends LitElement {
if (this.hash.startsWith('#@')) {
let r = await tfrpc.rpc.query(
`
WITH mine AS (SELECT messages.*
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
WHERE messages.author = ?
ORDER BY sequence DESC
LIMIT 20)
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM mine
JOIN messages_refs ON mine.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
@ -51,11 +51,11 @@ class TfTabNewsFeedElement extends LitElement {
} else if (this.hash.startsWith('#%')) {
return await tfrpc.rpc.query(
`
SELECT messages.*
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages
WHERE id = ?1
UNION
SELECT messages.*
SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages JOIN messages_refs
ON messages.id = messages_refs.message
WHERE messages_refs.ref = ?1
@ -69,17 +69,17 @@ class TfTabNewsFeedElement extends LitElement {
promises.push(
tfrpc.rpc.query(
`
WITH news AS (SELECT messages.*
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ? AND messages.timestamp < ?
ORDER BY messages.timestamp DESC)
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
@ -107,18 +107,18 @@ class TfTabNewsFeedElement extends LitElement {
this.start_time = last_start_time - 24 * 60 * 60 * 1000;
let more = await tfrpc.rpc.query(
`
WITH news AS (SELECT messages.*
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ?
AND messages.timestamp <= ?
ORDER BY messages.timestamp DESC)
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id

View File

@ -35,7 +35,7 @@ class TfTabSearchElement extends LitElement {
await tfrpc.rpc.setHash('#q=' + encodeURIComponent(query));
let results = await tfrpc.rpc.query(
`
SELECT messages.*
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages_fts(?)
JOIN messages ON messages.rowid = messages_fts.rowid
JOIN json_each(?) AS following ON messages.author = following.value

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📝",
"previous": "&/wl8HE2jZShRXTYEVYRrK3pjHwi41Wbxl9HoSJaQP6Y=.sha256"
"previous": "&DnfuAUGzzalSh9NgZXnzDc9Ru5aM0omfRJ4h27jYw4k=.sha256"
}

View File

@ -578,6 +578,7 @@ async function getProcessBlob(blobId, key, options) {
imports.ssb = Object.fromEntries(
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
);
imports.ssb.port = tildefriends.ssb_port;
imports.ssb.createIdentity = function () {
if (
process.credentials &&

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unprompted.tildefriends"
android:versionCode="16"
android:versionName="0.0.16">
android:versionCode="17"
android:versionName="0.0.17-wip">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

View File

@ -7,8 +7,13 @@
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register the bcrypt script interface.
** @param context The JS context.
*/
void tf_bcrypt_register(JSContext* context);
/** @} */

View File

@ -11,7 +11,23 @@
#include <stddef.h>
#include <stdint.h>
/**
** Convert a key from bytes to words.
** @param bytes A raw binary representation of a key.
** @param bytes_size The size of bytes.
** @param[out] out_words A human-readable English word representation of a key.
** @param words_size The size of the out_words buffer.
** @return True if the key was successfully converted.
*/
bool tf_bip39_bytes_to_words(const uint8_t* bytes, size_t bytes_size, char* out_words, size_t words_size);
/**
** Convert a key from words to bytes.
** @param words A space-separated list of English words forming a key.
** @param[out] out_bytes A buffer to receive the raw binary form of the key.
** @param bytes_size The size of the out_bytes buffer.
** @return True if the key was successfully converted.
*/
bool tf_bip39_words_to_bytes(const char* words, uint8_t* out_bytes, size_t bytes_size);
/** @} */

View File

@ -10,6 +10,8 @@ enum
{
k_bip39_words_count = 2048
};
/** An array of words used for BIP39-encoding a key. */
extern const char* k_bip39_words[k_bip39_words_count];
/** @} */

View File

@ -6,8 +6,13 @@
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/**
** Register the database script interface.
** @param context The JS context.
*/
void tf_database_register(JSContext* context);
/** @} */

View File

@ -234,6 +234,7 @@ static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int ar
},
.size = k_file_read_max,
};
memset(req + 1, 0, k_file_read_max);
int result = uv_fs_open(tf_task_get_loop(task), &req->fs, file_name, UV_FS_O_RDONLY, 0, _file_read_open_callback);
if (result < 0)
{

View File

@ -8,12 +8,33 @@
#include <uv.h>
/** A JS context. */
typedef struct JSContext JSContext;
/** A task. */
typedef struct _tf_task_t tf_task_t;
/**
** Register the file script interface.
** @param context The JS context.
*/
void tf_file_register(JSContext* context);
/**
** Asynchronously stat() a file.
** @param task The running task.
** @param path The path to the file to stat().
** @param callback A function that will be called with the stat result.
** @param user_data User data that will be passed to the callback.
*/
void tf_file_stat(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const uv_stat_t* stat, void* user_data), void* user_data);
/**
** Asynchronously read a file's contents.
** @param task The running task.
** @param path The path to the file.
** @param callback A callback that will be called with the file contents.
** @param user_data User data that will be provided to the callback.
*/
void tf_file_read(tf_task_t* task, const char* path, void (*callback)(tf_task_t* task, const char* path, int result, const void* data, void* user_data), void* user_data);
/** @} */

View File

@ -619,15 +619,28 @@ int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cle
if (r == 0)
{
#if defined(__HAIKU__)
/*
** Binding to IPv6 here fails with an odd error, and the socket
** becomes unusable. Since we probably want localhost only
** on this single-user OS, let's just assume IPv4.
*/
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr = { .s_addr = INADDR_ANY },
.sin_port = ntohs(port),
};
#else
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_addr = IN6ADDR_ANY_INIT,
.sin6_port = ntohs(port),
};
#endif
r = uv_tcp_bind(&listener->tcp, (struct sockaddr*)&addr, 0);
if (r)
{
tf_printf("uv_tcp_bind: %s\n", uv_strerror(r));
tf_printf("%s:%d: uv_tcp_bind: %s\n", __FILE__, __LINE__, uv_strerror(r));
}
}

View File

@ -14,38 +14,88 @@
#include <stdbool.h>
#include <stddef.h>
/** An HTTP connection. */
typedef struct _tf_http_connection_t tf_http_connection_t;
/** An HTTP request. */
typedef struct _tf_http_request_t tf_http_request_t;
/** An HTTP instance. */
typedef struct _tf_http_t tf_http_t;
/** A TLS context. */
typedef struct _tf_tls_context_t tf_tls_context_t;
/** A trace instance. */
typedef struct _tf_trace_t tf_trace_t;
/** An event loop. */
typedef struct uv_loop_s uv_loop_t;
/**
** A callback called when receiving a websocket message.
** @param request The HTTP request.
** @param op_code The type of websocket message.
** @param data The payload.
** @param size The size of the payload in bytes.
*/
typedef void(tf_http_message_callback)(tf_http_request_t* request, int op_code, const void* data, size_t size);
/**
** A callback called when a request closes.
** @param request The HTTP request.
*/
typedef void(tf_http_close_callback)(tf_http_request_t* request);
/**
** A callback called when an HTTP request is received.
** @param request The HTTP request.
*/
typedef void(tf_http_callback_t)(tf_http_request_t* request);
/**
** A callback called when the HTTP instance is destroyed.
** @param user_data User data provided with the callback.
*/
typedef void(tf_http_cleanup_t)(void* user_data);
/**
** An HTTP request.
*/
typedef struct _tf_http_request_t
{
/** The HTTP instance this request belongs to. */
tf_http_t* http;
/** The HTTP connection associated with this request. */
tf_http_connection_t* connection;
/** True if this is an HTTPS session. */
bool is_tls;
/** The HTTP method of the request (GET/POST/...). */
const char* method;
/** The HTTP request path. */
const char* path;
/** The raw HTTP query string. */
const char* query;
/** The HTTP request body. */
void* body;
/** The length of the HTTP request body. */
size_t content_length;
/** Header storage. Can also be accessed with tf_http_request_get_header(). */
struct phr_header* headers;
/** The number of headers stored. */
int headers_count;
/** A callback to be called when receiving a websocket message. */
tf_http_message_callback* on_message;
/** A callback to be called when the connection is closed. */
tf_http_close_callback* on_close;
/** Extra storage for user data. */
void* context;
/** User data available to callbacks. */
void* user_data;
/** The number of times tf_http_request_ref() has been called without tf_http_request_unref(). */
int ref_count;
} tf_http_request_t;
typedef void(tf_http_callback_t)(tf_http_request_t* request);
typedef void(tf_http_cleanup_t)(void* user_data);
/**
** Create an HTTP server using the given libuv loop.
** @param loop A libuv loop to use.
@ -53,22 +103,120 @@ typedef void(tf_http_cleanup_t)(void* user_data);
*/
tf_http_t* tf_http_create(uv_loop_t* loop);
/**
** Register a trace instance with the HTTP instance to record the begin and end
** time of request handlers.
** @param http The HTTP instance to trace.
** @param trace The trace instance to use, or NULL to disable.
*/
void tf_http_set_trace(tf_http_t* http, tf_trace_t* trace);
/**
** Begin listening for HTTP requests on a new port. May be called multiple
** times to listen on multiple ports.
** @param http The HTTP instance.
** @param port The port on which to listen, or 0 to assign a free port.
** @param tls An optional TLS context to use for HTTPS requests.
** @param cleanup A function called when the HTTP instance is being cleaned up.
** @param user_data User data passed to the cleanup callback.
** @return The port number on which the HTTP instance is now listening.
*/
int tf_http_listen(tf_http_t* http, int port, tf_tls_context_t* tls, tf_http_cleanup_t* cleanup, void* user_data);
/**
** Add an HTTP request handler.
** @param http The HTTP instance.
** @param pattern The prefix that the path of incoming requests must match to use this handler.
** @param callback The function to be called to handle the request.
** @param cleanup A function to be called when the request is complete.
** @param user_data User data to pass to the callbacks.
*/
void tf_http_add_handler(tf_http_t* http, const char* pattern, tf_http_callback_t* callback, tf_http_cleanup_t* cleanup, void* user_data);
/**
** Respond to an HTTP request.
** @param request The request.
** @param status The HTTP status with which to respond.
** @param headers Headers to include in the response. Content-Length will be added internally.
** @param headers_count The number of headers. The headers array must have
** twice as many entries as this value, since it is both keys and values.
** @param body The response body or NULL.
** @param content_length The length of the response body.
*/
void tf_http_respond(tf_http_request_t* request, int status, const char** headers, int headers_count, const void* body, size_t content_length);
/**
** Get the request body content.
** @param request An incoming request.
** @param out_data The request body content. Valid until the request is completed.
** @return The size of the request body content.
*/
size_t tf_http_get_body(const tf_http_request_t* request, const void** out_data);
/**
** Destroy an HTTP instance.
** @param http The HTTP instance.
*/
void tf_http_destroy(tf_http_t* http);
/**
** Set instance-wide HTTP user data and a callback to clean it up.
** @param http The HTTP instance.
** @param user_data The user data.
** @param cleanup The cleanup callback.
*/
void tf_http_set_user_data(tf_http_t* http, void* user_data, tf_http_cleanup_t* cleanup);
/**
** Get HTTP instance user data previous set with tf_http_set_user_data().
** @param http The HTTP instance.
** @return The user data.
*/
void* tf_http_get_user_data(tf_http_t* http);
/**
** Increment a requests refcount to keep it around after its callback.
** tf_http_respond() may be called at a later time, and tf_http_request_unref()
** must eventually be called in order to not leak the request.
** @param request The request to retain.
*/
void tf_http_request_ref(tf_http_request_t* request);
/**
** Decrement a requests refcount. tf_http_request_ref() must have been previously called.
** @param request The request.
*/
void tf_http_request_unref(tf_http_request_t* request);
/**
** Get the value of a header from an HTTP request.
** @param request The request.
** @param name The header key. Matched case insensitively.
** @return The value or NULL.
*/
const char* tf_http_request_get_header(tf_http_request_t* request, const char* name);
/**
** Send a websocket message.
** @param request The HTTP request which was previously updated to a websocket
** session with tf_http_request_websocket_upgrade().
** @param data The message data.
** @param size The size of data.
*/
void tf_http_request_send(tf_http_request_t* request, const void* data, size_t size);
/**
** Upgrade an HTTP request to a websocket session.
** @param request The HTTP request.
*/
void tf_http_request_websocket_upgrade(tf_http_request_t* request);
/**
** Get standard HTTP status text for common HTTP statuses. 200 = "OK", 404 =
** "File not found", etc.
** @param status The HTTP status.
** @return The status text or NULL.
*/
const char* tf_http_status_text(int status);
/** @} */

View File

@ -13,6 +13,12 @@
#include "quickjs.h"
/**
** Register the HTTP script interface. Also registers a number of built-in
** request handlers. An ongoing project is to move the JS request handlers
** into C, after which point this will only do the latter.
** @param context The JS context.
*/
void tf_httpd_register(JSContext* context);
/** @} */

View File

@ -5,6 +5,11 @@
** @{
*/
/**
** Log a message using printf-style formatting. Tries to use appropriate
** platform-specific functionality where necessary to make sure output goes
** somewhere that it can be seen.
*/
#if defined(__ANDROID__)
#include <android/log.h>
#define tf_printf(...) __android_log_print(ANDROID_LOG_INFO, "tildefriends", __VA_ARGS__)

View File

@ -166,7 +166,7 @@ static int _tf_command_import(const char* file, int argc, char* argv[])
return EXIT_FAILURE;
}
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path);
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
if (optind < argc)
{
for (int i = optind; i < argc; i++)
@ -232,7 +232,7 @@ static int _tf_command_export(const char* file, int argc, char* argv[])
return EXIT_FAILURE;
}
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path);
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL);
if (optind < argc)
{
for (int i = optind; i < argc; i++)
@ -270,6 +270,7 @@ static int _tf_command_export(const char* file, int argc, char* argv[])
typedef struct tf_run_args_t
{
const char* script;
const char* network_key;
int ssb_port;
int http_port;
int https_port;
@ -296,6 +297,7 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
tf_task_set_trusted(task, true);
tf_printf("setting zip path to %s\n", args->zip);
tf_task_set_zip_path(task, args->zip);
tf_task_set_ssb_network_key(task, args->network_key);
tf_task_set_ssb_port(task, args->ssb_port ? args->ssb_port + index : 0);
tf_task_set_http_port(task, args->http_port ? args->http_port + index : 0);
tf_task_set_https_port(task, args->https_port ? args->https_port + index : 0);
@ -419,6 +421,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
static const struct option k_options[] = {
{ "script", required_argument, NULL, 's' },
{ "ssb-port", required_argument, NULL, 'b' },
{ "ssb-network-key", required_argument, NULL, 'k' },
{ "http-port", required_argument, NULL, 'p' },
{ "https-port", required_argument, NULL, 'q' },
{ "db-path", required_argument, NULL, 'd' },
@ -429,7 +432,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
};
int c = getopt_long(argc, argv, "s:b:p:q:d:n:a:oz:vh", k_options, NULL);
int c = getopt_long(argc, argv, "s:b:k:p:q:d:n:a:oz:vh", k_options, NULL);
if (c == -1)
{
break;
@ -445,6 +448,9 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
case 's':
args.script = optarg;
break;
case 'k':
args.network_key = optarg;
break;
case 'b':
args.ssb_port = atoi(optarg);
break;

View File

@ -12,43 +12,138 @@
#include <stddef.h>
#include <stdint.h>
/** JS malloc functions. */
typedef struct JSMallocFunctions JSMallocFunctions;
/**
** Do early setup for memory tracking.
** @param tracking Whether tracking will be enabled, which adds a time and
** memory cost of storing stack traces for every allocation.
*/
void tf_mem_startup(bool tracking);
/**
** Clean up the memory system.
*/
void tf_mem_shutdown();
/**
** Register a custom allocator with libuv.
*/
void tf_mem_replace_uv_allocator();
/**
** Get the number of bytes currently allocated by libuv.
** @return The allocated size in bytes.
*/
size_t tf_mem_get_uv_malloc_size();
/**
** Register a custom allocator with OpenSSL.
*/
void tf_mem_replace_tls_allocator();
/**
** Get the number of bytes currently allocated by OpenSSL.
** @return The allocated size in bytes.
*/
size_t tf_mem_get_tls_malloc_size();
/**
** Register a custom allocator with SQLite.
*/
void tf_mem_replace_sqlite_allocator();
/**
** Get the number of bytes currently allocated by SQLite.
** @return The allocated size in bytes.
*/
size_t tf_mem_get_sqlite_malloc_size();
/**
** Get the number of bytes currently allocated by tf_malloc().
** @return The allocated size in bytes.
*/
size_t tf_mem_get_tf_malloc_size();
/**
** Allocate memory. Like malloc() but with more tracking.
** @param size The number of bytes to allocate.
** @return The allocated memory.
*/
void* tf_malloc(size_t size);
/**
** Reallocate memory. Like realloc() but with more tracking.
** @param ptr The previously allocated memory or NULL.
** @param size The new desired size.
** @return The new allocation.
*/
void* tf_realloc(void* ptr, size_t size);
/**
** Free memory allocated by tf_malloc() or tf_realloc().
** @param ptr The allocation.
*/
void tf_free(void* ptr);
/**
** Duplicate a string.
** @param string The string to copy.
** @return The newly allocated string. Free with tf_free().
*/
char* tf_strdup(const char* string);
/**
** Resize a vector. Like tf_realloc() but overallocatess and prefers not to
** shrink in order to speed up repeated growth.
** @param ptr The allocation to resize.
** @param size The desired new size.
** @return The new allocation.
*/
void* tf_resize_vec(void* ptr, size_t size);
/**
** Populate a struct with custom JS allocation functions.
** @param[out] out The struct to receive the functions.
*/
void tf_get_js_malloc_functions(JSMallocFunctions* out);
/**
** Get the number of bytes currently allocated by JS allocators.
** @return The allocated size in bytes.
*/
size_t tf_mem_get_js_malloc_size();
/**
** Call a function for every live memory allocation.
** @param callback The callback to call.
** @param user_data User data to pass to the callback.
*/
void tf_mem_walk_allocations(void (*callback)(void* ptr, size_t size, int frames_count, void* const* frames, void* user_data), void* user_data);
/**
** Information about a memory allocation.
*/
typedef struct _tf_mem_allocation_t
{
/** A hash of the callstack used for determining uniqueness. */
uint32_t stack_hash;
/** The number of instances of this allocation. */
int count;
/** The size of this allocation. */
size_t size;
/** The callstack from which this allocation was made. */
void* frames[32];
/** The number of frames in the callstack. */
int frames_count;
} tf_mem_allocation_t;
/**
** Generate a list of live allocations.
** @param[out] out_count The number of allocations returned.
** @return An array of allocation information. Free with tf_free().
*/
tf_mem_allocation_t* tf_mem_summarize_allocations(int* out_count);
/** @} */

View File

@ -8,19 +8,67 @@
#include <stddef.h>
/** A handle to a UNIX-style pipe. */
typedef struct uv_pipe_s uv_pipe_t;
/** A packet stream instance. */
typedef struct _tf_packetstream_t tf_packetstream_t;
/**
** A function called when a packet is received.
** @param packet_type The type of the packet as specified by the sender.
** @param begin The beginning of the data.
** @param length The length of the data.
** @param user_data User data.
*/
typedef void(tf_packetstream_onreceive_t)(int packet_type, const char* begin, size_t length, void* user_data);
/**
** Create a packet stream.
** @return The packet stream.
*/
tf_packetstream_t* tf_packetstream_create();
/**
** Destroy a packet stream.
** @param stream The packet stream.
*/
void tf_packetstream_destroy(tf_packetstream_t* stream);
/**
** Start a packet stream receiving.
** @param stream The packet stream.
*/
void tf_packetstream_start(tf_packetstream_t* stream);
/**
** Send a discrete packet over a packet stream.
** @param stream The packet stream.
** @param packet_type The type of the packet.
** @param begin The start of the packet data.
** @param length The length of the packet data.
*/
void tf_packetstream_send(tf_packetstream_t* stream, int packet_type, const char* begin, size_t length);
/**
** Register the callback to be called when a message is received.
** @param stream The packet stream.
** @param callback The callback function.
** @param user_data User data to pass to the callback.
*/
void tf_packetstream_set_on_receive(tf_packetstream_t* stream, tf_packetstream_onreceive_t* callback, void* user_data);
/**
** Close a packet stream.
** @param stream The packet stream.
*/
void tf_packetstream_close(tf_packetstream_t* stream);
/**
** Get the internal pipe object from a packet stream.
** @param stream The packet stream.
** @return The pipe.
*/
uv_pipe_t* tf_packetstream_get_pipe(tf_packetstream_t* stream);
/** @} */

View File

@ -9,10 +9,29 @@
#include "quickjs.h"
/** A task. */
typedef struct _tf_task_t tf_task_t;
/** A handle to a remote task. */
typedef struct _tf_taskstub_t tf_taskstub_t;
/**
** Store JS data in a binary blob.
** @param task The calling task.
** @param to The handle to the task to which the data will be sent.
** @param[out] out_buffer Populated with the stored data.
** @param[out] out_size Populated with the size of out_data.
** @param value The JS value to store.
*/
void tf_serialize_store(tf_task_t* task, tf_taskstub_t* to, void** out_buffer, size_t* out_size, JSValue value);
/**
** Retrieve JS data from a binary blob.
** @param task The calling task.
** @param from The handle to the task from which the data was received.
** @param buffer The data.
** @param size The size of the data.
** @return The received JS data.
*/
JSValue tf_serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size);
/** @} */

View File

@ -8,8 +8,23 @@
#include "quickjs.h"
/**
** Register the socket script interface.
** @param context The JS context.
** @return The Socket constructor.
*/
JSValue tf_socket_register(JSContext* context);
/**
** Get the number of active socket objects.
** @return The count.
*/
int tf_socket_get_count();
/**
** Get the number of connected socket objects.
** @return the count.
*/
int tf_socket_get_open_count();
/** @} */

124
src/ssb.c
View File

@ -42,8 +42,7 @@ static_assert(k_id_base64_len == sodium_base64_ENCODED_LEN(9 + crypto_box_PUBLIC
static_assert(k_id_bin_len == crypto_box_PUBLICKEYBYTES, "k_id_bin_len");
static_assert(k_blob_id_len == (sodium_base64_ENCODED_LEN(crypto_hash_sha256_BYTES, sodium_base64_VARIANT_ORIGINAL) + 8), "k_blob_id_len");
const uint8_t k_ssb_network[] = { 0xd4, 0xa1, 0xcb, 0x88, 0xa6, 0x6f, 0x02, 0xf8, 0xdb, 0x63, 0x5c, 0xe2, 0x64, 0x41, 0xcc, 0x5d, 0xac, 0x1b, 0x08, 0x42, 0x0c, 0xea, 0xac, 0x23,
0x08, 0x39, 0xb7, 0x55, 0x84, 0x5a, 0x9f, 0xfb };
const char* k_ssb_network_string = "d4a1cb88a66f02f8db635ce26441cc5dac1b08420ceaac230839b755845a9ffb";
const char* k_ssb_type_names[] = {
"binary",
@ -203,6 +202,8 @@ typedef struct _tf_ssb_t
uv_timer_t trace_timer;
uv_tcp_t server;
uint8_t network_key[32];
uint8_t pub[crypto_sign_PUBLICKEYBYTES];
uint8_t priv[crypto_sign_SECRETKEYBYTES];
@ -489,7 +490,7 @@ static void _tf_ssb_write(tf_ssb_connection_t* connection, void* data, size_t si
static void _tf_ssb_connection_send_identity(tf_ssb_connection_t* connection, uint8_t* hmac, uint8_t* pubkey)
{
memcpy(connection->serverepub, pubkey, sizeof(connection->serverepub));
if (crypto_auth_hmacsha512256_verify(hmac, connection->serverepub, 32, k_ssb_network) != 0)
if (crypto_auth_hmacsha512256_verify(hmac, connection->serverepub, 32, connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "invalid server hello");
return;
@ -519,10 +520,10 @@ static void _tf_ssb_connection_send_identity(tf_ssb_connection_t* connection, ui
uint8_t hash[crypto_hash_sha256_BYTES];
crypto_hash_sha256(hash, shared_secret_ab, sizeof(shared_secret_ab));
uint8_t msg[sizeof(k_ssb_network) + sizeof(connection->serverpub) + crypto_hash_sha256_BYTES];
memcpy(msg, k_ssb_network, sizeof(k_ssb_network));
memcpy(msg + sizeof(k_ssb_network), connection->serverpub, sizeof(connection->serverpub));
memcpy(msg + sizeof(k_ssb_network) + sizeof(connection->serverpub), hash, sizeof(hash));
uint8_t msg[sizeof(connection->ssb->network_key) + sizeof(connection->serverpub) + crypto_hash_sha256_BYTES];
memcpy(msg, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(msg + sizeof(connection->ssb->network_key), connection->serverpub, sizeof(connection->serverpub));
memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->serverpub), hash, sizeof(hash));
unsigned long long siglen;
if (crypto_sign_detached(connection->detached_signature_A, &siglen, msg, sizeof(msg), connection->ssb->priv) != 0)
@ -536,10 +537,10 @@ static void _tf_ssb_connection_send_identity(tf_ssb_connection_t* connection, ui
memcpy(tosend + sizeof(connection->detached_signature_A), connection->ssb->pub, sizeof(connection->ssb->pub));
uint8_t nonce[crypto_secretbox_NONCEBYTES] = { 0 };
uint8_t tohash[sizeof(k_ssb_network) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB)];
memcpy(tohash, k_ssb_network, sizeof(k_ssb_network));
memcpy(tohash + sizeof(k_ssb_network), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(tohash + sizeof(k_ssb_network) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
uint8_t tohash[sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB)];
memcpy(tohash, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(tohash + sizeof(connection->ssb->network_key), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(tohash + sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
uint8_t hash2[crypto_hash_sha256_BYTES];
crypto_hash_sha256(hash2, tohash, sizeof(tohash));
@ -1136,11 +1137,11 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection,
return;
}
uint8_t tohash[sizeof(k_ssb_network) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB) + sizeof(shared_secret_Ab)];
memcpy(tohash, k_ssb_network, sizeof(k_ssb_network));
memcpy(tohash + sizeof(k_ssb_network), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(tohash + sizeof(k_ssb_network) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
memcpy(tohash + sizeof(k_ssb_network) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB), shared_secret_Ab, sizeof(shared_secret_Ab));
uint8_t tohash[sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB) + sizeof(shared_secret_Ab)];
memcpy(tohash, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(tohash + sizeof(connection->ssb->network_key), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(tohash + sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
memcpy(tohash + sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB), shared_secret_Ab, sizeof(shared_secret_Ab));
uint8_t hash2[crypto_hash_sha256_BYTES];
crypto_hash_sha256(hash2, tohash, sizeof(tohash));
@ -1164,11 +1165,11 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection,
uint8_t hash3[crypto_hash_sha256_BYTES];
crypto_hash_sha256(hash3, shared_secret_ab, sizeof(shared_secret_ab));
uint8_t msg[sizeof(k_ssb_network) + sizeof(connection->detached_signature_A) + sizeof(connection->ssb->pub) + sizeof(hash3)];
memcpy(msg, k_ssb_network, sizeof(k_ssb_network));
memcpy(msg + sizeof(k_ssb_network), connection->detached_signature_A, sizeof(connection->detached_signature_A));
memcpy(msg + sizeof(k_ssb_network) + sizeof(connection->detached_signature_A), connection->ssb->pub, sizeof(connection->ssb->pub));
memcpy(msg + sizeof(k_ssb_network) + sizeof(connection->detached_signature_A) + sizeof(connection->ssb->pub), hash3, sizeof(hash3));
uint8_t msg[sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A) + sizeof(connection->ssb->pub) + sizeof(hash3)];
memcpy(msg, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(msg + sizeof(connection->ssb->network_key), connection->detached_signature_A, sizeof(connection->detached_signature_A));
memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A), connection->ssb->pub, sizeof(connection->ssb->pub));
memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->detached_signature_A) + sizeof(connection->ssb->pub), hash3, sizeof(hash3));
if (crypto_sign_verify_detached(m, msg, sizeof(msg), connection->serverpub) != 0)
{
_tf_ssb_connection_close(connection, "unable to verify server identity");
@ -1176,7 +1177,7 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection,
}
uint8_t nonce2[crypto_auth_hmacsha512256_BYTES];
if (crypto_auth_hmacsha512256(nonce2, connection->epub, sizeof(connection->epub), k_ssb_network) != 0)
if (crypto_auth_hmacsha512256(nonce2, connection->epub, sizeof(connection->epub), connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "unable to compute client recv nonce");
return;
@ -1184,7 +1185,7 @@ static void _tf_ssb_connection_verify_identity(tf_ssb_connection_t* connection,
memcpy(connection->nonce, nonce2, sizeof(connection->nonce));
uint8_t nonce3[crypto_auth_hmacsha512256_BYTES];
if (crypto_auth_hmacsha512256(nonce3, connection->serverepub, sizeof(connection->serverepub), k_ssb_network) != 0)
if (crypto_auth_hmacsha512256(nonce3, connection->serverepub, sizeof(connection->serverepub), connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "unable to compute client send nonce");
return;
@ -1290,11 +1291,11 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
return;
}
static_assert(sizeof(k_ssb_network) == crypto_auth_KEYBYTES, "network key size");
uint8_t tohash[sizeof(k_ssb_network) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB)];
memcpy(tohash, k_ssb_network, sizeof(k_ssb_network));
memcpy(tohash + sizeof(k_ssb_network), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(tohash + sizeof(k_ssb_network) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
static_assert(sizeof(connection->ssb->network_key) == crypto_auth_KEYBYTES, "network key size");
uint8_t tohash[sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB)];
memcpy(tohash, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(tohash + sizeof(connection->ssb->network_key), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(tohash + sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
uint8_t hash2[crypto_hash_sha256_BYTES];
crypto_hash_sha256(hash2, tohash, sizeof(tohash));
@ -1335,10 +1336,10 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
uint8_t hash3[crypto_hash_sha256_BYTES];
crypto_hash_sha256(hash3, shared_secret_ab, sizeof(shared_secret_ab));
uint8_t msg[sizeof(k_ssb_network) + sizeof(connection->ssb->pub) + sizeof(hash3)];
memcpy(msg, k_ssb_network, sizeof(k_ssb_network));
memcpy(msg + sizeof(k_ssb_network), connection->ssb->pub, sizeof(connection->ssb->pub));
memcpy(msg + sizeof(k_ssb_network) + sizeof(connection->ssb->pub), hash3, sizeof(hash3));
uint8_t msg[sizeof(connection->ssb->network_key) + sizeof(connection->ssb->pub) + sizeof(hash3)];
memcpy(msg, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(msg + sizeof(connection->ssb->network_key), connection->ssb->pub, sizeof(connection->ssb->pub));
memcpy(msg + sizeof(connection->ssb->network_key) + sizeof(connection->ssb->pub), hash3, sizeof(hash3));
if (crypto_sign_verify_detached(detached_signature_A, msg, sizeof(msg), connection->serverpub) != 0)
{
_tf_ssb_connection_close(connection, "unable to verify client identity");
@ -1346,7 +1347,7 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
}
uint8_t nonce2[crypto_auth_hmacsha512256_BYTES];
if (crypto_auth_hmacsha512256(nonce2, connection->epub, sizeof(connection->epub), k_ssb_network) != 0)
if (crypto_auth_hmacsha512256(nonce2, connection->epub, sizeof(connection->epub), connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "unable to compute initial recv nonce as server");
return;
@ -1354,7 +1355,7 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
memcpy(connection->nonce, nonce2, sizeof(connection->nonce));
uint8_t nonce3[crypto_auth_hmacsha512256_BYTES];
if (crypto_auth_hmacsha512256(nonce3, connection->serverepub, sizeof(connection->serverepub), k_ssb_network) != 0)
if (crypto_auth_hmacsha512256(nonce3, connection->serverepub, sizeof(connection->serverepub), connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "unable to compute initial send nonce as server");
return;
@ -1362,11 +1363,11 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
memcpy(connection->send_nonce, nonce3, sizeof(connection->send_nonce));
int detached_signature_A_size = 64;
uint8_t sign_b[sizeof(k_ssb_network) + detached_signature_A_size + sizeof(connection->serverpub) + sizeof(hash3)];
memcpy(sign_b, k_ssb_network, sizeof(k_ssb_network));
memcpy(sign_b + sizeof(k_ssb_network), detached_signature_A, detached_signature_A_size);
memcpy(sign_b + sizeof(k_ssb_network) + detached_signature_A_size, connection->serverpub, sizeof(connection->serverpub));
memcpy(sign_b + sizeof(k_ssb_network) + detached_signature_A_size + sizeof(connection->serverpub), hash3, sizeof(hash3));
uint8_t sign_b[sizeof(connection->ssb->network_key) + detached_signature_A_size + sizeof(connection->serverpub) + sizeof(hash3)];
memcpy(sign_b, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(sign_b + sizeof(connection->ssb->network_key), detached_signature_A, detached_signature_A_size);
memcpy(sign_b + sizeof(connection->ssb->network_key) + detached_signature_A_size, connection->serverpub, sizeof(connection->serverpub));
memcpy(sign_b + sizeof(connection->ssb->network_key) + detached_signature_A_size + sizeof(connection->serverpub), hash3, sizeof(hash3));
uint8_t detached_signature_B[crypto_sign_BYTES];
unsigned long long siglen;
@ -1390,11 +1391,11 @@ static void _tf_ssb_connection_verify_client_identity(tf_ssb_connection_t* conne
return;
}
uint8_t key_buf[sizeof(k_ssb_network) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB) + sizeof(shared_secret_Ab)];
memcpy(key_buf, k_ssb_network, sizeof(k_ssb_network));
memcpy(key_buf + sizeof(k_ssb_network), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(key_buf + sizeof(k_ssb_network) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
memcpy(key_buf + sizeof(k_ssb_network) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB), shared_secret_Ab, sizeof(shared_secret_Ab));
uint8_t key_buf[sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB) + sizeof(shared_secret_Ab)];
memcpy(key_buf, connection->ssb->network_key, sizeof(connection->ssb->network_key));
memcpy(key_buf + sizeof(connection->ssb->network_key), shared_secret_ab, sizeof(shared_secret_ab));
memcpy(key_buf + sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab), shared_secret_aB, sizeof(shared_secret_aB));
memcpy(key_buf + sizeof(connection->ssb->network_key) + sizeof(shared_secret_ab) + sizeof(shared_secret_aB), shared_secret_Ab, sizeof(shared_secret_Ab));
uint8_t key_hash[crypto_hash_sha256_BYTES];
crypto_hash_sha256(key_hash, key_buf, sizeof(key_buf));
@ -1941,7 +1942,7 @@ static void _tf_ssb_connection_on_tcp_recv_internal(tf_ssb_connection_t* connect
uint8_t* hmac = hello;
memcpy(connection->serverepub, hello + crypto_box_PUBLICKEYBYTES, crypto_box_PUBLICKEYBYTES);
static_assert(sizeof(connection->serverepub) == crypto_box_PUBLICKEYBYTES, "serverepub size");
if (crypto_auth_hmacsha512256_verify(hmac, connection->serverepub, 32, k_ssb_network) != 0)
if (crypto_auth_hmacsha512256_verify(hmac, connection->serverepub, 32, connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "crypto_auth_hmacsha512256_verify failed");
}
@ -1998,7 +1999,7 @@ static void _tf_ssb_connection_client_send_hello(tf_ssb_connection_t* connection
}
uint8_t a[crypto_auth_hmacsha512256_BYTES];
if (crypto_auth_hmacsha512256(a, connection->epub, sizeof(connection->epub), k_ssb_network) != 0)
if (crypto_auth_hmacsha512256(a, connection->epub, sizeof(connection->epub), connection->ssb->network_key) != 0)
{
_tf_ssb_connection_close(connection, "failed to create hello message");
return;
@ -2106,11 +2107,17 @@ void tf_ssb_get_stats(tf_ssb_t* ssb, tf_ssb_stats_t* out_stats)
ssb->rpc_out = 0;
}
tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path)
tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path, const char* network_key)
{
tf_ssb_t* ssb = tf_malloc(sizeof(tf_ssb_t));
memset(ssb, 0, sizeof(*ssb));
const char* actual_key = network_key ? network_key : k_ssb_network_string;
if (sodium_hex2bin(ssb->network_key, sizeof(ssb->network_key), actual_key, strlen(actual_key), ": ", NULL, NULL))
{
tf_printf("Error parsing network key: %s.", actual_key);
}
char buffer[8] = { 0 };
size_t buffer_size = sizeof(buffer);
ssb->store_debug_messages = uv_os_getenv("TF_DEBUG_CLOSE", buffer, &buffer_size) == 0 && strcmp(buffer, "1") == 0;
@ -2817,38 +2824,45 @@ static void _tf_ssb_broadcast_timer(uv_timer_t* timer)
}
}
void tf_ssb_server_open(tf_ssb_t* ssb, int port)
int tf_ssb_server_open(tf_ssb_t* ssb, int port)
{
if (ssb->server.data)
{
tf_printf("Already listening.\n");
return;
return 0;
}
ssb->server.data = ssb;
if (uv_tcp_init(ssb->loop, &ssb->server) != 0)
{
tf_printf("uv_tcp_init failed\n");
return;
return 0;
}
struct sockaddr_in addr = { 0 };
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (uv_tcp_bind(&ssb->server, (struct sockaddr*)&addr, 0) != 0)
int status = uv_tcp_bind(&ssb->server, (struct sockaddr*)&addr, 0);
if (status != 0)
{
tf_printf("uv_tcp_bind failed\n");
return;
tf_printf("%s:%d: uv_tcp_bind failed: %s\n", __FILE__, __LINE__, uv_strerror(status));
return 0;
}
int status = uv_listen((uv_stream_t*)&ssb->server, SOMAXCONN, _tf_ssb_on_connection);
status = uv_listen((uv_stream_t*)&ssb->server, SOMAXCONN, _tf_ssb_on_connection);
if (status != 0)
{
tf_printf("uv_listen failed: %s\n", uv_strerror(status));
/* TODO: cleanup */
return;
return 0;
}
struct sockaddr_storage name = { 0 };
int size = (int)sizeof(name);
status = uv_tcp_getsockname(&ssb->server, (struct sockaddr*)&name, &size);
int assigned_port = ntohs(((struct sockaddr_in*)&name)->sin_port);
return status == 0 ? assigned_port : 0;
}
void tf_ssb_server_close(tf_ssb_t* ssb)

View File

@ -7,14 +7,50 @@
** @{
*/
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/** An SSB connections tracker instance. */
typedef struct _tf_ssb_connections_t tf_ssb_connections_t;
/**
** Create a connection tracker.
** @param ssb The SSB instance.
** @return The connection tracker instance.
*/
tf_ssb_connections_t* tf_ssb_connections_create(tf_ssb_t* ssb);
/**
** Destroy a connection tracker.
** @param connections The connection tracker to destroy.
*/
void tf_ssb_connections_destroy(tf_ssb_connections_t* connections);
/**
** Store a connection in the connection tracker.
** @param connections The connection tracker.
** @param host The host name or address.
** @param port The network port number.
** @param key The identity on the other end of the connection.
*/
void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key);
/**
** Record that a connection was recently attempted.
** @param connections The connection tracker.
** @param host The host name or address.
** @param port The network port number.
** @param key The identity on the other end of the connection.
*/
void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key);
/**
** Record that a connection recently succeeded.
** @param connections The connection tracker.
** @param host The host name or address.
** @param port The network port number.
** @param key The identity on the other end of the connection.
*/
void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key);
/** @} */

View File

@ -101,11 +101,28 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
" timestamp REAL,"
" previous TEXT,"
" hash TEXT,"
" content TEXT,"
" content BLOB,"
" signature TEXT,"
" sequence_before_author INTEGER,"
" UNIQUE(author, sequence)"
")");
if (_tf_ssb_db_has_rows(db, "SELECT name FROM pragma_table_info('messages') WHERE name = 'content' AND type == 'TEXT'"))
{
tf_printf("converting to JSONB\n");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ai_refs");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad_refs");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ai");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ad");
_tf_ssb_db_exec(db, "DROP TABLE IF EXISTS messages_fts");
_tf_ssb_db_exec(db, "BEGIN TRANSACTION");
_tf_ssb_db_exec(db, "ALTER TABLE messages ADD COLUMN contentb BLOB");
_tf_ssb_db_exec(db, "UPDATE messages SET contentb = jsonb(content)");
_tf_ssb_db_exec(db, "ALTER TABLE messages DROP COLUMN content");
_tf_ssb_db_exec(db, "ALTER TABLE messages RENAME COLUMN contentb TO content");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
}
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_id_index ON messages (author, id)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_sequence_index ON messages (author, sequence)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)");
@ -161,11 +178,12 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
if (populate_fts)
{
tf_printf("Populating full-text search...\n");
_tf_ssb_db_exec(db, "INSERT INTO messages_fts (rowid, content) SELECT rowid, content FROM messages");
_tf_ssb_db_exec(db, "INSERT INTO messages_fts (rowid, content) SELECT rowid, json(content) FROM messages");
tf_printf("Done.\n");
}
_tf_ssb_db_exec(db, "CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, new.content); END");
_tf_ssb_db_exec(
db, "CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(rowid, content) VALUES (new.rowid, json(new.content)); END");
_tf_ssb_db_exec(db,
"CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.rowid, "
"old.content); END");
@ -287,7 +305,7 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous))
{
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, sequence_before_author) VALUES (?, ?, ?, ?, ?, ?, "
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, sequence_before_author) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING";
sqlite3_stmt* statement;
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
@ -557,7 +575,7 @@ bool tf_ssb_db_message_content_get(tf_ssb_t* ssb, const char* id, uint8_t** out_
bool result = false;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
const char* query = "SELECT content FROM messages WHERE id = ?";
const char* query = "SELECT json(content) FROM messages WHERE id = ?";
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW)
@ -764,7 +782,7 @@ bool tf_ssb_db_get_message_by_author_and_sequence(
{
bool found = false;
sqlite3_stmt* statement;
const char* query = "SELECT id, timestamp, content FROM messages WHERE author = ?1 AND sequence = ?2";
const char* query = "SELECT id, timestamp, json(content) FROM messages WHERE author = ?1 AND sequence = ?2";
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
{
@ -1481,8 +1499,8 @@ JSValue tf_ssb_db_get_message_by_id(tf_ssb_t* ssb, const char* id, bool is_keys)
JSContext* context = tf_ssb_get_context(ssb);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
sqlite3_stmt* statement;
if (sqlite3_prepare(
db, "SELECT previous, author, id, sequence, timestamp, hash, content, signature, sequence_before_author FROM messages WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
if (sqlite3_prepare(db, "SELECT previous, author, id, sequence, timestamp, hash, json(content), signature, sequence_before_author FROM messages WHERE id = ?", -1, &statement,
NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{

View File

@ -13,65 +13,314 @@
#include <stdbool.h>
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/**
** Initialize the database writer for an SSB instance.
** @param ssb The SSB instance.
*/
void tf_ssb_db_init(tf_ssb_t* ssb);
/**
** Configure an opened SQLite database for reading.
*/
void tf_ssb_db_init_reader(sqlite3* db);
/**
** Get message content by ID.
** @param ssb The SSB instance.
** @param id The message identifier.
** @param[out] out_blob Populated with the message content.
** @param[out] out_size POpulated with the size of the message content.
** @return true If the message content was found and retrieved.
*/
bool tf_ssb_db_message_content_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);
/**
** Determine whether a blob is in the database by ID.
** @param ssb The SSB instasnce.
** @param id The blob identifier.
** @return true If the blob is in the database.
*/
bool tf_ssb_db_blob_has(tf_ssb_t* ssb, const char* id);
/**
** Retrieve a blob from the database.
** @param ssb The SSB instance.
** @param id The blob identifier.
** @param[out] out_blob Populated with the blob data.
** @param[out] out_size The size of the blob data.
** @return true If the blob was found and retrieved.
*/
bool tf_ssb_db_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);
/**
** A function called when a message is stored in the database.
** @param id The message identifier.
** @param stored True if the message wasn't already in the database.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_store_message_callback_t)(const char* id, bool stored, void* user_data);
/**
** Store a message in the database.
** @param ssb The SSB instance.
** @param context The JS context.
** @param id The message identifier.
** @param val The message object.
** @param signature The signature of the message.
** @param sequence_before_author The order of the message fields.
** @param callback A callback to call upon completion.
** @param user_data User data for the callback.
*/
void tf_ssb_db_store_message(tf_ssb_t* ssb, JSContext* context, const char* id, JSValue val, const char* signature, bool sequence_before_author,
tf_ssb_db_store_message_callback_t* callback, void* user_data);
/**
** A function called when a block is stored in the database.
** @param id The blob identifier.
** @param is_new True if the blob wasn't already in the database.
** @param user_data The user data.
*/
typedef void(tf_ssb_db_blob_store_callback_t)(const char* id, bool is_new, void* user_data);
/**
** Store a blob in the database asynchronously.
** @param ssb The SSB instance.
** @param blob The blob data.
** @param size The size of the blob data.
** @param callback A callback to call upon completion.
** @param user_data User data for the callback.
*/
void tf_ssb_db_blob_store_async(tf_ssb_t* ssb, const uint8_t* blob, size_t size, tf_ssb_db_blob_store_callback_t* callback, void* user_data);
/**
** Store a blob in the database and wait for the operation to complete.
** @param ssb The SSB instance.
** @param blob The blob data.
** @param size The size of the blob.
** @param[out] out_id Populated with the blob identifier.
** @param out_id_size The size of the out_id buffer.
** @param[out] out_new True if the blob wasn't already in the datbase.
*/
bool tf_ssb_db_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char* out_id, size_t out_id_size, bool* out_new);
/**
** Get a message by its identifier.
** @param ssb The SSB instance.
** @param id The message identifier.
** @param is_keys Whether to produce {"key": id, "value": message, "timestamp": ts} or just the message.
** @return The message.
*/
JSValue tf_ssb_db_get_message_by_id(tf_ssb_t* ssb, const char* id, bool is_keys);
/**
** Get a message by its author and sequence number.
** @param ssb The SSB instance.
** @param author The author's identity.
** @param sequence The message sequence number.
** @param[out] out_message_id Populated with the message identifier.
** @param out_message_id_size The size of the out_message_id buffer.
** @param[out] out_timestamp Populated with the timestamp.
** @param[out] out_content Populated with the message content. Free with tf_free().
** @return True if the message was found and retrieved.
*/
bool tf_ssb_db_get_message_by_author_and_sequence(
tf_ssb_t* ssb, const char* author, int64_t sequence, char* out_message_id, size_t out_message_id_size, double* out_timestamp, char** out_content);
/**
** Get information about the last message from an author.
** @param ssb The SSB instance.
** @param author The author's identity.
** @param[out] out_sequence Populated with the message sequence number.
** @param[out] out_message_id Populated with the message identifier.
** @param out_message_id_size The size of the out_message_id buffer.
** @return True if the message was found and information was retrieved.
*/
bool tf_ssb_db_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, int64_t* out_sequence, char* out_message_id, size_t out_message_id_size);
/**
** Call a function for each result row of an SQL query.
** @param ssb The SSB instance.
** @param query The SQL query.
** @param binds An array of values to bind to SQL parameters.
** @param callback A callback to call for each result row.
** @param user_data User data to pass to the callback.
** @return A promise resolved when the query completes or rejected if it fails.
*/
JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue binds, void (*callback)(JSValue row, void* user_data), void* user_data);
typedef struct sqlite3 sqlite3;
/**
** Sanity check the feed for the given author.
** @param db The SQLite database instance to use.
** @param author The identity of the author to check.
** @return True if the author's feed is fully valid.
*/
bool tf_ssb_db_check(sqlite3* db, const char* author);
/**
** Get the number of SSB identities a Tilde Friends user has.
** @param ssb The SSB instance.
** @param user The user's username.
** @return The number of identities found.
*/
int tf_ssb_db_identity_get_count_for_user(tf_ssb_t* ssb, const char* user);
/**
** Create a new identity for a user.
** @param ssb The SSB instance.
** @param user The user's username.
** @param[out] out_public_key A buffer populated with the new public key.
** @param[out] out_private_key A buffer populated with the new privatee key.
** @return True if the identity was created.
*/
bool tf_ssb_db_identity_create(tf_ssb_t* ssb, const char* user, uint8_t* out_public_key, uint8_t* out_private_key);
/**
** Delete an identity for a user from the database. This is an unrecoverable operation.
** @param ssb The SSB instance.
** @param user The user's username.
** @param public_key The identity to delete.
** @return True if the identity was deleted.
*/
bool tf_ssb_db_identity_delete(tf_ssb_t* ssb, const char* user, const char* public_key);
/**
** Add an identity for a user to the database.
** @param ssb The SSB instance.
** @param user The user's username.
** @param public_key The public key of the identity.
** @param private_key The private key of the identity.
*/
bool tf_ssb_db_identity_add(tf_ssb_t* ssb, const char* user, const char* public_key, const char* private_key);
/**
** Call a function for each identity owned by a user.
** @param ssb The SSB instance.
** @param user The user's username.
** @param callback The function to call for each identity.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_db_identity_visit(tf_ssb_t* ssb, const char* user, void (*callback)(const char* identity, void* user_data), void* user_data);
/**
** Call a function for all identities in the database.
** @param ssb The SSB instance.
** @param callback The callback to call for each identity.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_db_identity_visit_all(tf_ssb_t* ssb, void (*callback)(const char* identity, void* user_data), void* user_data);
/**
** Get the private key for an identity in the database.
** @param ssb The SSB instance.
** @param user The owning user's username.
** @param public_key The public key of the identity.
** @param[out] out_private_key A buffer to receive the private key of the identity.
** @param private_key_size The size of the out_private_key buffer.
** @return True if the private key was found and retrieved.
*/
bool tf_ssb_db_identity_get_private_key(tf_ssb_t* ssb, const char* user, const char* public_key, uint8_t* out_private_key, size_t private_key_size);
/**
** Format a message in the standard format for SSB signing.
** @param context A JS context.
** @param previous The previous message identifier.
** @param author The author's public key.
** @param sequence The message sequence number.
** @param timestamp The message timestamp.
** @param hash The hash type (probably "sha256").
** @param content The message content.
** @param signature The signature of the message.
** @param sequence_before_author The order of the message fields (prefer false).
*/
JSValue tf_ssb_format_message(JSContext* context, const char* previous, const char* author, int64_t sequence, double timestamp, const char* hash, const char* content,
const char* signature, bool sequence_before_author);
/** Information about a single followed account. */
typedef struct _tf_ssb_following_t
{
/** The number of known users the account is following. */
int following_count;
/** The number of known users the account is blocking. */
int blocking_count;
/** The number of known users following the account. */
int followed_by_count;
/** The number of known users blocking the account. */
int blocked_by_count;
/** The account's identity. */
char id[k_id_base64_len];
} tf_ssb_following_t;
/**
** Get all the identities visible from a set of identities given known follows and blocks.
** @param ssb The SSB instance.
** @param ids An array of identities.
** @param count The number of identities.
** @param depth The following depth to use (prefer 2).
** @return An array of identities. Free with tf_free().
*/
const char** tf_ssb_db_following_deep_ids(tf_ssb_t* ssb, const char** ids, int count, int depth);
/**
** Return information about identities visible from a set of identities given known follows and blocks.
** @param ssb The SSB instance.
** @param ids An array of identities.
** @param count The number of identities.
** @param depth The following depth to use (prefer 2).
** @return An array of information about visible accounts. Fere with tf_free().
*/
tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, int count, int depth);
/**
** Get all visible identities from all local accounts.
** @param ssb The SSB instance.
** @param depth The following depth to consider (prefer 2).
** @return The visible identities. Free with tf_free().
*/
const char** tf_ssb_db_get_all_visible_identities(tf_ssb_t* ssb, int depth);
/**
** Information about a stored SHS connection.
*/
typedef struct _tf_ssb_db_stored_connection_t
{
/** The connection's address. */
char address[256];
/** The network port number of the connection. */
int port;
/** The identity. */
char pubkey[k_id_base64_len];
} tf_ssb_db_stored_connection_t;
/**
** Get the list of stored connections from the SSB connection tracker.
** @param ssb The SSB instance.
** @param[out] out_count Populated with the number of returned connections.
** @return Information about all the stored connections.
*/
tf_ssb_db_stored_connection_t* tf_ssb_db_get_stored_connections(tf_ssb_t* ssb, int* out_count);
/**
** Remove a stored connection.
** @param ssb The SSB instance.
** @param address The connection address.
** @param port The connection network port number.
** @param pubkey The identity of the connection.
*/
void tf_ssb_db_forget_stored_connection(tf_ssb_t* ssb, const char* address, int port, const char* pubkey);
/**
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
** @param user_data User data registered with the authorizer.
** @param action_code The type of action.
** @param arg0 Depends on the action.
** @param arg1 Depends on the action.
** @param arg2 Depends on the action.
** @param arg3 Depends on the action.
** @return A value indicating whether the operation is allowed.
*/
int tf_ssb_sqlite_authorizer(void* user_data, int action_code, const char* arg0, const char* arg1, const char* arg2, const char* arg3);
/** @} */

View File

@ -8,8 +8,14 @@
** @{
*/
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/**
** Export an app to disk.
** @param ssb The SSB instance.
** @param key The app path in the form "/~user/appname".
*/
void tf_ssb_export(tf_ssb_t* ssb, const char* key);
/** @} */

735
src/ssb.h
View File

@ -30,6 +30,9 @@ enum
k_ssb_blob_bytes_max = 5 * 1024 * 1024,
};
/**
** The type of change to a set of connections.
*/
typedef enum _tf_ssb_change_t
{
k_tf_ssb_change_create,
@ -37,12 +40,17 @@ typedef enum _tf_ssb_change_t
k_tf_ssb_change_remove,
} tf_ssb_change_t;
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
typedef struct _tf_ssb_rpc_t tf_ssb_rpc_t;
/** An SSB connection. */
typedef struct _tf_ssb_connection_t tf_ssb_connection_t;
/** A trace instance. */
typedef struct _tf_trace_t tf_trace_t;
/** An SQLite database handle. */
typedef struct sqlite3 sqlite3;
/** An event loop. */
typedef struct uv_loop_s uv_loop_t;
/** A socket address. */
struct sockaddr_in;
enum
@ -52,15 +60,26 @@ enum
k_blob_id_len = 53,
};
/**
** Statistics about an SSB instance.
*/
typedef struct _tf_ssb_stats_t
{
/** Number of active connections. */
int connections;
/** Number of active hosts discovered by network broadcast. */
int broadcasts;
/** Number of messages stored. */
int messages_stored;
/** Number of blobs stored. */
int blobs_stored;
/** Number of RPC messages received. */
int rpc_in;
/** Number of RPC messages sent. */
int rpc_out;
/** Number of active RPC requests. */
int request_count;
/** Number of callbacks registered. */
struct
{
int rpc;
@ -71,170 +90,874 @@ typedef struct _tf_ssb_stats_t
} callbacks;
} tf_ssb_stats_t;
/**
** State about requesting blobs.
*/
typedef struct _tf_ssb_blob_wants_t
{
/** The request number of the blob.wants RPC call. */
int32_t request_number;
/** The number of blob wants we are waiting for a response to. */
int wants_sent;
/** The last blob ID considered. */
char last_id[k_blob_id_len];
} tf_ssb_blob_wants_t;
/**
** A queue for storing messages.
*/
typedef struct _tf_ssb_store_queue_t
{
/** The first node in the queue. */
void* head;
/** The last node in the queue. */
void* tail;
/** Whether the queue is currently running. */
bool running;
} tf_ssb_store_queue_t;
tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path);
/**
** Create an SSB instance.
** @param loop The event loop to use or NULL to create a new one.
** @param context The JS context to use or NULL to create a new one.
** @param db_path The path to the SQLite database to use.
** @param network_key The SSB network key to use or NULL to use the standard key.
*/
tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, const char* db_path, const char* network_key);
/**
** Destroy an SSB instance.
** @param ssb The SSB instance to destroy.
*/
void tf_ssb_destroy(tf_ssb_t* ssb);
/**
** Start optional periodic work.
** @param ssb The SSB instance.
*/
void tf_ssb_start_periodic(tf_ssb_t* ssb);
/**
** Control logging verbosity.
** @param ssb The SSB instance.
** @param verbose True to log messages for every RPC message sent and received.
*/
void tf_ssb_set_verbose(tf_ssb_t* ssb, bool verbose);
/**
** Acquire an SQLite database for unrestricted reading. Release qith tf_ssb_release_db_reader().
** @param ssb The SSB instance.
** @return A database with full read access to the database.
*/
sqlite3* tf_ssb_acquire_db_reader(tf_ssb_t* ssb);
/**
** Acquire an SQLite database for restricted reading. Release qith
** tf_ssb_release_db_reader().
** @param ssb The SSB instance.
** @return A database with read access to a safe subset of the database.
*/
sqlite3* tf_ssb_acquire_db_reader_restricted(tf_ssb_t* ssb);
/**
** Release a database acquired with tf_ssb_acquire_db_reader() or
** tf_ssb_acquire_db_reader_restricted().
** @param ssb The SSB instance.
** @param db The database.
*/
void tf_ssb_release_db_reader(tf_ssb_t* ssb, sqlite3* db);
/**
** Acquire an SQLite database with full write access to the database. Release
** with tf_ssb_release_db_writer().
** @param ssb The SSB instance.
** @return The writable database.
*/
sqlite3* tf_ssb_acquire_db_writer(tf_ssb_t* ssb);
/**
** Release a database acquired with tf_ssb_acquire_db_writer().
** @param ssb The SSB instance.
** @param db The database.
*/
void tf_ssb_release_db_writer(tf_ssb_t* ssb, sqlite3* db);
/**
** Get the SSB instance's event loop.
** @param ssb The SSB instance.
** @return The loop.
*/
uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb);
/**
** Generate a public/private key pair for the SSB instance.
** @param ssb The SSB instance.
*/
void tf_ssb_generate_keys(tf_ssb_t* ssb);
/**
** Generate a public/private key pair and store in buffers.
** @param[out] out_public Buffer to receive the public key.
** @param public_size Size of the public key buffer.
** @param[out] out_private Buffer to receive the private key.
** @param private_size Size of the private key buffer.
*/
void tf_ssb_generate_keys_buffer(char* out_public, size_t public_size, char* out_private, size_t private_size);
/**
** Get the private key of the SSB instance.
** @param ssb The SSB instance.
** @param[out] out_private Buffer to receive the private key.
** @param private_size The size of the private key buffer.
*/
void tf_ssb_get_private_key(tf_ssb_t* ssb, uint8_t* out_private, size_t private_size);
/**
** Set the trace instance to use for the SSB instance.
** @param ssb The SSB instance.
** @param trace The trace instance to use.
*/
void tf_ssb_set_trace(tf_ssb_t* ssb, tf_trace_t* trace);
/**
** Get the SSB instance's trace instance.
** @param ssb The SSB instance.
** @return The trace instance.
*/
tf_trace_t* tf_ssb_get_trace(tf_ssb_t* ssb);
/**
** Get the SSB istance's JS context.
** @param ssb The SSB instance.
** @return The JS context.
*/
JSContext* tf_ssb_get_context(tf_ssb_t* ssb);
/**
** Begin listening for SSB discovery messages.
** @param ssb The SSB instance.
** @param linger True if listening for broadcasts should keep the event loop alive.
*/
void tf_ssb_broadcast_listener_start(tf_ssb_t* ssb, bool linger);
/**
** Begin sending SSB discovevry messages.
** @param ssb The SSB instance.
*/
void tf_ssb_broadcast_sender_start(tf_ssb_t* ssb);
/**
** Run the SSB instance until there is no work to do or stopped.
** @param ssb The SSB instance.
*/
void tf_ssb_run(tf_ssb_t* ssb);
/**
** Sign an SSB message.
** @param ssb The SSB instance.
** @param author The author's public key.
** @param private_key The author's private key.
** @param message The message to sign.
** @return The signed message.
*/
JSValue tf_ssb_sign_message(tf_ssb_t* ssb, const char* author, const uint8_t* private_key, JSValue message);
/**
** Get the server's identity.
** @param ssb The SSB instance.
** @param[out] out_id A buffer populated with the identity.
** @param out_id_size The size of the identity buffer.
** @return True if the identity was successfully retrieved.
*/
bool tf_ssb_whoami(tf_ssb_t* ssb, char* out_id, size_t out_id_size);
/**
** Call a callback for each active host discovered by network discovery broadcast.
** @param ssb The SSB instance.
** @param callback The callback.
** @param user_data User data for the callback.
*/
void tf_ssb_visit_broadcasts(
tf_ssb_t* ssb, void (*callback)(const char* host, const struct sockaddr_in* addr, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data), void* user_data);
/**
** Get the identities of all active connections.
** @param ssb The SSB instance.
** @return A NULL-terminated array of SSB identities. Free with tf_free().
*/
const char** tf_ssb_get_connection_ids(tf_ssb_t* ssb);
/**
** Retrieve a list of active connections.
** @param ssb The SSB instance.
** @param[out] out_connections An array to be populated with the connections.
** @param out_connections_count The size of the connections array.
** @return The number of connections populated in out_connections.
*/
int tf_ssb_get_connections(tf_ssb_t* ssb, tf_ssb_connection_t** out_connections, int out_connections_count);
/**
** Establish an SHS connection with a host.
** @param ssb The SSB instance.
** @param host The host name or address.
** @param port The host's SHS port.
** @param key The host's SSB identity.
*/
void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key);
/**
** Establish an SHS connection with a host by string address.
** @param ssb The SSB instance.
** @param address The address.
*/
void tf_ssb_connect_str(tf_ssb_t* ssb, const char* address);
void tf_ssb_server_open(tf_ssb_t* ssb, int port);
/**
** Begin listening for SHS connections on the given port.
** @param ssb The SSB instance.
** @param port The port number.
** @return The assigned port on success or 0 on failure.
*/
int tf_ssb_server_open(tf_ssb_t* ssb, int port);
/**
** Stop listening for SHS connections.
** @param ssb The SSB instance.
*/
void tf_ssb_server_close(tf_ssb_t* ssb);
/**
** Close all active SHS connections.
** @param ssb The SSB instance.
*/
void tf_ssb_close_all(tf_ssb_t* ssb);
/**
** Send a graceful close message to all active SHS connections.
** @param ssb The SSB instance.
*/
void tf_ssb_send_close(tf_ssb_t* ssb);
/**
** Convert an SSB identity from string to binary.
** @param[out] bin A buffer to receive the binary identity.
** @param str The string identity.
** @return True if the conversion was successful.
*/
bool tf_ssb_id_str_to_bin(uint8_t* bin, const char* str);
/**
** Convert an SSB identity from binary to string.
** @param[out] str A buffer to receive the identity string.
** @param str_size The size of the string buffer.
** @param bin The binary identity.
** @return True if the conversion was successful.
*/
bool tf_ssb_id_bin_to_str(char* str, size_t str_size, const uint8_t* bin);
/**
** Verify a message's signature and remove the signature if successful.
** @param context A JS context.
** @param val The message.
** @param[out] out_id A buffer to receive the message's identity.
** @param out_id_size The size of out_id.
** @param[out] out_signature A buffer to receive the message's signature.
** @param out_signature_size The size of out_signature.
** @param[out] out_sequence_before_author A flag describing the order of the sequence and author fields.
** @return True if the signature is valid and was successfully extracted.
*/
bool tf_ssb_verify_and_strip_signature(
JSContext* context, JSValue val, char* out_id, size_t out_id_size, char* out_signature, size_t out_signature_size, bool* out_sequence_before_author);
/**
** Determine the message identifier.
** @param context A JS context.
** @param message The message.
** @param[out] out_id A buffer to receive the identifier.
** @param out_id_size The size of out_id.
*/
void tf_ssb_calculate_message_id(JSContext* context, JSValue message, char* out_id, size_t out_id_size);
/**
** A function called on completion of tf_ssb_verify_strip_and_store_message().
** @param id The stored message identifier.
** @param verified True if the message was verified successfully.
** @param is_new True if the message was newly added to the database.
** @param user_data The user data.
*/
typedef void(tf_ssb_verify_strip_store_callback_t)(const char* id, bool verified, bool is_new, void* user_data);
/**
** Verify a message's signature, remove the signature, and store the message in the database.
** @param ssb The SSB instance.
** @param value The message.
** @param callback A callback called when the operation completed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_verify_strip_and_store_message(tf_ssb_t* ssb, JSValue value, tf_ssb_verify_strip_store_callback_t* callback, void* user_data);
/**
** Check if a connection is an outgoing connection.
** @param connection The connection.
** @return True if the connection is outgoing.
*/
bool tf_ssb_connection_is_client(tf_ssb_connection_t* connection);
/**
** Get the hostname of the remote end of a connection.
** @param connection The connection.
** @return The hostname or address.
*/
const char* tf_ssb_connection_get_host(tf_ssb_connection_t* connection);
/**
** Get a connection's remote port number.
** @param connection The connection.
** @return The port number.
*/
int tf_ssb_connection_get_port(tf_ssb_connection_t* connection);
/**
** If a connection is a tunnel, get its parent connection.
** @param connection The connection.
** @return The parent connection if the connection is tunneled or NULL.
*/
tf_ssb_connection_t* tf_ssb_connection_get_tunnel(tf_ssb_connection_t* connection);
/**
** Get a connection's SSB instance.
** @param connection The connection.
** @return The SSB instance.
*/
tf_ssb_t* tf_ssb_connection_get_ssb(tf_ssb_connection_t* connection);
/**
** Get a connection's JS context.
** @param connection The connection.
** @return The JS context.
*/
JSContext* tf_ssb_connection_get_context(tf_ssb_connection_t* connection);
sqlite3* tf_ssb_connection_get_db(tf_ssb_connection_t* connection);
void tf_ssb_connection_close(tf_ssb_connection_t* connect);
/**
** Close a connection.
** @param connection The connection.
*/
void tf_ssb_connection_close(tf_ssb_connection_t* connection);
/**
** Check whether a connection is connected.
** @param connection The connection.
** @return True if the connection is alive.
*/
bool tf_ssb_connection_is_connected(tf_ssb_connection_t* connection);
/**
** Get the next outgoing request number for a connection.
** @param connection The connection.
** @return The next request number.
*/
int32_t tf_ssb_connection_next_request_number(tf_ssb_connection_t* connection);
/**
** Get an active connection by its identity.
** @param ssb The SSB instance.
** @param id The SSB identity.
** @return The connection if found or NULL.
*/
tf_ssb_connection_t* tf_ssb_connection_get(tf_ssb_t* ssb, const char* id);
/**
** Get the SSB identity of a connection.
** @param connection The connection.
** @param[out] out_id A buffer to be populated with the identity.
** @param out_id_size The size of out_id.
** @return True if the identity was retrieved.
*/
bool tf_ssb_connection_get_id(tf_ssb_connection_t* connection, char* out_id, size_t out_id_size);
/**
** Get the JS object representing a connection.
** @param connection The connection.
** @return The object.
*/
JSValue tf_ssb_connection_get_object(tf_ssb_connection_t* connection);
/* Callbacks. */
/**
** A callback called when a callback is cleaned up.
** @param ssb The SSB instance.
** @param user_data User data.
*/
typedef void(tf_ssb_callback_cleanup_t)(tf_ssb_t* ssb, void* user_data);
/**
** A callback called when the connection list changes.
** @param ssb The SSB instance.
** @param change The type of change.
** @param connection The connection that changed.
** @param user_data User data.
*/
typedef void(tf_ssb_connections_changed_callback_t)(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data);
/**
** Register a callback when the connection list changes.
** @param ssb The SSB instance.
** @param callback The callback to register.
** @param cleanup The cleanup callback to register.
** @param user_data User data to pass to the callbacks.
*/
void tf_ssb_add_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_connections_changed_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a callback when the connection list changes.
** @param ssb The SSB instance.
** @param callback The callback.
** @param user_data The user data registered with the callback.
*/
void tf_ssb_remove_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_connections_changed_callback_t* callback, void* user_data);
/**
** A callback called when a new broadcast is received or one expires.
** @param ssb The SSB instance.
** @param user_data The user data.
*/
typedef void(tf_ssb_broadcasts_changed_callback_t)(tf_ssb_t* ssb, void* user_data);
/**
** Register a callback when broadcasts change.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param cleanup A function to call when the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_add_broadcasts_changed_callback(tf_ssb_t* ssb, tf_ssb_broadcasts_changed_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a callback registered for when broadcasts changed.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param user_data The user data registered with the callback.
*/
void tf_ssb_remove_broadcasts_changed_callback(tf_ssb_t* ssb, tf_ssb_broadcasts_changed_callback_t* callback, void* user_data);
/**
** A callback called when a message is added to the database.
** @param ssb The SSB instance.
** @param id The message identifier.
** @param user_data The user data.
*/
typedef void(tf_ssb_message_added_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data);
/**
** Register a callback called when a message is added to the database.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param cleanup A function to call when the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_add_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a callback registered for when a message is added to the database.
** @param ssb The SSB instance.
** @param callback The callback function.
** @param user_data User data registered with the callback.
*/
void tf_ssb_remove_message_added_callback(tf_ssb_t* ssb, tf_ssb_message_added_callback_t* callback, void* user_data);
/**
** Call all callbacks registered for when a message is added to the database.
** @param ssb The SSB instance.
** @param id The message identity added.
** @param message_with_keys The message added in the format required if keys are requested.
*/
void tf_ssb_notify_message_added(tf_ssb_t* ssb, const char* id, JSValue message_with_keys);
/**
** Record that a new blob was stored.
** @param ssb The SSB instance.
** @param id The identity of the newly stored blob.
*/
void tf_ssb_notify_blob_stored(tf_ssb_t* ssb, const char* id);
/**
** A callback called when a blob is newly requested.
** @param ssb The SSB instance.
** @param id The blob identity.
** @param user_data The user data.
*/
typedef void(tf_ssb_blob_want_added_callback_t)(tf_ssb_t* ssb, const char* id, void* user_data);
/**
** Register a function to be called when a blob is newly requested.
** @param ssb The SSB instance.
** @param callback The callback.
** @param cleanup A function to call when the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_add_blob_want_added_callback(tf_ssb_t* ssb, tf_ssb_blob_want_added_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a callback registered for when a blob is newly requested.
** @param ssb The SSB instance.
** @param callback The callback to remove.
** @param user_data The user data registered with the callback.
*/
void tf_ssb_remove_blob_want_added_callback(tf_ssb_t* ssb, tf_ssb_blob_want_added_callback_t* callback, void* user_data);
/**
** Call all callbacks registered for when a blob is newly requested.
** @param ssb The SSB instance.
** @param id The requested blob identity.
*/
void tf_ssb_notify_blob_want_added(tf_ssb_t* ssb, const char* id);
/**
** A function called when a MUXRPC request is made.
** @param connection The SSB connection.
** @param flags The RPC flags.
** @param request_number The request number.
** @param args Request arguments.
** @param message The raw message data.
** @param size The size of the raw message data.
** @param user_data User data registered with the callback.
*/
typedef void(tf_ssb_rpc_callback_t)(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data);
/**
** Register a MUXRPC callback by name.
** @param ssb The SSB instance.
** @param name The NULL-terminated name.
** @param callback The callback.
** @param cleanup A function to be called when the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_add_rpc_callback(tf_ssb_t* ssb, const char** name, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Remove a MUXRPC callback.
** @param ssb The SSB instance.
** @param name The NULL-terminated name.
** @param callback The callback to remove.
** @param user_data The user data registered with the callback.
*/
void tf_ssb_remove_rpc_callback(tf_ssb_t* ssb, const char** name, tf_ssb_rpc_callback_t* callback, void* user_data);
/**
** Send a MUXRPC message.
** @param connection The connection on which to send the message.
** @param flags The message flags.
** @param request_number The request number.
** @param message The message payload.
** @param size The size of the message.
** @param callback A callback to call if a response is received.
** @param cleanup A callback to call if the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const uint8_t* message, size_t size, tf_ssb_rpc_callback_t* callback,
tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Send a JSON MUXRPC message.
** @param connection The connection on which to send the message.
** @param flags The message flags.
** @param request_number The request number.
** @param message The JS message payload.
** @param callback A callback to call if a response is received.
** @param cleanup A callback to call if the callback is removed.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_connection_rpc_send_json(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue message, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data);
/**
** Send a MUXRPC error message.
** @param connection The connection on which to send the message.
** @param flags The message flags.
** @param request_number The request number.
** @param error The error string.
*/
void tf_ssb_connection_rpc_send_error(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const char* error);
/**
** Send a MUXRPC "method not allowed" error message.
** @param connection The connection on which to send the message.
** @param flags The message flags.
** @param request_number The request number.
** @param name The name of the not-allowed method.
*/
void tf_ssb_connection_rpc_send_error_method_not_allowed(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const char* name);
/**
** Register a callback to be called when a message is received for the given
** request number.
** @param connection The connection on which to register the callback.
** @param request_number The request number.
** @param callback The callback.
** @param cleanup The function to call when the callback is removed.
** @param user_data User data to pass to the callback.
** @param dependent_connection A connection, which, if removed, invalidates this request.
*/
void tf_ssb_connection_add_request(tf_ssb_connection_t* connection, int32_t request_number, tf_ssb_rpc_callback_t* callback, tf_ssb_callback_cleanup_t* cleanup, void* user_data,
tf_ssb_connection_t* dependent_connection);
/**
** Remove a callback registered to be called when a message is received for the
** given request number.
** @param connection The connection.
** @param request_number The request number.
*/
void tf_ssb_connection_remove_request(tf_ssb_connection_t* connection, int32_t request_number);
/**
** A function scheduled to be run later.
** @param connection The owning connection.
** @param user_data User data registered with the callback.
*/
typedef void(tf_ssb_scheduled_callback_t)(tf_ssb_connection_t* connection, void* user_data);
/**
** Schedule work to be run when the server is next idle.
** @param connection The owning connection.
** @param callback The callback to call.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_connection_schedule_idle(tf_ssb_connection_t* connection, tf_ssb_scheduled_callback_t* callback, void* user_data);
/**
** Schedule work to run on a worker thread.
** @param connection The owning connection.
** @param work_callback The callback to run on a thread.
** @param after_work_callback The callback to run on the main thread when the work is complete.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_connection_run_work(tf_ssb_connection_t* connection, void (*work_callback)(tf_ssb_connection_t* connection, void* user_data),
void (*after_work_callback)(tf_ssb_connection_t* connection, int result, void* user_data), void* user_data);
/**
** Register for new messages on a connection.
** @param connection The SHS connection.
** @param author The author for whom to request new messages.
** @param request_number The MUXRPC request on which to send new messages.
** @param keys Whether to send with keys.
*/
void tf_ssb_connection_add_new_message_request(tf_ssb_connection_t* connection, const char* author, int32_t request_number, bool keys);
/**
** Remove a request for new messages on a connection.
** @param connection the SHS connection.
** @param author The author for whom to no longer request new messages.
*/
void tf_ssb_connection_remove_new_message_request(tf_ssb_connection_t* connection, const char* author);
/**
** Get whether we are an attendant on a room connection.
** @param connection The SHS connection.
** @return True if this is an attendant connection.
*/
bool tf_ssb_connection_is_attendant(tf_ssb_connection_t* connection);
/**
** Get the request number used to notify of room attendant changes.
** @param connection the SHS connection.
** @return A request number.
*/
int32_t tf_ssb_connection_get_attendant_request_number(tf_ssb_connection_t* connection);
/**
** Register for attendant change notifications on a connection.
** @param connection The SHS connection.
** @param attendant Whether this connection will be an attendant.
** @param request_number The request number on which to send attendant changes.
*/
void tf_ssb_connection_set_attendant(tf_ssb_connection_t* connection, bool attendant, int request_number);
/**
** Clear all attendants from a room.
** @param connection The SHS connection.
*/
void tf_ssb_connection_clear_room_attendants(tf_ssb_connection_t* connection);
/**
** Add a room attendant.
** @param connection The SHS connection.
** @param id The attendant identifier.
*/
void tf_ssb_connection_add_room_attendant(tf_ssb_connection_t* connection, const char* id);
/**
** Remove a room attendant.
** @param connection The SHS connection.
** @param id The attendanr identifier.
*/
void tf_ssb_connection_remove_room_attendant(tf_ssb_connection_t* connection, const char* id);
/**
** Create a tunnel.
** @param ssb The SSB instance.
** @param portal_id The identity of the tunnel intermediary.
** @param request_number The tunnel request.
** @param target_id The identity being tunneled to.
** @return The new tunnel connection.
*/
tf_ssb_connection_t* tf_ssb_connection_tunnel_create(tf_ssb_t* ssb, const char* portal_id, int32_t request_number, const char* target_id);
/**
** Get the request number on which to send EBT responses.
** @param connection The SHS connection.
** @return The request number.
*/
int32_t tf_ssb_connection_get_ebt_request_number(tf_ssb_connection_t* connection);
/**
** Set the request number on which to send EBT responses.
** @param connection The SHS connection.
** @param request_number The request number.
*/
void tf_ssb_connection_set_ebt_request_number(tf_ssb_connection_t* connection, int32_t request_number);
/**
** Get the EBT clock for a connection.
** @param connection An SHS connection.
** @return The EBT clock.
*/
JSValue tf_ssb_connection_get_ebt_send_clock(tf_ssb_connection_t* connection);
/**
** Set the EBT clock for a connection.
** @param connection An SHS connection.
** @param send_clock The clock state.
*/
void tf_ssb_connection_set_ebt_send_clock(tf_ssb_connection_t* connection, JSValue send_clock);
/**
** Get whether the EBT clock has been sent for a connection.
** @param connection An SHS connection.
** @return True if the clock has been sent.
*/
bool tf_ssb_connection_get_sent_clock(tf_ssb_connection_t* connection);
/**
** Set the EBT clock sent state for a connection.
** @param connection An SHS connection.
** @param sent_clock Whether the clock has been sent.
*/
void tf_ssb_connection_set_sent_clock(tf_ssb_connection_t* connection, bool sent_clock);
/**
** Get the JS class ID of the SSB connection class.
** @return The class ID
*/
JSClassID tf_ssb_get_connection_class_id();
/**
** Get general statistics about an SSB instance.
** @param ssb The SSB instance.
** @param[out] out_stats Populated with performance statistics.
*/
void tf_ssb_get_stats(tf_ssb_t* ssb, tf_ssb_stats_t* out_stats);
/**
** Get information about requested blobs.
** @param connection An SHS connection.
** @return Blob wants information.
*/
tf_ssb_blob_wants_t* tf_ssb_connection_get_blob_wants_state(tf_ssb_connection_t* connection);
/**
** Get a report of information about recent disconnections.
** @param ssb The SSB instance.
** @param context A JS context.
** @return Information about disconnections.
*/
JSValue tf_ssb_get_disconnection_debug(tf_ssb_t* ssb, JSContext* context);
/**
** Record whether the calling thread is busy.
** @param ssb The SSB instance.
** @param busy True if the calling thread is now busy.
*/
void tf_ssb_record_thread_busy(tf_ssb_t* ssb, bool busy);
/**
** Get an estimate of utilization of all running threads.
** @param ssb The SSB instance.
** @return The utilization percent.
*/
float tf_ssb_get_average_thread_percent(tf_ssb_t* ssb);
/**
** Register a callback to be called when the main thread blocks for an
** unreasonable amount of time.
** @param ssb The SSB instance.
** @param callback The callback to call.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_set_hitch_callback(tf_ssb_t* ssb, void (*callback)(const char* name, uint64_t duration_ns, void* user_data), void* user_data);
/**
** Get the queue of messages in the progress of being stored.
** @param ssb The SSB instance.
** @return The queue.
*/
tf_ssb_store_queue_t* tf_ssb_get_store_queue(tf_ssb_t* ssb);
/**
** Increment the SSB instance's ref count. Prevents it from being destroyed
** until it reaches zero.
** @param ssb The SSB instance.
*/
void tf_ssb_ref(tf_ssb_t* ssb);
/**
** Decrement the SSB instance's ref count. May destroy the instance when the
** count returns to zero.
** @param ssb The SSB instance.
*/
void tf_ssb_unref(tf_ssb_t* ssb);
/**
** Record whether the calling thread is the main thread or not. Some
** operations are disallowed on the main thread for performance.
** @param ssb The SSB instance.
** @param main_thread Whether the calling thread is the main thread.
*/
void tf_ssb_set_main_thread(tf_ssb_t* ssb, bool main_thread);
/**
** Get whether the running server is operating a room.
** @param ssb The SSB instance.
** @return True if the server is a room.
*/
bool tf_ssb_is_room(tf_ssb_t* ssb);
/**
** Set whether the running server is operating a room.
** @param ssb The SSB instance.
** @param is_room Whether to run a room.
*/
void tf_ssb_set_is_room(tf_ssb_t* ssb, bool is_room);
/**
** Get the name of the room hosted by the running server.
** @param ssb The SSB instance.
** @return The room name or NULL.
*/
const char* tf_ssb_get_room_name(tf_ssb_t* ssb);
/**
** Set the name of the room hosted by the running server.
** @param ssb The SSB instance.
** @param room_name The name of the room.
*/
void tf_ssb_set_room_name(tf_ssb_t* ssb, const char* room_name);
/**
** Schedule work to be run after a time delay.
** @param ssb The SSB instance.
** @param delay_ms The duration to wait in milliseconds.
** @param callback The callback to call to run the work.
** @param user_data User data to pass to the callback.
*/
void tf_ssb_schedule_work(tf_ssb_t* ssb, int delay_ms, void (*callback)(tf_ssb_t* ssb, void* user_data), void* user_data);
/** @} */

View File

@ -128,7 +128,11 @@ static void _tf_ssb_import_recursive_add_files(tf_ssb_t* ssb, uv_loop_t* loop, J
uv_dirent_t ent;
while (uv_fs_scandir_next(&req, &ent) == 0)
{
if (ent.type == UV_DIRENT_FILE)
if (ent.type == UV_DIRENT_FILE
#if defined(__HAIKU__)
|| ent.type == UV_DIRENT_UNKNOWN
#endif
)
{
size_t len = strlen(path) + strlen(ent.name) + 2;
char* full_path = tf_malloc(len);

View File

@ -8,9 +8,24 @@
** @{
*/
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/**
** Import apps in a directory to a user's account.
** @param ssb The SSB instance.
** @param user The username.
** @param path The on-disk path of the apps.
*/
void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path);
/**
** Import apps from a zip file to a user's account.
** @param ssb The SSB instance.
** @param zip_path The path to the zip file on disk.
** @param user The user into whose account the apps will be imported.
** @param path The path in the zip to the apps.
*/
void tf_ssb_import_from_zip(tf_ssb_t* ssb, const char* zip_path, const char* user, const char* path);
/** @} */

View File

@ -272,7 +272,7 @@ static JSValue _tf_ssb_getPrivateKey(JSContext* context, JSValueConst this_val,
static JSValue _tf_ssb_getServerIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_NewArray(context);
JSValue result = JS_UNDEFINED;
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
if (ssb)
{

View File

@ -6,9 +6,14 @@
** @{
*/
/** A JS context. */
typedef struct JSContext JSContext;
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/**
** Register the SSB script interface.
*/
void tf_ssb_register(JSContext* context, tf_ssb_t* ssb);
/** @} */

View File

@ -30,7 +30,7 @@ static int64_t _get_global_setting_int64(tf_ssb_t* ssb, const char* name, int64_
{
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{
if (sqlite3_step(statement) == SQLITE_ROW)
if (sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_type(statement, 0) != SQLITE_NULL)
{
result = sqlite3_column_int64(statement, 0);
}
@ -678,7 +678,7 @@ static void _tf_ssb_connection_send_history_stream_work(tf_ssb_connection_t* con
sqlite3_stmt* statement;
const int k_max = 32;
if (sqlite3_prepare(db,
"SELECT previous, author, id, sequence, timestamp, hash, content, signature, sequence_before_author FROM messages WHERE author = ?1 AND sequence > ?2 AND "
"SELECT previous, author, id, sequence, timestamp, hash, json(content), signature, sequence_before_author FROM messages WHERE author = ?1 AND sequence > ?2 AND "
"sequence "
"< ?3 ORDER BY sequence",
-1, &statement, NULL) == SQLITE_OK)

View File

@ -7,9 +7,19 @@
** @{
*/
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/**
** Register standard muxrpc callbacks.
** @param ssb The SSB instance.
*/
void tf_ssb_rpc_register(tf_ssb_t* ssb);
/**
** Start periodic SSB maintenance tasks.
** @param ssb The SSB instance.
*/
void tf_ssb_rpc_start_periodic(tf_ssb_t* ssb);
/** @} */

View File

@ -143,10 +143,10 @@ void tf_ssb_test_ssb(const tf_test_options_t* options)
uv_loop_init(&loop);
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
unlink("out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
uv_idle_t idle0 = { .data = ssb0 };
@ -352,13 +352,13 @@ void tf_ssb_test_rooms(const tf_test_options_t* options)
uv_loop_init(&loop);
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb0), ssb0);
unlink("out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb1), ssb1);
unlink("out/test_db2.sqlite");
tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, "file:out/test_db2.sqlite");
tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, "file:out/test_db2.sqlite", NULL);
tf_ssb_register(tf_ssb_get_context(ssb2), ssb2);
uv_idle_t idle0 = { .data = ssb0 };
@ -513,7 +513,7 @@ void tf_ssb_test_following(const tf_test_options_t* options)
uv_loop_init(&loop);
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_generate_keys(ssb0);
char id0[k_id_base64_len] = { "@" };
@ -588,7 +588,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
tf_trace_t* trace = tf_trace_create();
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_set_trace(ssb0, trace);
tf_ssb_generate_keys(ssb0);
@ -618,7 +618,7 @@ void tf_ssb_test_bench(const tf_test_options_t* options)
tf_printf("insert = %f seconds\n", (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9);
unlink("out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
tf_ssb_set_trace(ssb1, trace);
tf_ssb_generate_keys(ssb1);
uint8_t id0bin[k_id_bin_len];
@ -793,12 +793,12 @@ void tf_ssb_test_go_ssb_room(const tf_test_options_t* options)
tf_trace_t* trace = tf_trace_create();
unlink("out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite");
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, "file:out/test_db0.sqlite", NULL);
tf_ssb_set_trace(ssb0, trace);
tf_ssb_generate_keys(ssb0);
unlink("out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite");
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, "file:out/test_db1.sqlite", NULL);
tf_ssb_set_trace(ssb1, trace);
tf_ssb_generate_keys(ssb1);

View File

@ -6,13 +6,45 @@
** @{
*/
/**
** Options to control how tests are run.
*/
typedef struct _tf_test_options_t tf_test_options_t;
/**
** Test converting SSB identities.
** @param options The test options.
*/
void tf_ssb_test_id_conversion(const tf_test_options_t* options);
/**
** Test SSB connections and replication.
** @param options The test options.
*/
void tf_ssb_test_ssb(const tf_test_options_t* options);
/**
** Test SSB following calculations.
** @param options The test options.
*/
void tf_ssb_test_following(const tf_test_options_t* options);
/**
** Test SSB rooms.
** @param options The test options.
*/
void tf_ssb_test_rooms(const tf_test_options_t* options);
/**
** Benchmark SSB replication performacnce.
** @param options The test options.
*/
void tf_ssb_test_bench(const tf_test_options_t* options);
/**
** Test communicating with go-ssb-room.
** @param options The test options.
*/
void tf_ssb_test_go_ssb_room(const tf_test_options_t* options);
/** @} */

View File

@ -150,6 +150,7 @@ typedef struct _tf_task_t
int _import_count;
JSValue _loadedFiles;
const char* _network_key;
int _ssb_port;
int _http_port;
int _https_port;
@ -949,9 +950,6 @@ char* tf_task_get_disconnections(tf_task_t* task)
return result;
}
char* tf_task_get_debug(tf_task_t* task);
char* tf_task_get_hitches(tf_task_t* task);
static JSValue _tf_task_getFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_task_t* task = JS_GetContextOpaque(context);
@ -1696,9 +1694,6 @@ void tf_task_activate(tf_task_t* task)
JS_FreeAtom(context, atom);
JSValue tildefriends = JS_NewObject(context);
JS_SetPropertyStr(context, tildefriends, "ssb_port", JS_NewInt32(context, task->_ssb_port));
JS_SetPropertyStr(context, tildefriends, "http_port", JS_NewInt32(context, task->_http_port));
JS_SetPropertyStr(context, tildefriends, "https_port", JS_NewInt32(context, task->_https_port));
JSValue args = JS_NewObject(context);
JS_SetPropertyStr(context, tildefriends, "args", args);
if (task->_args)
@ -1746,18 +1741,24 @@ void tf_task_activate(tf_task_t* task)
tf_database_register(context);
tf_httpd_register(context);
task->_ssb = tf_ssb_create(&task->_loop, task->_context, task->_db_path);
task->_ssb = tf_ssb_create(&task->_loop, task->_context, task->_db_path, task->_network_key);
tf_ssb_set_trace(task->_ssb, task->_trace);
tf_ssb_register(context, task->_ssb);
tf_ssb_set_hitch_callback(task->_ssb, _tf_task_record_hitch, task);
int actual_ssb_port = task->_ssb_port;
if (task->_ssb_port)
{
tf_ssb_broadcast_listener_start(task->_ssb, false);
tf_ssb_broadcast_sender_start(task->_ssb);
tf_ssb_server_open(task->_ssb, task->_ssb_port);
actual_ssb_port = tf_ssb_server_open(task->_ssb, task->_ssb_port);
}
JS_SetPropertyStr(context, tildefriends, "ssb_port", JS_NewInt32(context, actual_ssb_port));
JS_SetPropertyStr(context, tildefriends, "http_port", JS_NewInt32(context, task->_http_port));
JS_SetPropertyStr(context, tildefriends, "https_port", JS_NewInt32(context, task->_https_port));
JS_SetPropertyStr(context, global, "getStats", JS_NewCFunction(context, _tf_task_getStats, "getStats", 0));
}
else
@ -2000,6 +2001,11 @@ tf_task_t* tf_task_get(JSContext* context)
return JS_GetContextOpaque(context);
}
void tf_task_set_ssb_network_key(tf_task_t* task, const char* network_key)
{
task->_network_key = network_key;
}
void tf_task_set_ssb_port(tf_task_t* task, int port)
{
task->_ssb_port = port;

View File

@ -13,19 +13,30 @@
#include "quickjs.h"
/** An event loop. */
typedef struct uv_loop_s uv_loop_t;
/** A timer. */
typedef struct uv_timer_s uv_timer_t;
/** A task identifier. */
typedef int taskid_t;
/** A promise identifier. */
typedef int promiseid_t;
/** An exported function identifier. */
typedef int exportid_t;
/** A handle to a task. */
typedef struct _tf_taskstub_t tf_taskstub_t;
/** A task. */
typedef struct _tf_task_t tf_task_t;
/** A trace instance. */
typedef struct _tf_trace_t tf_trace_t;
/** An SSB instance. */
typedef struct _tf_ssb_t tf_ssb_t;
/** The fixed ID of the parent task. */
static const taskid_t k_task_parent_id = 0;
/** A message type that can be sent between tasks. */
typedef enum _tf_task_message_t
{
kResolvePromise,
@ -44,50 +55,282 @@ typedef enum _tf_task_message_t
kPrint,
} tf_task_message_t;
/**
** Create a task.
** @return A new task.
*/
tf_task_t* tf_task_create();
/**
** Configure a task from a file descriptor. Typically a pipe to the parent
** task's process.
** @param task The task to configure.
** @param fd The file descriptor.
*/
void tf_task_configure_from_fd(tf_task_t* task, int fd);
/**
** Set the SSB network key.
** @param task The task.
** @param network_key The network key.
*/
void tf_task_set_ssb_network_key(tf_task_t* task, const char* network_key);
/**
** Set the port number on which to run an SSB secure handshake server.
** @param task The task.
** @param port The port number or 0 to disable.
*/
void tf_task_set_ssb_port(tf_task_t* task, int port);
/**
** Set the port number on which to run an HTTP server.
** @param task The task.
** @param port The port number of 0 to disable.
*/
void tf_task_set_http_port(tf_task_t* task, int port);
/**
** Set the port number on which to run an HTTPS server.
** @param task The task.
** @param port The port number of 0 to disable.
*/
void tf_task_set_https_port(tf_task_t* task, int port);
/**
** Set the path to the SQLite database.
** @param task The task.
** @param path The database path.
*/
void tf_task_set_db_path(tf_task_t* task, const char* path);
/**
** Set the path to a zip file from which to load all static data.
** @param task The task.
** @param path The zip file path or NULL.
*/
void tf_task_set_zip_path(tf_task_t* task, const char* path);
/**
** Get the path to the zipp file being used for static data.
** @param task The task.
** @return The zip file path or NULL.
*/
const char* tf_task_get_zip_path(tf_task_t* task);
/**
** Set arbitrary named arguments that will be made available to the task.
** @param task The task.
** @param args A string of the form "key=value,other_key=other_value,..."
*/
void tf_task_set_args(tf_task_t* task, const char* args);
/**
** Get whether this instance is configure to run in a single process.
** @param task The running task.
** @return true if all tasks are running in a single process.
*/
bool tf_task_get_one_proc(tf_task_t* task);
/**
** Set whether all tasks should run in a single process. Only supported to
** appease Apple's limitations.
** @param task The running task.
** @param one_proc True if subprocesses should not be used.
*/
void tf_task_set_one_proc(tf_task_t* task, bool one_proc);
/**
** Start a task running its script.
** @param task The task.
*/
void tf_task_activate(tf_task_t* task);
/**
** Update a task until it is done or stopped.
** @param task The task.
*/
void tf_task_run(tf_task_t* task);
/**
** Run a script from file on disk.
** @param task The task.
** @param file The path to the script file to run.
** @return 0 if there was a problem or 1 if the script was started.
*/
int tf_task_execute(tf_task_t* task, const char* file);
/**
** Set a task as trusted or untrusted. Trusted tasks have more interface exposed to them.
** @param task The task.
** @param trusted true if the task is trusted.
*/
void tf_task_set_trusted(tf_task_t* task, bool trusted);
/**
** Get the JS context from a task.
** @param task The task.
** @return The context.
*/
JSContext* tf_task_get_context(tf_task_t* task);
/**
** Destroy a task.
** @param task The task.
*/
void tf_task_destroy(tf_task_t* task);
/**
** Convert a function to an integer handle that can be passed across processes.
** @param task The running task.
** @param to The task stub to which the handle will be passed.
** @param function The functoin to export.
** @return A handle representing the function.
*/
exportid_t tf_task_export_function(tf_task_t* task, tf_taskstub_t* to, JSValue function);
/**
** Create a function that can be called from a handle to an exported function
** from another task.
** @param task The running task.
** @param stub_id The task stub from which the function was exported.
** @param export_id The handle to the function.
** @return A function that, when called, invokes the corresponding function in
** the remote task.
*/
JSValue tf_task_add_import(tf_task_t* task, taskid_t stub_id, exportid_t export_id);
/**
** Get the event loop from a task.
** @param task The task.
** @return The loop.
*/
uv_loop_t* tf_task_get_loop(tf_task_t* task);
/**
** Get the task from a JS context.
** @param context The context.
** @return The task.
*/
tf_task_t* tf_task_get(JSContext* context);
/**
** Get the trace instance from a task.
** @param task The task.
** @return The trace instance.
*/
tf_trace_t* tf_task_get_trace(tf_task_t* task);
/**
** Get the SSB instance from a task.
** @param task The task.
** @return The SSB instance.
*/
tf_ssb_t* tf_task_get_ssb(tf_task_t* task);
/**
** Get the name of a task.
** @param task The task.
** @return The task's name as derived from the script it is running.
*/
const char* tf_task_get_name(tf_task_t* task);
/**
** Print through a task's parent.
** @param task The running task.
** @param argc The number of arguments to print.
** @param argv The arguments to print.
*/
void tf_task_print(tf_task_t* task, int argc, JSValueConst* argv);
/**
** Allocate a promise object.
** @param task The running task.
** @param[out] out_promise The promise that was allocated.
** @return The promise JS object.
*/
JSValue tf_task_allocate_promise(tf_task_t* task, promiseid_t* out_promise);
/**
** Reject a promise.
** @param task The running task.
** @param promise The promise to reject.
** @param error The value with which to reject the promise.
*/
void tf_task_reject_promise(tf_task_t* task, promiseid_t promise, JSValue error);
/**
** Resolve a promise.
** @param task The running task.
** @param promise The promise to resolve.
** @param result The value with which to resolve the promise.
*/
void tf_task_resolve_promise(tf_task_t* task, promiseid_t promise, JSValue result);
/**
** Send a message referencing a promise across a packet stream.
** @param from The task originating the message.
** @param to The task handle receiving the message.
** @param type The message type.
** @param promise The promise.
** @param payload The content of the message.
*/
void tf_task_send_promise_message(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t type, promiseid_t promise, JSValue payload);
/**
** Have a task handle a message from a packaet stream.
** @param packetType The type of the message.
** @param begin The data.
** @param length The size of the data.
** @param userData The task stub from which the packet was received.
*/
void tf_task_on_receive_packet(int packetType, const char* begin, size_t length, void* userData);
/**
** Generate an unused task identifier representing the task stub from the running task.
** @param task The running task.
** @param stub A handle to the task requesting an identifier.
** @return The new identifier.
*/
taskid_t tf_task_allocate_task_id(tf_task_t* task, tf_taskstub_t* stub);
/**
** Remove a task stub from a task.
** @param task The parent task.
** @param child The task handle to remove.
*/
void tf_task_remove_child(tf_task_t* task, tf_taskstub_t* child);
void tf_task_report_error(tf_task_t* task, JSValue error);
JSValue tf_try_get_typed_array_buffer(JSContext* ctx, JSValueConst obj, size_t* pbyte_offset, size_t* pbyte_length, size_t* pbytes_per_element);
uint8_t* tf_try_get_array_buffer(JSContext* ctx, size_t* psize, JSValueConst obj);
/**
** Send an error to the parent task.
** @param task The current task.
** @param error The potential error.
** @return true If the object was an error or exception and it was passed to
** the parent task.
*/
bool tf_task_send_error_to_parent(tf_task_t* task, JSValue error);
/**
** Get a report of recent disconnections.
** @param task The task.
** @return A JSON representation of recent disconnections that must be freed
** with tf_free().
*/
char* tf_task_get_disconnections(tf_task_t* task);
/**
** Get a report of miscellaneous debug information.
** @param task The task.
** @return A JSON representation of various debug information that must be
** freed with tf_free().
*/
char* tf_task_get_debug(tf_task_t* task);
/**
** Get a report of hitches that occurred.
** @param task The task.
** @return A JSON report of recent hitches that must be freed with tf_free().
*/
char* tf_task_get_hitches(tf_task_t* task);
/** @} */

View File

@ -10,20 +10,76 @@
#include "quickjs.h"
#include "uv.h"
/** A task identifier. */
typedef int taskid_t;
/** A packet stream. */
typedef struct _tf_packetstream_t tf_packetstream_t;
/** A task. */
typedef struct _tf_task_t tf_task_t;
/** A handle to another task. */
typedef struct _tf_taskstub_t tf_taskstub_t;
/** Initialize task stub. Call before using the rest. */
void tf_taskstub_startup();
/**
** Register the task stub script interface.
** @param context The JS context.
** @return The task stub constructor.
*/
JSValue tf_taskstub_register(JSContext* context);
/**
** Get a unique identifier for the task stub.
** @param stub The task stub.
** @return An identifier for the stub.
*/
taskid_t tf_taskstub_get_id(const tf_taskstub_t* stub);
/**
** Get the JS object representing the task stub.
** @param stub The task stub.
** @return The JS object.
*/
JSValue tf_taskstub_get_task_object(const tf_taskstub_t* stub);
/**
** Get the packet stream that can be used to communicate with the task stub.
** @param stub The task stub.
** @return The packet stream.
*/
tf_packetstream_t* tf_taskstub_get_stream(const tf_taskstub_t* stub);
/**
** Get the task owning the task stub.
** @param stub The task stub.
** @return The task from which the task stub was created.
*/
tf_task_t* tf_taskstub_get_owner(const tf_taskstub_t* stub);
/**
** Create a task stub representing the parent task of the running process.
** @param task The running task.
** @param file A file descriptor of a pipe connected to a parent process task.
** @return The created task stub.
*/
tf_taskstub_t* tf_taskstub_create_parent(tf_task_t* task, uv_file file);
/**
** Report an error to a task stub.
** @param stub The stub to which to report th eerror.
** @param error The error to report.
*/
void tf_taskstub_on_error(tf_taskstub_t* stub, JSValue error);
/**
** Print to a task stub.
** @param stub The task stub to which to print.
** @param arguments The values to print.
*/
void tf_taskstub_on_print(tf_taskstub_t* stub, JSValue arguments);
/** @} */

View File

@ -58,6 +58,7 @@ static void _test_nop(const tf_test_options_t* options)
assert(WEXITSTATUS(result) == 0);
}
#if !defined(__HAIKU__)
static void _test_sandbox(const tf_test_options_t* options)
{
_write_file("out/test.js",
@ -91,6 +92,7 @@ static void _test_sandbox(const tf_test_options_t* options)
unlink("out/test.js");
unlink("out/child.js");
}
#endif
static void _test_child(const tf_test_options_t* options)
{
@ -881,7 +883,9 @@ void tf_tests(const tf_test_options_t* options)
_tf_test_run(options, "ssb_id", tf_ssb_test_id_conversion, false);
_tf_test_run(options, "ssb_following", tf_ssb_test_following, false);
_tf_test_run(options, "nop", _test_nop, false);
#if !defined(__HAIKU__)
_tf_test_run(options, "sandbox", _test_sandbox, false);
#endif
_tf_test_run(options, "child", _test_child, false);
_tf_test_run(options, "promise", _test_promise, false);
_tf_test_run(options, "promise_remote_throw", _test_promise_remote_throw, false);

View File

@ -6,12 +6,21 @@
** @{
*/
/**
** Options to control how tests are run.
*/
typedef struct _tf_test_options_t
{
/** The path to the Tilde Friends executable, in order to run subprocesses. */
const char* exe_path;
/** A comma-separated list of tests to run, or NULL. */
const char* tests;
} tf_test_options_t;
/**
** Run tests.
** @param options Test options.
*/
void tf_tests(const tf_test_options_t* options);
/** @} */

128
src/tls.h
View File

@ -9,9 +9,19 @@
#include <stdbool.h>
#include <stddef.h>
/**
** A TLS context. May have many tf_tls_session_t instances.
*/
typedef struct _tf_tls_context_t tf_tls_context_t;
/**
** A TLS session. Belongs to one tf_tls_context_t and represents a single connection.
*/
typedef struct _tf_tls_session_t tf_tls_session_t;
/**
** The state of a TLS handshake.
*/
typedef enum _tf_tls_handshake_t
{
k_tls_handshake_done,
@ -19,31 +29,149 @@ typedef enum _tf_tls_handshake_t
k_tls_handshake_failed,
} tf_tls_handshake_t;
/**
** Possible error statuses from tf_tls_session_read_plain.
*/
typedef enum _tf_tls_read_t
{
k_tls_read_zero = -1,
k_tls_read_failed = -2,
} tf_tls_read_t;
/**
** Create a TLS context. Clean up with tf_tls_context_destroy().
** @return A new TLS context.
*/
tf_tls_context_t* tf_tls_context_create();
/**
** Set the TLS context's server certificate.
** @param context The TLS context.
** @param certificate The certificate in PEM format.
** @return true if set successfully.
*/
bool tf_tls_context_set_certificate(tf_tls_context_t* context, const char* certificate);
/**
** Set the TLS context's server certificate's private key.
** @param context The TLS context.
** @param private_key The private key in PEM format.
** @return true if set successfully.
*/
bool tf_tls_context_set_private_key(tf_tls_context_t* context, const char* private_key);
/**
** Add a trusted certificate.
** @param context The TLS context.
** @param certificate The certificate in PEM format.
** @return true if the certificate was added to the trusted list successfully.
*/
bool tf_tls_context_add_trusted_certificate(tf_tls_context_t* context, const char* certificate);
/**
** Create a TLS session from a context. Once created, call
** tf_tls_session_handshake() until it returns k_tls_handshake_done. Call
** tf_tls_session_[read/write]_[plain/encrypted]() as data is available.
** @param context The TLS context. Clean up with tf_tls_session_destroy().
** @return A new TLS session.
*/
tf_tls_session_t* tf_tls_context_create_session(tf_tls_context_t* context);
/**
** Destroy a TLS context.
** @param context The TLS contextx created by tf_tls_context_create().
*/
void tf_tls_context_destroy(tf_tls_context_t* context);
/**
** Destroy a TLS session.
** @param session A TLS sesssion created by tf_tls_context_create_session().
*/
void tf_tls_session_destroy(tf_tls_session_t* session);
/**
** Set the remote hostname for a session.
** @param session The TLS session.
** @param hostname The hostname.
*/
void tf_tls_session_set_hostname(tf_tls_session_t* session, const char* hostname);
/**
** Begin an outgoing TLS session.
** @param session The TLS session.
*/
void tf_tls_session_start_accept(tf_tls_session_t* session);
/**
** Begin an incoming TLS session.
** @param session The TLS session.
*/
void tf_tls_session_start_connect(tf_tls_session_t* session);
/**
** Begin the clean shutdown process for a TLS session.
** @param session The TLS session.
*/
void tf_tls_session_shutdown(tf_tls_session_t* session);
/**
** Get the certificate from the remote end of a TLS session if available.
** @param session The TLS session.
** @param buffer A buffer to receive the certificate.
** @param bytes The size of the buffer.
** @return The size of the returned certificate, or -1 on failure.
*/
int tf_tls_session_get_peer_certificate(tf_tls_session_t* session, char* buffer, size_t bytes);
/**
** Update the TLS handshake. Call repeatedly as new data is available until it returns done.
** @param session The TLS session.
** @return The current state of the handshake process.
*/
tf_tls_handshake_t tf_tls_session_handshake(tf_tls_session_t* session);
/**
** Read decrypted data from the TLS session.
** @param session The TLS session.
** @param buffer A buffer to receive the data.
** @param bytes The size of the buffer.
** @return The number of bytes returned.
*/
int tf_tls_session_read_plain(tf_tls_session_t* session, char* buffer, size_t bytes);
/**
** Write unencrypted data to the TLS session.
** @param session The TLS session.
** @param buffer The data to encrypt.
** @param bytes The size of the data.
** @return 1 on success, 0 on failure.
*/
int tf_tls_session_write_plain(tf_tls_session_t* session, const char* buffer, size_t bytes);
/**
** Read encrypted data from the TLS session that needs to be sent.
** @param session The TLS session.
** @param buffer A buffer to receive the data.
** @param bytes The size of the buffer.
** @return The number of bytes returned.
*/
int tf_tls_session_read_encrypted(tf_tls_session_t* session, char* buffer, size_t bytes);
/**
** Write encrypted data to the TLS session.
** @param session The TLS session.
** @param buffer The encrypted data.
** @param bytes The number of bytes written.
*/
int tf_tls_session_write_encrypted(tf_tls_session_t* session, const char* buffer, size_t bytes);
/**
** Retrieve the last error from a TLS session.
** @param session The TLS session.
** @param buffer A buffer to receive the error text.
** @param bytes The size of the buffer.
** @return true if an error was retrieved.
*/
bool tf_tls_session_get_error(tf_tls_session_t* session, char* buffer, size_t bytes);
/** @} */

View File

@ -8,10 +8,30 @@
#include "quickjs.h"
/**
** A TLS context instance.
*/
typedef struct _tf_tls_context_t tf_tls_context_t;
/**
** Register TLS script interface.
** @param context The TLS context.
** @return the TlsContext constructor.
*/
JSValue tf_tls_context_register(JSContext* context);
/**
** Get a TLS context instance from its JS object.
** @param value A TlsContext JS object.
** @return The corresponding instance.
*/
tf_tls_context_t* tf_tls_context_get(JSValue value);
/**
** Get the number of active TLS context instances.
** @return The number of TlsContext objects created that have not been
** finalized.
*/
int tf_tls_context_get_count();
/** @} */

View File

@ -3,32 +3,103 @@
/**
** \defgroup trace Performance Tracing
** Generates trace output that is compatible with speedscope.app,
** chrome://tracing or ui.perfetto.dev for scrutining what each thread is doing
** for optimization purposes.
** chrome://tracing or ui.perfetto.dev for scrutinizing what each thread is
** doing for optimization purposes.
** @{
*/
#include <inttypes.h>
#include <stddef.h>
/**
** A trace instance.
*/
typedef struct _tf_trace_t tf_trace_t;
/**
** An SQLite database instance.
*/
typedef struct sqlite3 sqlite3;
/**
** Create a trace instance. Can be used to produce a Chrome trace event
** document for analyzing performance. Clean up with tf_trace_destroy().
** @return A trace instance.
*/
tf_trace_t* tf_trace_create();
/**
** Destroy a trace instance that was created with tf_trace_create().
*/
void tf_trace_destroy(tf_trace_t* trace);
/**
** Set the name of the current process.
** @param trace The trace instance.
** @param name The name of the process.
*/
void tf_trace_set_process_name(tf_trace_t* trace, const char* name);
/**
** Record counter values.
** @param trace The trace instance.
** @param name The counter group name.
** @param argc The number of counters.
** @param arg_names The counter names.
** @param arg_values The counter values.
*/
void tf_trace_counter(tf_trace_t* trace, const char* name, int argc, const char** arg_names, const int64_t* arg_values);
/**
** Begin tracing a time interval. End with tf_trace_end().
** @param trace The trace instance.
** @param name The interval name.
*/
void tf_trace_begin(tf_trace_t* trace, const char* name);
/**
** End tracing the next time interval previously started with tf_trace_begin().
** @param trace The trace instance.
*/
void tf_trace_end(tf_trace_t* trace);
/**
** Export all currently stored trace data to a string.
** @param trace The trace instance.
** @return A string representation of the trace data.
*/
char* tf_trace_export(tf_trace_t* trace);
/**
** A callback to collect trace data.
** @param trace The trace instance.
** @param buffer The trace data.
** @param size The size of the trace data.
** @param user_data User data registered with the callback.
*/
typedef void(tf_trace_write_callback_t)(tf_trace_t* trace, const char* buffer, size_t size, void* user_data);
/**
** Replace the trace recording behavior.
** @param trace The trace instance.
** @param callback A callback that will be called instead of collecting the trace data in a buffer.
** @param user_data User data to pass to the callback.
*/
void tf_trace_set_write_callback(tf_trace_t* trace, tf_trace_write_callback_t* callback, void* user_data);
/**
** Inject raw trace data.
** @param trace The trace instance.
** @param buffer The trace data.
** @param size The size of the trace data.
*/
void tf_trace_raw(tf_trace_t* trace, const char* buffer, size_t size);
/**
** Register for trace-worthy events from sqlite and record them going forward.
** @param trace The trace instance.
** @param sqlite The SQLite database.
*/
void tf_trace_sqlite(tf_trace_t* trace, sqlite3* sqlite);
/** @} */

View File

@ -46,10 +46,10 @@ static JSValue _util_utf8_decode(JSContext* context, JSValueConst this_val, int
}
else
{
size_t offset;
size_t element_size;
size_t offset = 0;
size_t element_size = 0;
JSValue buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
size_t size;
size_t size = 0;
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &size, buffer);

View File

@ -1,2 +1,2 @@
#define VERSION_NUMBER "0.0.16"
#define VERSION_NAME "Now with 38% more process."
#define VERSION_NUMBER "0.0.17-wip"
#define VERSION_NAME "Please enjoy responsibly."

View File

@ -1,16 +1,20 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import time
if sys.platform == 'haiku1':
print('Automation tests are disabled on Haiku.')
exit(0)
from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.ui import WebDriverWait
import os
import subprocess
import sys
import time
def exists_in_shadow_root(shadow_root, by, value):
return lambda driver: shadow_root.find_element(by, value)