55 Commits

Author SHA1 Message Date
3c0b680b8e Let's release 0.0.17. 2024-03-27 18:59:40 -04:00
895356897b archive whichever branch. 2024-03-25 18:10:07 -04:00
9164be2f37 Fix loading from not standalone zip. 2024-03-25 16:34:27 -04:00
5385264f94 Fix an http use after free during shutdown. 2024-03-25 16:31:09 -04:00
610e756c07 Ever closer to the elusive clean http shutdown. 2024-03-25 16:23:45 -04:00
15c9f8f458 Rudimentary support for building the executable with data attached. Pushed some things around in the makefile to fix issues along the way. 2024-03-25 13:50:17 -04:00
fb704a5b83 I will get better at keeping this tree clean. 2024-03-25 12:56:33 -04:00
fdda628be8 Fix paths in the source tarball. 2024-03-20 20:43:41 -04:00
2b45d8aa05 Doh. Never mean to add that. 2024-03-20 20:37:52 -04:00
0e2fc65301 Document run -k flag. 2024-03-20 20:33:23 -04:00
e8ef7e74de Fixed a leak in JS blob store. 2024-03-18 12:46:12 -04:00
c32e1b9583 http request cleanup crash fix. 2024-03-18 16:34:07 +00:00
9d0f6ec155 Fix the sneaker app RE: JSONB. 2024-03-18 12:32:40 -04:00
855d603795 docs + prettier 2024-03-17 13:21:33 -04:00
af25782185 More http/request shutdown issues. 2024-03-17 12:38:37 -04:00
e5ba51b80a Chasing a leak that looks like an EBT clock. Deleted some unneeded code and adding a missing JS free. 2024-03-17 13:44:05 +00:00
5e240de677 Fix requesting blobs from blob_wants. ids were trucated. Yikes. 2024-03-17 09:16:06 -04:00
418cfac0e3 Add a stock app with local room connection info. 2024-03-14 00:43:11 +00:00
9d09607013 Update CodeMirror. 2024-03-13 20:23:48 -04:00
eddf25b622 Give libuv the same download treatment as sqlite. 2024-03-13 19:53:57 -04:00
537a8654fa Rename sequence_before_author => flags. 2024-03-13 19:40:09 -04:00
9de33d06d2 Specifying -fsanitize=... early seems good. 2024-03-13 18:26:24 -04:00
0e5f320664 sqlite 3.45.2. 2024-03-13 12:30:14 -04:00
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. 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
67 changed files with 2808 additions and 478 deletions

8
.gitignore vendored

@ -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

@ -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`.

@ -3,9 +3,12 @@
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
VERSION_NAME := Please enjoy responsibly.
SQLITE_URL := https://www.sqlite.org/2024/sqlite-amalgamation-3450200.zip
LIBUV_URL := https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
PROJECT = tildefriends
BUILD_DIR ?= out
@ -55,7 +58,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
@ -209,14 +212,14 @@ $(IOSSIM_TARGETS): LDFLAGS += -Ldeps/openssl/ios/iossimulator-xcrun/usr/local/li
ifeq ($(UNAME_M),x86_64)
ifneq ($(UNAME_S),Haiku)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
endif
ifeq ($(UNAME_M),aarch64)
debug: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
debug: LDFLAGS += -fsanitize=address -fsanitize=undefined
out/debug/tildefriends: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-common
out/debug/tildefriends: LDFLAGS += -fsanitize=address -fsanitize=undefined
endif
get_objs = \
@ -575,7 +578,7 @@ $(MINIUNZIP_OBJS): CFLAGS += \
LDFLAGS += \
-pthread \
-lm
debug release $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
$(LINUX_TARGETS) $(MACOS_TARGETS) $(IOS_TARGETS) $(IOSSIM_TARGETS): LDFLAGS += \
-lssl \
-lcrypto
ifneq ($(UNAME_S),Haiku)
@ -725,8 +728,8 @@ out/apk/TildeFriends-arm-%.unsigned.apk:
@cp out/apk/res.apk $@.zip
@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 $@
@zip -u $@.zip -q -9 $(RAW_FILES)
@$(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/
@ -738,14 +741,19 @@ out/apk/TildeFriends-x86-%.unsigned.apk:
@cp out/apk/res.apk $@.zip
@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 $@
@zip -u $@.zip -q -9 $(RAW_FILES)
@$(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
@ -761,10 +769,10 @@ out/%.app/tildefriends.png: src/ios/tildefriends.png
@mkdir -p $(dir $@)
@cp -v $< $@
out/%/data.zip: $(RAW_FILES)
out/data.zip: $(RAW_FILES)
@zip -u $@ -q -9 $(RAW_FILES)
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/tildefriends-%.app/data.zip
out/tildefriends-%.app/tildefriends: out/%/tildefriends out/tildefriends-%.app/Info.plist out/tildefriends-%.app/tildefriends.png out/data.zip
@mkdir -p $(dir $@)
@cp -v $< $@
ifeq ($(HAVE_LINUX_IOS),1)
@ -779,6 +787,12 @@ out/tildefriends-%.ipa: out/tildefriends-ios%.app/tildefriends
@cd $@.tmp/ && zip -u ../../$@ -q -9 -r ./
@rm -rf $@.tmp/
out/%/tildefriends.standalone: out/%/tildefriends out/data.zip
@echo "[standalone] $@"
@cat $< out/data.zip > $@
@chmod +x $@
iossimdebug-app: out/tildefriends-iossimdebug.app/tildefriends
iossimrelease-app: out/tildefriends-iossimrelease.app/tildefriends
iosdebug-app: out/tildefriends-iosdebug.app/tildefriends
@ -802,11 +816,13 @@ apklog:
fetchdeps:
@echo "[fetch] libuv"
@test -f out/deps/libuv.tar.gz || (mkdir -p out/deps/ && curl -q https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz -o out/deps/libuv.tar.gz)
@test -d deps/libuv/ || (mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
@test -f out/deps/libuv.tar.gz && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (mkdir -p out/deps/ && curl -q $(LIBUV_URL) -o out/deps/libuv.tar.gz)
@test -d deps/libuv/ && test "$$(cat out/deps/libuv.txt 2>/dev/null)" = $(LIBUV_URL) || (rm -rf deps/libuv/ && mkdir -p deps/libuv/ && tar -C deps/libuv/ -m --strip=1 -xf out/deps/libuv.tar.gz)
@echo -n $(LIBUV_URL) > out/deps/libuv.txt
@echo "[fetch] sqlite"
@test -f out/deps/sqlite.zip || (mkdir -p out/deps/ && curl -q https://www.sqlite.org/2024/sqlite-amalgamation-3450100.zip -o out/deps/sqlite.zip)
@test -d deps/sqlite/ || (mkdir -p deps/sqlite/ && unzip -qDj -d deps/sqlite/ out/deps/sqlite.zip)
@test -f out/deps/sqlite.zip && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p out/deps/ && curl -q $(SQLITE_URL) -o out/deps/sqlite.zip)
@test -d deps/sqlite/ && test "$$(cat out/deps/sqlite.txt 2>/dev/null)" = $(SQLITE_URL) || (mkdir -p deps/sqlite/ && unzip -qDjo -d deps/sqlite/ out/deps/sqlite.zip)
@echo -n $(SQLITE_URL) > out/deps/sqlite.txt
@echo "[fetch] prettier"
@test -f deps/prettier/standalone.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/standalone.mjs
@test -f deps/prettier/html.mjs || curl -q --create-dirs -O --output-dir deps/prettier/ https://cdn.jsdelivr.net/npm/prettier@3.2.5/plugins/html.mjs
@ -841,7 +857,7 @@ dist: release-apk iosrelease-ipa
@echo [archive] dist/tildefriends-$(VERSION_NUMBER).tar.xz
@rm -rf out/tildefriends-$(VERSION_NUMBER)
@mkdir -p dist/ out/tildefriends-$(VERSION_NUMBER)
@git archive main | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@git archive HEAD | tar -x -C out/tildefriends-$(VERSION_NUMBER)
@tar \
--exclude=apps/welcome* \
--exclude=deps/libbacktrace/Isaac.Newton-Opticks.txt \
@ -857,11 +873,13 @@ dist: release-apk iosrelease-ipa
--exclude=deps/sqlite/shell.c \
--exclude=deps/zlib/contrib/vstudio \
--exclude=deps/zlib/doc \
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz out/tildefriends-$(VERSION_NUMBER)
-caf dist/tildefriends-$(VERSION_NUMBER).tar.xz \
-C 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 +895,10 @@ format:
@clang-format -i $(wildcard src/*.c src/*.h src/*.m)
.PHONY: format
prettier:
@npm run prettier
.PHONY: prettier
docs:
@doxygen
.PHONY: docs

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

@ -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) {

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

5
apps/room.json Normal file

@ -0,0 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📦",
"previous": "&IU+TwyM7TznD8NBfnw7tgW2zxVlMqTVxSqWFjuosLwo=.sha256"
}

13
apps/room/app.js Normal file

@ -0,0 +1,13 @@
async function main() {
let host = core.url.match(/.*\/\/(.*?)\//)[1];
let id = (await ssb.getServerIdentity()).substring(1);
let room = `net:${host}:${ssb.port}~shs:${id}:SSB+Room+SK3TLYC2T86EHQCUHBUHASCASE18JBV24=`;
await app.setDocument(`
<body style="color: #fff">
<h1>Server</h1>
<div>The local server address is:</div>
<div><input type="text" readonly value="${room}" style="width: 100%"></input></div>
</body>
`);
}
main();

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

@ -38,10 +38,11 @@ class TfSneakerAppElement extends LitElement {
}
format_message(message) {
const k_flag_sequence_before_author = 1;
let out = {
previous: message.previous ?? null,
};
if (message.sequence_before_author) {
if (message.flags & k_flag_sequence_before_author) {
out.sequence = message.sequence;
out.author = message.author;
} else {
@ -188,7 +189,12 @@ class TfSneakerAppElement extends LitElement {
)[0].total;
while (true) {
let messages = await tfrpc.rpc.query(
'SELECT * FROM messages WHERE author = ? AND SEQUENCE > ? ORDER BY sequence LIMIT 100',
`
SELECT author, id, sequence, timestamp, hash, json(content) AS content, signature, flags
FROM messages
WHERE author = ? AND SEQUENCE > ?
ORDER BY sequence LIMIT 100
`,
[id, sequence]
);
if (messages?.length) {

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

@ -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

@ -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

@ -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}

@ -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

@ -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

@ -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

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

@ -1670,6 +1670,7 @@ function toggleVisibleWhitespace() {
*/
if (editor_style.innerHTML.length) {
editor_style.innerHTML = '';
window.localStorage.setItem('visible_whitespace', '0');
} else {
editor_style.innerHTML = css`
.cm-trailingSpace {
@ -1682,6 +1683,7 @@ function toggleVisibleWhitespace() {
content: unset !important;
}
`;
window.localStorage.setItem('visible_whitespace', '1');
}
}
@ -1726,4 +1728,7 @@ window.addEventListener('load', function () {
} else {
closeEditor();
}
if (window.localStorage.getItem('visible_whitespace') == '1') {
toggleVisibleWhitespace();
}
});

@ -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 &&

File diff suppressed because one or more lines are too long

176
deps/codemirror_src/package-lock.json generated vendored

@ -7,21 +7,21 @@
"dependencies": {
"@codemirror/lang-css": "^6.2.1",
"@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@rollup/plugin-node-resolve": "^15.2.3",
"codemirror": "^6.0.1",
"rollup": "^4.9.6"
"rollup": "^4.13.0"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.12.0.tgz",
"integrity": "sha512-r4IjdYFthwbCQyvqnSlx0WBHRHi8nBvU+WjJxFUij81qsBfhNudf/XKKmmC2j3m0LaOYUQTf3qiEK1J8lO1sdg==",
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.15.0.tgz",
"integrity": "sha512-G2Zm0mXznxz97JhaaOdoEG2cVupn4JjPaS4AcNvZzhOsnnG9YVN68VzfoUw6dYTsIxT6a/cmoFEN47KAWhXaOg==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
@ -147,9 +147,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.1.tgz",
"integrity": "sha512-sBfP4rniPBRQzNakwuQEqjEuiJDWJyF2kqLLqij4WXRoVwPPJfjx966Eq3F7+OPQxDtMt/Q9MWLoZLWjeveBlg==",
"version": "6.25.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.25.1.tgz",
"integrity": "sha512-2LXLxsQnHDdfGzDvjzAwZh2ZviNJm7im6tGpa0IONIDnFd8RZ80D2SNi8PDi6YjKcMoMRK20v6OmKIdsrwsyoQ==",
"dependencies": {
"@codemirror/state": "^6.4.0",
"style-mod": "^4.1.0",
@ -157,14 +157,14 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz",
"integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
@ -180,22 +180,22 @@
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
@ -205,9 +205,9 @@
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz",
"integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==",
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -343,9 +343,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
"integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz",
"integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==",
"cpu": [
"arm"
],
@ -355,9 +355,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
"integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz",
"integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==",
"cpu": [
"arm64"
],
@ -367,9 +367,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
"integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz",
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==",
"cpu": [
"arm64"
],
@ -379,9 +379,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
"integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz",
"integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==",
"cpu": [
"x64"
],
@ -391,9 +391,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
"integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz",
"integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==",
"cpu": [
"arm"
],
@ -403,9 +403,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
"integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz",
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==",
"cpu": [
"arm64"
],
@ -415,9 +415,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
"integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz",
"integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==",
"cpu": [
"arm64"
],
@ -427,9 +427,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
"integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz",
"integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==",
"cpu": [
"riscv64"
],
@ -439,9 +439,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
"integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz",
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==",
"cpu": [
"x64"
],
@ -451,9 +451,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
"integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz",
"integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==",
"cpu": [
"x64"
],
@ -463,9 +463,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
"integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz",
"integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==",
"cpu": [
"arm64"
],
@ -475,9 +475,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
"integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz",
"integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==",
"cpu": [
"ia32"
],
@ -487,9 +487,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
"integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz",
"integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==",
"cpu": [
"x64"
],
@ -597,9 +597,9 @@
}
},
"node_modules/hasown": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": {
"function-bind": "^1.1.2"
},
@ -679,9 +679,9 @@
}
},
"node_modules/rollup": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
"integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz",
"integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==",
"dependencies": {
"@types/estree": "1.0.5"
},
@ -693,19 +693,19 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.12.0",
"@rollup/rollup-android-arm64": "4.12.0",
"@rollup/rollup-darwin-arm64": "4.12.0",
"@rollup/rollup-darwin-x64": "4.12.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
"@rollup/rollup-linux-arm64-gnu": "4.12.0",
"@rollup/rollup-linux-arm64-musl": "4.12.0",
"@rollup/rollup-linux-riscv64-gnu": "4.12.0",
"@rollup/rollup-linux-x64-gnu": "4.12.0",
"@rollup/rollup-linux-x64-musl": "4.12.0",
"@rollup/rollup-win32-arm64-msvc": "4.12.0",
"@rollup/rollup-win32-ia32-msvc": "4.12.0",
"@rollup/rollup-win32-x64-msvc": "4.12.0",
"@rollup/rollup-android-arm-eabi": "4.13.0",
"@rollup/rollup-android-arm64": "4.13.0",
"@rollup/rollup-darwin-arm64": "4.13.0",
"@rollup/rollup-darwin-x64": "4.13.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.13.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.0",
"@rollup/rollup-linux-arm64-musl": "4.13.0",
"@rollup/rollup-linux-riscv64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-gnu": "4.13.0",
"@rollup/rollup-linux-x64-musl": "4.13.0",
"@rollup/rollup-win32-arm64-msvc": "4.13.0",
"@rollup/rollup-win32-ia32-msvc": "4.13.0",
"@rollup/rollup-win32-x64-msvc": "4.13.0",
"fsevents": "~2.3.2"
}
},
@ -764,9 +764,9 @@
}
},
"node_modules/style-mod": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
@ -780,9 +780,9 @@
}
},
"node_modules/terser": {
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.28.1.tgz",
"integrity": "sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==",
"version": "5.29.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz",
"integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",

@ -5,12 +5,12 @@
"dependencies": {
"@codemirror/lang-css": "^6.2.1",
"@codemirror/lang-html": "^6.4.8",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@rollup/plugin-node-resolve": "^15.2.3",
"codemirror": "^6.0.1",
"rollup": "^4.9.6"
"rollup": "^4.13.0"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4"

93
deps/sqlite/shell.c vendored

@ -580,6 +580,9 @@ zSkipValidUtf8(const char *z, int nAccept, long ccm);
#ifndef HAVE_CONSOLE_IO_H
# include "console_io.h"
#endif
#if defined(_MSC_VER)
# pragma warning(disable : 4204)
#endif
#ifndef SQLITE_CIO_NO_TRANSLATE
# if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT
@ -678,6 +681,10 @@ static short streamOfConsole(FILE *pf, /* out */ PerStreamTags *ppst){
# endif
}
# ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
# define ENABLE_VIRTUAL_TERMINAL_PROCESSING (0x4)
# endif
# if CIO_WIN_WC_XLATE
/* Define console modes for use with the Windows Console API. */
# define SHELL_CONI_MODE \
@ -1228,6 +1235,10 @@ SQLITE_INTERNAL_LINKAGE char* fGetsUtf8(char *cBuf, int ncMax, FILE *pfIn){
}
#endif /* !defined(SQLITE_CIO_NO_TRANSLATE) */
#if defined(_MSC_VER)
# pragma warning(default : 4204)
#endif
#undef SHELL_INVALID_FILE_PTR
/************************* End ../ext/consio/console_io.c ********************/
@ -20619,6 +20630,7 @@ static void exec_prepared_stmt_columnar(
rc = sqlite3_step(pStmt);
if( rc!=SQLITE_ROW ) return;
nColumn = sqlite3_column_count(pStmt);
if( nColumn==0 ) goto columnar_end;
nAlloc = nColumn*4;
if( nAlloc<=0 ) nAlloc = 1;
azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
@ -20704,7 +20716,6 @@ static void exec_prepared_stmt_columnar(
if( n>p->actualWidth[j] ) p->actualWidth[j] = n;
}
if( seenInterrupt ) goto columnar_end;
if( nColumn==0 ) goto columnar_end;
switch( p->cMode ){
case MODE_Column: {
colSep = " ";
@ -25553,16 +25564,15 @@ static int do_meta_command(char *zLine, ShellState *p){
#ifndef SQLITE_SHELL_FIDDLE
if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
char *zTable = 0; /* Insert data into this table */
char *zSchema = 0; /* within this schema (may default to "main") */
char *zSchema = 0; /* Schema of zTable */
char *zFile = 0; /* Name of file to extra content from */
sqlite3_stmt *pStmt = NULL; /* A statement */
int nCol; /* Number of columns in the table */
int nByte; /* Number of bytes in an SQL string */
i64 nByte; /* Number of bytes in an SQL string */
int i, j; /* Loop counters */
int needCommit; /* True to COMMIT or ROLLBACK at end */
int nSep; /* Number of bytes in p->colSeparator[] */
char *zSql; /* An SQL statement */
char *zFullTabName; /* Table name with schema if applicable */
char *zSql = 0; /* An SQL statement */
ImportCtx sCtx; /* Reader context */
char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
int eVerbose = 0; /* Larger for more console output */
@ -25696,24 +25706,14 @@ static int do_meta_command(char *zLine, ShellState *p){
while( (nSkip--)>0 ){
while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
}
if( zSchema!=0 ){
zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
}else{
zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
}
zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
if( zSql==0 || zFullTabName==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
nByte = strlen30(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){
/* Table does not exist. Create it. */
sqlite3 *dbCols = 0;
char *zRenames = 0;
char *zColDefs;
zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
zSchema ? zSchema : "main", zTable);
while( xRead(&sCtx) ){
zAutoColumn(sCtx.z, &dbCols, 0);
if( sCtx.cTerm!=sCtx.cColSep ) break;
@ -25728,34 +25728,50 @@ static int do_meta_command(char *zLine, ShellState *p){
assert(dbCols==0);
if( zColDefs==0 ){
eputf("%s: empty file\n", sCtx.zFile);
import_fail:
sqlite3_free(zCreate);
sqlite3_free(zSql);
sqlite3_free(zFullTabName);
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
if( zCreate==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
if( eVerbose>=1 ){
oputf("%s\n", zCreate);
}
rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
if( rc ){
eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
goto import_fail;
}
sqlite3_free(zCreate);
zCreate = 0;
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
if( rc ){
eputf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
}
zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);",
zTable, zSchema);
if( zSql==0 ){
import_cleanup(&sCtx);
shell_out_of_memory();
}
nByte = strlen(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
zSql = 0;
if( rc ){
if (pStmt) sqlite3_finalize(pStmt);
eputf("Error: %s\n", sqlite3_errmsg(p->db));
goto import_fail;
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
if( sqlite3_step(pStmt)==SQLITE_ROW ){
nCol = sqlite3_column_int(pStmt, 0);
}else{
nCol = 0;
}
sqlite3_free(zSql);
nCol = sqlite3_column_count(pStmt);
sqlite3_finalize(pStmt);
pStmt = 0;
if( nCol==0 ) return 0; /* no columns, no error */
@ -25764,7 +25780,12 @@ static int do_meta_command(char *zLine, ShellState *p){
import_cleanup(&sCtx);
shell_out_of_memory();
}
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
if( zSchema ){
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?",
zSchema, zTable);
}else{
sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
}
j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
@ -25776,13 +25797,15 @@ static int do_meta_command(char *zLine, ShellState *p){
oputf("Insert using: %s\n", zSql);
}
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
zSql = 0;
if( rc ){
eputf("Error: %s\n", sqlite3_errmsg(p->db));
if (pStmt) sqlite3_finalize(pStmt);
goto import_fail;
import_cleanup(&sCtx);
rc = 1;
goto meta_command_exit;
}
sqlite3_free(zSql);
sqlite3_free(zFullTabName);
needCommit = sqlite3_get_autocommit(p->db);
if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
do{

295
deps/sqlite/sqlite3.c vendored

@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
** version 3.45.1. By combining all the individual C code files into this
** version 3.45.2. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
** e876e51a0ed5c5b3126f52e532044363a014.
** d8cd6d49b46a395b13955387d05e9e1a2a47.
*/
#define SQLITE_CORE 1
#define SQLITE_AMALGAMATION 1
@ -459,9 +459,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.45.1"
#define SQLITE_VERSION_NUMBER 3045001
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
#define SQLITE_VERSION "3.45.2"
#define SQLITE_VERSION_NUMBER 3045002
#define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -733,6 +733,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not modify the SQL statement text passed into
** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not dereference the arrays or string pointers
** passed as the 3rd and 4th callback parameters after it returns.
** </ul>
*/
SQLITE_API int sqlite3_exec(
@ -15097,6 +15099,7 @@ SQLITE_PRIVATE u32 sqlite3TreeTrace;
** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing
** 0x00020000 Transform DISTINCT into GROUP BY
** 0x00040000 SELECT tree dump after all code has been generated
** 0x00080000 NOT NULL strength reduction
*/
/*
@ -19346,6 +19349,7 @@ struct NameContext {
#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */
#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */
#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */
#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */
#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */
/*
@ -19369,6 +19373,7 @@ struct Upsert {
Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */
u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */
u8 isDup; /* True if 2nd or later with same pUpsertIdx */
/* Above this point is the parse tree for the ON CONFLICT clauses.
** The next group of fields stores intermediate data. */
void *pToFree; /* Free memory when deleting the Upsert object */
@ -21444,7 +21449,7 @@ SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8);
SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*);
SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*);
SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*);
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*);
SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*);
SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*);
@ -31309,6 +31314,7 @@ SQLITE_API void sqlite3_str_vappendf(
if( xtype==etFLOAT ){
iRound = -precision;
}else if( xtype==etGENERIC ){
if( precision==0 ) precision = 1;
iRound = precision;
}else{
iRound = precision+1;
@ -35199,6 +35205,9 @@ do_atof_calc:
u64 s2;
rr[0] = (double)s;
s2 = (u64)rr[0];
#if defined(_MSC_VER) && _MSC_VER<1700
if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); }
#endif
rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
if( e>0 ){
while( e>=100 ){
@ -35641,7 +35650,7 @@ SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRou
assert( p->n>0 );
assert( p->n<sizeof(p->zBuf) );
p->iDP = p->n + exp;
if( iRound<0 ){
if( iRound<=0 ){
iRound = p->iDP - iRound;
if( iRound==0 && p->zBuf[i+1]>='5' ){
iRound = 1;
@ -53262,6 +53271,14 @@ SQLITE_API unsigned char *sqlite3_serialize(
pOut = 0;
}else{
sz = sqlite3_column_int64(pStmt, 0)*szPage;
if( sz==0 ){
sqlite3_reset(pStmt);
sqlite3_exec(db, "BEGIN IMMEDIATE; COMMIT;", 0, 0, 0);
rc = sqlite3_step(pStmt);
if( rc==SQLITE_ROW ){
sz = sqlite3_column_int64(pStmt, 0)*szPage;
}
}
if( piSize ) *piSize = sz;
if( mFlags & SQLITE_SERIALIZE_NOCOPY ){
pOut = 0;
@ -77088,7 +77105,10 @@ static int fillInCell(
n = nHeader + nPayload;
testcase( n==3 );
testcase( n==4 );
if( n<4 ) n = 4;
if( n<4 ){
n = 4;
pPayload[nPayload] = 0;
}
*pnSize = n;
assert( nSrc<=nPayload );
testcase( nSrc<nPayload );
@ -79534,7 +79554,10 @@ SQLITE_PRIVATE int sqlite3BtreeInsert(
if( flags & BTREE_PREFORMAT ){
rc = SQLITE_OK;
szNew = p->pBt->nPreformatSize;
if( szNew<4 ) szNew = 4;
if( szNew<4 ){
szNew = 4;
newCell[3] = 0;
}
if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){
CellInfo info;
pPage->xParseCell(pPage, newCell, &info);
@ -88379,6 +88402,23 @@ static void serialGet(
pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real;
}
}
static int serialGet7(
const unsigned char *buf, /* Buffer to deserialize from */
Mem *pMem /* Memory cell to write value into */
){
u64 x = FOUR_BYTE_UINT(buf);
u32 y = FOUR_BYTE_UINT(buf+4);
x = (x<<32) + y;
assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 );
swapMixedEndianFloat(x);
memcpy(&pMem->u.r, &x, sizeof(x));
if( IsNaN(x) ){
pMem->flags = MEM_Null;
return 1;
}
pMem->flags = MEM_Real;
return 0;
}
SQLITE_PRIVATE void sqlite3VdbeSerialGet(
const unsigned char *buf, /* Buffer to deserialize from */
u32 serial_type, /* Serial type to deserialize */
@ -89058,7 +89098,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}else if( serial_type==0 ){
rc = -1;
}else if( serial_type==7 ){
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
serialGet7(&aKey1[d1], &mem1);
rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r);
}else{
i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]);
@ -89083,14 +89123,18 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
}else if( serial_type==0 ){
rc = -1;
}else{
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
if( serial_type==7 ){
if( mem1.u.r<pRhs->u.r ){
if( serialGet7(&aKey1[d1], &mem1) ){
rc = -1; /* mem1 is a NaN */
}else if( mem1.u.r<pRhs->u.r ){
rc = -1;
}else if( mem1.u.r>pRhs->u.r ){
rc = +1;
}else{
assert( rc==0 );
}
}else{
sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1);
rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r);
}
}
@ -89160,7 +89204,14 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(
/* RHS is null */
else{
serial_type = aKey1[idx1];
rc = (serial_type!=0 && serial_type!=10);
if( serial_type==0
|| serial_type==10
|| (serial_type==7 && serialGet7(&aKey1[d1], &mem1)!=0)
){
assert( rc==0 );
}else{
rc = 1;
}
}
if( rc!=0 ){
@ -94858,7 +94909,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
}
}
}else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){
if( (flags1 & MEM_Str)==0 && (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
if( (flags1 & MEM_Str)!=0 ){
pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
}else if( (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn1->flags & MEM_Int );
testcase( pIn1->flags & MEM_Real );
testcase( pIn1->flags & MEM_IntReal );
@ -94867,7 +94920,9 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */
flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask);
if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str;
}
if( (flags3 & MEM_Str)==0 && (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
if( (flags3 & MEM_Str)!=0 ){
pIn3->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal);
}else if( (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){
testcase( pIn3->flags & MEM_Int );
testcase( pIn3->flags & MEM_Real );
testcase( pIn3->flags & MEM_IntReal );
@ -106212,6 +106267,8 @@ static void resolveAlias(
assert( iCol>=0 && iCol<pEList->nExpr );
pOrig = pEList->a[iCol].pExpr;
assert( pOrig!=0 );
assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) );
if( pExpr->pAggInfo ) return;
db = pParse->db;
pDup = sqlite3ExprDup(db, pOrig, 0);
if( db->mallocFailed ){
@ -107097,6 +107154,19 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
** resolved. This prevents "column" from being counted as having been
** referenced, which might prevent a SELECT from being erroneously
** marked as correlated.
**
** 2024-03-28: Beware of aggregates. A bare column of aggregated table
** can still evaluate to NULL even though it is marked as NOT NULL.
** Example:
**
** CREATE TABLE t1(a INT NOT NULL);
** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1;
**
** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized
** here because at the time this case is hit, we do not yet know whether
** or not t1 is being aggregated. We have to assume the worst and omit
** the optimization. The only time it is safe to apply this optimization
** is within the WHERE clause.
*/
case TK_NOTNULL:
case TK_ISNULL: {
@ -107107,19 +107177,36 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
anRef[i] = p->nRef;
}
sqlite3WalkExpr(pWalker, pExpr->pLeft);
if( 0==sqlite3ExprCanBeNull(pExpr->pLeft) && !IN_RENAME_OBJECT ){
testcase( ExprHasProperty(pExpr, EP_OuterON) );
assert( !ExprHasProperty(pExpr, EP_IntValue) );
pExpr->u.iValue = (pExpr->op==TK_NOTNULL);
pExpr->flags |= EP_IntValue;
pExpr->op = TK_INTEGER;
for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
p->nRef = anRef[i];
}
sqlite3ExprDelete(pParse->db, pExpr->pLeft);
pExpr->pLeft = 0;
if( IN_RENAME_OBJECT ) return WRC_Prune;
if( sqlite3ExprCanBeNull(pExpr->pLeft) ){
/* The expression can be NULL. So the optimization does not apply */
return WRC_Prune;
}
for(i=0, p=pNC; p; p=p->pNext, i++){
if( (p->ncFlags & NC_Where)==0 ){
return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */
}
}
testcase( ExprHasProperty(pExpr, EP_OuterON) );
assert( !ExprHasProperty(pExpr, EP_IntValue) );
#if TREETRACE_ENABLED
if( sqlite3TreeTrace & 0x80000 ){
sqlite3DebugPrintf(
"NOT NULL strength reduction converts the following to %d:\n",
pExpr->op==TK_NOTNULL
);
sqlite3ShowExpr(pExpr);
}
#endif /* TREETRACE_ENABLED */
pExpr->u.iValue = (pExpr->op==TK_NOTNULL);
pExpr->flags |= EP_IntValue;
pExpr->op = TK_INTEGER;
for(i=0, p=pNC; p && i<ArraySize(anRef); p=p->pNext, i++){
p->nRef = anRef[i];
}
sqlite3ExprDelete(pParse->db, pExpr->pLeft);
pExpr->pLeft = 0;
return WRC_Prune;
}
@ -108019,7 +108106,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort;
}
sNC.ncFlags |= NC_Where;
if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort;
sNC.ncFlags &= ~NC_Where;
/* Resolve names in table-valued-function arguments */
for(i=0; i<p->pSrc->nSrc; i++){
@ -128947,13 +129036,13 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue){
double r1, r2;
const char *zVal;
r1 = sqlite3_value_double(pValue);
sqlite3_str_appendf(pStr, "%!.15g", r1);
sqlite3_str_appendf(pStr, "%!0.15g", r1);
zVal = sqlite3_str_value(pStr);
if( zVal ){
sqlite3AtoF(zVal, &r2, pStr->nChar, SQLITE_UTF8);
if( r1!=r2 ){
sqlite3_str_reset(pStr);
sqlite3_str_appendf(pStr, "%!.20e", r1);
sqlite3_str_appendf(pStr, "%!0.20e", r1);
}
}
break;
@ -129255,7 +129344,7 @@ static void replaceFunc(
}
if( zPattern[0]==0 ){
assert( sqlite3_value_type(argv[1])!=SQLITE_NULL );
sqlite3_result_value(context, argv[0]);
sqlite3_result_text(context, (const char*)zStr, nStr, SQLITE_TRANSIENT);
return;
}
nPattern = sqlite3_value_bytes(argv[1]);
@ -133175,7 +133264,7 @@ SQLITE_PRIVATE void sqlite3Insert(
pNx->iDataCur = iDataCur;
pNx->iIdxCur = iIdxCur;
if( pNx->pUpsertTarget ){
if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx) ){
if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){
goto insert_cleanup;
}
}
@ -139474,31 +139563,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
int mxCol; /* Maximum non-virtual column number */
if( pObjTab && pObjTab!=pTab ) continue;
if( !IsOrdinaryTable(pTab) ){
#ifndef SQLITE_OMIT_VIRTUALTABLE
sqlite3_vtab *pVTab;
int a1;
if( !IsVirtual(pTab) ) continue;
if( pTab->nCol<=0 ){
const char *zMod = pTab->u.vtab.azArg[0];
if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
}
sqlite3ViewGetColumnNames(pParse, pTab);
if( pTab->u.vtab.p==0 ) continue;
pVTab = pTab->u.vtab.p->pVtab;
if( NEVER(pVTab==0) ) continue;
if( NEVER(pVTab->pModule==0) ) continue;
if( pVTab->pModule->iVersion<4 ) continue;
if( pVTab->pModule->xIntegrity==0 ) continue;
sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
pTab->nTabRef++;
sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF);
a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
integrityCheckResultRow(v);
sqlite3VdbeJumpHere(v, a1);
#endif
continue;
}
if( !IsOrdinaryTable(pTab) ) continue;
if( isQuick || HasRowid(pTab) ){
pPk = 0;
r2 = 0;
@ -139633,6 +139698,7 @@ SQLITE_PRIVATE void sqlite3Pragma(
** is REAL, we have to load the actual data using OP_Column
** to reliably determine if the value is a NULL. */
sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3);
sqlite3ColumnDefault(v, pTab, j, 3);
jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk);
VdbeCoverage(v);
}
@ -139823,6 +139889,38 @@ SQLITE_PRIVATE void sqlite3Pragma(
}
}
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
/* Second pass to invoke the xIntegrity method on all virtual
** tables.
*/
for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){
Table *pTab = sqliteHashData(x);
sqlite3_vtab *pVTab;
int a1;
if( pObjTab && pObjTab!=pTab ) continue;
if( IsOrdinaryTable(pTab) ) continue;
if( !IsVirtual(pTab) ) continue;
if( pTab->nCol<=0 ){
const char *zMod = pTab->u.vtab.azArg[0];
if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue;
}
sqlite3ViewGetColumnNames(pParse, pTab);
if( pTab->u.vtab.p==0 ) continue;
pVTab = pTab->u.vtab.p->pVtab;
if( NEVER(pVTab==0) ) continue;
if( NEVER(pVTab->pModule==0) ) continue;
if( pVTab->pModule->iVersion<4 ) continue;
if( pVTab->pModule->xIntegrity==0 ) continue;
sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick);
pTab->nTabRef++;
sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF);
a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v);
integrityCheckResultRow(v);
sqlite3VdbeJumpHere(v, a1);
continue;
}
#endif
}
{
static const int iLn = VDBE_OFFSET_LINENO(2);
@ -153460,7 +153558,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew(
SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
Parse *pParse, /* The parsing context */
SrcList *pTabList, /* Table into which we are inserting */
Upsert *pUpsert /* The ON CONFLICT clauses */
Upsert *pUpsert, /* The ON CONFLICT clauses */
Upsert *pAll /* Complete list of all ON CONFLICT clauses */
){
Table *pTab; /* That table into which we are inserting */
int rc; /* Result code */
@ -153563,6 +153662,14 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(
continue;
}
pUpsert->pUpsertIdx = pIdx;
if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){
/* Really this should be an error. The isDup ON CONFLICT clause will
** never fire. But this problem was not discovered until three years
** after multi-CONFLICT upsert was added, and so we silently ignore
** the problem to prevent breaking applications that might actually
** have redundant ON CONFLICT clauses. */
pUpsert->isDup = 1;
}
break;
}
if( pUpsert->pUpsertIdx==0 ){
@ -153589,9 +153696,13 @@ SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){
Upsert *pNext;
if( NEVER(pUpsert==0) ) return 0;
pNext = pUpsert->pNextUpsert;
if( pNext==0 ) return 1;
if( pNext->pUpsertTarget==0 ) return 1;
if( pNext->pUpsertIdx==0 ) return 1;
while( 1 /*exit-by-return*/ ){
if( pNext==0 ) return 1;
if( pNext->pUpsertTarget==0 ) return 1;
if( pNext->pUpsertIdx==0 ) return 1;
if( !pNext->isDup ) return 0;
pNext = pNext->pNextUpsert;
}
return 0;
}
@ -204785,6 +204896,7 @@ json_parse_restart:
case '[': {
/* Parse array */
iThis = pParse->nBlob;
assert( i<=(u32)pParse->nJson );
jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0);
iStart = pParse->nBlob;
if( pParse->oom ) return -1;
@ -205183,6 +205295,10 @@ static void jsonReturnStringAsBlob(JsonString *pStr){
JsonParse px;
memset(&px, 0, sizeof(px));
jsonStringTerminate(pStr);
if( pStr->eErr ){
sqlite3_result_error_nomem(pStr->pCtx);
return;
}
px.zJson = pStr->zBuf;
px.nJson = pStr->nUsed;
px.db = sqlite3_context_db_handle(pStr->pCtx);
@ -206508,8 +206624,9 @@ rebuild_from_cache:
}
p->zJson = (char*)sqlite3_value_text(pArg);
p->nJson = sqlite3_value_bytes(pArg);
if( db->mallocFailed ) goto json_pfa_oom;
if( p->nJson==0 ) goto json_pfa_malformed;
if( NEVER(p->zJson==0) ) goto json_pfa_oom;
assert( p->zJson!=0 );
if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){
if( flgs & JSON_KEEPERROR ){
p->nErr = 1;
@ -206675,10 +206792,10 @@ static void jsonDebugPrintBlob(
if( sz==0 && x<=JSONB_FALSE ){
sqlite3_str_append(pOut, "\n", 1);
}else{
u32 i;
u32 j;
sqlite3_str_appendall(pOut, ": \"");
for(i=iStart+n; i<iStart+n+sz; i++){
u8 c = pParse->aBlob[i];
for(j=iStart+n; j<iStart+n+sz; j++){
u8 c = pParse->aBlob[j];
if( c<0x20 || c>=0x7f ) c = '.';
sqlite3_str_append(pOut, (char*)&c, 1);
}
@ -208086,6 +208203,9 @@ static int jsonEachColumn(
case JEACH_VALUE: {
u32 i = jsonSkipLabel(p);
jsonReturnFromBlob(&p->sParse, i, ctx, 1);
if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
}
break;
}
case JEACH_TYPE: {
@ -208132,9 +208252,9 @@ static int jsonEachColumn(
case JEACH_JSON: {
if( p->sParse.zJson==0 ){
sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob,
SQLITE_STATIC);
SQLITE_TRANSIENT);
}else{
sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC);
sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_TRANSIENT);
}
break;
}
@ -209160,11 +209280,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){
** Clear the Rtree.pNodeBlob object
*/
static void nodeBlobReset(Rtree *pRtree){
if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){
sqlite3_blob *pBlob = pRtree->pNodeBlob;
pRtree->pNodeBlob = 0;
sqlite3_blob_close(pBlob);
}
sqlite3_blob *pBlob = pRtree->pNodeBlob;
pRtree->pNodeBlob = 0;
sqlite3_blob_close(pBlob);
}
/*
@ -209208,7 +209326,6 @@ static int nodeAcquire(
&pRtree->pNodeBlob);
}
if( rc ){
nodeBlobReset(pRtree);
*ppNode = 0;
/* If unable to open an sqlite3_blob on the desired row, that can only
** be because the shadow tables hold erroneous data. */
@ -209268,6 +209385,7 @@ static int nodeAcquire(
}
*ppNode = pNode;
}else{
nodeBlobReset(pRtree);
if( pNode ){
pRtree->nNodeRef--;
sqlite3_free(pNode);
@ -209412,6 +209530,7 @@ static void nodeGetCoord(
int iCoord, /* Which coordinate to extract */
RtreeCoord *pCoord /* OUT: Space to write result to */
){
assert( iCell<NCELL(pNode) );
readCoord(&pNode->zData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord);
}
@ -209601,7 +209720,9 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){
sqlite3_finalize(pCsr->pReadAux);
sqlite3_free(pCsr);
pRtree->nCursor--;
nodeBlobReset(pRtree);
if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){
nodeBlobReset(pRtree);
}
return SQLITE_OK;
}
@ -210186,7 +210307,11 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){
int rc = SQLITE_OK;
RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc);
if( rc==SQLITE_OK && ALWAYS(p) ){
*pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
if( p->iCell>=NCELL(pNode) ){
rc = SQLITE_ABORT;
}else{
*pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell);
}
}
return rc;
}
@ -210204,6 +210329,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){
if( rc ) return rc;
if( NEVER(p==0) ) return SQLITE_OK;
if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT;
if( i==0 ){
sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell));
}else if( i<=pRtree->nDim2 ){
@ -211685,8 +211811,7 @@ constraint:
*/
static int rtreeBeginTransaction(sqlite3_vtab *pVtab){
Rtree *pRtree = (Rtree *)pVtab;
assert( pRtree->inWrTrans==0 );
pRtree->inWrTrans++;
pRtree->inWrTrans = 1;
return SQLITE_OK;
}
@ -211700,6 +211825,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){
nodeBlobReset(pRtree);
return SQLITE_OK;
}
static int rtreeRollback(sqlite3_vtab *pVtab){
return rtreeEndTransaction(pVtab);
}
/*
** The xRename method for rtree module virtual tables.
@ -211818,7 +211946,7 @@ static sqlite3_module rtreeModule = {
rtreeBeginTransaction, /* xBegin - begin transaction */
rtreeEndTransaction, /* xSync - sync transaction */
rtreeEndTransaction, /* xCommit - commit transaction */
rtreeEndTransaction, /* xRollback - rollback transaction */
rtreeRollback, /* xRollback - rollback transaction */
0, /* xFindFunction - function overloading */
rtreeRename, /* xRename - rename the table */
rtreeSavepoint, /* xSavepoint */
@ -245377,23 +245505,26 @@ static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){
static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){
int ii;
Fts5TokenDataIter *pT = pIter->pTokenDataIter;
Fts5Index *pIndex = pIter->pIndex;
for(ii=0; ii<pT->nIter; ii++){
Fts5Iter *p = pT->apIter[ii];
if( p->base.bEof==0
&& (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowid<iFrom))
){
fts5MultiIterNext(p->pIndex, p, bFrom, iFrom);
fts5MultiIterNext(pIndex, p, bFrom, iFrom);
while( bFrom && p->base.bEof==0
&& p->base.iRowid<iFrom
&& p->pIndex->rc==SQLITE_OK
&& pIndex->rc==SQLITE_OK
){
fts5MultiIterNext(p->pIndex, p, 0, 0);
fts5MultiIterNext(pIndex, p, 0, 0);
}
}
}
fts5IterSetOutputsTokendata(pIter);
if( pIndex->rc==SQLITE_OK ){
fts5IterSetOutputsTokendata(pIter);
}
}
/*
@ -250547,7 +250678,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "fts5: 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a", -1, SQLITE_TRANSIENT);
sqlite3_result_text(pCtx, "fts5: 2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77", -1, SQLITE_TRANSIENT);
}
/*

@ -146,9 +146,9 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION "3.45.1"
#define SQLITE_VERSION_NUMBER 3045001
#define SQLITE_SOURCE_ID "2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467a"
#define SQLITE_VERSION "3.45.2"
#define SQLITE_VERSION_NUMBER 3045002
#define SQLITE_SOURCE_ID "2024-03-12 11:06:23 d8cd6d49b46a395b13955387d05e9e1a2a47e54fb99f3c9b59835bbefad6af77"
/*
** CAPI3REF: Run-Time Library Version Numbers
@ -420,6 +420,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**);
** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not modify the SQL statement text passed into
** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running.
** <li> The application must not dereference the arrays or string pointers
** passed as the 3rd and 4th callback parameters after it returns.
** </ul>
*/
SQLITE_API int sqlite3_exec(

@ -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">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application

@ -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);
/** @} */

@ -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);
/** @} */

@ -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];
/** @} */

@ -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);
/** @} */

@ -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)
{
@ -315,7 +316,9 @@ static void _file_read_file_zip_after_work(uv_work_t* work, int status)
tf_trace_begin(trace, "file_read_zip_after_work");
if (data->result >= 0)
{
tf_task_resolve_promise(data->task, data->promise, tf_util_new_uint8_array(data->context, data->buffer, data->result));
JSValue array = tf_util_new_uint8_array(data->context, data->buffer, data->result);
tf_task_resolve_promise(data->task, data->promise, array);
JS_FreeValue(data->context, array);
}
else
{

@ -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);
/** @} */

@ -156,17 +156,28 @@ static void _http_connection_on_close(uv_handle_t* handle)
_http_connection_destroy(connection, "handle closed");
}
static void _http_request_destroy(tf_http_request_t* request)
{
tf_http_close_callback* on_close = request->on_close;
if (on_close)
{
tf_trace_t* trace = request->http->trace;
request->on_close = NULL;
tf_trace_begin(trace, request->connection && request->connection->trace_name ? request->connection->trace_name : "websocket");
on_close(request);
tf_trace_end(trace);
}
}
static void _http_connection_destroy(tf_http_connection_t* connection, const char* reason)
{
connection->is_shutting_down = true;
if (connection->request && connection->request->on_close)
if (connection->request)
{
tf_http_close_callback* on_close = connection->request->on_close;
connection->request->on_close = NULL;
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "websocket");
on_close(connection->request);
tf_trace_end(connection->http->trace);
tf_http_request_t* request = connection->request;
connection->request = NULL;
_http_request_destroy(request);
}
if (connection->tcp.data && !uv_is_closing((uv_handle_t*)&connection->tcp))
@ -370,11 +381,19 @@ static void _http_add_body_bytes(tf_http_connection_t* connection, const void* d
};
connection->request = request;
tf_http_request_ref(request);
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "http");
connection->callback(request);
tf_trace_end(connection->http->trace);
tf_http_request_unref(request);
if (!connection->http->is_shutting_down)
{
tf_http_request_ref(request);
tf_trace_begin(connection->http->trace, connection->trace_name ? connection->trace_name : "http");
connection->callback(request);
tf_trace_end(connection->http->trace);
tf_http_request_unref(request);
}
else
{
const char* k_payload = tf_http_status_text(503);
tf_http_respond(request, 503, NULL, 0, k_payload, strlen(k_payload));
}
}
}
}
@ -619,15 +638,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));
}
}
@ -760,6 +792,8 @@ const char* tf_http_status_text(int status)
return "File not found";
case 500:
return "Internal server error";
case 503:
return "Service Unavailable";
default:
return "Unknown";
}
@ -936,29 +970,28 @@ void tf_http_request_ref(tf_http_request_t* request)
void tf_http_request_unref(tf_http_request_t* request)
{
bool connection_destroyed = false;
if (--request->connection->ref_count == 0)
{
if (request->connection->http->is_shutting_down)
{
_http_connection_destroy(request->connection, "unref during shutdown");
connection_destroyed = true;
}
else if (!request->connection->is_websocket)
{
_http_reset_connection(request->connection);
}
}
tf_http_connection_t* connection = request->connection;
if (--request->ref_count == 0)
{
if (!connection_destroyed)
if (connection)
{
request->connection->request = NULL;
connection->request = NULL;
}
_http_request_destroy(request);
tf_free(request);
}
if (--connection->ref_count == 0)
{
if (connection->http->is_shutting_down)
{
_http_connection_destroy(connection, "unref during shutdown");
}
else if (!connection->is_websocket)
{
_http_reset_connection(connection);
}
}
}
static const char* _http_connection_get_header(const tf_http_connection_t* connection, const char* name)

@ -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);
/** @} */

@ -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);
/** @} */

@ -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__)

@ -11,6 +11,7 @@
#include "backtrace.h"
#include "sqlite3.h"
#include "unzip.h"
#include <getopt.h>
#include <stdlib.h>
@ -166,7 +167,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 +233,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 +271,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 +298,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);
@ -414,11 +417,20 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
};
bool show_usage = false;
/* Check if the executable has data attached. */
unzFile zip = unzOpen(file);
if (zip)
{
args.zip = file;
unzClose(zip);
}
while (!show_usage)
{
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 +441,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 +457,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;
@ -479,17 +494,18 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
{
tf_printf("\n%s run [options]\n\n", file);
tf_printf("options\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n");
tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n");
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", k_db_path_default);
tf_printf(" -n, --count count Number of instances to run.\n");
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n");
tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n");
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
tf_printf(" -v, --verbose Log raw messages.\n");
tf_printf(" -h, --help Show this usage information.\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n");
tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n");
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", k_db_path_default);
tf_printf(" -k, --ssb-network-key key SSB network key to use.\n");
tf_printf(" -n, --count count Number of instances to run.\n");
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n");
tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n");
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
tf_printf(" -v, --verbose Log raw messages.\n");
tf_printf(" -h, --help Show this usage information.\n");
return EXIT_FAILURE;
}

@ -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);
/** @} */

@ -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);
/** @} */

@ -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);
/** @} */

@ -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();
/** @} */

157
src/ssb.c

@ -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];
@ -286,7 +287,6 @@ typedef struct _tf_ssb_connection_t
tf_ssb_blob_wants_t blob_wants;
bool sent_clock;
int32_t ebt_request_number;
JSValue ebt_send_clock;
JSValue object;
@ -489,7 +489,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 +519,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 +536,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));
@ -1002,14 +1002,13 @@ static bool _tf_ssb_verify_and_strip_signature_internal(JSContext* context, JSVa
return verified;
}
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)
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, int* out_flags)
{
if (_tf_ssb_verify_and_strip_signature_internal(context, val, out_id, out_id_size, out_signature, out_signature_size))
{
if (out_sequence_before_author)
if (out_flags)
{
*out_sequence_before_author = false;
*out_flags = 0;
}
return true;
}
@ -1027,9 +1026,9 @@ bool tf_ssb_verify_and_strip_signature(
JS_FreeValue(context, reordered);
if (result)
{
if (out_sequence_before_author)
if (out_flags)
{
*out_sequence_before_author = true;
*out_flags = k_tf_ssb_message_flag_sequence_before_author;
}
return true;
}
@ -1136,11 +1135,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 +1163,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 +1175,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 +1183,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 +1289,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 +1334,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 +1345,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 +1353,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 +1361,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 +1389,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));
@ -1862,8 +1861,6 @@ static void _tf_ssb_connection_destroy(tf_ssb_connection_t* connection, const ch
if (JS_IsUndefined(connection->object) && !connection->async.data && !connection->tcp.data && !connection->connect.data && connection->ref_count == 0)
{
JS_FreeValue(ssb->context, connection->ebt_send_clock);
connection->ebt_send_clock = JS_UNDEFINED;
tf_free(connection->message_requests);
connection->message_requests = NULL;
connection->message_requests_count = 0;
@ -1941,7 +1938,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 +1995,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 +2103,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 +2820,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)
@ -3548,11 +3558,11 @@ void tf_ssb_verify_strip_and_store_message(tf_ssb_t* ssb, JSValue value, tf_ssb_
.user_data = user_data,
};
char signature[crypto_sign_BYTES + 128] = { 0 };
bool sequence_before_author = false;
if (tf_ssb_verify_and_strip_signature(context, value, async->id, sizeof(async->id), signature, sizeof(signature), &sequence_before_author))
int flags = 0;
if (tf_ssb_verify_and_strip_signature(context, value, async->id, sizeof(async->id), signature, sizeof(signature), &flags))
{
async->verified = true;
tf_ssb_db_store_message(ssb, context, async->id, value, signature, sequence_before_author, _tf_ssb_verify_strip_and_store_callback, async);
tf_ssb_db_store_message(ssb, context, async->id, value, signature, flags, _tf_ssb_verify_strip_and_store_callback, async);
}
else
{
@ -3581,19 +3591,6 @@ void tf_ssb_connection_set_ebt_request_number(tf_ssb_connection_t* connection, i
connection->ebt_request_number = request_number;
}
JSValue tf_ssb_connection_get_ebt_send_clock(tf_ssb_connection_t* connection)
{
JSContext* context = connection->ssb->context;
return JS_DupValue(context, connection->ebt_send_clock);
}
void tf_ssb_connection_set_ebt_send_clock(tf_ssb_connection_t* connection, JSValue send_clock)
{
JSContext* context = connection->ssb->context;
JS_FreeValue(context, connection->ebt_send_clock);
connection->ebt_send_clock = JS_DupValue(context, send_clock);
}
JSValue tf_ssb_get_disconnection_debug(tf_ssb_t* ssb, JSContext* context)
{
JSValue result = JS_NewObject(context);

@ -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);
/** @} */

@ -101,11 +101,34 @@ 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,"
" flags 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");
}
if (_tf_ssb_db_has_rows(db, "SELECT name FROM pragma_table_info('messages') WHERE name = 'sequence_before_author'"))
{
tf_printf("Renaming sequence_before_author -> flags.\n");
_tf_ssb_db_exec(db, "ALTER TABLE messages RENAME COLUMN sequence_before_author TO flags");
}
_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 +184,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");
@ -214,7 +238,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
" AND LENGTH(messages_refs.ref) = 52 "
" AND messages_refs.ref LIKE '&%.sha256'");
bool need_add_sequence_before_author = true;
bool need_add_flags = true;
bool need_convert_timestamp_to_real = false;
if (sqlite3_prepare(db, "PRAGMA table_info(messages)", -1, &statement, NULL) == SQLITE_OK)
@ -228,9 +252,9 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
{
need_convert_timestamp_to_real = true;
}
if (name && strcmp(name, "sequence_before_author") == 0)
if (name && strcmp(name, "flags") == 0)
{
need_add_sequence_before_author = false;
need_add_flags = false;
}
}
sqlite3_finalize(statement);
@ -248,10 +272,10 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS messages_author_timestamp_index ON messages (author, timestamp)");
_tf_ssb_db_exec(db, "COMMIT TRANSACTION");
}
if (need_add_sequence_before_author)
if (need_add_flags)
{
tf_printf("Adding sequence_before_author column.\n");
_tf_ssb_db_exec(db, "ALTER TABLE messages ADD COLUMN sequence_before_author INTEGER");
tf_printf("Adding flags column.\n");
_tf_ssb_db_exec(db, "ALTER TABLE messages ADD COLUMN flags INTEGER");
}
tf_ssb_release_db_writer(ssb, db);
}
@ -280,14 +304,14 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
}
static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const char* previous, const char* author, int64_t sequence, double timestamp, const char* content,
size_t content_len, const char* signature, bool sequence_before_author)
size_t content_len, const char* signature, int flags)
{
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
int64_t last_row_id = -1;
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, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING";
sqlite3_stmt* statement;
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK)
@ -297,7 +321,7 @@ static int64_t _tf_ssb_db_store_message_raw(tf_ssb_t* ssb, const char* id, const
sqlite3_bind_text(statement, 3, author, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 4, sequence) == SQLITE_OK &&
sqlite3_bind_double(statement, 5, timestamp) == SQLITE_OK && sqlite3_bind_text(statement, 6, content, content_len, NULL) == SQLITE_OK &&
sqlite3_bind_text(statement, 7, "sha256", 6, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 8, signature, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int(statement, 9, sequence_before_author) == SQLITE_OK)
sqlite3_bind_int(statement, 9, flags) == SQLITE_OK)
{
int r = sqlite3_step(statement);
if (r != SQLITE_DONE)
@ -379,7 +403,7 @@ typedef struct _message_store_t
tf_ssb_t* ssb;
char id[k_id_base64_len];
char signature[512];
bool sequence_before_author;
int flags;
char previous[k_id_base64_len];
char author[k_id_base64_len];
int64_t sequence;
@ -403,7 +427,7 @@ static void _tf_ssb_db_store_message_work(uv_work_t* work)
tf_trace_t* trace = tf_ssb_get_trace(store->ssb);
tf_trace_begin(trace, "message_store_work");
int64_t last_row_id = _tf_ssb_db_store_message_raw(store->ssb, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence, store->timestamp,
store->content, store->length, store->signature, store->sequence_before_author);
store->content, store->length, store->signature, store->flags);
if (last_row_id != -1)
{
store->out_stored = true;
@ -459,8 +483,8 @@ static void _tf_ssb_db_store_message_after_work(uv_work_t* work, int status)
{
tf_trace_begin(trace, "notify_message_added");
JSContext* context = tf_ssb_get_context(store->ssb);
JSValue formatted = tf_ssb_format_message(
context, store->previous, store->author, store->sequence, store->timestamp, "sha256", store->content, store->signature, store->sequence_before_author);
JSValue formatted =
tf_ssb_format_message(context, store->previous, store->author, store->sequence, store->timestamp, "sha256", store->content, store->signature, store->flags);
JSValue message = JS_NewObject(context);
JS_SetPropertyStr(context, message, "key", JS_NewString(context, store->id));
JS_SetPropertyStr(context, message, "value", formatted);
@ -485,8 +509,8 @@ static void _tf_ssb_db_store_message_after_work(uv_work_t* work, int status)
tf_trace_end(trace);
}
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)
void tf_ssb_db_store_message(
tf_ssb_t* ssb, JSContext* context, const char* id, JSValue val, const char* signature, int flags, tf_ssb_db_store_message_callback_t* callback, void* user_data)
{
JSValue previousval = JS_GetPropertyStr(context, val, "previous");
const char* previous = JS_IsNull(previousval) ? NULL : JS_ToCString(context, previousval);
@ -525,7 +549,7 @@ void tf_ssb_db_store_message(tf_ssb_t* ssb, JSContext* context, const char* id,
.timestamp = timestamp,
.content = contentstr,
.length = content_len,
.sequence_before_author = sequence_before_author,
.flags = flags,
.callback = callback,
.user_data = user_data,
@ -557,7 +581,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 +788,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)
{
@ -984,12 +1008,12 @@ JSValue tf_ssb_db_visit_query(tf_ssb_t* ssb, const char* query, const JSValue bi
return result;
}
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)
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, int flags)
{
JSValue value = JS_NewObject(context);
JS_SetPropertyStr(context, value, "previous", (previous && *previous) ? JS_NewString(context, previous) : JS_NULL);
if (sequence_before_author)
if (flags & k_tf_ssb_message_flag_sequence_before_author)
{
JS_SetPropertyStr(context, value, "sequence", JS_NewInt64(context, sequence));
JS_SetPropertyStr(context, value, "author", JS_NewString(context, author));
@ -1481,8 +1505,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, flags FROM messages WHERE id = ?", -1, &statement, NULL) ==
SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK)
{

@ -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);
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);
/**
** 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 flags tf_ssb_message_flags_t describing the message.
** @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, int flags, 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);
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);
/**
** 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 flags tf_ssb_message_flags_t describing the message.
*/
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, int flags);
/** 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);
/** @} */

@ -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);
/** @} */

740
src/ssb.h

@ -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,29 @@ typedef enum _tf_ssb_change_t
k_tf_ssb_change_remove,
} tf_ssb_change_t;
/**
** Flags describing the structure of a message.
*/
typedef enum _tf_ssb_message_flags_t
{
/**
** The sequence field precedes the author field if specified. The
** other way around, otherwise.
*/
k_tf_ssb_message_flag_sequence_before_author = 1,
} tf_ssb_message_flags_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 +72,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 +102,859 @@ 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);
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);
/**
** 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_flags tf_ssb_message_flags_t describing the message.
** @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, int* out_flags);
/**
** 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);
JSValue tf_ssb_connection_get_ebt_send_clock(tf_ssb_connection_t* connection);
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);
/** @} */

@ -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);

@ -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);
/** @} */

@ -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)
{
@ -415,6 +415,7 @@ static void _tf_ssb_blob_store_complete(blob_store_t* store, const char* id)
{
JSValue id_value = JS_NewString(store->context, id);
JSValue result = JS_Call(store->context, store->promise[0], JS_UNDEFINED, 1, &id_value);
JS_FreeValue(store->context, id_value);
tf_util_report_error(store->context, result);
JS_FreeValue(store->context, result);
}

@ -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);
/** @} */

@ -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);
}
@ -142,7 +142,7 @@ static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id,
typedef struct _blob_wants_work_t
{
char out_id[k_blob_id_len][32];
char out_id[32][k_blob_id_len];
int out_id_count;
} blob_wants_work_t;
@ -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, flags FROM messages WHERE author = ?1 AND sequence > ?2 AND "
"sequence "
"< ?3 ORDER BY sequence",
-1, &statement, NULL) == SQLITE_OK)
@ -875,6 +875,7 @@ static void _tf_ssb_rpc_ebt_replicate_send_clock_work(tf_ssb_connection_t* conne
tf_ssb_db_get_latest_message_by_author(ssb, work->clock[i].id, &sequence, NULL, 0);
JS_SetPropertyStr(context, full_clock, work->clock[i].id, JS_NewInt64(context, sequence == -1 ? -1 : (sequence << 1)));
}
JS_FreeValue(context, in_clock);
}
JSValue json = JS_JSONStringify(context, full_clock, JS_NULL, JS_NULL);
@ -1025,7 +1026,6 @@ static void _tf_ssb_rpc_ebt_replicate(tf_ssb_connection_t* connection, uint8_t f
else
{
/* EBT clock. */
tf_ssb_connection_set_ebt_send_clock(connection, args);
if (!tf_ssb_connection_get_sent_clock(connection))
{
_tf_ssb_rpc_ebt_replicate_send_clock(connection, request_number, in_clock);

@ -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);
/** @} */

@ -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);

@ -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);
/** @} */

@ -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
@ -1938,6 +1939,11 @@ void tf_task_destroy(tf_task_t* task)
tf_free(task->_promise_stacks);
tf_free((void*)task->_path);
bool was_trusted = task->_trusted;
if (task->_zip)
{
unzClose(task->_zip);
task->_zip = NULL;
}
tf_free(task);
if (was_trusted)
{
@ -2000,6 +2006,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;

@ -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);
/** @} */

@ -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);
/** @} */

@ -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);

@ -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

@ -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);
/** @} */

@ -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();
/** @} */

@ -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);
/** @} */

@ -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);

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

@ -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)