Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
3c0b680b8e | |||
895356897b | |||
9164be2f37 | |||
5385264f94 | |||
610e756c07 | |||
15c9f8f458 | |||
fb704a5b83 | |||
fdda628be8 | |||
2b45d8aa05 | |||
0e2fc65301 | |||
e8ef7e74de | |||
c32e1b9583 | |||
9d0f6ec155 | |||
855d603795 | |||
af25782185 | |||
e5ba51b80a | |||
5e240de677 | |||
418cfac0e3 | |||
9d09607013 | |||
eddf25b622 | |||
537a8654fa | |||
9de33d06d2 | |||
0e5f320664 | |||
88d8e60511 | |||
439f07162e | |||
efe2b6cbd9 | |||
0aa1ed9464 | |||
cb94ed6a2a | |||
cf187ee46b | |||
3e71fc20fd | |||
f3601321f7 | |||
540059368c | |||
7ce89123f7 | |||
e3c7c86212 | |||
794804e27f | |||
6d89c1da6e | |||
d059554464 | |||
3a392d4a9f | |||
e3071b372a | |||
18bd279b0c | |||
5b93db7463 | |||
5b7e5eb91b | |||
78ca383e3c | |||
c1eed9ada3 | |||
8d6feb5394 | |||
42994f8977 | |||
f0a871e1f8 | |||
a710c30572 | |||
c991763b00 | |||
72dae14f87 | |||
5800340762 | |||
c5f5adcac6 | |||
591642efb3 | |||
6182ffa1d4 | |||
402a898d96 |
.gitignoreCONTRIBUTING.mdGNUmakefile
apps
core
deps
src
android
bcrypt.js.hbip39.hbip39.words.hdatabase.js.hfile.js.cfile.js.hhttp.chttp.hhttpd.js.hlog.hmain.cmem.hpacketstream.hserialize.hsocket.js.hssb.cssb.connections.hssb.db.cssb.db.hssb.export.hssb.hssb.import.cssb.import.hssb.js.cssb.js.hssb.rpc.cssb.rpc.hssb.tests.cssb.tests.htask.ctask.htaskstub.js.htests.ctests.htls.htlscontext.js.htrace.hutil.js.cversion.htools
8
.gitignore
vendored
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
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`.
|
70
GNUmakefile
70
GNUmakefile
@ -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
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
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 &&
|
||||
|
2
deps/codemirror/cm6.js
vendored
2
deps/codemirror/cm6.js
vendored
File diff suppressed because one or more lines are too long
176
deps/codemirror_src/package-lock.json
generated
vendored
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",
|
||||
|
4
deps/codemirror_src/package.json
vendored
4
deps/codemirror_src/package.json
vendored
@ -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
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
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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
8
deps/sqlite/sqlite3.h
vendored
8
deps/sqlite/sqlite3.h
vendored
@ -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);
|
||||
|
||||
/** @} */
|
||||
|
16
src/bip39.h
16
src/bip39.h
@ -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);
|
||||
|
||||
/** @} */
|
||||
|
91
src/http.c
91
src/http.c
@ -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)
|
||||
|
154
src/http.h
154
src/http.h
@ -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__)
|
||||
|
44
src/main.c
44
src/main.c
@ -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;
|
||||
}
|
||||
|
||||
|
95
src/mem.h
95
src/mem.h
@ -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
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);
|
||||
|
||||
/** @} */
|
||||
|
78
src/ssb.db.c
78
src/ssb.db.c
@ -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)
|
||||
{
|
||||
|
259
src/ssb.db.h
259
src/ssb.db.h
@ -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
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);
|
||||
|
||||
/** @} */
|
||||
|
27
src/task.c
27
src/task.c
@ -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;
|
||||
|
253
src/task.h
253
src/task.h
@ -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
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();
|
||||
|
||||
/** @} */
|
||||
|
75
src/trace.h
75
src/trace.h
@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user