mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-11-22 03:04:35 +03:00
import from previous repo
This commit is contained in:
commit
3bce8c973b
13
.github/workflows/main.yml
vendored
Normal file
13
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Rustfmt
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mbrobbel/rustfmt-check@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
target/
|
||||
.vscode
|
||||
.app-signing
|
||||
*.swp
|
||||
/home
|
||||
modules/**/wit
|
||||
target.wasm
|
||||
world
|
3
Cargo-component.lock
Normal file
3
Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
5833
Cargo.lock
generated
Normal file
5833
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
Cargo.toml
Normal file
57
Cargo.toml
Normal file
@ -0,0 +1,57 @@
|
||||
[package]
|
||||
name = "uqbar"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
aes-gcm = "0.10.2"
|
||||
anyhow = "1.0.71"
|
||||
async-recursion = "1.0.4"
|
||||
async-trait = "0.1.71"
|
||||
bincode = "1.3.3"
|
||||
blake3 = "1.4.1"
|
||||
bytes = "1.4.0"
|
||||
cap-std = "2.0.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
cita_trie = "4.0.0"
|
||||
crossterm = { version = "0.26.1", features = ["event-stream", "bracketed-paste"] }
|
||||
digest = "0.10"
|
||||
dotenv = "0.15.0"
|
||||
elliptic-curve = { version = "0.13.5", features = ["ecdh"] }
|
||||
ethers = "2.0"
|
||||
ethers-providers = "2.0.9"
|
||||
futures = "0.3"
|
||||
generic-array = "0.14"
|
||||
getrandom = "0.2.10"
|
||||
hasher = "*"
|
||||
hex = "0.4.3"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12"
|
||||
http = "0.2.9"
|
||||
jwt = "0.16"
|
||||
lazy_static = "1.4.0"
|
||||
log = "*"
|
||||
num-traits = "0.2"
|
||||
open = "5.0.0"
|
||||
public-ip = "0.2.2"
|
||||
rand = "0.8.4"
|
||||
reqwest = "0.11.18"
|
||||
ring = "0.16.20"
|
||||
rsa = "0.9"
|
||||
rusoto_core = "0.48.0"
|
||||
rusoto_s3 = "0.48.0"
|
||||
rusoto_credential = "0.48.0"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "0.7"
|
||||
sha2 = "0.10"
|
||||
thiserror = "1.0.43"
|
||||
tokio = { version = "1.28", features = ["fs", "macros", "rt-multi-thread", "sync"] }
|
||||
tokio-tungstenite = "*"
|
||||
url = "*"
|
||||
uuid = { version = "1.1.2", features = ["serde", "v4"] }
|
||||
warp = "0.3.5"
|
||||
wasmtime = "12.0.1"
|
||||
wasmtime-wasi = "12.0.1"
|
68
README.md
Normal file
68
README.md
Normal file
@ -0,0 +1,68 @@
|
||||
Last updated: 10/02/23
|
||||
## Setup
|
||||
|
||||
### Building components
|
||||
|
||||
```bash
|
||||
# Clone the repo.
|
||||
|
||||
git clone git@github.com:uqbar-dao/operationOJ.git
|
||||
|
||||
# Get some stuff so we can build wasm.
|
||||
|
||||
cargo install wasm-tools
|
||||
rustup install nightly
|
||||
rustup target add wasm32-wasi
|
||||
rustup target add wasm32-wasi --toolchain nightly
|
||||
cargo install cargo-wasi
|
||||
cargo install --git https://github.com/bytecodealliance/cargo-component --locked cargo-component
|
||||
|
||||
# Build the runtime, along with a number of booted-at-startup WASM modules including terminal and key_value
|
||||
# OPTIONAL: --release flag
|
||||
cargo +nightly build
|
||||
|
||||
# To build all of the apps
|
||||
# OPTIONAL: --release flag
|
||||
./build.sh --all
|
||||
|
||||
# Create the home directory for your node
|
||||
# If you boot multiple nodes, make sure each has their own home directory.
|
||||
mkdir home
|
||||
```
|
||||
|
||||
### Boot
|
||||
|
||||
Before booting, compile all the apps with `./build.sh --all` (this may take some time). Then, booting your node takes one argument: the home directory where all your files will be stored. You can use your own custom eth-rpc URL using the `--rpc` flag (NOTE: RPC URL must begin with `wss://` NOT `https://`). You can also include the `--release` if you want optimized performance.
|
||||
|
||||
If you do not receive QNS updates in terminal, it's a sign that the default public-access RPC endpoint is rate-limiting or blocking you. Get an eth-sepolia-rpc API key and pass that as an argument. You can get one for free at `alchemy.com`.
|
||||
|
||||
Also, make sure not to use the same home directory for two nodes at once! You can use any name for the home directory.
|
||||
```bash
|
||||
cargo +nightly run --release home
|
||||
```
|
||||
|
||||
On boot you will be prompted to navigate to `localhost:8080`. Make sure your eth wallet is connected to the Sepolia test network. Login should be very straightforward, just submit the transactions and follow the flow.
|
||||
|
||||
### Development
|
||||
Running `./build.sh` will automatically build any apps that have changes in git. Developing with `./build.sh && cargo +nightly run home` anytime you make a change to your app should be very fast. You can also manually recompile just your app with, for example, `./build-app chess`, where `chess` is the folder name inside `/modules`.
|
||||
|
||||
## Terminal syntax
|
||||
|
||||
- CTRL+C or CTRL+D to shutdown node
|
||||
- CTRL+V to toggle verbose mode, which is on by default
|
||||
- CTRL+J to toggle debug mode
|
||||
- CTRL+S to step through events in debug mode
|
||||
|
||||
- CTRL+A to jump to beginning of input
|
||||
- CTRL+E to jump to end of input
|
||||
- UpArrow/DownArrow or CTRL+P/CTRL+N to move up and down through command history
|
||||
- CTRL+R to search history, CTRL+R again to toggle through search results, CTRL+G to cancel search
|
||||
|
||||
- `!message <name> <app> <json>`: send a card with a JSON value to another node or yourself. <name> can be `our`, which will be interpreted as our node's username.
|
||||
- `!hi <name> <string>`: send a text message to another node's command line.
|
||||
- `<name>` is either the name of a node or `our`, which will fill in the present node name
|
||||
- more to come
|
||||
|
||||
## Example usage
|
||||
|
||||
TODO
|
45
build-app.sh
Executable file
45
build-app.sh
Executable file
@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
release_flag=""
|
||||
|
||||
if [ $# -ne 1 ] && [ $# -ne 2 ]; then
|
||||
echo "Usage: $0 <name> [--release]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
name="$1"
|
||||
|
||||
if [[ "$2" == "--release" ]]; then
|
||||
release_flag="--release"
|
||||
fi
|
||||
|
||||
pwd=$(pwd)
|
||||
|
||||
# Check if the --release flag is present
|
||||
if [[ "$@" == *"--release"* ]]; then
|
||||
release_flag="--release"
|
||||
fi
|
||||
|
||||
rm -rf "$pwd/modules/$name/wit" || { echo "Command failed"; exit 1; }
|
||||
cp -r wit "$pwd/modules/$name" || { echo "Command failed"; exit 1; }
|
||||
mkdir -p "$pwd/modules/$name/target/bindings/$name" || { echo "Command failed"; exit 1; }
|
||||
|
||||
cp target.wasm "$pwd/modules/$name/target/bindings/$name/" || { echo "Command failed"; exit 1; }
|
||||
cp world "$pwd/modules/$name/target/bindings/$name/" || { echo "Command failed"; exit 1; }
|
||||
|
||||
mkdir -p "$pwd/modules/$name/target/wasm32-unknown-unknown/release" || { echo "Command failed"; exit 1; }
|
||||
|
||||
# Build the module using Cargo
|
||||
cargo build \
|
||||
$release_flag \
|
||||
--no-default-features \
|
||||
--manifest-path="$pwd/modules/$name/Cargo.toml"\
|
||||
--target "wasm32-wasi" || {
|
||||
echo "Command failed"; exit 1;
|
||||
}
|
||||
|
||||
# Adapt the module using wasm-tools
|
||||
wasm-tools component new "$pwd/modules/$name/target/wasm32-wasi/release/$name.wasm" -o "$pwd/modules/$name/target/wasm32-wasi/release/${name}_adapted.wasm" --adapt "$pwd/wasi_snapshot_preview1.wasm" || { echo "Command failed"; exit 1; }
|
||||
|
||||
# Embed "wit" into the component and place it in the expected location
|
||||
wasm-tools component embed wit --world uq-process "$pwd/modules/$name/target/wasm32-wasi/release/${name}_adapted.wasm" -o "$pwd/modules/$name/target/wasm32-unknown-unknown/release/$name.wasm" || { echo "Command failed"; exit 1; }
|
58
build.sh
Executable file
58
build.sh
Executable file
@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
|
||||
all=false
|
||||
release=""
|
||||
|
||||
# prase arguments (--all, --release)
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--all)
|
||||
all=true
|
||||
;;
|
||||
--release)
|
||||
release="--release"
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unrecognized argument: $arg"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
pwd=$(pwd)
|
||||
|
||||
# create target.wasm (compiled .wit) & world
|
||||
wasm-tools component wit "${pwd}/wit/" -o target.wasm --wasm || {
|
||||
echo "Command failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run the second command and exit if it fails
|
||||
touch "${pwd}/world" || {
|
||||
echo "Command failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# if --all compile all apps
|
||||
if $all; then
|
||||
modules_dir="./modules"
|
||||
for dir in "$modules_dir"/*; do
|
||||
# Check if it's a directory
|
||||
if [ -d "$dir" ]; then
|
||||
dir_name=$(basename "$dir")
|
||||
./build-app.sh "$dir_name" $release
|
||||
fi
|
||||
done
|
||||
# else just compile the ones that have git changes
|
||||
# NOTE: this can screw you up if you
|
||||
# 1. make a change
|
||||
# 2. compile it with ./build.sh
|
||||
# 3. revert those changes
|
||||
# this script will not recompile it after that because it uses git to detect changes
|
||||
# so every once in a while just run --all to make sure everything is in line
|
||||
else
|
||||
DIRS=($(git -C . status --porcelain | grep 'modules/' | sed -n 's|^.*modules/\([^/]*\)/.*$|\1|p' | sort -u))
|
||||
for dir in "${DIRS[@]}"; do
|
||||
./build-app.sh $dir $release
|
||||
done
|
||||
fi
|
3
modules/apps_home/Cargo-component.lock
Normal file
3
modules/apps_home/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
462
modules/apps_home/Cargo.lock
generated
Normal file
462
modules/apps_home/Cargo.lock
generated
Normal file
@ -0,0 +1,462 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "apps_home"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5c3d15a04ce994fad2c5442a754b404ab1fee23c903a04a560f84f94fdf63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
30
modules/apps_home/Cargo.toml
Normal file
30
modules/apps_home/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "apps_home"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:microkernel-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
258
modules/apps_home/src/home.html
Normal file
258
modules/apps_home/src/home.html
Normal file
File diff suppressed because one or more lines are too long
174
modules/apps_home/src/lib.rs
Normal file
174
modules/apps_home/src/lib.rs
Normal file
@ -0,0 +1,174 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::{print_to_terminal, receive, send_request, send_requests, send_response, get_payload, Guest};
|
||||
use bindings::component::uq_process::types::*;
|
||||
use serde_json::json;
|
||||
|
||||
mod process_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
const APPS_HOME_PAGE: &str = include_str!("home.html");
|
||||
|
||||
fn generate_http_binding(add: Address, path: &str, authenticated: bool) -> (Address, Request, Option<Context>, Option<Payload>) {
|
||||
(
|
||||
add,
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": path,
|
||||
"app": "apps_home",
|
||||
"authenticated": authenticated
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
print_to_terminal(1, "apps_home: start");
|
||||
|
||||
let bindings_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("http_bindings".to_string()),
|
||||
};
|
||||
|
||||
// <address, request, option<context>, option<payload>>
|
||||
let http_endpoint_binding_requests: [(Address, Request, Option<Context>, Option<Payload>); 1] = [
|
||||
generate_http_binding(bindings_address.clone(), "/", true),
|
||||
];
|
||||
send_requests(&http_endpoint_binding_requests);
|
||||
|
||||
loop {
|
||||
let Ok((_source, message)) = receive() else {
|
||||
print_to_terminal(0, "apps_home: got network error");
|
||||
continue;
|
||||
};
|
||||
let Message::Request(request) = message else {
|
||||
print_to_terminal(0, &format!("apps_home: got unexpected message: {:?}", message));
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(json) = request.ipc {
|
||||
print_to_terminal(1, format!("apps_home: JSON {}", json).as_str());
|
||||
let message_json: serde_json::Value = match serde_json::from_str(&json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "apps_home: failed to parse ipc JSON, skipping");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
print_to_terminal(1, "apps_home: parsed ipc JSON");
|
||||
|
||||
if message_json["path"] == "/" && message_json["method"] == "GET" {
|
||||
print_to_terminal(1, "apps_home: sending response");
|
||||
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "response",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: APPS_HOME_PAGE.replace("${our}", &our.node).to_string().as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
} else if message_json["path"].is_string() {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(json!({
|
||||
"action": "response",
|
||||
"status": 404,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: "Not Found"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
} else if message_json["hello"] == "world" {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"hello": "to you too"
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({
|
||||
"hello": "to you too"
|
||||
}).to_string().as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if let Some(payload) = get_payload() {
|
||||
if let Ok(json) = serde_json::from_slice::<serde_json::Value>(&payload.bytes) {
|
||||
print_to_terminal(1, format!("JSON: {}", json).as_str());
|
||||
if json["message"] == "ping" {
|
||||
// WebSocket pushes are sent as requests
|
||||
send_request(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("encryptor".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"EncryptAndForwardAction": {
|
||||
"channel_id": "apps_home",
|
||||
"forward_to": {
|
||||
"node": our.node.clone(),
|
||||
"process": {
|
||||
"Name": "http_server"
|
||||
}, // If the message passed in an ID then we could send to just that ID
|
||||
}, // node, process
|
||||
"json": Some(serde_json::json!({ // this is the JSON to forward
|
||||
"WebSocketPush": {
|
||||
"target": {
|
||||
"node": our.node.clone(),
|
||||
"id": "apps_home", // If the message passed in an ID then we could send to just that ID
|
||||
}
|
||||
}
|
||||
})),
|
||||
}
|
||||
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({
|
||||
"pong": true
|
||||
}).to_string().as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
modules/apps_home/src/process_lib.rs
Normal file
142
modules/apps_home/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
3
modules/chess/Cargo-component.lock
Normal file
3
modules/chess/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
765
modules/chess/Cargo.lock
generated
Normal file
765
modules/chess/Cargo.lock
generated
Normal file
@ -0,0 +1,765 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chess"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"pleco",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mucow"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c55d0c9dc43dedfd2414deb74ade67687749ef88b1d3482024d4c81d901a7a83"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "pleco"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a8c8ab569c544644c468a63f4fe4b33c0706b1472bebb517fabb75ec0f688e"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"mucow",
|
||||
"num_cpus",
|
||||
"rand",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
|
||||
dependencies = [
|
||||
"autocfg 0.1.8",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core 0.4.2",
|
||||
"rand_hc",
|
||||
"rand_isaac",
|
||||
"rand_jitter",
|
||||
"rand_os",
|
||||
"rand_pcg",
|
||||
"rand_xorshift",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
|
||||
dependencies = [
|
||||
"autocfg 0.1.8",
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_isaac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_jitter"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_os"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
dependencies = [
|
||||
"cloudabi",
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_pcg"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
|
||||
dependencies = [
|
||||
"autocfg 0.1.8",
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xorshift"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5c3d15a04ce994fad2c5442a754b404ab1fee23c903a04a560f84f94fdf63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
32
modules/chess/Cargo.toml
Normal file
32
modules/chess/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "chess"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
base64 = "0.13"
|
||||
pleco = "0.5"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:microkernel-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
17
modules/chess/src/chess.html
Normal file
17
modules/chess/src/chess.html
Normal file
File diff suppressed because one or more lines are too long
1
modules/chess/src/index.css
Normal file
1
modules/chess/src/index.css
Normal file
File diff suppressed because one or more lines are too long
113
modules/chess/src/index.js
Normal file
113
modules/chess/src/index.js
Normal file
File diff suppressed because one or more lines are too long
990
modules/chess/src/lib.rs
Normal file
990
modules/chess/src/lib.rs
Normal file
@ -0,0 +1,990 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::component::uq_process::types::*;
|
||||
use bindings::{
|
||||
get_payload, print_to_terminal, receive, send_and_await_response, send_request, send_requests,
|
||||
send_response, Guest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
extern crate base64;
|
||||
extern crate pleco;
|
||||
use pleco::Board;
|
||||
|
||||
mod process_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Game {
|
||||
pub id: String, // the node with whom we are playing
|
||||
pub turns: u64,
|
||||
pub board: Board,
|
||||
pub white: String,
|
||||
pub black: String,
|
||||
pub ended: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct StoredGame {
|
||||
pub id: String, // the node with whom we are playing
|
||||
pub turns: u64,
|
||||
pub board: String,
|
||||
pub white: String,
|
||||
pub black: String,
|
||||
pub ended: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ChessState {
|
||||
pub games: HashMap<String, Game>, // game is by opposing player id
|
||||
pub records: HashMap<String, (u64, u64, u64)>, // wins, losses, draws
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct StoredChessState {
|
||||
pub games: HashMap<String, StoredGame>, // game is by opposing player id
|
||||
pub records: HashMap<String, (u64, u64, u64)>, // wins, losses, draws
|
||||
}
|
||||
|
||||
fn convert_game(game: Game) -> StoredGame {
|
||||
StoredGame {
|
||||
id: game.id,
|
||||
turns: game.turns,
|
||||
board: game.board.fen(),
|
||||
white: game.white,
|
||||
black: game.black,
|
||||
ended: game.ended,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_state(state: ChessState) -> StoredChessState {
|
||||
StoredChessState {
|
||||
games: state
|
||||
.games
|
||||
.iter()
|
||||
.map(|(id, game)| (id.to_string(), convert_game(game.clone())))
|
||||
.collect(),
|
||||
records: state.records.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn json_game(game: &Game) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"id": game.id,
|
||||
"turns": game.turns,
|
||||
"board": game.board.fen(),
|
||||
"white": game.white,
|
||||
"black": game.black,
|
||||
"ended": game.ended,
|
||||
})
|
||||
}
|
||||
|
||||
fn send_http_response(status: u16, headers: HashMap<String, String>, payload_bytes: Vec<u8>) {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"status": status,
|
||||
"headers": headers,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: payload_bytes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn send_ws_update(our_name: String, game: Game) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name("encryptor".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"EncryptAndForwardAction": {
|
||||
"channel_id": "chess",
|
||||
"forward_to": {
|
||||
"node": our_name.clone(),
|
||||
"process": {
|
||||
"Name": "http_server"
|
||||
}, // If the message passed in an ID then we could send to just that ID
|
||||
}, // node, process
|
||||
"json": Some(serde_json::json!({ // this is the JSON to forward
|
||||
"WebSocketPush": {
|
||||
"target": {
|
||||
"node": our_name.clone(),
|
||||
"id": "chess", // If the message passed in an ID then we could send to just that ID
|
||||
}
|
||||
}
|
||||
})),
|
||||
}
|
||||
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({
|
||||
"kind": "game_update",
|
||||
"data": json_game(&game),
|
||||
})
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn response_success() -> bool {
|
||||
let Some(payload) = get_payload() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Ok(status) = String::from_utf8(payload.bytes) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
status == "success"
|
||||
}
|
||||
|
||||
fn binary_encoded_string_to_bytes(s: &str) -> Vec<u8> {
|
||||
s.chars().map(|c| c as u8).collect()
|
||||
}
|
||||
|
||||
fn save_chess_state(our: String, state: ChessState) {
|
||||
let stored_state = convert_state(state);
|
||||
process_lib::set_state(our, bincode::serialize(&stored_state).unwrap());
|
||||
}
|
||||
|
||||
const CHESS_PAGE: &str = include_str!("chess.html");
|
||||
const CHESS_JS: &str = include_str!("index.js");
|
||||
const CHESS_CSS: &str = include_str!("index.css");
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
print_to_terminal(0, "CHESS: start");
|
||||
|
||||
let bindings_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("http_bindings".to_string()),
|
||||
};
|
||||
|
||||
// <address, request, option<context>, option<payload>>
|
||||
let http_endpoint_binding_requests: [(Address, Request, Option<Context>, Option<Payload>);
|
||||
2] = [
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/chess",
|
||||
"app": "chess",
|
||||
"authenticated": true,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/chess/games",
|
||||
"app": "chess",
|
||||
"authenticated": true,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
),
|
||||
];
|
||||
send_requests(&http_endpoint_binding_requests);
|
||||
|
||||
let mut state: ChessState = match process_lib::get_state(our.node.clone()) {
|
||||
Some(payload) => match bincode::deserialize::<StoredChessState>(&payload.bytes) {
|
||||
Ok(state) => {
|
||||
let mut games = HashMap::new();
|
||||
for (id, game) in state.games {
|
||||
if let Ok(board) = Board::from_fen(&game.board) {
|
||||
games.insert(
|
||||
id,
|
||||
Game {
|
||||
id: game.id.clone(),
|
||||
turns: game.turns,
|
||||
board: board,
|
||||
white: game.white.clone(),
|
||||
black: game.black.clone(),
|
||||
ended: game.ended,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
games.insert(
|
||||
id,
|
||||
Game {
|
||||
id: game.id.clone(),
|
||||
turns: 0,
|
||||
board: Board::start_pos(),
|
||||
white: game.white.clone(),
|
||||
black: game.black.clone(),
|
||||
ended: game.ended,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ChessState {
|
||||
games: games,
|
||||
records: state.records,
|
||||
}
|
||||
}
|
||||
Err(_) => ChessState {
|
||||
games: HashMap::new(),
|
||||
records: HashMap::new(),
|
||||
},
|
||||
},
|
||||
None => ChessState {
|
||||
games: HashMap::new(),
|
||||
records: HashMap::new(),
|
||||
},
|
||||
};
|
||||
|
||||
loop {
|
||||
let Ok((source, message)) = receive() else {
|
||||
print_to_terminal(0, "chess: got network error");
|
||||
continue;
|
||||
};
|
||||
let Message::Request(request) = message else {
|
||||
print_to_terminal(1, "chess: got unexpected Response");
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(json) = request.ipc {
|
||||
print_to_terminal(1, format!("chess: JSON {}", json).as_str());
|
||||
let message_json: serde_json::Value = match serde_json::from_str(&json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "chess: failed to parse ipc JSON, skipping");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
print_to_terminal(1, "chess: parsed ipc JSON");
|
||||
|
||||
if source.process == ProcessId::Name("chess".to_string()) {
|
||||
let action = message_json["action"].as_str().unwrap_or("");
|
||||
let game_id = source.node.clone();
|
||||
|
||||
match action {
|
||||
"new_game" => {
|
||||
// make a new game with source.node if the current game has ended
|
||||
if let Some(game) = state.games.get(&game_id) {
|
||||
if !game.ended {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "conflict".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let game = Game {
|
||||
id: game_id.clone(),
|
||||
turns: 0,
|
||||
board: Board::start_pos(),
|
||||
white: message_json["white"]
|
||||
.as_str()
|
||||
.unwrap_or(game_id.as_str())
|
||||
.to_string(),
|
||||
black: message_json["black"]
|
||||
.as_str()
|
||||
.unwrap_or(our.node.as_str())
|
||||
.to_string(),
|
||||
ended: false,
|
||||
};
|
||||
state.games.insert(game_id.clone(), game.clone());
|
||||
|
||||
send_ws_update(our.node.clone(), game.clone());
|
||||
|
||||
save_chess_state(our.node.clone(), state.clone());
|
||||
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "success".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
"make_move" => {
|
||||
// check the move and then update if correct and send WS update
|
||||
let Some(game) = state.games.get_mut(&game_id) else {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "not found".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let valid_move = game
|
||||
.board
|
||||
.apply_uci_move(message_json["move"].as_str().unwrap_or(""));
|
||||
if valid_move {
|
||||
game.turns += 1;
|
||||
let checkmate = game.board.checkmate();
|
||||
let draw = game.board.stalemate();
|
||||
|
||||
if checkmate || draw {
|
||||
game.ended = true;
|
||||
let winner = if checkmate {
|
||||
if game.turns % 2 == 1 {
|
||||
game.white.clone()
|
||||
} else {
|
||||
game.black.clone()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
// update the records
|
||||
if draw {
|
||||
if let Some(record) = state.records.get_mut(&game.id) {
|
||||
record.2 += 1;
|
||||
} else {
|
||||
state.records.insert(game.id.clone(), (0, 0, 1));
|
||||
}
|
||||
} else {
|
||||
if let Some(record) = state.records.get_mut(&game.id) {
|
||||
if winner == our.node {
|
||||
record.0 += 1;
|
||||
} else {
|
||||
record.1 += 1;
|
||||
}
|
||||
} else {
|
||||
if winner == our.node {
|
||||
state.records.insert(game.id.clone(), (1, 0, 0));
|
||||
} else {
|
||||
state.records.insert(game.id.clone(), (0, 1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send_ws_update(our.node.clone(), game.clone());
|
||||
save_chess_state(our.node.clone(), state.clone());
|
||||
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "success".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "invalid move".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
"end_game" => {
|
||||
// end the game and send WS update, update the standings
|
||||
let Some(game) = state.games.get_mut(&game_id) else {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "not found".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
game.ended = true;
|
||||
|
||||
if let Some(record) = state.records.get_mut(&game.id) {
|
||||
record.0 += 1;
|
||||
} else {
|
||||
state.records.insert(game.id.clone(), (1, 0, 0));
|
||||
}
|
||||
|
||||
send_ws_update(our.node.clone(), game.clone());
|
||||
save_chess_state(our.node.clone(), state.clone());
|
||||
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: "success".as_bytes().to_vec(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
print_to_terminal(1, "chess: got unexpected action");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if source.process == ProcessId::Name("http_bindings".to_string()) {
|
||||
let path = message_json["path"].as_str().unwrap_or("");
|
||||
let method = message_json["method"].as_str().unwrap_or("");
|
||||
|
||||
let mut default_headers = HashMap::new();
|
||||
default_headers.insert("Content-Type".to_string(), "text/html".to_string());
|
||||
// Handle incoming http
|
||||
match path {
|
||||
"/chess" => {
|
||||
let process: String = match our.process {
|
||||
ProcessId::Name(ref name) => name.clone(),
|
||||
ProcessId::Id(id) => id.to_string(),
|
||||
};
|
||||
|
||||
send_http_response(
|
||||
200,
|
||||
default_headers.clone(),
|
||||
CHESS_PAGE
|
||||
.replace("${node}", &our.node)
|
||||
.replace("${process}", &process)
|
||||
.replace("${js}", CHESS_JS)
|
||||
.replace("${css}", CHESS_CSS)
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
"/chess/games" => {
|
||||
match method {
|
||||
"GET" => {
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers = default_headers.clone();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"application/json".to_string(),
|
||||
);
|
||||
headers
|
||||
},
|
||||
{
|
||||
let mut json_games: HashMap<String, serde_json::Value> =
|
||||
HashMap::new();
|
||||
for (id, game) in &state.games {
|
||||
json_games.insert(id.to_string(), json_game(&game));
|
||||
}
|
||||
json!(json_games).to_string().as_bytes().to_vec()
|
||||
},
|
||||
);
|
||||
}
|
||||
"POST" => {
|
||||
// create a new game
|
||||
if let Some(payload) = get_payload() {
|
||||
if let Ok(payload_json) =
|
||||
serde_json::from_slice::<serde_json::Value>(
|
||||
&payload.bytes,
|
||||
)
|
||||
{
|
||||
let game_id = String::from(
|
||||
payload_json["id"].as_str().unwrap_or(""),
|
||||
);
|
||||
if game_id == "" {
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"Bad Request".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(game) = state.games.get(&game_id) {
|
||||
if !game.ended {
|
||||
send_http_response(
|
||||
409,
|
||||
default_headers.clone(),
|
||||
"Conflict".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let white = payload_json["white"]
|
||||
.as_str()
|
||||
.unwrap_or(our.node.as_str())
|
||||
.to_string();
|
||||
let black = payload_json["black"]
|
||||
.as_str()
|
||||
.unwrap_or(game_id.as_str())
|
||||
.to_string();
|
||||
|
||||
let response = send_and_await_response(
|
||||
&Address {
|
||||
node: game_id.clone(),
|
||||
process: ProcessId::Name("chess".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(30), // TODO check this!
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "new_game",
|
||||
"white": white.clone(),
|
||||
"black": black.clone(),
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
||||
match response {
|
||||
Ok(_reponse) => {
|
||||
if !response_success() {
|
||||
send_http_response(
|
||||
503,
|
||||
default_headers.clone(),
|
||||
"Service Unavailable"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// create a new game
|
||||
let game = Game {
|
||||
id: game_id.clone(),
|
||||
turns: 0,
|
||||
board: Board::start_pos(),
|
||||
white: white.clone(),
|
||||
black: black.clone(),
|
||||
ended: false,
|
||||
};
|
||||
state
|
||||
.games
|
||||
.insert(game_id.clone(), game.clone());
|
||||
|
||||
save_chess_state(
|
||||
our.node.clone(),
|
||||
state.clone(),
|
||||
);
|
||||
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers =
|
||||
default_headers.clone();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"application/json".to_string(),
|
||||
);
|
||||
headers
|
||||
},
|
||||
json_game(&game)
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
send_http_response(
|
||||
503,
|
||||
default_headers.clone(),
|
||||
"Service Unavailable"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"Bad Request".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
"PUT" => {
|
||||
// make a move
|
||||
if let Some(payload) = get_payload() {
|
||||
print_to_terminal(
|
||||
1,
|
||||
format!(
|
||||
"payload: {}",
|
||||
String::from_utf8(payload.bytes.clone())
|
||||
.unwrap_or("".to_string())
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
if let Ok(payload_json) =
|
||||
serde_json::from_slice::<serde_json::Value>(
|
||||
&payload.bytes,
|
||||
)
|
||||
{
|
||||
let game_id = String::from(
|
||||
payload_json["id"].as_str().unwrap_or(""),
|
||||
);
|
||||
|
||||
if game_id == "" {
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"No game ID".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(game) = state.games.get_mut(&game_id) {
|
||||
if game.turns % 2 == 0 && game.white != our.node {
|
||||
send_http_response(
|
||||
403,
|
||||
default_headers.clone(),
|
||||
"Forbidden".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
} else if game.turns % 2 == 1
|
||||
&& game.black != our.node
|
||||
{
|
||||
send_http_response(
|
||||
403,
|
||||
default_headers.clone(),
|
||||
"Forbidden".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
} else if game.ended {
|
||||
send_http_response(
|
||||
409,
|
||||
default_headers.clone(),
|
||||
"Conflict".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let move_str =
|
||||
payload_json["move"].as_str().unwrap_or("");
|
||||
let valid_move =
|
||||
game.board.apply_uci_move(move_str);
|
||||
if valid_move {
|
||||
// send the move to the other player
|
||||
// check if the game is over
|
||||
// if so, update the records
|
||||
let response = send_and_await_response(
|
||||
&Address {
|
||||
node: game_id.clone(),
|
||||
process: ProcessId::Name(
|
||||
"chess".to_string(),
|
||||
),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(30), // TODO check this!
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "make_move",
|
||||
"move": move_str,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
||||
match response {
|
||||
Ok(_reponse) => {
|
||||
if !response_success() {
|
||||
send_http_response(
|
||||
503,
|
||||
default_headers.clone(),
|
||||
"Service Unavailable"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// update the game
|
||||
game.turns += 1;
|
||||
let checkmate = game.board.checkmate();
|
||||
let draw = game.board.stalemate();
|
||||
|
||||
if checkmate || draw {
|
||||
game.ended = true;
|
||||
let winner = if checkmate {
|
||||
if game.turns % 2 == 1 {
|
||||
game.white.clone()
|
||||
} else {
|
||||
game.black.clone()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
// update the records
|
||||
if draw {
|
||||
if let Some(record) = state
|
||||
.records
|
||||
.get_mut(&game.id)
|
||||
{
|
||||
record.2 += 1;
|
||||
} else {
|
||||
state.records.insert(
|
||||
game.id.clone(),
|
||||
(0, 0, 1),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if let Some(record) = state
|
||||
.records
|
||||
.get_mut(&game.id)
|
||||
{
|
||||
if winner == our.node {
|
||||
record.0 += 1;
|
||||
} else {
|
||||
record.1 += 1;
|
||||
}
|
||||
} else {
|
||||
if winner == our.node {
|
||||
state.records.insert(
|
||||
game.id.clone(),
|
||||
(1, 0, 0),
|
||||
);
|
||||
} else {
|
||||
state.records.insert(
|
||||
game.id.clone(),
|
||||
(0, 1, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let game = game.clone();
|
||||
save_chess_state(
|
||||
our.node.clone(),
|
||||
state.clone(),
|
||||
);
|
||||
// return the game
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers =
|
||||
default_headers.clone();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"application/json"
|
||||
.to_string(),
|
||||
);
|
||||
headers
|
||||
},
|
||||
json_game(&game)
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
send_http_response(
|
||||
503,
|
||||
default_headers.clone(),
|
||||
"Service Unavailable"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_to_terminal(0, "never got a response");
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"Bad Request".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
"DELETE" => {
|
||||
let game_id = message_json["query_params"]["id"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
if game_id == "" {
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"Bad Request".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
if let Some(game) = state.games.get_mut(&game_id) {
|
||||
let response = send_and_await_response(
|
||||
&Address {
|
||||
node: game_id.clone(),
|
||||
process: ProcessId::Name("chess".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(30), // TODO check this!
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "end_game",
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
|
||||
match response {
|
||||
Ok(_response) => {
|
||||
if !response_success() {
|
||||
send_http_response(
|
||||
503,
|
||||
default_headers.clone(),
|
||||
"Service Unavailable"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
game.ended = true;
|
||||
|
||||
if let Some(record) =
|
||||
state.records.get_mut(&game.id)
|
||||
{
|
||||
record.1 += 1;
|
||||
} else {
|
||||
state
|
||||
.records
|
||||
.insert(game.id.clone(), (0, 1, 0));
|
||||
}
|
||||
|
||||
let game = game.clone();
|
||||
save_chess_state(
|
||||
our.node.clone(),
|
||||
state.clone(),
|
||||
);
|
||||
|
||||
// return the game
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers =
|
||||
default_headers.clone();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"application/json".to_string(),
|
||||
);
|
||||
headers
|
||||
},
|
||||
json_game(&game)
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
send_http_response(
|
||||
503,
|
||||
default_headers.clone(),
|
||||
"Service Unavailable"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// end a game
|
||||
}
|
||||
_ => {
|
||||
send_http_response(
|
||||
404,
|
||||
default_headers.clone(),
|
||||
"Not Found".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
send_http_response(
|
||||
404,
|
||||
default_headers.clone(),
|
||||
"Not Found".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
modules/chess/src/process_lib.rs
Normal file
142
modules/chess/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
3
modules/http_bindings/Cargo-component.lock
Normal file
3
modules/http_bindings/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
580
modules/http_bindings/Cargo.lock
generated
Normal file
580
modules/http_bindings/Cargo.lock
generated
Normal file
@ -0,0 +1,580 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http_bindings"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"hmac",
|
||||
"jwt",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"url",
|
||||
"wit-bindgen 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "jwt"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"crypto-common",
|
||||
"digest",
|
||||
"hmac",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5c3d15a04ce994fad2c5442a754b404ab1fee23c903a04a560f84f94fdf63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
34
modules/http_bindings/Cargo.toml
Normal file
34
modules/http_bindings/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "http_bindings"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.72"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde_json = "1.0"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
url = "2"
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
jwt = "0.16"
|
||||
sha2 = "0.10"
|
||||
hmac = "0.12"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:microkernel-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
625
modules/http_bindings/src/lib.rs
Normal file
625
modules/http_bindings/src/lib.rs
Normal file
@ -0,0 +1,625 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use hmac::{Hmac, Mac};
|
||||
use jwt::{Error, SignWithKey, VerifyWithKey};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha256;
|
||||
use std::collections::HashMap;
|
||||
use url::form_urlencoded;
|
||||
|
||||
use bindings::component::uq_process::types::*;
|
||||
use bindings::{get_payload, print_to_terminal, receive, send_request, send_response, Guest};
|
||||
|
||||
mod process_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct BoundPath {
|
||||
app: String,
|
||||
authenticated: bool,
|
||||
local_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct JwtClaims {
|
||||
username: String,
|
||||
expiration: u64,
|
||||
}
|
||||
|
||||
fn generate_token(our_node: String, secret: Hmac<Sha256>) -> Option<String> {
|
||||
let claims = JwtClaims {
|
||||
username: our_node,
|
||||
expiration: 0,
|
||||
};
|
||||
let token: Option<String> = match claims.sign_with_key(&secret) {
|
||||
Ok(token) => Some(token),
|
||||
Err(_) => None,
|
||||
};
|
||||
token
|
||||
}
|
||||
|
||||
fn auth_cookie_valid(our_node: String, cookie: &str, secret: Hmac<Sha256>) -> bool {
|
||||
let cookie_parts: Vec<&str> = cookie.split("; ").collect();
|
||||
let mut auth_token = None;
|
||||
for cookie_part in cookie_parts {
|
||||
let cookie_part_parts: Vec<&str> = cookie_part.split("=").collect();
|
||||
if cookie_part_parts.len() == 2
|
||||
&& cookie_part_parts[0] == format!("uqbar-auth_{}", our_node)
|
||||
{
|
||||
auth_token = Some(cookie_part_parts[1].to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let auth_token = match auth_token {
|
||||
Some(token) if !token.is_empty() => token,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
print_to_terminal(
|
||||
1,
|
||||
format!("http_bindings: auth_token: {}", auth_token).as_str(),
|
||||
);
|
||||
|
||||
let claims: Result<JwtClaims, Error> = auth_token.verify_with_key(&secret);
|
||||
|
||||
match claims {
|
||||
Ok(data) => {
|
||||
print_to_terminal(
|
||||
1,
|
||||
format!(
|
||||
"http_bindings: our name: {}, token_name {}",
|
||||
our_node, data.username
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
data.username == our_node
|
||||
}
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "http_bindings: failed to verify token");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_http_response(status: u16, headers: HashMap<String, String>, payload_bytes: Vec<u8>) {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"status": status,
|
||||
"headers": headers,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: payload_bytes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
print_to_terminal(1, "http_bindings: start");
|
||||
let mut path_bindings: HashMap<String, BoundPath> = HashMap::new();
|
||||
let mut jwt_secret: Option<Hmac<Sha256>> = None;
|
||||
|
||||
// get jwt secret from http_server, handle as a request with set-jwt-secret
|
||||
send_request(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("http_server".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"ServerAction": {
|
||||
"action": "get-jwt-secret",
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
loop {
|
||||
let Ok((source, message)) = receive() else {
|
||||
print_to_terminal(0, "http_bindings: got network error");
|
||||
continue;
|
||||
};
|
||||
let Message::Request(request) = message else {
|
||||
// Ignore responses for now
|
||||
print_to_terminal(0, "http_bindings: got unexpected Respose, ignoring");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(json) = request.ipc else {
|
||||
print_to_terminal(0, "http_bindings: no ipc JSON, skipping");
|
||||
continue;
|
||||
};
|
||||
|
||||
let message_json: serde_json::Value = match serde_json::from_str(&json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "http_bindings: failed to parse ipc JSON, skipping");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let action = message_json["action"].as_str().unwrap_or("");
|
||||
let address = message_json["address"].as_str().unwrap_or(""); // origin HTTP address
|
||||
let path = message_json["path"].as_str().unwrap_or("");
|
||||
let app = match source.process {
|
||||
ProcessId::Name(name) => name,
|
||||
_ => "".to_string(),
|
||||
};
|
||||
|
||||
print_to_terminal(1, "http_bindings: got message");
|
||||
|
||||
if action == "set-jwt-secret" {
|
||||
let Some(payload) = get_payload() else {
|
||||
panic!("set-jwt-secret with no payload");
|
||||
};
|
||||
|
||||
let jwt_secret_bytes = payload.bytes;
|
||||
|
||||
print_to_terminal(1, "http_bindings: generating token secret...");
|
||||
jwt_secret = match Hmac::new_from_slice(&jwt_secret_bytes) {
|
||||
Ok(secret) => Some(secret),
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "http_bindings: failed to generate token secret");
|
||||
None
|
||||
}
|
||||
};
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
} else if action == "bind-app" && path != "" && app != "" {
|
||||
print_to_terminal(1, "http_bindings: binding app 1");
|
||||
let path_segments = path
|
||||
.trim_start_matches('/')
|
||||
.split("/")
|
||||
.collect::<Vec<&str>>();
|
||||
if app != "apps_home"
|
||||
&& (path_segments.is_empty()
|
||||
|| path_segments[0] != app.clone().replace("_", "-"))
|
||||
{
|
||||
print_to_terminal(
|
||||
1,
|
||||
format!(
|
||||
"http_bindings: first path segment does not match process: {}",
|
||||
path
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
print_to_terminal(
|
||||
1,
|
||||
format!("http_bindings: binding app 2 {}", path.to_string()).as_str(),
|
||||
);
|
||||
path_bindings.insert(path.to_string(), {
|
||||
BoundPath {
|
||||
app: app.to_string(),
|
||||
authenticated: message_json
|
||||
.get("authenticated")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false),
|
||||
local_only: message_json
|
||||
.get("local_only")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false),
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if action == "request" {
|
||||
print_to_terminal(1, "http_bindings: got request");
|
||||
|
||||
// Start Login logic
|
||||
if path == "/login" {
|
||||
print_to_terminal(1, "http_bindings: got login request");
|
||||
|
||||
if message_json["method"] == "GET" {
|
||||
print_to_terminal(1, "http_bindings: got login GET request");
|
||||
let login_page_content = include_str!("login.html");
|
||||
let personalized_login_page =
|
||||
login_page_content.replace("${our}", &our.node);
|
||||
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "text/html".to_string());
|
||||
headers
|
||||
},
|
||||
personalized_login_page.as_bytes().to_vec(),
|
||||
);
|
||||
} else if message_json["method"] == "POST" {
|
||||
print_to_terminal(1, "http_bindings: got login POST request");
|
||||
|
||||
let Some(payload) = get_payload() else {
|
||||
panic!("/login POST with no bytes");
|
||||
};
|
||||
let body_json_string = match String::from_utf8(payload.bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
let body: serde_json::Value =
|
||||
serde_json::from_str(&body_json_string).unwrap();
|
||||
let password = body["password"].as_str().unwrap_or("");
|
||||
|
||||
if password == "" {
|
||||
send_http_response(
|
||||
400,
|
||||
HashMap::new(),
|
||||
"Bad Request".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
|
||||
match jwt_secret.clone() {
|
||||
Some(secret) => {
|
||||
match generate_token(our.node.clone(), secret) {
|
||||
Some(token) => {
|
||||
// Token was generated successfully; you can use it here.
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"text/html".to_string(),
|
||||
);
|
||||
headers.insert(
|
||||
"set-cookie".to_string(),
|
||||
format!("uqbar-auth_{}={};", our.node, token),
|
||||
);
|
||||
headers
|
||||
},
|
||||
"".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
print_to_terminal(1, "so secret 1");
|
||||
// Failed to generate token; you should probably return an error.
|
||||
send_http_response(
|
||||
500,
|
||||
HashMap::new(),
|
||||
"Server Error".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
print_to_terminal(1, "so secret 2");
|
||||
send_http_response(
|
||||
500,
|
||||
HashMap::new(),
|
||||
"Server Error".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if message_json["method"] == "PUT" {
|
||||
print_to_terminal(1, "http_bindings: got login PUT request");
|
||||
|
||||
let Some(payload) = get_payload() else {
|
||||
panic!("/login PUT with no bytes");
|
||||
};
|
||||
let body_json_string = match String::from_utf8(payload.bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
let body: serde_json::Value =
|
||||
serde_json::from_str(&body_json_string).unwrap();
|
||||
// let password = body["password"].as_str().unwrap_or("");
|
||||
let signature = body["signature"].as_str().unwrap_or("");
|
||||
|
||||
if signature == "" {
|
||||
send_http_response(
|
||||
400,
|
||||
HashMap::new(),
|
||||
"Bad Request".as_bytes().to_vec(),
|
||||
);
|
||||
} else {
|
||||
// TODO: Check signature against our address
|
||||
print_to_terminal(1, "http_bindings: generating secret...");
|
||||
// jwt_secret = generate_secret(password);
|
||||
print_to_terminal(1, "http_bindings: generating token...");
|
||||
|
||||
match jwt_secret.clone() {
|
||||
Some(secret) => {
|
||||
match generate_token(our.node.clone(), secret) {
|
||||
Some(token) => {
|
||||
// Token was generated successfully; you can use it here.
|
||||
send_http_response(
|
||||
200,
|
||||
{
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"text/html".to_string(),
|
||||
);
|
||||
headers.insert(
|
||||
"set-cookie".to_string(),
|
||||
format!(
|
||||
"uqbar-auth_{}={};",
|
||||
our.node, token
|
||||
),
|
||||
);
|
||||
headers
|
||||
},
|
||||
"".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Failed to generate token; you should probably return an error.
|
||||
send_http_response(
|
||||
500,
|
||||
HashMap::new(),
|
||||
"Server Error".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
send_http_response(
|
||||
500,
|
||||
HashMap::new(),
|
||||
"Server Error".as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
send_http_response(404, HashMap::new(), "Not Found".as_bytes().to_vec());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// End Login logic
|
||||
|
||||
// Start Encryption Secret Logic
|
||||
if path == "/encryptor" {
|
||||
bindings::print_to_terminal(1, "http_bindings: got encryptor request");
|
||||
let auth_success = match jwt_secret.clone() {
|
||||
Some(secret) => {
|
||||
bindings::print_to_terminal(1, "HAVE SECRET");
|
||||
auth_cookie_valid(
|
||||
our.node.clone(),
|
||||
message_json["headers"]["cookie"].as_str().unwrap_or(""),
|
||||
secret,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
bindings::print_to_terminal(1, "NO SECRET");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if auth_success {
|
||||
let body_bytes = match get_payload() {
|
||||
Some(payload) => payload.bytes,
|
||||
None => vec![],
|
||||
};
|
||||
let body_json_string = match String::from_utf8(body_bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => String::new(),
|
||||
};
|
||||
let body: serde_json::Value =
|
||||
serde_json::from_str(&body_json_string).unwrap();
|
||||
let channel_id = body["channel_id"].as_str().unwrap_or("");
|
||||
let public_key_hex = body["public_key_hex"].as_str().unwrap_or("");
|
||||
|
||||
send_request(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("encryptor".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: true,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"GetKeyAction": {
|
||||
"channel_id": channel_id,
|
||||
"public_key_hex": public_key_hex,
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
send_http_response(401, HashMap::new(), "Unauthorized".as_bytes().to_vec());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// End Encryption Secret Logic
|
||||
|
||||
let path_segments = path
|
||||
.trim_start_matches('/')
|
||||
.trim_end_matches('/')
|
||||
.split("/")
|
||||
.collect::<Vec<&str>>();
|
||||
let mut registered_path = path;
|
||||
let mut url_params: HashMap<String, String> = HashMap::new();
|
||||
|
||||
for (key, _value) in &path_bindings {
|
||||
let key_segments = key
|
||||
.trim_start_matches('/')
|
||||
.trim_end_matches('/')
|
||||
.split("/")
|
||||
.collect::<Vec<&str>>();
|
||||
if key_segments.len() != path_segments.len()
|
||||
&& (!key.contains("/.*") || (key_segments.len() - 1) > path_segments.len())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut paths_match = true;
|
||||
for i in 0..key_segments.len() {
|
||||
if key_segments[i] == "*" {
|
||||
break;
|
||||
} else if !(key_segments[i].starts_with(":")
|
||||
|| key_segments[i] == path_segments[i])
|
||||
{
|
||||
paths_match = false;
|
||||
break;
|
||||
} else if key_segments[i].starts_with(":") {
|
||||
url_params.insert(
|
||||
key_segments[i][1..].to_string(),
|
||||
path_segments[i].to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if paths_match {
|
||||
registered_path = key;
|
||||
break;
|
||||
}
|
||||
url_params = HashMap::new();
|
||||
}
|
||||
|
||||
print_to_terminal(
|
||||
1,
|
||||
&("http_bindings: registered path ".to_string() + registered_path),
|
||||
);
|
||||
|
||||
match path_bindings.get(registered_path) {
|
||||
Some(bound_path) => {
|
||||
let app = bound_path.app.as_str();
|
||||
print_to_terminal(
|
||||
1,
|
||||
&("http_bindings: properly unwrapped path ".to_string()
|
||||
+ registered_path),
|
||||
);
|
||||
|
||||
if bound_path.authenticated {
|
||||
print_to_terminal(1, "AUTHENTICATED ROUTE");
|
||||
let auth_success = match jwt_secret.clone() {
|
||||
Some(secret) => auth_cookie_valid(
|
||||
our.node.clone(),
|
||||
message_json["headers"]["cookie"].as_str().unwrap_or(""),
|
||||
secret,
|
||||
),
|
||||
None => {
|
||||
print_to_terminal(1, "NO SECRET");
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !auth_success {
|
||||
print_to_terminal(1, "http_bindings: failure to authenticate");
|
||||
let proxy_path = message_json["proxy_path"].as_str();
|
||||
|
||||
let redirect_path: String = match proxy_path {
|
||||
Some(pp) => {
|
||||
form_urlencoded::byte_serialize(pp.as_bytes()).collect()
|
||||
}
|
||||
None => {
|
||||
form_urlencoded::byte_serialize(path.as_bytes()).collect()
|
||||
}
|
||||
};
|
||||
|
||||
let location = match proxy_path {
|
||||
Some(_) => format!(
|
||||
"/http-proxy/serve/{}/login?redirect={}",
|
||||
&our.node, redirect_path
|
||||
),
|
||||
None => format!("/login?redirect={}", redirect_path),
|
||||
};
|
||||
|
||||
send_http_response(
|
||||
302,
|
||||
{
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"text/html".to_string(),
|
||||
);
|
||||
headers.insert("Location".to_string(), location);
|
||||
headers
|
||||
},
|
||||
"Auth cookie not valid".as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if bound_path.local_only && !address.starts_with("127.0.0.1:") {
|
||||
send_http_response(
|
||||
403,
|
||||
{
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"text/html".to_string(),
|
||||
);
|
||||
headers
|
||||
},
|
||||
"<h1>Localhost Origin Required</h1>".as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// import send-request: func(target: address, request: request, context: option<context>, payload: option<payload>)
|
||||
send_request(
|
||||
&Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name(app.to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: true,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"path": registered_path,
|
||||
"raw_path": path,
|
||||
"method": message_json["method"],
|
||||
"headers": message_json["headers"],
|
||||
"query_params": message_json["query_params"],
|
||||
"url_params": url_params,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
get_payload().as_ref(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
print_to_terminal(1, "http_bindings: no app found at this path");
|
||||
send_http_response(404, HashMap::new(), "Not Found".as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print_to_terminal(
|
||||
1,
|
||||
format!(
|
||||
"http_bindings: unexpected action: {:?}",
|
||||
&message_json["action"],
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle auth correctly, generate a secret and store in filesystem if non-existent
|
335
modules/http_bindings/src/login.html
Normal file
335
modules/http_bindings/src/login.html
Normal file
File diff suppressed because one or more lines are too long
142
modules/http_bindings/src/process_lib.rs
Normal file
142
modules/http_bindings/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
3
modules/http_proxy/Cargo-component.lock
Normal file
3
modules/http_proxy/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
462
modules/http_proxy/Cargo.lock
generated
Normal file
462
modules/http_proxy/Cargo.lock
generated
Normal file
@ -0,0 +1,462 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#aa6e3c1168273b5cf6221fa0206f07f2ffb8567d"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#aa6e3c1168273b5cf6221fa0206f07f2ffb8567d"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http_proxy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5c3d15a04ce994fad2c5442a754b404ab1fee23c903a04a560f84f94fdf63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
30
modules/http_proxy/Cargo.toml
Normal file
30
modules/http_proxy/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "http_proxy"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.72"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:microkernel-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
308
modules/http_proxy/src/http_proxy.html
Normal file
308
modules/http_proxy/src/http_proxy.html
Normal file
File diff suppressed because one or more lines are too long
353
modules/http_proxy/src/lib.rs
Normal file
353
modules/http_proxy/src/lib.rs
Normal file
@ -0,0 +1,353 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use std::collections::HashMap;
|
||||
use serde_json::json;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use bindings::{print_to_terminal, receive, send_requests, send_request, send_response, get_payload, Guest};
|
||||
use bindings::component::uq_process::types::*;
|
||||
|
||||
mod process_lib;
|
||||
|
||||
const PROXY_HOME_PAGE: &str = include_str!("http_proxy.html");
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemAction {
|
||||
Read,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FileSystemRequest {
|
||||
pub uri_string: String,
|
||||
pub action: FileSystemAction,
|
||||
}
|
||||
|
||||
fn send_http_response(
|
||||
status: u16,
|
||||
headers: HashMap<String, String>,
|
||||
payload_bytes: Vec<u8>,
|
||||
) {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"status": status,
|
||||
"headers": headers,
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: payload_bytes,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
fn send_not_found () {
|
||||
send_http_response(404,HashMap::new(),"Not Found".to_string().as_bytes().to_vec())
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
print_to_terminal(1, "http_proxy: start");
|
||||
|
||||
let mut registrations: HashMap<String, String> = HashMap::new();
|
||||
|
||||
let bindings_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("http_bindings".to_string()),
|
||||
};
|
||||
|
||||
// <address, request, option<context>, option<payload>>
|
||||
let http_endpoint_binding_requests: [(Address, Request, Option<Context>, Option<Payload>); 5] = [
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/http-proxy",
|
||||
"authenticated": true,
|
||||
"app": "http_proxy",
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/http-proxy/static/*",
|
||||
"authenticated": true,
|
||||
"app": "http_proxy",
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/http-proxy/list",
|
||||
"app": "http_proxy",
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/http-proxy/register",
|
||||
"app": "http_proxy",
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/http-proxy/serve/:username/*",
|
||||
"app": "http_proxy",
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None
|
||||
),
|
||||
];
|
||||
send_requests(&http_endpoint_binding_requests);
|
||||
|
||||
loop {
|
||||
let Ok((_source, message)) = receive() else {
|
||||
print_to_terminal(0, "http_proxy: got network error");
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "text/html".to_string());
|
||||
send_http_response(503, headers, format!("<h1>Node Offline</h1>").as_bytes().to_vec());
|
||||
continue;
|
||||
};
|
||||
let Message::Request(request) = message else {
|
||||
print_to_terminal(0, "http_proxy: got unexpected message");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(json) = request.ipc else {
|
||||
print_to_terminal(1, "http_proxy: no ipc json");
|
||||
continue;
|
||||
};
|
||||
|
||||
let message_json: serde_json::Value = match serde_json::from_str(&json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "http_proxy: failed to parse ipc JSON, skipping");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
print_to_terminal(1, format!("http_proxy: got request: {}", message_json).as_str());
|
||||
|
||||
if message_json["path"] == "/http-proxy" && message_json["method"] == "GET" {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "response",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: PROXY_HOME_PAGE
|
||||
.replace("${our}", &our.node)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
} else if message_json["path"] == "/http-proxy/list" && message_json["method"] == "GET" {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "response",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({"registrations": registrations})
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
} else if message_json["path"] == "/http-proxy/register" && message_json["method"] == "POST" {
|
||||
let mut status = 204;
|
||||
|
||||
let Some(payload) = get_payload() else {
|
||||
print_to_terminal(1, "/http-proxy/register POST with no bytes");
|
||||
continue;
|
||||
};
|
||||
|
||||
let body: serde_json::Value = match serde_json::from_slice(&payload.bytes) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
print_to_terminal(1, format!("Bad body format: {}", e).as_str());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let username = body["username"].as_str().unwrap_or("");
|
||||
|
||||
print_to_terminal(1, format!("Register proxy for: {}", username).as_str());
|
||||
|
||||
if !username.is_empty() {
|
||||
registrations.insert(username.to_string(), "foo".to_string());
|
||||
} else {
|
||||
status = 400;
|
||||
}
|
||||
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "response",
|
||||
"status": status,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: (if status == 400 { "Bad Request" } else { "Success" })
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
} else if message_json["path"] == "/http-proxy/register" && message_json["method"] == "DELETE" {
|
||||
print_to_terminal(1, "HERE IN /http-proxy/register to delete something");
|
||||
let username = message_json["query_params"]["username"].as_str().unwrap_or("");
|
||||
|
||||
let mut status = 204;
|
||||
|
||||
if !username.is_empty() {
|
||||
registrations.remove(username);
|
||||
} else {
|
||||
status = 400;
|
||||
}
|
||||
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "response",
|
||||
"status": status,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: (if status == 400 { "Bad Request" } else { "Success" })
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
}),
|
||||
);
|
||||
} else if message_json["path"] == "/http-proxy/serve/:username/*" {
|
||||
let username = message_json["url_params"]["username"].as_str().unwrap_or("");
|
||||
let raw_path = message_json["raw_path"].as_str().unwrap_or("");
|
||||
print_to_terminal(1, format!("proxy for user: {}", username).as_str());
|
||||
|
||||
if username.is_empty() || raw_path.is_empty() {
|
||||
send_not_found();
|
||||
} else if !registrations.contains_key(username) {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(json!({
|
||||
"action": "response",
|
||||
"status": 403,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: "Not Authorized"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
let path_parts: Vec<&str> = raw_path.split('/').collect();
|
||||
let mut proxied_path = "/".to_string();
|
||||
|
||||
if let Some(pos) = path_parts.iter().position(|&x| x == "serve") {
|
||||
proxied_path = format!("/{}", path_parts[pos+2..].join("/"));
|
||||
print_to_terminal(1, format!("Path to proxy: {}", proxied_path).as_str());
|
||||
}
|
||||
|
||||
let payload = get_payload();
|
||||
|
||||
send_request(
|
||||
&Address {
|
||||
node: username.into(),
|
||||
process: ProcessId::Name("http_bindings".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: true,
|
||||
expects_response: None,
|
||||
ipc: Some(json!({
|
||||
"action": "request",
|
||||
"method": message_json["method"],
|
||||
"path": proxied_path,
|
||||
"headers": message_json["headers"],
|
||||
"proxy_path": raw_path,
|
||||
"query_params": message_json["query_params"],
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
payload.as_ref(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
send_not_found();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
modules/http_proxy/src/process_lib.rs
Normal file
142
modules/http_proxy/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
469
modules/orgs/Cargo.lock
generated
Normal file
469
modules/orgs/Cargo.lock
generated
Normal file
@ -0,0 +1,469 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "orgs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5c3d15a04ce994fad2c5442a754b404ab1fee23c903a04a560f84f94fdf63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
31
modules/orgs/Cargo.toml
Normal file
31
modules/orgs/Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "orgs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
base64 = "0.13"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:microkernel-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
1796
modules/orgs/src/lib.rs
Normal file
1796
modules/orgs/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
142
modules/orgs/src/process_lib.rs
Normal file
142
modules/orgs/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
3
modules/persist/Cargo-component.lock
Normal file
3
modules/persist/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
453
modules/persist/Cargo.lock
generated
Normal file
453
modules/persist/Cargo.lock
generated
Normal file
@ -0,0 +1,453 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#aa6e3c1168273b5cf6221fa0206f07f2ffb8567d"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#aa6e3c1168273b5cf6221fa0206f07f2ffb8567d"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "persist"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
30
modules/persist/Cargo.toml
Normal file
30
modules/persist/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "persist"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.11.0", default_features = false }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:uq-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
127
modules/persist/src/lib.rs
Normal file
127
modules/persist/src/lib.rs
Normal file
@ -0,0 +1,127 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::component::uq_process::types::*;
|
||||
use bindings::{Address, Guest, print_to_terminal, receive, send_response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod process_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State {
|
||||
val: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum PersistRequest {
|
||||
Get,
|
||||
Set { new: u64 },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsResponse {
|
||||
// bytes are in payload_bytes
|
||||
Read(u128),
|
||||
ReadChunk(u128),
|
||||
Write(u128),
|
||||
Append(u128),
|
||||
Delete(u128),
|
||||
Length(u64),
|
||||
GetState,
|
||||
SetState
|
||||
// use FileSystemError
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
print_to_terminal(1, "persist: start");
|
||||
|
||||
let mut state = State {
|
||||
val: None
|
||||
};
|
||||
match process_lib::get_state(our.node.clone()) {
|
||||
None => {
|
||||
print_to_terminal(
|
||||
0,
|
||||
"persist: no previous boot state",
|
||||
);
|
||||
},
|
||||
Some(p) => {
|
||||
match bincode::deserialize(&p.bytes) {
|
||||
Err(e) => print_to_terminal(
|
||||
0,
|
||||
&format!("persist: failed to deserialize payload from fs: {}", e),
|
||||
),
|
||||
Ok(s) => {
|
||||
state = s;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
process_lib::await_set_state(our.node.clone(), &state);
|
||||
|
||||
loop {
|
||||
let Ok((_source, message)) = receive() else {
|
||||
print_to_terminal(0, "persist: got network error");
|
||||
continue;
|
||||
};
|
||||
|
||||
match message {
|
||||
Message::Request(request) => {
|
||||
let persist_msg = serde_json::from_str::<PersistRequest>(&request.clone().ipc.unwrap_or_default());
|
||||
let Ok(msg) = persist_msg else {
|
||||
print_to_terminal(0, &format!("persist: got invalid request {:?}", request.clone()));
|
||||
continue;
|
||||
};
|
||||
match msg {
|
||||
PersistRequest::Get => {
|
||||
print_to_terminal(0, &format!("persist: Get state: {:?}", state));
|
||||
},
|
||||
PersistRequest::Set { new } => {
|
||||
print_to_terminal(1, "persist: got Set request");
|
||||
state.val = Some(new);
|
||||
process_lib::await_set_state(our.node.clone(), &state);
|
||||
// let _ = process_lib::set_state(our.node.clone(), bincode::serialize(&state).unwrap());
|
||||
print_to_terminal(1, "persist: done Set request");
|
||||
},
|
||||
}
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
print_to_terminal(0, "persist: got unexpected message");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
142
modules/persist/src/process_lib.rs
Normal file
142
modules/persist/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
3
modules/qns_indexer/Cargo-component.lock
Normal file
3
modules/qns_indexer/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
1074
modules/qns_indexer/Cargo.lock
generated
Normal file
1074
modules/qns_indexer/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
modules/qns_indexer/Cargo.toml
Normal file
34
modules/qns_indexer/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "qns_indexer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde_json = "1.0"
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
thiserror = "1.0.43"
|
||||
anyhow = "1.0"
|
||||
alloy-sol-types = "0.3.2"
|
||||
hex = "0.4.3"
|
||||
alloy-primitives = "0.3.3"
|
||||
bincode = "1.3.3"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:uq-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
369
modules/qns_indexer/src/lib.rs
Normal file
369
modules/qns_indexer/src/lib.rs
Normal file
@ -0,0 +1,369 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::component::uq_process::types::*;
|
||||
use bindings::{print_to_terminal, receive, send_request, send_response, UqProcess};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use alloy_primitives::FixedBytes;
|
||||
use alloy_sol_types::{sol, SolEvent};
|
||||
use hex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod process_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State {
|
||||
// namehash to human readable name
|
||||
names: HashMap<String, String>,
|
||||
// human readable name to most recent on-chain routing information as json
|
||||
// NOTE: not every namehash will have a node registered
|
||||
nodes: HashMap<String, String>,
|
||||
// last block we read from
|
||||
block: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum AllActions {
|
||||
EventSubscription(EthEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct EthEvent {
|
||||
address: String,
|
||||
blockHash: String,
|
||||
blockNumber: String,
|
||||
data: String,
|
||||
logIndex: String,
|
||||
removed: bool,
|
||||
topics: Vec<String>,
|
||||
transactionHash: String,
|
||||
transactionIndex: String,
|
||||
}
|
||||
|
||||
sol! {
|
||||
event WsChanged(
|
||||
uint256 indexed node,
|
||||
uint96 indexed protocols,
|
||||
bytes32 publicKey,
|
||||
uint32 ip,
|
||||
uint16 port,
|
||||
bytes32[] routers
|
||||
);
|
||||
|
||||
event NodeRegistered(uint256 indexed node, bytes name);
|
||||
}
|
||||
|
||||
fn subscribe_to_qns(from_block: u64) -> String {
|
||||
json!({
|
||||
"SubscribeEvents": {
|
||||
"addresses": [
|
||||
// QNSRegistry on sepolia
|
||||
"0x9e5ed0e7873E0d7f10eEb6dE72E87fE087A12776",
|
||||
],
|
||||
"from_block": from_block,
|
||||
"to_block": null,
|
||||
"events": [
|
||||
"NodeRegistered(uint256,bytes)",
|
||||
"WsChanged(uint256,uint96,bytes32,uint32,uint16,bytes32[])",
|
||||
],
|
||||
"topic1": null,
|
||||
"topic2": null,
|
||||
"topic3": null,
|
||||
}
|
||||
}).to_string()
|
||||
}
|
||||
|
||||
impl UqProcess for Component {
|
||||
fn init(our: Address) {
|
||||
bindings::print_to_terminal(0, "qns_indexer: start");
|
||||
|
||||
let mut state: State = State {
|
||||
names: HashMap::new(),
|
||||
nodes: HashMap::new(),
|
||||
block: 1,
|
||||
};
|
||||
|
||||
// if we have state, load it in
|
||||
match process_lib::get_state(our.node.clone()) {
|
||||
None => {},
|
||||
Some(p) => {
|
||||
match bincode::deserialize::<State>(&p.bytes) {
|
||||
Err(e) => print_to_terminal(
|
||||
0,
|
||||
&format!("qns_indexer: failed to deserialize payload from fs: {}", e),
|
||||
),
|
||||
Ok(s) => {
|
||||
state = s;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// shove all state into net::net
|
||||
for (_, ipc) in state.nodes.iter() {
|
||||
send_request(
|
||||
&Address{
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("net".to_string()),
|
||||
},
|
||||
&Request{
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
metadata: None,
|
||||
ipc: Some(ipc.to_string()),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
let event_sub_res = send_request(
|
||||
&Address{
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("eth_rpc".to_string()),
|
||||
},
|
||||
&Request{
|
||||
inherit: false, // TODO what
|
||||
expects_response: Some(5), // TODO evaluate
|
||||
metadata: None,
|
||||
// -1 because there could be other events in the last processed block
|
||||
ipc: Some(subscribe_to_qns(state.block - 1)),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let _register_endpoint = send_request(
|
||||
&Address{
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("http_bindings".to_string()),
|
||||
},
|
||||
&Request{
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
metadata: None,
|
||||
ipc: Some(serde_json::json!({
|
||||
"action": "bind-app",
|
||||
"path": "/qns-indexer/node/:name",
|
||||
"app": "qns_indexer",
|
||||
"authenticated": true,
|
||||
}).to_string()),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
loop {
|
||||
let Ok((source, message)) = receive() else {
|
||||
print_to_terminal(0, "qns_indexer: got network error");
|
||||
continue;
|
||||
};
|
||||
let Message::Request(request) = message else {
|
||||
// TODO we should store the subscription ID for eth_rpc
|
||||
// incase we want to cancel/reset it
|
||||
// print_to_terminal(0, "qns_indexer: got response");
|
||||
continue;
|
||||
};
|
||||
|
||||
if source.process == ProcessId::Name("http_bindings".to_string()) {
|
||||
if let Ok(ipc_json) = serde_json::from_str::<serde_json::Value>(&request.ipc.clone().unwrap_or_default()) {
|
||||
if ipc_json["path"].as_str().unwrap_or_default() == "/qns-indexer/node/:name" {
|
||||
if let Some(name) = ipc_json["url_params"]["name"].as_str() {
|
||||
if let Some(node) = state.nodes.get(name) {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: node.as_bytes().to_vec(),
|
||||
})
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"status": 404,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: "Not Found".to_string().as_bytes().to_vec(),
|
||||
})
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(msg) = serde_json::from_str::<AllActions>(&request.ipc.unwrap_or_default()) else {
|
||||
print_to_terminal(0, "qns_indexer: got invalid message");
|
||||
continue;
|
||||
};
|
||||
|
||||
match msg {
|
||||
// Probably more message types later...maybe not...
|
||||
AllActions::EventSubscription(e) => {
|
||||
match decode_hex(&e.topics[0].clone()) {
|
||||
NodeRegistered::SIGNATURE_HASH => {
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: got NodeRegistered event: {:?}", e).as_str());
|
||||
|
||||
let node = &e.topics[1];
|
||||
let decoded = NodeRegistered::decode_data(&decode_hex_to_vec(&e.data), true).unwrap();
|
||||
let name = dnswire_decode(decoded.0);
|
||||
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: NODE1: {:?}", node).as_str());
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: NAME: {:?}", name.to_string()).as_str());
|
||||
|
||||
state.names.insert(node.to_string(), name);
|
||||
state.block = hex_to_u64(&e.blockNumber).unwrap();
|
||||
}
|
||||
WsChanged::SIGNATURE_HASH => {
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: got WsChanged event: {:?}", e).as_str());
|
||||
|
||||
let node = &e.topics[1];
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: NODE2: {:?}", node.to_string()).as_str());
|
||||
let decoded = WsChanged::decode_data(&decode_hex_to_vec(&e.data), true).unwrap();
|
||||
let public_key = hex::encode(decoded.0);
|
||||
let ip = decoded.1;
|
||||
let port = decoded.2;
|
||||
let routers_raw = decoded.3;
|
||||
let routers: Vec<String> = routers_raw
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let key = hex::encode(r);
|
||||
match state.names.get(&key) {
|
||||
Some(name) => name.clone(),
|
||||
None => format!("0x{}", key), // TODO it should actually just panic here
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let name = state.names.get(node).unwrap();
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: NAME: {:?}", name).as_str());
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: DECODED: {:?}", decoded).as_str());
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: PUB KEY: {:?}", public_key).as_str());
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: IP PORT: {:?} {:?}", ip, port).as_str());
|
||||
// bindings::print_to_terminal(0, format!("qns_indexer: ROUTERS: {:?}", routers).as_str());
|
||||
|
||||
let json_payload = json!({
|
||||
"QnsUpdate": {
|
||||
"name": name,
|
||||
"owner": "0x", // TODO or get rid of
|
||||
"node": node,
|
||||
"public_key": format!("0x{}", public_key),
|
||||
"ip": format!(
|
||||
"{}.{}.{}.{}",
|
||||
(ip >> 24) & 0xFF,
|
||||
(ip >> 16) & 0xFF,
|
||||
(ip >> 8) & 0xFF,
|
||||
ip & 0xFF
|
||||
),
|
||||
"port": port,
|
||||
"routers": routers,
|
||||
}
|
||||
}).to_string();
|
||||
|
||||
state.nodes.insert(name.clone(), json_payload.clone());
|
||||
|
||||
send_request(
|
||||
&Address{
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("net".to_string()),
|
||||
},
|
||||
&Request{
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
metadata: None,
|
||||
ipc: Some(json_payload),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
event => {
|
||||
bindings::print_to_terminal(0, format!("qns_indexer: got unknown event: {:?}", event).as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process_lib::await_set_state(our.node.clone(), &state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
// TODO these probably exist somewhere in alloy...not sure where though.
|
||||
fn decode_hex(s: &str) -> FixedBytes<32> {
|
||||
// If the string starts with "0x", skip the prefix
|
||||
let hex_part = if s.starts_with("0x") {
|
||||
&s[2..]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
||||
let mut arr = [0_u8; 32];
|
||||
arr.copy_from_slice(&hex::decode(hex_part).unwrap()[0..32]);
|
||||
FixedBytes(arr)
|
||||
}
|
||||
|
||||
fn decode_hex_to_vec(s: &str) -> Vec<u8> {
|
||||
// If the string starts with "0x", skip the prefix
|
||||
let hex_part = if s.starts_with("0x") {
|
||||
&s[2..]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
||||
hex::decode(hex_part).unwrap()
|
||||
}
|
||||
|
||||
fn hex_to_u64(hex: &str) -> Result<u64, std::num::ParseIntError> {
|
||||
let without_prefix = if hex.starts_with("0x") {
|
||||
&hex[2..]
|
||||
} else {
|
||||
hex
|
||||
};
|
||||
u64::from_str_radix(without_prefix, 16)
|
||||
}
|
||||
|
||||
fn dnswire_decode(wire_format_bytes: Vec<u8>) -> String {
|
||||
let mut i = 0;
|
||||
let mut result = Vec::new();
|
||||
|
||||
while i < wire_format_bytes.len() {
|
||||
let len = wire_format_bytes[i] as usize;
|
||||
if len == 0 { break; }
|
||||
let end = i + len + 1;
|
||||
let mut span = wire_format_bytes[i+1..end].to_vec();
|
||||
span.push('.' as u8);
|
||||
result.push(span);
|
||||
i = end;
|
||||
};
|
||||
|
||||
let flat: Vec<_> = result.into_iter().flatten().collect();
|
||||
|
||||
let name = String::from_utf8(flat).unwrap();
|
||||
|
||||
// Remove the trailing '.' if it exists (it should always exist)
|
||||
if name.ends_with('.') {
|
||||
name[0..name.len()-1].to_string()
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
142
modules/qns_indexer/src/process_lib.rs
Normal file
142
modules/qns_indexer/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
3
modules/rpc/Cargo-component.lock
Normal file
3
modules/rpc/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
469
modules/rpc/Cargo.lock
generated
Normal file
469
modules/rpc/Cargo.lock
generated
Normal file
@ -0,0 +1,469 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#36c221e41db3e87dec4c82eadcb9bc8f37626533"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5c3d15a04ce994fad2c5442a754b404ab1fee23c903a04a560f84f94fdf63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
31
modules/rpc/Cargo.toml
Normal file
31
modules/rpc/Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "rpc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.9.0", default_features = false }
|
||||
base64 = "0.13"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:microkernel-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
504
modules/rpc/src/lib.rs
Normal file
504
modules/rpc/src/lib.rs
Normal file
@ -0,0 +1,504 @@
|
||||
cargo_component_bindings::generate!();
|
||||
|
||||
use bindings::component::uq_process::types::*;
|
||||
use bindings::{
|
||||
get_capabilities, get_capability, get_payload, print_to_terminal, receive,
|
||||
send_and_await_response, send_request, send_requests, send_response, Guest,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
extern crate base64;
|
||||
|
||||
mod process_lib;
|
||||
|
||||
struct Component;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct RpcMessage {
|
||||
pub node: String,
|
||||
pub process: String,
|
||||
pub inherit: Option<bool>,
|
||||
pub expects_response: Option<u64>, // always false?
|
||||
pub ipc: Option<String>,
|
||||
pub metadata: Option<String>,
|
||||
pub context: Option<String>,
|
||||
pub mime: Option<String>,
|
||||
pub data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CapabilitiesTransfer {
|
||||
pub destination_node: String,
|
||||
pub destination_process: String,
|
||||
pub node: String,
|
||||
pub process: String,
|
||||
pub params: String,
|
||||
}
|
||||
|
||||
// curl http://localhost:8080/rpc/message -H 'content-type: application/json' -d '{"node": "hosted", "process": "vfs", "inherit": false, "expects_response": null, "ipc": "{\"New\": {\"identifier\": \"foo\"}}", "metadata": null, "context": null, "mime": null, "data": null}'
|
||||
|
||||
fn send_http_response(status: u16, headers: HashMap<String, String>, payload_bytes: Vec<u8>) {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(
|
||||
json!({
|
||||
"status": status,
|
||||
"headers": headers,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: payload_bytes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const RPC_PAGE: &str = include_str!("rpc.html");
|
||||
|
||||
fn binary_encoded_string_to_bytes(s: &str) -> Vec<u8> {
|
||||
s.chars().map(|c| c as u8).collect()
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
print_to_terminal(0, "RPC: start");
|
||||
|
||||
let bindings_address = Address {
|
||||
node: our.node.clone(),
|
||||
process: ProcessId::Name("http_bindings".to_string()),
|
||||
};
|
||||
|
||||
// <address, request, option<context>, option<payload>>
|
||||
let http_endpoint_binding_requests: [(Address, Request, Option<Context>, Option<Payload>);
|
||||
3] = [
|
||||
// (
|
||||
// bindings_address.clone(),
|
||||
// Request {
|
||||
// inherit: false,
|
||||
// expects_response: None,
|
||||
// ipc: Some(json!({
|
||||
// "action": "bind-app",
|
||||
// "path": "/rpc",
|
||||
// "app": "rpc",
|
||||
// "local_only": true,
|
||||
// }).to_string()),
|
||||
// metadata: None,
|
||||
// },
|
||||
// None,
|
||||
// None
|
||||
// ),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
json!({
|
||||
"action": "bind-app",
|
||||
"path": "/rpc/message",
|
||||
"app": "rpc",
|
||||
"local_only": true,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
json!({
|
||||
"action": "bind-app",
|
||||
"path": "/rpc/capabilities",
|
||||
"app": "rpc",
|
||||
"local_only": true,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
bindings_address.clone(),
|
||||
Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
json!({
|
||||
"action": "bind-app",
|
||||
"path": "/rpc/capabilities/transfer",
|
||||
"app": "rpc",
|
||||
"local_only": true,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
),
|
||||
];
|
||||
send_requests(&http_endpoint_binding_requests);
|
||||
|
||||
loop {
|
||||
let Ok((_source, message)) = receive() else {
|
||||
print_to_terminal(0, "rpc: got network error");
|
||||
continue;
|
||||
};
|
||||
let Message::Request(request) = message else {
|
||||
print_to_terminal(0, "rpc: got unexpected message");
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(json) = request.ipc {
|
||||
print_to_terminal(1, format!("rpc: JSON {}", json).as_str());
|
||||
let message_json: serde_json::Value = match serde_json::from_str(&json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(1, "rpc: failed to parse ipc JSON, skipping");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
print_to_terminal(1, "rpc: parsed ipc JSON");
|
||||
|
||||
let path = message_json["path"].as_str().unwrap_or("");
|
||||
let method = message_json["method"].as_str().unwrap_or("");
|
||||
|
||||
let mut default_headers = HashMap::new();
|
||||
default_headers.insert("Content-Type".to_string(), "text/html".to_string());
|
||||
// Handle incoming http
|
||||
print_to_terminal(1, format!("rpc: path {}", path).as_str());
|
||||
print_to_terminal(1, format!("rpc: method {}", method).as_str());
|
||||
match method {
|
||||
"GET" => match path {
|
||||
"/rpc" => {
|
||||
send_response(
|
||||
&Response {
|
||||
ipc: Some(
|
||||
json!({
|
||||
"action": "response",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
Some(&Payload {
|
||||
mime: Some("text/html".to_string()),
|
||||
bytes: RPC_PAGE
|
||||
.replace("${our}", &our.node)
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
"/rpc/capabilities" => {
|
||||
let capabilities = get_capabilities();
|
||||
let caps = capabilities
|
||||
.iter()
|
||||
.map(|cap| {
|
||||
let process = match &cap.issuer.process {
|
||||
ProcessId::Name(name) => name.clone(),
|
||||
ProcessId::Id(id) => id.to_string(),
|
||||
};
|
||||
json!({
|
||||
"issuer": {
|
||||
"node": cap.issuer.node.clone(),
|
||||
"process": process,
|
||||
},
|
||||
"params": cap.params.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<serde_json::Value>>();
|
||||
|
||||
send_http_response(
|
||||
200,
|
||||
default_headers.clone(),
|
||||
json!(caps).to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
send_http_response(
|
||||
404,
|
||||
default_headers.clone(),
|
||||
"Not Found".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
"POST" => match path {
|
||||
"/rpc/message" => {
|
||||
let Some(payload) = get_payload() else {
|
||||
print_to_terminal(1, "rpc: no bytes in payload, skipping...");
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"No payload".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let body_json: RpcMessage =
|
||||
match serde_json::from_slice::<RpcMessage>(&payload.bytes) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(
|
||||
1,
|
||||
&format!(
|
||||
"rpc: JSON is not valid RpcMessage: {:?}",
|
||||
serde_json::from_slice::<serde_json::Value>(
|
||||
&payload.bytes
|
||||
)
|
||||
),
|
||||
);
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"JSON is not valid RpcMessage"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let payload =
|
||||
match base64::decode(&body_json.data.unwrap_or("".to_string())) {
|
||||
Ok(bytes) => Some(Payload {
|
||||
mime: body_json.mime,
|
||||
bytes,
|
||||
}),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
let caps = get_capabilities();
|
||||
print_to_terminal(
|
||||
0,
|
||||
format!("rpc: got capabilities {:?}", caps).as_str(),
|
||||
);
|
||||
|
||||
let result = send_and_await_response(
|
||||
&Address {
|
||||
node: body_json.node,
|
||||
process: ProcessId::Name(body_json.process),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: body_json.ipc,
|
||||
metadata: body_json.metadata,
|
||||
},
|
||||
payload.as_ref(),
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok((_source, message)) => {
|
||||
let Message::Response((response, _context)) = message
|
||||
else {
|
||||
print_to_terminal(
|
||||
1,
|
||||
"rpc: got unexpected response to message",
|
||||
);
|
||||
send_http_response(
|
||||
500,
|
||||
default_headers,
|
||||
"Invalid Internal Response"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let (mime, data) = match get_payload() {
|
||||
Some(p) => {
|
||||
let mime = match p.mime {
|
||||
Some(mime) => mime,
|
||||
None => "application/octet-stream".to_string(),
|
||||
};
|
||||
let bytes = p.bytes;
|
||||
|
||||
(mime, base64::encode(bytes))
|
||||
}
|
||||
None => ("".to_string(), "".to_string()),
|
||||
};
|
||||
|
||||
let body = json!({
|
||||
"ipc": response.ipc,
|
||||
"payload": {
|
||||
"mime": mime,
|
||||
"data": data,
|
||||
},
|
||||
})
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
|
||||
send_http_response(200, default_headers.clone(), body);
|
||||
continue;
|
||||
}
|
||||
Err(error) => {
|
||||
print_to_terminal(1, "rpc: error coming back");
|
||||
send_http_response(
|
||||
500,
|
||||
default_headers.clone(),
|
||||
"Network Error".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
"/rpc/capabilities/transfer" => {
|
||||
let Some(payload) = get_payload() else {
|
||||
print_to_terminal(1, "rpc: no bytes in payload, skipping...");
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"No payload".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let body_json: CapabilitiesTransfer = match serde_json::from_slice::<
|
||||
CapabilitiesTransfer,
|
||||
>(
|
||||
&payload.bytes
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
print_to_terminal(
|
||||
1,
|
||||
&format!(
|
||||
"rpc: JSON is not valid CapabilitiesTransfer: {:?}",
|
||||
serde_json::from_slice::<serde_json::Value>(
|
||||
&payload.bytes
|
||||
)
|
||||
),
|
||||
);
|
||||
send_http_response(
|
||||
400,
|
||||
default_headers.clone(),
|
||||
"JSON is not valid CapabilitiesTransfer"
|
||||
.to_string()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
print_to_terminal(
|
||||
0,
|
||||
format!("rpc: node {:?}", body_json.node).as_str(),
|
||||
);
|
||||
print_to_terminal(
|
||||
0,
|
||||
format!("rpc: process {:?}", body_json.process).as_str(),
|
||||
);
|
||||
print_to_terminal(
|
||||
0,
|
||||
format!("rpc: params {:?}", body_json.params).as_str(),
|
||||
);
|
||||
|
||||
let capability = get_capability(
|
||||
&Address {
|
||||
node: body_json.node,
|
||||
process: ProcessId::Name(body_json.process),
|
||||
},
|
||||
&body_json.params,
|
||||
);
|
||||
|
||||
print_to_terminal(
|
||||
0,
|
||||
format!("rpc: got capability {:?}", capability).as_str(),
|
||||
);
|
||||
|
||||
match capability {
|
||||
Some(capability) => {
|
||||
let process = match capability.issuer.process {
|
||||
ProcessId::Name(name) => name,
|
||||
ProcessId::Id(id) => id.to_string(),
|
||||
};
|
||||
send_request(
|
||||
&Address {
|
||||
node: body_json.destination_node,
|
||||
process: ProcessId::Name(body_json.destination_process),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
json!({
|
||||
"action": "transfer_capability",
|
||||
"info": {
|
||||
"issuer": {
|
||||
"node": capability.issuer.node,
|
||||
"process": process,
|
||||
},
|
||||
"params": capability.params,
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: Some("bytes".to_string()),
|
||||
bytes: capability.signature,
|
||||
}),
|
||||
);
|
||||
|
||||
send_http_response(
|
||||
200,
|
||||
default_headers.clone(),
|
||||
"Success".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
}
|
||||
None => send_http_response(
|
||||
404,
|
||||
default_headers.clone(),
|
||||
"Not Found".to_string().as_bytes().to_vec(),
|
||||
),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
send_http_response(
|
||||
404,
|
||||
default_headers.clone(),
|
||||
"Not Found".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
send_http_response(
|
||||
405,
|
||||
default_headers.clone(),
|
||||
"Method Not Allowed".to_string().as_bytes().to_vec(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
modules/rpc/src/process_lib.rs
Normal file
142
modules/rpc/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
289
modules/rpc/src/rpc.html
Normal file
289
modules/rpc/src/rpc.html
Normal file
File diff suppressed because one or more lines are too long
3
modules/terminal/Cargo-component.lock
Normal file
3
modules/terminal/Cargo-component.lock
Normal file
@ -0,0 +1,3 @@
|
||||
# This file is automatically generated by cargo-component.
|
||||
# It is not intended for manual editing.
|
||||
version = 1
|
453
modules/terminal/Cargo.lock
generated
Normal file
453
modules/terminal/Cargo.lock
generated
Normal file
@ -0,0 +1,453 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-bindings"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#6a2996f280dd8671a2a2d3c83cbe09a39225b526"
|
||||
dependencies = [
|
||||
"cargo-component-macro",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-component-macro"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/bytecodealliance/cargo-component#6a2996f280dd8671a2a2d3c83cbe09a39225b526"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
"cargo-component-bindings",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08dc59d1fa569150851542143ca79438ca56845ccb31696c70225c638e063471"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.112.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e986b010f47fcce49cf8ea5d5f9e5d2737832f12b53ae8ae785bbe895d0877bf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a3e8e965dc50e6eb4410d9a11720719fadc6a1713803ea5f3be390b81c8279"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77255512565dfbd0b61de466e854918041d1da53c7bc049d6188c6e02643dc1e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"wit-component",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "399c60e6ea8598d1380e792f13d557007834f0fb799fea6503408cbc5debb4ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-lib"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd9fb7a43c7dc28b0b727d6ae01bf369981229b7539e768fba2b7a4df13feeeb"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"wit-bindgen-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44cea5ed784da06da0e55836a6c160e7502dbe28771c2368a595e8606243bf22"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
"wit-bindgen-rust-lib",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d9f2d16dd55d1a372dcfd4b7a466ea876682a5a3cb97e71ec9eef04affa876"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e8b849bea13cc2315426b16efe6eb6813466d78f5fde69b0bb150c9c40e0dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"semver",
|
||||
"unicode-xid",
|
||||
"url",
|
||||
]
|
30
modules/terminal/Cargo.toml
Normal file
30
modules/terminal/Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "terminal"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bincode = "1.3.3"
|
||||
cargo-component-bindings = { git = "https://github.com/bytecodealliance/cargo-component" }
|
||||
serde = {version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
wit-bindgen = { version = "0.11.0", default_features = false }
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[package.metadata.component]
|
||||
package = "component:uq-process"
|
||||
|
||||
[package.metadata.component.target]
|
||||
path = "wit"
|
||||
|
||||
[package.metadata.component.dependencies]
|
111
modules/terminal/src/lib.rs
Normal file
111
modules/terminal/src/lib.rs
Normal file
@ -0,0 +1,111 @@
|
||||
cargo_component_bindings::generate!();
|
||||
mod process_lib;
|
||||
struct Component;
|
||||
use bindings::{component::uq_process::types::*, Guest, print_to_terminal, receive, send_request};
|
||||
|
||||
fn parse_command(our_name: &str, line: String) {
|
||||
let (head, tail) = line.split_once(" ").unwrap_or((&line, ""));
|
||||
match head {
|
||||
"" | " " => {}
|
||||
"!hi" => {
|
||||
let (target, message) = match tail.split_once(" ") {
|
||||
Some((s, t)) => (s, t),
|
||||
None => {
|
||||
print_to_terminal(0, &format!("invalid command: \"{}\"", line));
|
||||
return;
|
||||
}
|
||||
};
|
||||
send_request(
|
||||
&Address {
|
||||
node: if target == "our" {
|
||||
our_name.into()
|
||||
} else {
|
||||
target.into()
|
||||
},
|
||||
process: ProcessId::Name("net".into()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(message.into()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
"!message" => {
|
||||
let (target_node, tail) = match tail.split_once(" ") {
|
||||
Some((s, t)) => (s, t),
|
||||
None => {
|
||||
print_to_terminal(0, &format!("invalid command: \"{}\"", line));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (target_process, ipc) = match tail.split_once(" ") {
|
||||
Some((a, p)) => (a, p),
|
||||
None => {
|
||||
print_to_terminal(0, &format!("invalid command: \"{}\"", line));
|
||||
return;
|
||||
}
|
||||
};
|
||||
// TODO: why does this work but using the API below does not?
|
||||
// Is it related to passing json in rather than a Serialize type?
|
||||
send_request(
|
||||
&Address {
|
||||
node: if target_node == "our" {
|
||||
our_name.into()
|
||||
} else {
|
||||
target_node.into()
|
||||
},
|
||||
process: ProcessId::Name(target_process.into()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(ipc.into()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
print_to_terminal(0, &format!("invalid command: \"{line}\""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(our: Address) {
|
||||
assert_eq!(our.process, ProcessId::Name("terminal".into()));
|
||||
print_to_terminal(0, &format!("terminal: running"));
|
||||
loop {
|
||||
let message = match receive() {
|
||||
Ok((source, message)) => {
|
||||
if our.node != source.node {
|
||||
continue;
|
||||
}
|
||||
message
|
||||
}
|
||||
Err((error, _context)) => {
|
||||
print_to_terminal(0, &format!("net error: {:?}!", error.kind));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match message {
|
||||
Message::Request(Request {
|
||||
expects_response,
|
||||
ipc,
|
||||
..
|
||||
}) => {
|
||||
let Some(command) = ipc else {
|
||||
continue;
|
||||
};
|
||||
parse_command(&our.node, command);
|
||||
}
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
modules/terminal/src/process_lib.rs
Normal file
142
modules/terminal/src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
429
src/QNSRegistry.json
Normal file
429
src/QNSRegistry.json
Normal file
@ -0,0 +1,429 @@
|
||||
[
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "previousAdmin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "AdminChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "beacon",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "BeaconUpgraded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint8",
|
||||
"name": "version",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"name": "Initialized",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "node",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "name",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "nft",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "NewSubdomainContract",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "node",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "name",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "NodeRegistered",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "implementation",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "Upgraded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "node",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint96",
|
||||
"name": "protocols",
|
||||
"type": "uint96"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "publicKey",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint32",
|
||||
"name": "ip",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint16",
|
||||
"name": "port",
|
||||
"type": "uint16"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32[]",
|
||||
"name": "routers",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "WsChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "node",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "protocols",
|
||||
"type": "uint32"
|
||||
}
|
||||
],
|
||||
"name": "clearProtocols",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getInitializedVersion",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "initialize",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "proxiableUUID",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "records",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint96",
|
||||
"name": "protocols",
|
||||
"type": "uint96"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "fqdn",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "registerNode",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "fqdn",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"internalType": "contract IQNSNFT",
|
||||
"name": "nft",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "registerSubdomainContract",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "node",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "publicKey",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "ip",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint16",
|
||||
"name": "port",
|
||||
"type": "uint16"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "routers",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "setWsRecord",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "interfaceID",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"name": "supportsInterface",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newImplementation",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "upgradeTo",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newImplementation",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "upgradeToAndCall",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "node",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ws",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "publicKey",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "ip",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint16",
|
||||
"name": "port",
|
||||
"type": "uint16"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "routers",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct IQNS.WsRecord",
|
||||
"name": "",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
433
src/encryptor.rs
Normal file
433
src/encryptor.rs
Normal file
@ -0,0 +1,433 @@
|
||||
extern crate generic_array;
|
||||
extern crate num_traits;
|
||||
extern crate rand;
|
||||
|
||||
use crate::types::*;
|
||||
use aes_gcm::{
|
||||
aead::{Aead, KeyInit},
|
||||
Aes256Gcm,
|
||||
Key, // Or `Aes128Gcm`
|
||||
Nonce,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use generic_array::GenericArray;
|
||||
use rand::{thread_rng, Rng};
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use rsa::{BigUint, Oaep, RsaPublicKey};
|
||||
use serde_json;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::encryptor::num_traits::Num;
|
||||
|
||||
fn encrypt_data(secret_key_bytes: [u8; 32], data: Vec<u8>) -> Vec<u8> {
|
||||
let key = Key::<Aes256Gcm>::from_slice(&secret_key_bytes);
|
||||
let cipher = Aes256Gcm::new(key);
|
||||
|
||||
let mut nonce_bytes: [u8; 12] = [0; 12];
|
||||
thread_rng().fill(&mut nonce_bytes);
|
||||
let nonce = Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
let ciphertext = cipher
|
||||
.encrypt(nonce, data.as_ref())
|
||||
.expect("encryption failure!");
|
||||
let mut data = ciphertext;
|
||||
data.extend(nonce_bytes);
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
fn decrypt_data(secret_key_bytes: [u8; 32], data: Vec<u8>) -> Vec<u8> {
|
||||
let nonce_bytes = data[data.len() - 12..].to_vec();
|
||||
let encrypted_bytes = data[..data.len() - 12].to_vec();
|
||||
let key = Key::<Aes256Gcm>::from_slice(&secret_key_bytes);
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
let nonce = GenericArray::from_slice(&nonce_bytes);
|
||||
let decrypted_bytes = cipher
|
||||
.decrypt(nonce, encrypted_bytes.as_ref())
|
||||
.expect("decryption failure!");
|
||||
|
||||
decrypted_bytes
|
||||
}
|
||||
|
||||
pub async fn encryptor(
|
||||
our: String,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
message_tx: MessageSender,
|
||||
mut recv_in_encryptor: MessageReceiver,
|
||||
print_tx: PrintSender,
|
||||
) -> Result<()> {
|
||||
// Generally, the secret_id will be the ID that corresponds to a particular app or websocket connection
|
||||
// For authenticated + encrypted HTTP routes, the secret_id will always be "http_bindings"
|
||||
let mut secrets: HashMap<String, [u8; 32]> = HashMap::new(); // Store secrets as hex strings? Or as bytes?
|
||||
|
||||
while let Some(kernel_message) = recv_in_encryptor.recv().await {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "ENCRYPTOR MESSAGE".to_string(),
|
||||
})
|
||||
.await;
|
||||
let KernelMessage {
|
||||
ref id,
|
||||
source,
|
||||
rsvp,
|
||||
message,
|
||||
payload,
|
||||
..
|
||||
} = kernel_message;
|
||||
let Message::Request(Request { ipc: Some(ipc), .. }) = message else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "encryptor: bad message".to_string(),
|
||||
})
|
||||
.await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ENCRYPTOR IPC: {}", ipc.clone()),
|
||||
})
|
||||
.await;
|
||||
|
||||
match serde_json::from_str::<EncryptorMessage>(&ipc) {
|
||||
Ok(message) => {
|
||||
match message {
|
||||
EncryptorMessage::GetKeyAction(GetKeyAction {
|
||||
channel_id,
|
||||
public_key_hex,
|
||||
}) => {
|
||||
let n = BigUint::from_str_radix(&public_key_hex.clone(), 16)
|
||||
.expect("failed to parse hex string");
|
||||
let e = BigUint::from(65537u32);
|
||||
|
||||
match RsaPublicKey::new(n, e) {
|
||||
Ok(public_key) => {
|
||||
let padding = Oaep::new::<sha2::Sha256>();
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let public_key_bytes = hex::decode(public_key_hex)
|
||||
.expect("failed to decode hex string");
|
||||
|
||||
let signed_public_key =
|
||||
keypair.sign(&public_key_bytes).as_ref().to_vec();
|
||||
|
||||
let encrypted_secret: Vec<u8>;
|
||||
if let Some(secret) = secrets.get(&channel_id) {
|
||||
// Secret already exists
|
||||
// Encrypt the secret with the public key and return it
|
||||
encrypted_secret = public_key
|
||||
.encrypt(&mut rng, padding, secret)
|
||||
.expect("failed to encrypt message");
|
||||
} else {
|
||||
// Secret does not exist, must create
|
||||
// Create a new secret, store it, encrypt it with the public key, and return it
|
||||
let mut secret = [0u8; 32];
|
||||
thread_rng().fill(&mut secret);
|
||||
secrets.insert(channel_id, secret);
|
||||
|
||||
// Create a new AES-GCM cipher with the given key
|
||||
// So do I encrypt the
|
||||
encrypted_secret = public_key
|
||||
.encrypt(&mut rng, padding, &secret)
|
||||
.expect("failed to encrypt message");
|
||||
}
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert(
|
||||
"Content-Type".to_string(),
|
||||
"application/json".to_string(),
|
||||
);
|
||||
|
||||
let target = match rsvp {
|
||||
Some(rsvp) => rsvp,
|
||||
None => Address {
|
||||
node: source.node.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
};
|
||||
// Generate and send the response
|
||||
let response = KernelMessage {
|
||||
id: *id,
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("encryptor".into()),
|
||||
},
|
||||
target,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(serde_json::json!({
|
||||
"status": 201,
|
||||
"headers": headers,
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/json".to_string()),
|
||||
bytes: serde_json::json!({
|
||||
"encrypted_secret": hex::encode(encrypted_secret).to_string(),
|
||||
"signed_public_key": hex::encode(&signed_public_key).to_string(),
|
||||
}).to_string().as_bytes().to_vec(),
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
message_tx.send(response).await.unwrap();
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("Error: {}", e),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
EncryptorMessage::DecryptAndForwardAction(DecryptAndForwardAction {
|
||||
channel_id,
|
||||
forward_to,
|
||||
json,
|
||||
}) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!(
|
||||
"DECRYPTOR TO FORWARD: {}",
|
||||
json.clone().unwrap_or_default().to_string()
|
||||
),
|
||||
})
|
||||
.await;
|
||||
|
||||
// The payload.bytes should be the encrypted data, with the last 12 bytes being the nonce
|
||||
let Some(payload) = payload else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "No payload".to_string(),
|
||||
})
|
||||
.await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = payload.bytes.clone();
|
||||
|
||||
if let Some(secret_key_bytes) = secrets.get(&channel_id) {
|
||||
let decrypted_bytes = decrypt_data(secret_key_bytes.clone(), data);
|
||||
|
||||
// Forward the unencrypted data to the target
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("encryptor".into()),
|
||||
},
|
||||
target: forward_to,
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None, // A forwarded message does not expect a response
|
||||
ipc: Some(json.clone().unwrap_or_default().to_string()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()), // TODO adjust MIME type as needed
|
||||
bytes: decrypted_bytes,
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
message_tx.send(message).await.unwrap();
|
||||
} else {
|
||||
panic!("No secret found");
|
||||
}
|
||||
}
|
||||
EncryptorMessage::EncryptAndForwardAction(EncryptAndForwardAction {
|
||||
channel_id,
|
||||
forward_to,
|
||||
json,
|
||||
}) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ENCRYPTOR TO FORWARD"),
|
||||
})
|
||||
.await;
|
||||
|
||||
let Some(payload) = payload else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "No payload".to_string(),
|
||||
})
|
||||
.await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = payload.bytes.clone();
|
||||
|
||||
if let Some(secret_key_bytes) = secrets.get(&channel_id) {
|
||||
let encrypted_bytes = encrypt_data(secret_key_bytes.clone(), data);
|
||||
|
||||
// Forward the ciphertext and nonce_hex to the specified process
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id,
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("encryptor".into()),
|
||||
},
|
||||
target: forward_to,
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None, // A forwarded message does not expect a response
|
||||
ipc: Some(json.clone().unwrap_or_default().to_string()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()), // TODO adjust MIME type as needed
|
||||
bytes: encrypted_bytes,
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
message_tx.send(message).await.unwrap();
|
||||
} else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ERROR: No secret found"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
EncryptorMessage::DecryptAction(DecryptAction { channel_id }) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ENCRYPTOR TO DECRYPT"),
|
||||
})
|
||||
.await;
|
||||
|
||||
let Some(payload) = payload else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "No payload".to_string(),
|
||||
})
|
||||
.await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = payload.bytes.clone();
|
||||
|
||||
if let Some(secret_key_bytes) = secrets.get(&channel_id) {
|
||||
let decrypted_bytes = decrypt_data(secret_key_bytes.clone(), data);
|
||||
|
||||
let message = KernelMessage {
|
||||
id: *id,
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("encryptor".into()),
|
||||
},
|
||||
target: source,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()), // TODO adjust MIME type as needed
|
||||
bytes: decrypted_bytes,
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
message_tx.send(message).await.unwrap();
|
||||
} else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ERROR: No secret found"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
EncryptorMessage::EncryptAction(EncryptAction { channel_id }) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ENCRYPTOR TO ENCRYPT"),
|
||||
})
|
||||
.await;
|
||||
|
||||
let Some(payload) = payload else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "No payload".to_string(),
|
||||
})
|
||||
.await;
|
||||
continue;
|
||||
};
|
||||
|
||||
let data = payload.bytes.clone();
|
||||
|
||||
if let Some(secret_key_bytes) = secrets.get(&channel_id) {
|
||||
let encrypted_bytes = encrypt_data(secret_key_bytes.clone(), data);
|
||||
|
||||
let message = KernelMessage {
|
||||
id: *id,
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("encryptor".into()),
|
||||
},
|
||||
target: source,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()), // TODO adjust MIME type as needed
|
||||
bytes: encrypted_bytes,
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
message_tx.send(message).await.unwrap();
|
||||
} else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ERROR: No secret found"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: "Not a valid EncryptorMessage".to_string(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(anyhow::anyhow!("encryptor: exited"))
|
||||
}
|
199
src/engine.rs
Normal file
199
src/engine.rs
Normal file
@ -0,0 +1,199 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use futures::lock::Mutex;
|
||||
use ethers::utils::keccak256;
|
||||
use hasher::{Hasher, HasherKeccak};
|
||||
use cita_trie::MemoryDB;
|
||||
use cita_trie::{PatriciaTrie, Trie};
|
||||
use ethers::prelude::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
// use anyhow::{anyhow, Result};
|
||||
use wasmtime::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StateItem {
|
||||
pub source: H256,
|
||||
pub holder: H256,
|
||||
pub town_id: u32,
|
||||
pub salt: Bytes,
|
||||
pub label: String,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ContractItem {
|
||||
pub source: H256,
|
||||
pub holder: H256,
|
||||
pub town_id: u32,
|
||||
pub code_hex: String, // source code of contract represented as hex string?
|
||||
}
|
||||
|
||||
struct _ContractContext {
|
||||
this: H256,
|
||||
}
|
||||
type _Process = Arc<Mutex<_ContractContext>>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Transaction {
|
||||
pub from: H256,
|
||||
pub signature: Option<Signature>,
|
||||
pub to: H256, // contract address
|
||||
pub town_id: u32,
|
||||
pub calldata: serde_json::Value,
|
||||
pub nonce: U256,
|
||||
pub gas_price: U256,
|
||||
pub gas_limit: U256,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn hash(&self) -> H256 {
|
||||
let mut _hasher = HasherKeccak::new();
|
||||
let message = format!("{}{}{}{}{}{}{}",
|
||||
self.from,
|
||||
self.to,
|
||||
self.town_id,
|
||||
self.calldata,
|
||||
self.nonce,
|
||||
self.gas_price,
|
||||
self.gas_limit,
|
||||
);
|
||||
keccak256(message).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UqChain {
|
||||
state: PatriciaTrie<MemoryDB, HasherKeccak>,
|
||||
nonces: HashMap<H256, U256>,
|
||||
}
|
||||
|
||||
impl UqChain {
|
||||
pub fn new() -> UqChain {
|
||||
let memdb = Arc::new(MemoryDB::new(true));
|
||||
let hasher = Arc::new(HasherKeccak::new());
|
||||
|
||||
let my_item = StateItem {
|
||||
source: H256::zero(),
|
||||
holder: H256::zero(),
|
||||
town_id: 0,
|
||||
salt: Bytes::new(),
|
||||
label: String::new(),
|
||||
data: serde_json::Value::Null,
|
||||
};
|
||||
|
||||
let my_contract = ContractItem {
|
||||
source: H256::zero(),
|
||||
holder: H256::zero(),
|
||||
town_id: 0,
|
||||
code_hex: "(module
|
||||
(func (export \"write\") (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
)".into(),
|
||||
};
|
||||
|
||||
let item_key = HasherKeccak::digest(&hasher, &bincode::serialize(&my_item).unwrap());
|
||||
|
||||
let contract_key: H256 = "0x0000000000000000000000000000000000000000000000000000000000005678".parse().unwrap();
|
||||
|
||||
println!("contract id: {:?}", contract_key);
|
||||
|
||||
let mut trie = PatriciaTrie::new(Arc::clone(&memdb), Arc::clone(&hasher));
|
||||
trie.insert(item_key.to_vec(), bincode::serialize(&my_item).unwrap()).unwrap();
|
||||
trie.insert(contract_key.as_bytes().to_vec(), bincode::serialize(&my_contract).unwrap()).unwrap();
|
||||
|
||||
UqChain {
|
||||
state: trie,
|
||||
nonces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_batch(self, txns: Vec<Transaction>) -> UqChain {
|
||||
return engine(self, txns);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn engine(chain: UqChain, txns: Vec<Transaction>) -> UqChain {
|
||||
let start_time = Instant::now();
|
||||
// An engine stores and configures global compilation settings like
|
||||
// optimization level, enabled wasm features, etc.
|
||||
let wasm_engine = Engine::default();
|
||||
|
||||
// sort txns by gas_price
|
||||
let txns = sort_transactions(txns);
|
||||
// execute txns in order
|
||||
for txn in txns {
|
||||
// check signature
|
||||
match txn.signature {
|
||||
Some(sig) => {
|
||||
let message = txn.hash();
|
||||
match sig.verify(message, txn.from) {
|
||||
Ok(_) => {},
|
||||
Err(_) => { continue; }
|
||||
}
|
||||
},
|
||||
None => {}, // TODO handle unsigned transactions to abstract accounts
|
||||
}
|
||||
// check nonce
|
||||
let last_nonce = *chain.nonces.get(&txn.from).unwrap_or(&U256::zero());
|
||||
if txn.nonce != last_nonce + 1 {
|
||||
continue;
|
||||
}
|
||||
// audit account's gas balance
|
||||
// XX
|
||||
// execute transaction against current chain state
|
||||
// We start off by creating a `Module` which represents a compiled form
|
||||
// of our input wasm module. In this case it'll be JIT-compiled after
|
||||
// we parse the text format.
|
||||
let contract_item = chain.state.get(&txn.to.as_bytes()).unwrap().unwrap();
|
||||
let contract_item = bincode::deserialize::<ContractItem>(&contract_item).unwrap();
|
||||
let contract = Module::new(&wasm_engine, contract_item.code_hex).unwrap();
|
||||
|
||||
// A `Store` is what will own instances, functions, globals, etc. All wasm
|
||||
// items are stored within a `Store`, and it's what we'll always be using to
|
||||
// interact with the wasm world. Custom data can be stored in stores but for
|
||||
// now we just use `()`.
|
||||
let mut store = Store::new(&wasm_engine, txn.to);
|
||||
|
||||
// With a compiled `Module` we can then instantiate it, creating
|
||||
// an `Instance` which we can actually poke at functions on.
|
||||
let instance = Instance::new(&mut store, &contract, &[]).unwrap();
|
||||
|
||||
// The `Instance` gives us access to various exported functions and items,
|
||||
// which we access here to pull out our `answer` exported function and
|
||||
// run it.
|
||||
let write_func = instance.get_func(&mut store, "write")
|
||||
.expect("`write` was not an exported function");
|
||||
|
||||
// There's a few ways we can call the `answer` `Func` value. The easiest
|
||||
// is to statically assert its signature with `typed` (in this case
|
||||
// asserting it takes no arguments and returns one i32) and then call it.
|
||||
let typed_write_func = write_func.typed::<(i32, i32), i32>(&store).unwrap();
|
||||
|
||||
// And finally we can call our function! Note that the error propagation
|
||||
// with `?` is done to handle the case where the wasm function traps.
|
||||
let result = typed_write_func.call(&mut store, (3, 5)).unwrap();
|
||||
println!("Answer: {:?}", result);
|
||||
// validate output
|
||||
}
|
||||
|
||||
let exec_duration = start_time.elapsed();
|
||||
println!("engine: time taken to execute: {:?}", exec_duration);
|
||||
// return updated chain
|
||||
chain
|
||||
}
|
||||
|
||||
/// produce ordered vector of transactions by gas_price, adjusting for nonce of caller.
|
||||
/// XX check for correctness
|
||||
fn sort_transactions(mut txns: Vec<Transaction>) -> Vec<Transaction> {
|
||||
txns.sort_unstable_by(|a, b|
|
||||
a.gas_price.cmp(&b.gas_price)
|
||||
);
|
||||
txns.sort_by(|a, b|
|
||||
a.nonce.cmp(&b.nonce)
|
||||
);
|
||||
txns
|
||||
}
|
310
src/eth_rpc.rs
Normal file
310
src/eth_rpc.rs
Normal file
@ -0,0 +1,310 @@
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
use ethers::core::types::Filter;
|
||||
use ethers::prelude::Provider;
|
||||
use ethers::types::{ValueOrArray, U256, U64};
|
||||
use ethers_providers::{Middleware, StreamExt, Ws};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum EthRpcAction {
|
||||
SubscribeEvents(EthEventSubscription),
|
||||
Unsubscribe(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct EthEventSubscription {
|
||||
addresses: Option<Vec<String>>,
|
||||
from_block: Option<u64>,
|
||||
to_block: Option<u64>,
|
||||
events: Option<Vec<String>>, // aka topic0s
|
||||
topic1: Option<U256>,
|
||||
topic2: Option<U256>,
|
||||
topic3: Option<U256>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum EthRpcError {
|
||||
NoRsvp,
|
||||
BadJson,
|
||||
NoJson,
|
||||
EventSubscriptionFailed,
|
||||
}
|
||||
impl EthRpcError {
|
||||
pub fn kind(&self) -> &str {
|
||||
match *self {
|
||||
EthRpcError::NoRsvp { .. } => "NoRsvp",
|
||||
EthRpcError::BadJson { .. } => "BapJson",
|
||||
EthRpcError::NoJson { .. } => "NoJson",
|
||||
EthRpcError::EventSubscriptionFailed { .. } => "EventSubscriptionFailed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn eth_rpc(
|
||||
our: String,
|
||||
rpc_url: String,
|
||||
send_to_loop: MessageSender,
|
||||
mut recv_in_client: MessageReceiver,
|
||||
print_tx: PrintSender,
|
||||
) -> Result<()> {
|
||||
// TODO maybe don't need to do Arc Mutex
|
||||
let subscriptions = Arc::new(Mutex::new(HashMap::<
|
||||
u64,
|
||||
tokio::task::JoinHandle<Result<(), EthRpcError>>,
|
||||
>::new()));
|
||||
|
||||
while let Some(message) = recv_in_client.recv().await {
|
||||
let our = our.clone();
|
||||
let send_to_loop = send_to_loop.clone();
|
||||
let print_tx = print_tx.clone();
|
||||
|
||||
let KernelMessage {
|
||||
id,
|
||||
source,
|
||||
ref rsvp,
|
||||
message:
|
||||
Message::Request(Request {
|
||||
inherit: _,
|
||||
expects_response,
|
||||
ipc: json,
|
||||
metadata: _,
|
||||
}),
|
||||
..
|
||||
} = message
|
||||
else {
|
||||
panic!("eth_rpc: bad message");
|
||||
};
|
||||
|
||||
let target = if expects_response.is_some() {
|
||||
Address {
|
||||
node: our.clone(),
|
||||
process: source.process.clone(),
|
||||
}
|
||||
} else {
|
||||
let Some(rsvp) = rsvp else {
|
||||
send_to_loop
|
||||
.send(make_error_message(
|
||||
our.clone(),
|
||||
id.clone(),
|
||||
source.clone(),
|
||||
EthRpcError::NoRsvp,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
continue;
|
||||
};
|
||||
rsvp.clone()
|
||||
};
|
||||
|
||||
// let call_data = content.payload.bytes.content.clone().unwrap_or(vec![]);
|
||||
|
||||
let Some(json) = json.clone() else {
|
||||
send_to_loop
|
||||
.send(make_error_message(
|
||||
our.clone(),
|
||||
id.clone(),
|
||||
source.clone(),
|
||||
EthRpcError::NoJson,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(action) = serde_json::from_str::<EthRpcAction>(&json) else {
|
||||
send_to_loop
|
||||
.send(make_error_message(
|
||||
our.clone(),
|
||||
id.clone(),
|
||||
source.clone(),
|
||||
EthRpcError::BadJson,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
continue;
|
||||
};
|
||||
|
||||
match action {
|
||||
EthRpcAction::SubscribeEvents(sub) => {
|
||||
let id: u64 = rand::random();
|
||||
send_to_loop
|
||||
.send(KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("eth_rpc".into()),
|
||||
},
|
||||
target: target.clone(),
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(
|
||||
serde_json::to_string::<Result<u64, EthRpcError>>(&Ok(id))
|
||||
.unwrap(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut filter = Filter::new();
|
||||
if let Some(addresses) = sub.addresses {
|
||||
filter = filter.address(ValueOrArray::Array(
|
||||
addresses.into_iter().map(|s| s.parse().unwrap()).collect(),
|
||||
));
|
||||
}
|
||||
|
||||
// TODO is there a cleaner way to do all of this?
|
||||
if let Some(from_block) = sub.from_block {
|
||||
filter = filter.from_block(from_block);
|
||||
}
|
||||
if let Some(to_block) = sub.to_block {
|
||||
filter = filter.to_block(to_block);
|
||||
}
|
||||
if let Some(events) = sub.events {
|
||||
filter = filter.events(&events);
|
||||
}
|
||||
if let Some(topic1) = sub.topic1 {
|
||||
filter = filter.topic1(topic1);
|
||||
}
|
||||
if let Some(topic2) = sub.topic2 {
|
||||
filter = filter.topic2(topic2);
|
||||
}
|
||||
if let Some(topic3) = sub.topic3 {
|
||||
filter = filter.topic3(topic3);
|
||||
}
|
||||
|
||||
let rpc_url = rpc_url.clone();
|
||||
|
||||
let handle = tokio::task::spawn(async move {
|
||||
// when connection dies you need to restart at the last block you saw
|
||||
// otherwise you replay events unnecessarily
|
||||
let mut from_block: U64 =
|
||||
filter.clone().get_from_block().unwrap_or(U64::zero());
|
||||
loop {
|
||||
// NOTE give main.rs uses rpc_url and panics if it can't connect, we do
|
||||
// know that this should work in theory...can keep trying to reconnect
|
||||
let Ok(ws_rpc) = Provider::<Ws>::connect(rpc_url.clone()).await else {
|
||||
// TODO grab and print error
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("eth_rpc: connection retrying"),
|
||||
})
|
||||
.await;
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
continue;
|
||||
};
|
||||
|
||||
match ws_rpc
|
||||
.subscribe_logs(&filter.clone().from_block(from_block))
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
continue;
|
||||
}
|
||||
Ok(mut stream) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("eth_rpc: connection established"),
|
||||
})
|
||||
.await;
|
||||
|
||||
while let Some(event) = stream.next().await {
|
||||
send_to_loop.send(
|
||||
KernelMessage {
|
||||
id: rand::random(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("eth_rpc".into()),
|
||||
},
|
||||
target: target.clone(),
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false, // TODO what
|
||||
expects_response: None,
|
||||
ipc: Some(json!({
|
||||
"EventSubscription": serde_json::to_value(event.clone()).unwrap()
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
}
|
||||
).await.unwrap();
|
||||
from_block = event.block_number.unwrap_or(from_block);
|
||||
}
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!(
|
||||
"eth_rpc: subscription connection lost, reconnecting"
|
||||
),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
subscriptions.lock().await.insert(id, handle);
|
||||
}
|
||||
EthRpcAction::Unsubscribe(sub_id) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("eth_rpc: unsubscribing from {}", sub_id),
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(handle) = subscriptions.lock().await.remove(&sub_id) {
|
||||
handle.abort();
|
||||
} else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("eth_rpc: no task found with id {}", sub_id),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
// helpers
|
||||
//
|
||||
|
||||
fn make_error_message(our: String, id: u64, source: Address, error: EthRpcError) -> KernelMessage {
|
||||
KernelMessage {
|
||||
id,
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("eth_rpc".into()),
|
||||
},
|
||||
target: source,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(serde_json::to_string::<Result<u64, EthRpcError>>(&Err(error)).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
}
|
||||
}
|
1327
src/filesystem/manifest.rs
Normal file
1327
src/filesystem/manifest.rs
Normal file
File diff suppressed because it is too large
Load Diff
580
src/filesystem/mod.rs
Normal file
580
src/filesystem/mod.rs
Normal file
@ -0,0 +1,580 @@
|
||||
use crate::filesystem::manifest::{FileIdentifier, Manifest};
|
||||
use crate::types::*;
|
||||
/// log structured filesystem
|
||||
use anyhow::Result;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
use tokio::sync::oneshot::{Receiver, Sender};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::{interval, Duration};
|
||||
mod manifest;
|
||||
|
||||
pub async fn bootstrap(
|
||||
our_name: String,
|
||||
home_directory_path: String,
|
||||
file_key: Vec<u8>,
|
||||
fs_config: FsConfig,
|
||||
) -> Result<(ProcessMap, Manifest), FileSystemError> {
|
||||
// fs bootstrapping, create home_directory, fs directory inside it, manifest + log if none.
|
||||
if let Err(e) = create_dir_if_dne(&home_directory_path).await {
|
||||
panic!("{}", e);
|
||||
}
|
||||
let fs_directory_path_str = format!("{}/fs", &home_directory_path);
|
||||
|
||||
if let Err(e) = create_dir_if_dne(&fs_directory_path_str).await {
|
||||
panic!("{}", e);
|
||||
}
|
||||
let fs_directory_path: std::path::PathBuf =
|
||||
fs::canonicalize(fs_directory_path_str).await.unwrap();
|
||||
|
||||
// open and load manifest+log
|
||||
|
||||
let manifest_path = fs_directory_path.join("manifest.bin");
|
||||
|
||||
let manifest_file = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.open(&manifest_path)
|
||||
.await
|
||||
.expect("fs: failed to open manifest file");
|
||||
|
||||
let wal_path = fs_directory_path.join("wal.bin");
|
||||
|
||||
let wal_file = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.open(&wal_path)
|
||||
.await
|
||||
.expect("fs: failed to open WAL file");
|
||||
|
||||
// in memory details about files.
|
||||
let manifest = Manifest::load(
|
||||
manifest_file,
|
||||
wal_file,
|
||||
&fs_directory_path,
|
||||
file_key,
|
||||
fs_config,
|
||||
)
|
||||
.await
|
||||
.expect("manifest load failed!");
|
||||
// mimic the FsAction::GetState case and get current state of ProcessId::name("kernel")
|
||||
// serialize it to a ProcessHandles from process id to JoinHandle
|
||||
|
||||
let kernel_process_id = FileIdentifier::Process(ProcessId::Name("kernel".into()));
|
||||
let mut process_map: ProcessMap = HashMap::new();
|
||||
|
||||
// get current processes' wasm_bytes handles. GetState(kernel)
|
||||
match manifest.read(&kernel_process_id, None, None).await {
|
||||
Err(_) => {
|
||||
// first time!
|
||||
}
|
||||
Ok(bytes) => {
|
||||
process_map = bincode::deserialize(&bytes).expect("state map deserialization error!");
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE OnPanic
|
||||
// wasm bytes initial source of truth is the compiled .wasm file on-disk, but onPanic needs to come from somewhere to.
|
||||
// for apps in /modules, special cases can be defined here.
|
||||
|
||||
// we also add special-case capabilities spawned "out of thin air" here, for distro processes.
|
||||
// at the moment, all bootstrapped processes are given the capability to message all others.
|
||||
// this can be easily changed in the future.
|
||||
// they are also given access to all runtime modules by name
|
||||
let names_and_bytes = get_processes_from_directories().await;
|
||||
const RUNTIME_MODULES: [&str; 8] = [
|
||||
"filesystem",
|
||||
"http_server",
|
||||
"http_client",
|
||||
"encryptor",
|
||||
"net",
|
||||
"vfs",
|
||||
"kernel",
|
||||
"eth_rpc",
|
||||
];
|
||||
|
||||
let mut special_capabilities: HashSet<Capability> = HashSet::new();
|
||||
for (process_name, _) in &names_and_bytes {
|
||||
special_capabilities.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name(process_name.into()),
|
||||
},
|
||||
params: format!(
|
||||
"{{\"messaging\": \"{}\"}}",
|
||||
serde_json::to_string(&ProcessId::Name(process_name.into())).unwrap()
|
||||
),
|
||||
});
|
||||
}
|
||||
for runtime_module in RUNTIME_MODULES {
|
||||
special_capabilities.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name(runtime_module.into()),
|
||||
},
|
||||
params: format!(
|
||||
"{{\"messaging\": \"{}\"}}",
|
||||
serde_json::to_string(&ProcessId::Name(runtime_module.into())).unwrap()
|
||||
),
|
||||
});
|
||||
}
|
||||
// give all distro processes the ability to send messages across the network
|
||||
special_capabilities.insert(Capability {
|
||||
issuer: Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name("kernel".into()),
|
||||
},
|
||||
params: "\"network\"".into(),
|
||||
});
|
||||
|
||||
// for a module in /modules, put its bytes into filesystem, add to process_map
|
||||
for (process_name, wasm_bytes) in names_and_bytes {
|
||||
let hash: [u8; 32] = hash_bytes(&wasm_bytes);
|
||||
|
||||
if let Some(id) = manifest.get_uuid_by_hash(&hash).await {
|
||||
let entry =
|
||||
process_map
|
||||
.entry(ProcessId::Name(process_name))
|
||||
.or_insert(PersistedProcess {
|
||||
wasm_bytes_handle: id,
|
||||
on_panic: OnPanic::Restart,
|
||||
capabilities: HashSet::new(),
|
||||
});
|
||||
entry.capabilities.extend(special_capabilities.clone());
|
||||
entry.wasm_bytes_handle = id;
|
||||
} else {
|
||||
// FsAction::Write
|
||||
let file = FileIdentifier::new_uuid();
|
||||
|
||||
let _ = manifest.write(&file, &wasm_bytes).await;
|
||||
let id = file.to_uuid().unwrap();
|
||||
|
||||
let entry =
|
||||
process_map
|
||||
.entry(ProcessId::Name(process_name))
|
||||
.or_insert(PersistedProcess {
|
||||
wasm_bytes_handle: id,
|
||||
on_panic: OnPanic::Restart,
|
||||
capabilities: HashSet::new(),
|
||||
});
|
||||
entry.capabilities.extend(special_capabilities.clone());
|
||||
entry.wasm_bytes_handle = id;
|
||||
}
|
||||
}
|
||||
|
||||
// finally, save runtime modules in state map as well, somewhat fakely
|
||||
for runtime_module in RUNTIME_MODULES {
|
||||
let entry = process_map
|
||||
.entry(ProcessId::Name(runtime_module.into()))
|
||||
.or_insert(PersistedProcess {
|
||||
wasm_bytes_handle: 0,
|
||||
on_panic: OnPanic::Restart,
|
||||
capabilities: HashSet::new(),
|
||||
});
|
||||
entry.capabilities.extend(special_capabilities.clone());
|
||||
}
|
||||
|
||||
// save kernel process state. FsAction::SetState(kernel)
|
||||
let serialized_process_map =
|
||||
bincode::serialize(&process_map).expect("state map serialization error!");
|
||||
let process_map_hash: [u8; 32] = hash_bytes(&serialized_process_map);
|
||||
|
||||
if manifest.get_by_hash(&process_map_hash).await.is_none() {
|
||||
let _ = manifest
|
||||
.write(&kernel_process_id, &serialized_process_map)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok((process_map, manifest))
|
||||
}
|
||||
|
||||
async fn get_processes_from_directories() -> Vec<(String, Vec<u8>)> {
|
||||
let mut processes = Vec::new();
|
||||
|
||||
// Get the path to the /modules directory
|
||||
let modules_path = std::path::Path::new("modules");
|
||||
|
||||
// Read the /modules directory
|
||||
if let Ok(mut entries) = fs::read_dir(modules_path).await {
|
||||
// Loop through the entries in the directory
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
// If the entry is a directory, add its name to the list of processes
|
||||
if let Ok(metadata) = entry.metadata().await {
|
||||
if metadata.is_dir() {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
// Get the path to the wasm file for the process
|
||||
let wasm_path = format!(
|
||||
"modules/{}/target/wasm32-unknown-unknown/release/{}.wasm",
|
||||
name, name
|
||||
);
|
||||
// Read the wasm file
|
||||
if let Ok(wasm_bytes) = fs::read(wasm_path).await {
|
||||
// Add the process name and wasm bytes to the list of processes
|
||||
processes.push((name.to_string(), wasm_bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processes
|
||||
}
|
||||
|
||||
pub async fn fs_sender(
|
||||
our_name: String,
|
||||
manifest: Manifest,
|
||||
send_to_loop: MessageSender,
|
||||
send_to_terminal: PrintSender,
|
||||
mut recv_in_fs: MessageReceiver,
|
||||
mut recv_kill: Receiver<()>,
|
||||
send_kill_confirm: Sender<()>,
|
||||
) -> Result<()> {
|
||||
// process queues for consistency
|
||||
// todo: use file_identifier for moar concurrency!
|
||||
let process_queues = Arc::new(Mutex::new(
|
||||
HashMap::<ProcessId, VecDeque<KernelMessage>>::new(),
|
||||
));
|
||||
|
||||
// interval for deleting/(flushing), don't want to run immediately upon bootup.
|
||||
// -> separate wal_flush and cold_flush
|
||||
let mut interval = interval(Duration::from_secs(manifest.flush_cold_freq as u64));
|
||||
let mut first_open = true;
|
||||
|
||||
// into main loop
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(kernel_message) = recv_in_fs.recv() => {
|
||||
if our_name != kernel_message.source.node {
|
||||
println!(
|
||||
"fs: request must come from our_name={}, got: {}",
|
||||
our_name, &kernel_message,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// internal structures have Arc::clone setup.
|
||||
let manifest_clone = manifest.clone();
|
||||
|
||||
let our_name = our_name.clone();
|
||||
let source = kernel_message.source.clone();
|
||||
let send_to_loop = send_to_loop.clone();
|
||||
let send_to_terminal = send_to_terminal.clone();
|
||||
|
||||
let mut process_lock = process_queues.lock().await;
|
||||
|
||||
if let Some(queue) = process_lock.get_mut(&source.process) {
|
||||
queue.push_back(kernel_message.clone());
|
||||
} else {
|
||||
let mut new_queue = VecDeque::new();
|
||||
new_queue.push_back(kernel_message.clone());
|
||||
process_lock.insert(source.process.clone(), new_queue);
|
||||
|
||||
// clone Arc for thread
|
||||
let process_lock_clone = process_queues.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut process_lock = process_lock_clone.lock().await;
|
||||
|
||||
while let Some(km) = process_lock.get_mut(&source.process).and_then(|q| q.pop_front()) {
|
||||
if let Err(e) = handle_request(
|
||||
our_name.clone(),
|
||||
km.clone(),
|
||||
manifest_clone.clone(),
|
||||
send_to_loop.clone(),
|
||||
send_to_terminal.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
send_to_loop
|
||||
.send(make_error_message(our_name.clone(), km.id, km.source.clone(), e))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
// Remove the process entry if no more tasks are left
|
||||
if let Some(queue) = process_lock.get(&source.process) {
|
||||
if queue.is_empty() {
|
||||
process_lock.remove(&source.process);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_ = interval.tick() => {
|
||||
if !first_open {
|
||||
let manifest_clone = manifest.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let _ = manifest_clone.flush_to_cold().await;
|
||||
// let _ = manifest_clone.cleanup().await;
|
||||
});
|
||||
}
|
||||
first_open = false;
|
||||
}
|
||||
_ = &mut recv_kill => {
|
||||
let manifest_clone = manifest.clone();
|
||||
let _ = manifest_clone.flush_to_wal_main().await;
|
||||
|
||||
let _ = send_kill_confirm.send(());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(
|
||||
our_name: String,
|
||||
kernel_message: KernelMessage,
|
||||
manifest: Manifest,
|
||||
send_to_loop: MessageSender,
|
||||
_send_to_terminal: PrintSender,
|
||||
) -> Result<(), FileSystemError> {
|
||||
let KernelMessage {
|
||||
id,
|
||||
source,
|
||||
rsvp,
|
||||
message,
|
||||
payload,
|
||||
..
|
||||
} = kernel_message;
|
||||
let Message::Request(Request {
|
||||
expects_response,
|
||||
ipc: Some(json_string),
|
||||
metadata, // for kernel
|
||||
..
|
||||
}) = message
|
||||
else {
|
||||
return Err(FileSystemError::BadJson {
|
||||
json: "".into(),
|
||||
error: "not a Request with payload".into(),
|
||||
});
|
||||
};
|
||||
|
||||
let action: FsAction = match serde_json::from_str(&json_string) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Err(FileSystemError::BadJson {
|
||||
json: json_string.into(),
|
||||
error: format!("parse failed: {:?}", e),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// println!("got action! {:?}", action);
|
||||
|
||||
let (ipc, bytes) = match action {
|
||||
FsAction::Write => {
|
||||
let Some(ref payload) = payload else {
|
||||
return Err(FileSystemError::BadBytes {
|
||||
action: "Write".into(),
|
||||
});
|
||||
};
|
||||
|
||||
let file_uuid = FileIdentifier::new_uuid();
|
||||
manifest.write(&file_uuid, &payload.bytes).await?;
|
||||
|
||||
(FsResponse::Write(file_uuid.to_uuid().unwrap()), None)
|
||||
}
|
||||
FsAction::WriteOffset((file_uuid, offset)) => {
|
||||
let Some(ref payload) = payload else {
|
||||
return Err(FileSystemError::BadBytes {
|
||||
action: "Write".into(),
|
||||
});
|
||||
};
|
||||
|
||||
let file_uuid = FileIdentifier::UUID(file_uuid);
|
||||
|
||||
manifest
|
||||
.write_at(&file_uuid, offset, &payload.bytes)
|
||||
.await?;
|
||||
|
||||
(FsResponse::Write(file_uuid.to_uuid().unwrap()), None)
|
||||
}
|
||||
FsAction::Read(file_uuid) => {
|
||||
let file = FileIdentifier::UUID(file_uuid);
|
||||
|
||||
match manifest.read(&file, None, None).await {
|
||||
Err(e) => return Err(e),
|
||||
Ok(bytes) => (FsResponse::Read(file_uuid), Some(bytes)),
|
||||
}
|
||||
}
|
||||
FsAction::ReadChunk(req) => {
|
||||
let file = FileIdentifier::UUID(req.file);
|
||||
|
||||
match manifest
|
||||
.read(&file, Some(req.start), Some(req.length))
|
||||
.await
|
||||
{
|
||||
Err(e) => return Err(e),
|
||||
Ok(bytes) => (FsResponse::Read(req.file), Some(bytes)),
|
||||
}
|
||||
}
|
||||
FsAction::Replace(old_file_uuid) => {
|
||||
let Some(ref payload) = payload else {
|
||||
return Err(FileSystemError::BadBytes {
|
||||
action: "Write".into(),
|
||||
});
|
||||
};
|
||||
|
||||
let file = FileIdentifier::UUID(old_file_uuid);
|
||||
manifest.write(&file, &payload.bytes).await?;
|
||||
|
||||
(FsResponse::Write(old_file_uuid), None)
|
||||
}
|
||||
FsAction::Delete(del) => {
|
||||
let file = FileIdentifier::UUID(del);
|
||||
manifest.delete(&file).await?;
|
||||
|
||||
(FsResponse::Delete(del), None)
|
||||
}
|
||||
FsAction::Append(maybe_file_uuid) => {
|
||||
let Some(ref payload) = payload else {
|
||||
return Err(FileSystemError::BadBytes {
|
||||
action: "Append".into(),
|
||||
});
|
||||
};
|
||||
|
||||
let file_uuid = match maybe_file_uuid {
|
||||
Some(uuid) => FileIdentifier::UUID(uuid),
|
||||
None => FileIdentifier::new_uuid(),
|
||||
};
|
||||
|
||||
manifest.append(&file_uuid, &payload.bytes).await?;
|
||||
// note expecting file_uuid here, if we want process state to access append, we would change this.
|
||||
(FsResponse::Append(file_uuid.to_uuid().unwrap()), None)
|
||||
}
|
||||
FsAction::Length(file_uuid) => {
|
||||
let file = FileIdentifier::UUID(file_uuid);
|
||||
let length = manifest.get_length(&file).await;
|
||||
match length {
|
||||
Some(len) => (FsResponse::Length(len), None),
|
||||
None => {
|
||||
return Err(FileSystemError::LFSError {
|
||||
error: "file not found".into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
FsAction::SetLength((file_uuid, length)) => {
|
||||
let file = FileIdentifier::UUID(file_uuid);
|
||||
manifest.set_length(&file, length).await?;
|
||||
|
||||
// doublecheck if this is the type of return statement we want.
|
||||
(FsResponse::Length(length), None)
|
||||
}
|
||||
// process state handlers
|
||||
FsAction::SetState => {
|
||||
let Some(ref payload) = payload else {
|
||||
return Err(FileSystemError::BadBytes {
|
||||
action: "Write".into(),
|
||||
});
|
||||
};
|
||||
|
||||
let file = FileIdentifier::Process(source.process.clone());
|
||||
let _ = manifest.write(&file, &payload.bytes).await;
|
||||
|
||||
(FsResponse::SetState, None)
|
||||
}
|
||||
FsAction::GetState => {
|
||||
let file = FileIdentifier::Process(source.process.clone());
|
||||
|
||||
match manifest.read(&file, None, None).await {
|
||||
Err(e) => return Err(e),
|
||||
Ok(bytes) => (FsResponse::GetState, Some(bytes)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if expects_response.is_some() {
|
||||
let response = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name("filesystem".into()),
|
||||
},
|
||||
target: source.clone(),
|
||||
rsvp,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(
|
||||
serde_json::to_string::<Result<FsResponse, FileSystemError>>(&Ok(ipc))
|
||||
.unwrap(),
|
||||
),
|
||||
metadata, // for kernel
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: Some(Payload {
|
||||
mime: None,
|
||||
bytes: bytes.unwrap_or_default(),
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
let _ = send_to_loop.send(response).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// HELPERS
|
||||
|
||||
pub fn hash_bytes(bytes: &[u8]) -> [u8; 32] {
|
||||
let mut hasher = blake3::Hasher::new();
|
||||
let chunk_size: usize = 1024 * 256;
|
||||
|
||||
for chunk in bytes.chunks(chunk_size) {
|
||||
let chunk_hash: [u8; 32] = blake3::hash(chunk).into();
|
||||
hasher.update(&chunk_hash);
|
||||
}
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
async fn create_dir_if_dne(path: &str) -> Result<(), FileSystemError> {
|
||||
if let Err(_) = fs::read_dir(&path).await {
|
||||
match fs::create_dir_all(&path).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(FileSystemError::CouldNotMakeDir {
|
||||
path: path.into(),
|
||||
error: format!("{}", e),
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn make_error_message(
|
||||
our_name: String,
|
||||
id: u64,
|
||||
target: Address,
|
||||
error: FileSystemError,
|
||||
) -> KernelMessage {
|
||||
KernelMessage {
|
||||
id,
|
||||
source: Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name("fileystem".into()),
|
||||
},
|
||||
target,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(
|
||||
serde_json::to_string::<Result<FileSystemResponse, FileSystemError>>(&Err(
|
||||
error,
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
}
|
||||
}
|
235
src/http_client.rs
Normal file
235
src/http_client.rs
Normal file
@ -0,0 +1,235 @@
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Test http_client with these commands in the terminal
|
||||
// !message tuna http_client {"method": "GET", "uri": "https://jsonplaceholder.typicode.com/posts", "headers": {}, "body": ""}
|
||||
// !message tuna http_client {"method": "POST", "uri": "https://jsonplaceholder.typicode.com/posts", "headers": {"Content-Type": "application/json"}, "body": "{\"title\": \"foo\", \"body\": \"bar\"}"}
|
||||
// !message tuna http_client {"method": "PUT", "uri": "https://jsonplaceholder.typicode.com/posts", "headers": {"Content-Type": "application/json"}, "body": "{\"title\": \"foo\", \"body\": \"bar\"}"}
|
||||
|
||||
pub async fn http_client(
|
||||
our_name: String,
|
||||
send_to_loop: MessageSender,
|
||||
mut recv_in_client: MessageReceiver,
|
||||
print_tx: PrintSender,
|
||||
) -> Result<()> {
|
||||
while let Some(message) = recv_in_client.recv().await {
|
||||
let KernelMessage {
|
||||
id,
|
||||
source,
|
||||
rsvp,
|
||||
message:
|
||||
Message::Request(Request {
|
||||
expects_response,
|
||||
ipc: json,
|
||||
..
|
||||
}),
|
||||
payload,
|
||||
..
|
||||
} = message.clone()
|
||||
else {
|
||||
return Err(anyhow::anyhow!("http_client: bad message"));
|
||||
};
|
||||
|
||||
let our_name = our_name.clone();
|
||||
let send_to_loop = send_to_loop.clone();
|
||||
let print_tx = print_tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_message(
|
||||
our_name.clone(),
|
||||
send_to_loop.clone(),
|
||||
id,
|
||||
rsvp,
|
||||
expects_response,
|
||||
source.clone(),
|
||||
json,
|
||||
{
|
||||
if let Some(payload) = payload {
|
||||
Some(payload.bytes)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
send_to_loop
|
||||
.send(make_error_message(our_name.clone(), id, source, e))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(anyhow::anyhow!("http_client: exited"))
|
||||
}
|
||||
|
||||
async fn handle_message(
|
||||
our: String,
|
||||
send_to_loop: MessageSender,
|
||||
id: u64,
|
||||
rsvp: Option<Address>,
|
||||
expects_response: Option<u64>,
|
||||
source: Address,
|
||||
json: Option<String>,
|
||||
body: Option<Vec<u8>>,
|
||||
_print_tx: PrintSender,
|
||||
) -> Result<(), HttpClientError> {
|
||||
let target = if expects_response.is_some() {
|
||||
source.clone()
|
||||
} else {
|
||||
let Some(rsvp) = rsvp else {
|
||||
return Err(HttpClientError::BadRsvp);
|
||||
};
|
||||
rsvp.clone()
|
||||
};
|
||||
|
||||
let Some(ref json) = json else {
|
||||
return Err(HttpClientError::NoJson);
|
||||
};
|
||||
|
||||
let req: HttpClientRequest = match serde_json::from_str(json) {
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
return Err(HttpClientError::BadJson {
|
||||
json: json.to_string(),
|
||||
error: format!("{}", e),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let request_builder = match req.method.to_uppercase()[..].to_string().as_str() {
|
||||
"GET" => client.get(req.uri),
|
||||
"PUT" => client.put(req.uri),
|
||||
"POST" => client.post(req.uri),
|
||||
"DELETE" => client.delete(req.uri),
|
||||
method => {
|
||||
return Err(HttpClientError::BadMethod {
|
||||
method: method.into(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let request = request_builder
|
||||
.headers(deserialize_headers(req.headers))
|
||||
.body(body.unwrap_or(vec![]))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let response = match client.execute(request).await {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
return Err(HttpClientError::RequestFailed {
|
||||
error: format!("{}", e),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let http_client_response = HttpClientResponse {
|
||||
status: response.status().as_u16(),
|
||||
headers: serialize_headers(&response.headers().clone()),
|
||||
};
|
||||
|
||||
let message = KernelMessage {
|
||||
id,
|
||||
source: Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("http_client".to_string()),
|
||||
},
|
||||
target,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(
|
||||
serde_json::to_string::<Result<HttpClientResponse, HttpClientError>>(&Ok(
|
||||
http_client_response,
|
||||
))
|
||||
.unwrap(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/json".into()),
|
||||
bytes: response.bytes().await.unwrap().to_vec(),
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
// helpers
|
||||
//
|
||||
fn to_pascal_case(s: &str) -> String {
|
||||
s.split('-')
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
match chars.next() {
|
||||
None => String::new(),
|
||||
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("-")
|
||||
}
|
||||
|
||||
fn serialize_headers(headers: &HeaderMap) -> HashMap<String, String> {
|
||||
let mut hashmap = HashMap::new();
|
||||
for (key, value) in headers.iter() {
|
||||
let key_str = to_pascal_case(&key.to_string());
|
||||
let value_str = value.to_str().unwrap_or("").to_string();
|
||||
hashmap.insert(key_str, value_str);
|
||||
}
|
||||
hashmap
|
||||
}
|
||||
|
||||
fn deserialize_headers(hashmap: HashMap<String, String>) -> HeaderMap {
|
||||
let mut header_map = HeaderMap::new();
|
||||
for (key, value) in hashmap {
|
||||
let key_bytes = key.as_bytes();
|
||||
let key_name = HeaderName::from_bytes(key_bytes).unwrap();
|
||||
let value_header = HeaderValue::from_str(&value).unwrap();
|
||||
header_map.insert(key_name, value_header);
|
||||
}
|
||||
header_map
|
||||
}
|
||||
|
||||
fn make_error_message(
|
||||
our_name: String,
|
||||
id: u64,
|
||||
source: Address,
|
||||
error: HttpClientError,
|
||||
) -> KernelMessage {
|
||||
KernelMessage {
|
||||
id,
|
||||
source: source.clone(),
|
||||
target: Address {
|
||||
node: our_name.clone(),
|
||||
process: source.process.clone(),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(
|
||||
serde_json::to_string::<Result<HttpClientResponse, HttpClientError>>(&Err(
|
||||
error,
|
||||
))
|
||||
.unwrap(),
|
||||
), // TODO: handle error?
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
}
|
||||
}
|
739
src/http_server/mod.rs
Normal file
739
src/http_server/mod.rs
Normal file
@ -0,0 +1,739 @@
|
||||
use crate::http_server::server_fns::*;
|
||||
use crate::register;
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
use serde_urlencoded;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::Mutex;
|
||||
use warp::http::{header::HeaderValue, StatusCode};
|
||||
use warp::ws::{WebSocket, Ws};
|
||||
use warp::{Filter, Reply};
|
||||
|
||||
mod server_fns;
|
||||
|
||||
// types and constants
|
||||
type HttpSender = tokio::sync::oneshot::Sender<HttpResponse>;
|
||||
type HttpResponseSenders = Arc<Mutex<HashMap<u64, (String, HttpSender)>>>;
|
||||
|
||||
// node -> ID -> random ID
|
||||
|
||||
/// http driver
|
||||
pub async fn http_server(
|
||||
our_name: String,
|
||||
our_port: u16,
|
||||
jwt_secret_bytes: Vec<u8>,
|
||||
mut recv_in_server: MessageReceiver,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender,
|
||||
) -> Result<()> {
|
||||
let http_response_senders = Arc::new(Mutex::new(HashMap::new()));
|
||||
let websockets: WebSockets = Arc::new(Mutex::new(HashMap::new()));
|
||||
let ws_proxies: WebSocketProxies = Arc::new(Mutex::new(HashMap::new())); // channel_id -> node
|
||||
|
||||
let _ = tokio::join!(
|
||||
http_serve(
|
||||
our_name.clone(),
|
||||
our_port,
|
||||
http_response_senders.clone(),
|
||||
websockets.clone(),
|
||||
jwt_secret_bytes.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone()
|
||||
),
|
||||
async move {
|
||||
while let Some(kernel_message) = recv_in_server.recv().await {
|
||||
let KernelMessage {
|
||||
id,
|
||||
source,
|
||||
message,
|
||||
payload,
|
||||
..
|
||||
} = kernel_message;
|
||||
|
||||
if let Err(e) = http_handle_messages(
|
||||
our_name.clone(),
|
||||
id.clone(),
|
||||
source.clone(),
|
||||
message,
|
||||
payload,
|
||||
http_response_senders.clone(),
|
||||
websockets.clone(),
|
||||
ws_proxies.clone(),
|
||||
jwt_secret_bytes.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
send_to_loop
|
||||
.send(make_error_message(
|
||||
our_name.clone(),
|
||||
id.clone(),
|
||||
source.clone(),
|
||||
e,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
Err(anyhow::anyhow!("http_server: exited"))
|
||||
}
|
||||
|
||||
async fn handle_websocket(
|
||||
ws: WebSocket,
|
||||
our: String,
|
||||
jwt_secret_bytes: Vec<u8>,
|
||||
websockets: WebSockets,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender,
|
||||
) {
|
||||
let (write_stream, mut read_stream) = ws.split();
|
||||
let write_stream = Arc::new(Mutex::new(write_stream));
|
||||
|
||||
// How do we handle authentication?
|
||||
let ws_id: u64 = rand::random();
|
||||
|
||||
while let Some(Ok(msg)) = read_stream.next().await {
|
||||
if msg.is_binary() {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("GOT WEBSOCKET BYTES"),
|
||||
})
|
||||
.await;
|
||||
let bytes = msg.as_bytes();
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!(
|
||||
"WEBSOCKET MESSAGE (BYTES) {}",
|
||||
String::from_utf8(bytes.to_vec()).unwrap_or_default()
|
||||
),
|
||||
})
|
||||
.await;
|
||||
match serde_json::from_slice::<WebSocketClientMessage>(&bytes) {
|
||||
Ok(parsed_msg) => {
|
||||
handle_incoming_ws(
|
||||
parsed_msg,
|
||||
our.clone(),
|
||||
jwt_secret_bytes.clone().to_vec(),
|
||||
websockets.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
write_stream.clone(),
|
||||
ws_id.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("Failed to parse WebSocket message: {}", e),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else if msg.is_text() {
|
||||
match msg.to_str() {
|
||||
Ok(msg_str) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("WEBSOCKET MESSAGE (TEXT): {}", msg_str),
|
||||
})
|
||||
.await;
|
||||
match serde_json::from_str(&msg_str) {
|
||||
Ok(parsed_msg) => {
|
||||
handle_incoming_ws(
|
||||
parsed_msg,
|
||||
our.clone(),
|
||||
jwt_secret_bytes.clone().to_vec(),
|
||||
websockets.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
write_stream.clone(),
|
||||
ws_id.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if msg.is_close() {
|
||||
// Delete the websocket from the map
|
||||
let mut ws_map = websockets.lock().await;
|
||||
for (node, node_map) in ws_map.iter_mut() {
|
||||
for (channel_id, id_map) in node_map.iter_mut() {
|
||||
if let Some(_) = id_map.remove(&ws_id) {
|
||||
// Send disconnect message
|
||||
send_ws_disconnect(
|
||||
node.clone(),
|
||||
our.clone(),
|
||||
channel_id.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn http_handle_messages(
|
||||
our: String,
|
||||
id: u64,
|
||||
source: Address,
|
||||
message: Message,
|
||||
payload: Option<Payload>,
|
||||
http_response_senders: HttpResponseSenders,
|
||||
websockets: WebSockets,
|
||||
ws_proxies: WebSocketProxies,
|
||||
jwt_secret_bytes: Vec<u8>,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender,
|
||||
) -> Result<(), HttpServerError> {
|
||||
match message {
|
||||
Message::Response((ref response, _)) => {
|
||||
let mut senders = http_response_senders.lock().await;
|
||||
|
||||
let json =
|
||||
serde_json::from_str::<HttpResponse>(&response.ipc.clone().unwrap_or_default());
|
||||
|
||||
match json {
|
||||
Ok(mut response) => {
|
||||
let Some(payload) = payload else {
|
||||
return Err(HttpServerError::NoBytes);
|
||||
};
|
||||
|
||||
let bytes = payload.bytes;
|
||||
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ID: {}", id.to_string()),
|
||||
})
|
||||
.await;
|
||||
for (id, _) in senders.iter() {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("existing: {}", id.to_string()),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
match senders.remove(&id) {
|
||||
Some((path, channel)) => {
|
||||
let segments: Vec<&str> = path
|
||||
.split('/')
|
||||
.filter(|&segment| !segment.is_empty())
|
||||
.collect();
|
||||
|
||||
// If we're getting back a /login from a proxy (or our own node), then we should generate a jwt from the secret + the name of the ship, and then attach it to a header
|
||||
if response.status < 400
|
||||
&& (segments.len() == 1 || segments.len() == 4)
|
||||
&& matches!(segments.last(), Some(&"login"))
|
||||
{
|
||||
if let Some(auth_cookie) = response.headers.get("set-cookie") {
|
||||
let mut ws_auth_username = our.clone();
|
||||
if segments.len() == 4
|
||||
&& matches!(segments.get(0), Some(&"http-proxy"))
|
||||
&& matches!(segments.get(1), Some(&"serve"))
|
||||
{
|
||||
if let Some(segment) = segments.get(2) {
|
||||
ws_auth_username = segment.to_string();
|
||||
}
|
||||
}
|
||||
if let Some(token) = register::generate_jwt(
|
||||
jwt_secret_bytes.to_vec().as_slice(),
|
||||
ws_auth_username.clone(),
|
||||
) {
|
||||
let auth_cookie_with_ws = format!(
|
||||
"{}; uqbar-ws-auth_{}={};",
|
||||
auth_cookie,
|
||||
ws_auth_username.clone(),
|
||||
token
|
||||
);
|
||||
response
|
||||
.headers
|
||||
.insert("set-cookie".to_string(), auth_cookie_with_ws);
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!(
|
||||
"SET WS AUTH COOKIE WITH USERNAME: {}",
|
||||
ws_auth_username
|
||||
),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = channel.send(HttpResponse {
|
||||
status: response.status,
|
||||
headers: response.headers,
|
||||
body: Some(bytes),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
println!(
|
||||
"http_server: inconsistent state, no key found for id {}",
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_json_parsing_err) => {
|
||||
let mut error_headers = HashMap::new();
|
||||
error_headers.insert("Content-Type".to_string(), "text/html".to_string());
|
||||
match senders.remove(&id) {
|
||||
Some((_path, channel)) => {
|
||||
let _ = channel.send(HttpResponse {
|
||||
status: 503,
|
||||
headers: error_headers,
|
||||
body: Some(format!("Internal Server Error").as_bytes().to_vec()),
|
||||
});
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Request(Request { ipc, .. }) => {
|
||||
let Some(json) = ipc else {
|
||||
return Err(HttpServerError::NoJson);
|
||||
};
|
||||
|
||||
match serde_json::from_str(&json) {
|
||||
Ok(message) => {
|
||||
match message {
|
||||
HttpServerMessage::WebSocketPush(WebSocketPush { target, is_text }) => {
|
||||
let Some(payload) = payload else {
|
||||
return Err(HttpServerError::NoBytes);
|
||||
};
|
||||
let bytes = payload.bytes;
|
||||
|
||||
let mut ws_map = websockets.lock().await;
|
||||
let send_text = is_text.unwrap_or(false);
|
||||
let response_data = if send_text {
|
||||
warp::ws::Message::text(
|
||||
String::from_utf8(bytes.clone()).unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
warp::ws::Message::binary(bytes.clone())
|
||||
};
|
||||
|
||||
// Send to the proxy, if registered
|
||||
if let Some(channel_id) = target.id.clone() {
|
||||
let locked_proxies = ws_proxies.lock().await;
|
||||
|
||||
if let Some(proxy_nodes) = locked_proxies.get(&channel_id) {
|
||||
for proxy_node in proxy_nodes {
|
||||
let id: u64 = rand::random();
|
||||
let bytes_content = bytes.clone();
|
||||
|
||||
// Send a message to the encryptor
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: proxy_node.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!({ // this is the JSON to forward
|
||||
"WebSocketPush": {
|
||||
"target": {
|
||||
"node": our.clone(), // it's ultimately for us, but through the proxy
|
||||
"id": channel_id.clone(),
|
||||
},
|
||||
"is_text": send_text,
|
||||
}
|
||||
}).to_string()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: bytes_content,
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send to the websocket if registered
|
||||
if let Some(node_map) = ws_map.get_mut(&target.node) {
|
||||
if let Some(socket_id) = &target.id {
|
||||
if let Some(ws_map) = node_map.get_mut(socket_id) {
|
||||
// Iterate over ws_map values and send message to all websockets
|
||||
for ws in ws_map.values_mut() {
|
||||
let mut locked_write_stream = ws.lock().await;
|
||||
let _ = locked_write_stream
|
||||
.send(response_data.clone())
|
||||
.await; // TODO: change this to binary
|
||||
}
|
||||
} else {
|
||||
// Send to all websockets
|
||||
for ws_map in node_map.values_mut() {
|
||||
for ws in ws_map.values_mut() {
|
||||
let mut locked_write_stream = ws.lock().await;
|
||||
let _ = locked_write_stream
|
||||
.send(response_data.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Send to all websockets
|
||||
for ws_map in node_map.values_mut() {
|
||||
for ws in ws_map.values_mut() {
|
||||
let mut locked_write_stream = ws.lock().await;
|
||||
let _ = locked_write_stream
|
||||
.send(response_data.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Do nothing because we don't have a WS for that node
|
||||
}
|
||||
}
|
||||
HttpServerMessage::ServerAction(ServerAction { action }) => {
|
||||
if action == "get-jwt-secret" && source.node == our {
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: source,
|
||||
rsvp: Some(Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
}),
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "set-jwt-secret"
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()), // TODO adjust MIME type as needed
|
||||
bytes: jwt_secret_bytes.clone(),
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
}
|
||||
}
|
||||
HttpServerMessage::WsRegister(WsRegister {
|
||||
auth_token,
|
||||
ws_auth_token: _,
|
||||
channel_id,
|
||||
}) => {
|
||||
if let Ok(_node) =
|
||||
parse_auth_token(auth_token, jwt_secret_bytes.clone().to_vec())
|
||||
{
|
||||
add_ws_proxy(ws_proxies.clone(), channel_id, source.node.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
HttpServerMessage::WsProxyDisconnect(WsProxyDisconnect { channel_id }) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("WsDisconnect"),
|
||||
})
|
||||
.await;
|
||||
// Check the ws_proxies for this channel_id, if it exists, delete the node that forwarded
|
||||
let mut locked_proxies = ws_proxies.lock().await;
|
||||
if let Some(proxy_nodes) = locked_proxies.get_mut(&channel_id) {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("disconnected"),
|
||||
})
|
||||
.await;
|
||||
proxy_nodes.remove(&source.node);
|
||||
}
|
||||
}
|
||||
HttpServerMessage::WsMessage(WsMessage {
|
||||
auth_token,
|
||||
ws_auth_token: _,
|
||||
channel_id,
|
||||
target,
|
||||
json,
|
||||
}) => {
|
||||
if let Ok(_node) =
|
||||
parse_auth_token(auth_token, jwt_secret_bytes.clone().to_vec())
|
||||
{
|
||||
add_ws_proxy(ws_proxies.clone(), channel_id, source.node.clone())
|
||||
.await;
|
||||
|
||||
handle_ws_message(
|
||||
target.clone(),
|
||||
json.clone(),
|
||||
our.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
HttpServerMessage::EncryptedWsMessage(EncryptedWsMessage {
|
||||
auth_token,
|
||||
ws_auth_token: _,
|
||||
channel_id,
|
||||
target,
|
||||
encrypted,
|
||||
nonce,
|
||||
}) => {
|
||||
if let Ok(_node) =
|
||||
parse_auth_token(auth_token, jwt_secret_bytes.clone().to_vec())
|
||||
{
|
||||
add_ws_proxy(
|
||||
ws_proxies.clone(),
|
||||
channel_id.clone(),
|
||||
source.node.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
handle_encrypted_ws_message(
|
||||
target.clone(),
|
||||
our.clone(),
|
||||
channel_id.clone(),
|
||||
encrypted.clone(),
|
||||
nonce.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: add a way to register a websocket connection (should be a Vector of websockets)
|
||||
// Then forward websocket messages to the correct place
|
||||
async fn http_serve(
|
||||
our: String,
|
||||
our_port: u16,
|
||||
http_response_senders: HttpResponseSenders,
|
||||
websockets: WebSockets,
|
||||
jwt_secret_bytes: Vec<u8>,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender,
|
||||
) {
|
||||
let cloned_msg_tx = send_to_loop.clone();
|
||||
let cloned_print_tx = print_tx.clone();
|
||||
let cloned_our = our.clone();
|
||||
let cloned_jwt_secret_bytes = jwt_secret_bytes.clone();
|
||||
let ws_route = warp::path::end()
|
||||
.and(warp::ws())
|
||||
.and(warp::any().map(move || cloned_our.clone()))
|
||||
.and(warp::any().map(move || cloned_jwt_secret_bytes.clone()))
|
||||
.and(warp::any().map(move || websockets.clone()))
|
||||
.and(warp::any().map(move || cloned_msg_tx.clone()))
|
||||
.and(warp::any().map(move || cloned_print_tx.clone()))
|
||||
.map(
|
||||
|ws_connection: Ws,
|
||||
our: String,
|
||||
jwt_secret_bytes: Vec<u8>,
|
||||
websockets: WebSockets,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender| {
|
||||
ws_connection.on_upgrade(move |ws: WebSocket| async move {
|
||||
handle_websocket(
|
||||
ws,
|
||||
our,
|
||||
jwt_secret_bytes,
|
||||
websockets,
|
||||
send_to_loop,
|
||||
print_tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let print_tx_move = print_tx.clone();
|
||||
let filter = warp::filters::method::method()
|
||||
.and(warp::addr::remote())
|
||||
.and(warp::path::full())
|
||||
.and(warp::filters::header::headers_cloned())
|
||||
.and(
|
||||
warp::filters::query::raw()
|
||||
.or(warp::any().map(|| String::default()))
|
||||
.unify()
|
||||
.map(|query_string: String| {
|
||||
if query_string.is_empty() {
|
||||
HashMap::new()
|
||||
} else {
|
||||
match serde_urlencoded::from_str(&query_string) {
|
||||
Ok(map) => map,
|
||||
Err(_) => HashMap::new(),
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
.and(warp::filters::body::bytes())
|
||||
.and(warp::any().map(move || our.clone()))
|
||||
.and(warp::any().map(move || http_response_senders.clone()))
|
||||
.and(warp::any().map(move || send_to_loop.clone()))
|
||||
.and(warp::any().map(move || print_tx_move.clone()))
|
||||
.and_then(handler);
|
||||
|
||||
let filter_with_ws = ws_route.or(filter);
|
||||
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("http_server: running on: {}", our_port),
|
||||
})
|
||||
.await;
|
||||
warp::serve(filter_with_ws)
|
||||
.run(([0, 0, 0, 0], our_port))
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handler(
|
||||
method: warp::http::Method,
|
||||
address: Option<SocketAddr>,
|
||||
path: warp::path::FullPath,
|
||||
headers: warp::http::HeaderMap,
|
||||
query_params: HashMap<String, String>,
|
||||
body: warp::hyper::body::Bytes,
|
||||
our: String,
|
||||
http_response_senders: HttpResponseSenders,
|
||||
send_to_loop: MessageSender,
|
||||
_print_tx: PrintSender,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
let address = match address {
|
||||
Some(a) => a.to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
let path_str = path.as_str().to_string();
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_bindings".into()),
|
||||
},
|
||||
rsvp: Some(Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
}),
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: Some(30), // TODO evaluate timeout
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"action": "request".to_string(),
|
||||
"address": address,
|
||||
"method": method.to_string(),
|
||||
"path": path_str.clone(),
|
||||
"headers": serialize_headers(&headers),
|
||||
"query_params": query_params,
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()), // TODO adjust MIME type as needed
|
||||
bytes: body.to_vec(),
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
let (response_sender, response_receiver) = oneshot::channel();
|
||||
http_response_senders
|
||||
.lock()
|
||||
.await
|
||||
.insert(id, (path_str, response_sender));
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
let from_channel = response_receiver.await.unwrap();
|
||||
let reply = warp::reply::with_status(
|
||||
match from_channel.body {
|
||||
Some(val) => val,
|
||||
None => vec![],
|
||||
},
|
||||
StatusCode::from_u16(from_channel.status).unwrap(),
|
||||
);
|
||||
let mut response = reply.into_response();
|
||||
|
||||
// Merge the deserialized headers into the existing headers
|
||||
let existing_headers = response.headers_mut();
|
||||
for (header_name, header_value) in deserialize_headers(from_channel.headers).iter() {
|
||||
if header_name == "set-cookie" || header_name == "Set-Cookie" {
|
||||
if let Ok(cookie) = header_value.to_str() {
|
||||
let cookie_headers: Vec<&str> = cookie
|
||||
.split("; ")
|
||||
.filter(|&cookie| !cookie.is_empty())
|
||||
.collect();
|
||||
for cookie_header in cookie_headers {
|
||||
if let Ok(valid_cookie) = HeaderValue::from_str(cookie_header) {
|
||||
existing_headers.append(header_name, valid_cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
existing_headers.insert(header_name.clone(), header_value.clone());
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn find_open_port(start_at: u16) -> Option<u16> {
|
||||
for port in start_at..=u16::MAX {
|
||||
let bind_addr = format!("0.0.0.0:{}", port);
|
||||
if is_port_available(&bind_addr).await {
|
||||
return Some(port);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
468
src/http_server/server_fns.rs
Normal file
468
src/http_server/server_fns.rs
Normal file
@ -0,0 +1,468 @@
|
||||
use crate::types::*;
|
||||
use futures::stream::SplitSink;
|
||||
use hmac::{Hmac, Mac};
|
||||
use jwt::{Error, VerifyWithKey};
|
||||
use sha2::Sha256;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::Mutex;
|
||||
use warp::http::{header::HeaderName, header::HeaderValue, HeaderMap};
|
||||
use warp::ws::WebSocket;
|
||||
|
||||
pub type SharedWriteStream = Arc<Mutex<SplitSink<WebSocket, warp::ws::Message>>>;
|
||||
pub type WebSockets = Arc<Mutex<HashMap<String, HashMap<String, HashMap<u64, SharedWriteStream>>>>>;
|
||||
pub type WebSocketProxies = Arc<Mutex<HashMap<String, HashSet<String>>>>;
|
||||
|
||||
pub fn parse_auth_token(auth_token: String, jwt_secret: Vec<u8>) -> Result<String, Error> {
|
||||
let secret: Hmac<Sha256> = match Hmac::new_from_slice(&jwt_secret.as_slice()) {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => {
|
||||
return Ok("Error recovering jwt secret".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let claims: Result<JwtClaims, Error> = auth_token.verify_with_key(&secret);
|
||||
|
||||
match claims {
|
||||
Ok(data) => Ok(data.username),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_incoming_ws(
|
||||
parsed_msg: WebSocketClientMessage,
|
||||
our: String,
|
||||
jwt_secret_bytes: Vec<u8>,
|
||||
websockets: WebSockets,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender,
|
||||
write_stream: SharedWriteStream,
|
||||
ws_id: u64,
|
||||
) {
|
||||
let cloned_parsed_msg = parsed_msg.clone();
|
||||
match parsed_msg {
|
||||
WebSocketClientMessage::WsRegister(WsRegister {
|
||||
ws_auth_token,
|
||||
auth_token: _,
|
||||
channel_id,
|
||||
}) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("REGISTER: {} {}", ws_auth_token, channel_id),
|
||||
})
|
||||
.await;
|
||||
// Get node from auth token
|
||||
if let Ok(node) = parse_auth_token(ws_auth_token, jwt_secret_bytes.clone()) {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("NODE: {}", node),
|
||||
})
|
||||
.await;
|
||||
handle_ws_register(
|
||||
node,
|
||||
cloned_parsed_msg,
|
||||
channel_id.clone(),
|
||||
our.clone(),
|
||||
websockets.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
write_stream.clone(),
|
||||
ws_id.clone(),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("Auth token parsing failed for WsRegister"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
// Forward to target's http_server with the auth_token
|
||||
WebSocketClientMessage::WsMessage(WsMessage {
|
||||
ws_auth_token,
|
||||
auth_token: _,
|
||||
target,
|
||||
json,
|
||||
..
|
||||
}) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ACTION: {}", target.node.clone()),
|
||||
})
|
||||
.await;
|
||||
// TODO: restrict sending actions to ourself and nodes for which we are proxying
|
||||
// TODO: use the channel_id
|
||||
if let Ok(node) = parse_auth_token(ws_auth_token, jwt_secret_bytes.clone()) {
|
||||
if node == target.node {
|
||||
if target.node == our {
|
||||
handle_ws_message(
|
||||
target.clone(),
|
||||
json.clone(),
|
||||
our.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
proxy_ws_message(
|
||||
node,
|
||||
cloned_parsed_msg,
|
||||
our.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Forward to target's http_server with the auth_token
|
||||
WebSocketClientMessage::EncryptedWsMessage(EncryptedWsMessage {
|
||||
ws_auth_token,
|
||||
auth_token: _,
|
||||
channel_id,
|
||||
target,
|
||||
encrypted,
|
||||
nonce,
|
||||
}) => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("ENCRYPTED ACTION: {}", target.node.clone()),
|
||||
})
|
||||
.await;
|
||||
if let Ok(node) = parse_auth_token(ws_auth_token, jwt_secret_bytes.clone()) {
|
||||
if node == target.node {
|
||||
if target.node == our {
|
||||
handle_encrypted_ws_message(
|
||||
target.clone(),
|
||||
our.clone(),
|
||||
channel_id.clone(),
|
||||
encrypted.clone(),
|
||||
nonce.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
proxy_ws_message(
|
||||
node,
|
||||
cloned_parsed_msg,
|
||||
our.clone(),
|
||||
send_to_loop.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_headers(headers: &HeaderMap) -> HashMap<String, String> {
|
||||
let mut hashmap = HashMap::new();
|
||||
for (key, value) in headers.iter() {
|
||||
let key_str = key.to_string();
|
||||
let value_str = value.to_str().unwrap_or("").to_string();
|
||||
hashmap.insert(key_str, value_str);
|
||||
}
|
||||
hashmap
|
||||
}
|
||||
|
||||
pub fn deserialize_headers(hashmap: HashMap<String, String>) -> HeaderMap {
|
||||
let mut header_map = HeaderMap::new();
|
||||
for (key, value) in hashmap {
|
||||
let key_bytes = key.as_bytes();
|
||||
let key_name = HeaderName::from_bytes(key_bytes).unwrap();
|
||||
let value_header = HeaderValue::from_str(&value).unwrap();
|
||||
header_map.insert(key_name, value_header);
|
||||
}
|
||||
header_map
|
||||
}
|
||||
|
||||
pub async fn is_port_available(bind_addr: &str) -> bool {
|
||||
match TcpListener::bind(bind_addr).await {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binary_encoded_string_to_bytes(s: &str) -> Vec<u8> {
|
||||
s.chars().map(|c| c as u8).collect()
|
||||
}
|
||||
|
||||
pub async fn handle_ws_register(
|
||||
node: String,
|
||||
parsed_msg: WebSocketClientMessage,
|
||||
channel_id: String,
|
||||
our: String,
|
||||
websockets: WebSockets,
|
||||
send_to_loop: MessageSender,
|
||||
print_tx: PrintSender,
|
||||
write_stream: SharedWriteStream,
|
||||
ws_id: u64,
|
||||
) {
|
||||
// let _ = print_tx.send(Printout { verbosity: 1, content: format!("1.2 {}", node) }).await;
|
||||
// TODO: restrict registration to ourself and nodes for which we are proxying
|
||||
let mut ws_map = websockets.lock().await;
|
||||
let node_map = ws_map.entry(node.clone()).or_insert(HashMap::new());
|
||||
let id_map = node_map.entry(channel_id.clone()).or_insert(HashMap::new());
|
||||
id_map.insert(ws_id.clone(), write_stream.clone());
|
||||
|
||||
// Send a message to the target node to add to let it know we are proxying
|
||||
if node != our {
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: node.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!(parsed_msg).to_string()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: vec![],
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("WEBSOCKET CHANNEL FORWARDED!"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 1,
|
||||
content: format!("WEBSOCKET CHANNEL REGISTERED!"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn handle_ws_message(
|
||||
target: Address,
|
||||
json: Option<serde_json::Value>,
|
||||
our: String,
|
||||
send_to_loop: MessageSender,
|
||||
_print_tx: PrintSender,
|
||||
) {
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: target.clone(),
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: None,
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: json.unwrap_or_default().to_string().as_bytes().to_vec(),
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn handle_encrypted_ws_message(
|
||||
target: Address,
|
||||
our: String,
|
||||
channel_id: String,
|
||||
encrypted: String,
|
||||
nonce: String,
|
||||
send_to_loop: MessageSender,
|
||||
_print_tx: PrintSender,
|
||||
) {
|
||||
let encrypted_bytes = binary_encoded_string_to_bytes(&encrypted);
|
||||
let nonce_bytes = binary_encoded_string_to_bytes(&nonce);
|
||||
|
||||
let mut encrypted_data = encrypted_bytes;
|
||||
encrypted_data.extend(nonce_bytes);
|
||||
|
||||
let id: u64 = rand::random();
|
||||
|
||||
// Send a message to the encryptor
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: target.node.clone(),
|
||||
process: ProcessId::Name("encryptor".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"DecryptAndForwardAction": {
|
||||
"channel_id": channel_id.clone(),
|
||||
"forward_to": target.clone(),
|
||||
"json": {
|
||||
"forwarded_from": {
|
||||
"node": our.clone(),
|
||||
"process": "http_server",
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: encrypted_data,
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn proxy_ws_message(
|
||||
node: String,
|
||||
parsed_msg: WebSocketClientMessage,
|
||||
our: String,
|
||||
send_to_loop: MessageSender,
|
||||
_print_tx: PrintSender,
|
||||
) {
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: Address {
|
||||
node,
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::json!(parsed_msg).to_string()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: vec![],
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn add_ws_proxy(ws_proxies: WebSocketProxies, channel_id: String, source_node: String) {
|
||||
let mut locked_proxies = ws_proxies.lock().await;
|
||||
if let Some(proxy_nodes) = locked_proxies.get_mut(&channel_id) {
|
||||
if !proxy_nodes.contains(&source_node) {
|
||||
proxy_nodes.insert(source_node);
|
||||
}
|
||||
} else {
|
||||
let mut proxy_nodes = HashSet::new();
|
||||
proxy_nodes.insert(source_node);
|
||||
locked_proxies.insert(channel_id, proxy_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_ws_disconnect(
|
||||
node: String,
|
||||
our: String,
|
||||
channel_id: String,
|
||||
send_to_loop: MessageSender,
|
||||
_print_tx: PrintSender,
|
||||
) {
|
||||
let id: u64 = rand::random();
|
||||
let message = KernelMessage {
|
||||
id: id.clone(),
|
||||
source: Address {
|
||||
node: our.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: node.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(
|
||||
serde_json::json!({
|
||||
"WsProxyDisconnect": {
|
||||
"channel_id": channel_id.clone(),
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: Some(Payload {
|
||||
mime: Some("application/octet-stream".to_string()),
|
||||
bytes: vec![],
|
||||
}),
|
||||
signed_capabilities: None,
|
||||
};
|
||||
|
||||
send_to_loop.send(message).await.unwrap();
|
||||
}
|
||||
|
||||
pub fn make_error_message(
|
||||
our_name: String,
|
||||
id: u64,
|
||||
target: Address,
|
||||
error: HttpServerError,
|
||||
) -> KernelMessage {
|
||||
KernelMessage {
|
||||
id,
|
||||
source: Address {
|
||||
node: our_name.clone(),
|
||||
process: ProcessId::Name("http_server".into()),
|
||||
},
|
||||
target,
|
||||
rsvp: None,
|
||||
message: Message::Response((
|
||||
Response {
|
||||
ipc: Some(serde_json::to_string(&error).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
)),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
}
|
||||
}
|
1745
src/kernel/mod.rs
Normal file
1745
src/kernel/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
150
src/kernel/utils.rs
Normal file
150
src/kernel/utils.rs
Normal file
@ -0,0 +1,150 @@
|
||||
use crate::kernel::component::uq_process::types as wit;
|
||||
use crate::types as t;
|
||||
|
||||
//
|
||||
// conversions between wit types and kernel types (annoying)
|
||||
//
|
||||
|
||||
pub fn en_wit_process_id(process_id: t::ProcessId) -> wit::ProcessId {
|
||||
match process_id {
|
||||
t::ProcessId::Id(id) => wit::ProcessId::Id(id),
|
||||
t::ProcessId::Name(name) => wit::ProcessId::Name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_process_id(process_id: wit::ProcessId) -> t::ProcessId {
|
||||
match process_id {
|
||||
wit::ProcessId::Id(id) => t::ProcessId::Id(id),
|
||||
wit::ProcessId::Name(name) => t::ProcessId::Name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_address(address: t::Address) -> wit::Address {
|
||||
wit::Address {
|
||||
node: address.node,
|
||||
process: match address.process {
|
||||
t::ProcessId::Id(id) => wit::ProcessId::Id(id),
|
||||
t::ProcessId::Name(name) => wit::ProcessId::Name(name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_address(wit: wit::Address) -> t::Address {
|
||||
t::Address {
|
||||
node: wit.node,
|
||||
process: match wit.process {
|
||||
wit::ProcessId::Id(id) => t::ProcessId::Id(id),
|
||||
wit::ProcessId::Name(name) => t::ProcessId::Name(name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_message(message: t::Message) -> wit::Message {
|
||||
match message {
|
||||
t::Message::Request(request) => wit::Message::Request(en_wit_request(request)),
|
||||
t::Message::Response((response, context)) => {
|
||||
wit::Message::Response((en_wit_response(response), context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_request(wit: wit::Request) -> t::Request {
|
||||
t::Request {
|
||||
inherit: wit.inherit,
|
||||
expects_response: wit.expects_response,
|
||||
ipc: wit.ipc,
|
||||
metadata: wit.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_request(request: t::Request) -> wit::Request {
|
||||
wit::Request {
|
||||
inherit: request.inherit,
|
||||
expects_response: request.expects_response,
|
||||
ipc: request.ipc,
|
||||
metadata: request.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_response(wit: wit::Response) -> t::Response {
|
||||
t::Response {
|
||||
ipc: wit.ipc,
|
||||
metadata: wit.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_response(response: t::Response) -> wit::Response {
|
||||
wit::Response {
|
||||
ipc: response.ipc,
|
||||
metadata: response.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_send_error(error: t::SendError) -> wit::SendError {
|
||||
wit::SendError {
|
||||
kind: en_wit_send_error_kind(error.kind),
|
||||
message: en_wit_message(error.message),
|
||||
payload: en_wit_payload(error.payload),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_send_error_kind(kind: t::SendErrorKind) -> wit::SendErrorKind {
|
||||
match kind {
|
||||
t::SendErrorKind::Offline => wit::SendErrorKind::Offline,
|
||||
t::SendErrorKind::Timeout => wit::SendErrorKind::Timeout,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_payload(wit: Option<wit::Payload>) -> Option<t::Payload> {
|
||||
match wit {
|
||||
None => None,
|
||||
Some(wit) => Some(t::Payload {
|
||||
mime: wit.mime,
|
||||
bytes: wit.bytes,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_payload(payload: Option<t::Payload>) -> Option<wit::Payload> {
|
||||
match payload {
|
||||
None => None,
|
||||
Some(payload) => Some(wit::Payload {
|
||||
mime: payload.mime,
|
||||
bytes: payload.bytes,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_signed_capability(wit: wit::SignedCapability) -> t::SignedCapability {
|
||||
t::SignedCapability {
|
||||
issuer: de_wit_address(wit.issuer),
|
||||
params: wit.params,
|
||||
signature: wit.signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_signed_capability(cap: t::SignedCapability) -> wit::SignedCapability {
|
||||
wit::SignedCapability {
|
||||
issuer: en_wit_address(cap.issuer),
|
||||
params: cap.params,
|
||||
signature: cap.signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_on_panic(wit: wit::OnPanic) -> t::OnPanic {
|
||||
match wit {
|
||||
wit::OnPanic::None => t::OnPanic::None,
|
||||
wit::OnPanic::Restart => t::OnPanic::Restart,
|
||||
wit::OnPanic::Requests(reqs) => t::OnPanic::Requests(
|
||||
reqs.into_iter()
|
||||
.map(|(address, request, payload)| {
|
||||
(
|
||||
de_wit_address(address),
|
||||
de_wit_request(request),
|
||||
de_wit_payload(payload),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
215
src/kernel_types.rs
Normal file
215
src/kernel_types.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types as wit;
|
||||
|
||||
//
|
||||
// process-facing kernel types, used for process
|
||||
// management and message-passing
|
||||
// matches types in uqbar.wit
|
||||
//
|
||||
|
||||
pub type Context = String; // JSON-string
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ProcessId {
|
||||
Id(u64),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(s) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Address {
|
||||
pub node: String,
|
||||
pub process: ProcessId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Payload {
|
||||
pub mime: Option<String>, // MIME type
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub inherit: bool,
|
||||
pub expects_response: Option<u64>,
|
||||
pub ipc: Option<String>, // JSON-string
|
||||
pub metadata: Option<String>, // JSON-string
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Response {
|
||||
pub ipc: Option<String>, // JSON-string
|
||||
pub metadata: Option<String>, // JSON-string
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Message {
|
||||
Request(Request),
|
||||
Response((Response, Option<Context>)),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Capability {
|
||||
pub issuer: Address,
|
||||
pub params: String, // JSON-string
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SignedCapability {
|
||||
pub issuer: Address,
|
||||
pub params: String, // JSON-string
|
||||
pub signature: Vec<u8>, // signed by the kernel, so we can verify that the kernel issued it
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SendError {
|
||||
pub kind: SendErrorKind,
|
||||
pub target: Address,
|
||||
pub message: Message,
|
||||
pub payload: Option<Payload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum SendErrorKind {
|
||||
Offline,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OnPanic {
|
||||
None,
|
||||
Restart,
|
||||
Requests(Vec<(Address, Request)>),
|
||||
}
|
||||
|
||||
//
|
||||
// conversions between wit types and kernel types (annoying)
|
||||
//
|
||||
|
||||
pub fn en_wit_process_id(process_id: ProcessId) -> wit::ProcessId {
|
||||
match process_id {
|
||||
ProcessId::Id(id) => wit::ProcessId::Id(id),
|
||||
ProcessId::Name(name) => wit::ProcessId::Name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_process_id(wit: wit::ProcessId) -> ProcessId {
|
||||
match wit {
|
||||
wit::ProcessId::Id(id) => ProcessId::Id(id),
|
||||
wit::ProcessId::Name(name) => ProcessId::Name(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_address(address: Address) -> wit::Address {
|
||||
wit::Address {
|
||||
node: address.node,
|
||||
process: match address.process {
|
||||
ProcessId::Id(id) => wit::ProcessId::Id(id),
|
||||
ProcessId::Name(name) => wit::ProcessId::Name(name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_address(wit: wit::Address) -> Address {
|
||||
Address {
|
||||
node: wit.node,
|
||||
process: match wit.process {
|
||||
wit::ProcessId::Id(id) => ProcessId::Id(id),
|
||||
wit::ProcessId::Name(name) => ProcessId::Name(name),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_request(wit: wit::Request) -> Request {
|
||||
Request {
|
||||
inherit: wit.inherit,
|
||||
expects_response: wit.expects_response,
|
||||
ipc: wit.ipc,
|
||||
metadata: wit.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_request(request: Request) -> wit::Request {
|
||||
wit::Request {
|
||||
inherit: request.inherit,
|
||||
expects_response: request.expects_response,
|
||||
ipc: request.ipc,
|
||||
metadata: request.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_response(wit: wit::Response) -> Response {
|
||||
Response {
|
||||
ipc: wit.ipc,
|
||||
metadata: wit.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_response(response: Response) -> wit::Response {
|
||||
wit::Response {
|
||||
ipc: response.ipc,
|
||||
metadata: response.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_payload(wit: Option<wit::Payload>) -> Option<Payload> {
|
||||
match wit {
|
||||
None => None,
|
||||
Some(wit) => Some(Payload {
|
||||
mime: wit.mime,
|
||||
bytes: wit.bytes,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_payload(load: Option<Payload>) -> Option<wit::Payload> {
|
||||
match load {
|
||||
None => None,
|
||||
Some(load) => Some(wit::Payload {
|
||||
mime: load.mime,
|
||||
bytes: load.bytes,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn de_wit_signed_capability(wit: wit::SignedCapability) -> SignedCapability {
|
||||
SignedCapability {
|
||||
issuer: de_wit_address(wit.issuer),
|
||||
params: wit.params,
|
||||
signature: wit.signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn en_wit_signed_capability(cap: SignedCapability) -> wit::SignedCapability {
|
||||
wit::SignedCapability {
|
||||
issuer: en_wit_address(cap.issuer),
|
||||
params: cap.params,
|
||||
signature: cap.signature,
|
||||
}
|
||||
}
|
147
src/keygen.rs
Normal file
147
src/keygen.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use aes_gcm::{
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
Aes256Gcm, Key,
|
||||
};
|
||||
use digest::generic_array;
|
||||
use lazy_static::__Deref;
|
||||
use ring::pbkdf2;
|
||||
use ring::pkcs8::Document;
|
||||
use ring::rand::SystemRandom;
|
||||
use ring::signature::{self, KeyPair};
|
||||
use ring::{digest as ring_digest, rand::SecureRandom};
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
type DiskKey = [u8; CREDENTIAL_LEN];
|
||||
|
||||
pub const CREDENTIAL_LEN: usize = ring_digest::SHA256_OUTPUT_LEN;
|
||||
pub const ITERATIONS: u32 = 1_000_000;
|
||||
pub static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256; // TODO maybe look into Argon2
|
||||
|
||||
pub fn encode_keyfile(
|
||||
password: String,
|
||||
username: String,
|
||||
routers: Vec<String>,
|
||||
networking_key: Document,
|
||||
jwt: Vec<u8>,
|
||||
file_key: Vec<u8>,
|
||||
) -> Vec<u8> {
|
||||
println!("generating disk encryption keys...");
|
||||
let mut disk_key: DiskKey = [0u8; CREDENTIAL_LEN];
|
||||
|
||||
let rng = SystemRandom::new();
|
||||
|
||||
let mut salt = [0u8; 32]; // generate a unique salt
|
||||
rng.fill(&mut salt).unwrap();
|
||||
|
||||
pbkdf2::derive(
|
||||
PBKDF2_ALG,
|
||||
NonZeroU32::new(ITERATIONS).unwrap(),
|
||||
&salt,
|
||||
password.as_bytes(),
|
||||
&mut disk_key,
|
||||
);
|
||||
|
||||
let key = Key::<Aes256Gcm>::from_slice(&disk_key);
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
|
||||
let network_nonce = Aes256Gcm::generate_nonce(&mut OsRng); // 96-bits; unique per message
|
||||
let jwt_nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let file_nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
|
||||
let keyciphertext: Vec<u8> = cipher
|
||||
.encrypt(&network_nonce, networking_key.as_ref())
|
||||
.unwrap();
|
||||
let jwtciphertext: Vec<u8> = cipher.encrypt(&jwt_nonce, jwt.as_ref()).unwrap();
|
||||
let fileciphertext: Vec<u8> = cipher.encrypt(&file_nonce, file_key.as_ref()).unwrap();
|
||||
|
||||
bincode::serialize(&(
|
||||
username.clone(),
|
||||
routers.clone(),
|
||||
salt.to_vec(),
|
||||
[network_nonce.deref().to_vec(), keyciphertext].concat(),
|
||||
[jwt_nonce.deref().to_vec(), jwtciphertext].concat(),
|
||||
[file_nonce.deref().to_vec(), fileciphertext].concat(),
|
||||
))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn decode_keyfile(
|
||||
keyfile: Vec<u8>,
|
||||
password: &str,
|
||||
) -> (
|
||||
String,
|
||||
Vec<String>,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
) {
|
||||
let (username, routers, salt, key_enc, jtw_enc, file_enc) =
|
||||
bincode::deserialize::<(String, Vec<String>, Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>)>(&keyfile)
|
||||
.unwrap();
|
||||
|
||||
// rederive disk key
|
||||
let mut disk_key: DiskKey = [0u8; CREDENTIAL_LEN];
|
||||
pbkdf2::derive(
|
||||
PBKDF2_ALG,
|
||||
NonZeroU32::new(ITERATIONS).unwrap(),
|
||||
&salt,
|
||||
password.as_bytes(),
|
||||
&mut disk_key,
|
||||
);
|
||||
|
||||
let cipher_key = Key::<Aes256Gcm>::from_slice(&disk_key);
|
||||
let cipher = Aes256Gcm::new(&cipher_key);
|
||||
|
||||
let net_nonce = generic_array::GenericArray::from_slice(&key_enc[..12]);
|
||||
let jwt_nonce = generic_array::GenericArray::from_slice(&jtw_enc[..12]);
|
||||
let file_nonce = generic_array::GenericArray::from_slice(&file_enc[..12]);
|
||||
|
||||
println!("decrypting saved networking key...");
|
||||
let serialized_networking_keypair: Vec<u8> = match cipher.decrypt(net_nonce, &key_enc[12..]) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
panic!("failed to decrypt networking keys: {}", e);
|
||||
}
|
||||
};
|
||||
let networking_keypair =
|
||||
match signature::Ed25519KeyPair::from_pkcs8(&serialized_networking_keypair) {
|
||||
Ok(k) => k,
|
||||
Err(_) => panic!("failed to parse networking keys"),
|
||||
};
|
||||
|
||||
// TODO: check if jwt_secret_file is valid and then proceed to unwrap and decrypt. If there is a failure, generate a new jwt_secret and save it
|
||||
// use password to decrypt jwt secret
|
||||
println!("decrypting saved jwt secret...");
|
||||
|
||||
let jwt: Vec<u8> = match cipher.decrypt(jwt_nonce, &jtw_enc[12..]) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
panic!("failed to decrypt jwt secret: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
let file_key: Vec<u8> = match cipher.decrypt(file_nonce, &file_enc[12..]) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
panic!("failed to decrypt file key: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
(username, routers, networking_keypair, jwt, file_key)
|
||||
}
|
||||
|
||||
/// # Returns
|
||||
/// a pair of (public key (encoded as a hex string), serialized key as a pkcs8 Document)
|
||||
pub fn generate_networking_key() -> (String, Document) {
|
||||
let seed = SystemRandom::new();
|
||||
let doc = signature::Ed25519KeyPair::generate_pkcs8(&seed).unwrap();
|
||||
let keys = signature::Ed25519KeyPair::from_pkcs8(doc.as_ref()).unwrap();
|
||||
(hex::encode(keys.public_key().as_ref()), doc)
|
||||
}
|
||||
/// randomly generated key to encrypt file chunks, encrypted on-disk with disk_key
|
||||
pub fn generate_file_key() -> Vec<u8> {
|
||||
let mut key = [0u8; 32];
|
||||
let rng = SystemRandom::new();
|
||||
rng.fill(&mut key).unwrap();
|
||||
key.to_vec()
|
||||
}
|
320
src/login.html
Normal file
320
src/login.html
Normal file
File diff suppressed because one or more lines are too long
537
src/main.rs
Normal file
537
src/main.rs
Normal file
@ -0,0 +1,537 @@
|
||||
use anyhow::Result;
|
||||
use dotenv;
|
||||
use ethers::prelude::{abigen, namehash, Address as EthAddress, Provider, U256};
|
||||
use ethers_providers::Ws;
|
||||
use ring::pkcs8::Document;
|
||||
use ring::signature::{self, KeyPair};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::{fs, time::timeout};
|
||||
|
||||
use crate::types::*;
|
||||
|
||||
mod encryptor;
|
||||
mod eth_rpc;
|
||||
mod filesystem;
|
||||
mod http_client;
|
||||
mod http_server;
|
||||
mod kernel;
|
||||
mod keygen;
|
||||
mod net;
|
||||
mod register;
|
||||
mod terminal;
|
||||
mod types;
|
||||
mod vfs;
|
||||
|
||||
const EVENT_LOOP_CHANNEL_CAPACITY: usize = 10_000;
|
||||
const EVENT_LOOP_DEBUG_CHANNEL_CAPACITY: usize = 50;
|
||||
const TERMINAL_CHANNEL_CAPACITY: usize = 32;
|
||||
const WEBSOCKET_SENDER_CHANNEL_CAPACITY: usize = 32;
|
||||
const FILESYSTEM_CHANNEL_CAPACITY: usize = 32;
|
||||
const HTTP_CHANNEL_CAPACITY: usize = 32;
|
||||
const HTTP_CLIENT_CHANNEL_CAPACITY: usize = 32;
|
||||
const ETH_RPC_CHANNEL_CAPACITY: usize = 32;
|
||||
const VFS_CHANNEL_CAPACITY: usize = 1_000;
|
||||
const ENCRYPTOR_CHANNEL_CAPACITY: usize = 32;
|
||||
|
||||
const QNS_SEPOLIA_ADDRESS: &str = "0x9e5ed0e7873E0d7f10eEb6dE72E87fE087A12776";
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
abigen!(QNSRegistry, "src/QNSRegistry.json");
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// For use with https://github.com/tokio-rs/console
|
||||
// console_subscriber::init();
|
||||
|
||||
// DEMO ONLY: remove all CLI arguments
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let home_directory_path = &args[1];
|
||||
// let home_directory_path = "home";
|
||||
// create home directory if it does not already exist
|
||||
if let Err(e) = fs::create_dir_all(home_directory_path).await {
|
||||
panic!("failed to create home directory: {:?}", e);
|
||||
}
|
||||
// read PKI from websocket endpoint served by public RPC
|
||||
// if you get rate-limited or something, pass in your own RPC as a boot argument
|
||||
let mut rpc_url = "wss://eth-sepolia.public.blastapi.io".to_string();
|
||||
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if arg == "--rpc" {
|
||||
// Check if the next argument exists and is not another flag
|
||||
if i + 1 < args.len() && !args[i + 1].starts_with('-') {
|
||||
rpc_url = args[i + 1].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// kernel receives system messages via this channel, all other modules send messages
|
||||
let (kernel_message_sender, kernel_message_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(EVENT_LOOP_CHANNEL_CAPACITY);
|
||||
// kernel informs other runtime modules of capabilities through this
|
||||
let (caps_oracle_sender, caps_oracle_receiver) = mpsc::unbounded_channel::<CapMessage>();
|
||||
// networking module sends error messages to kernel
|
||||
let (network_error_sender, network_error_receiver): (NetworkErrorSender, NetworkErrorReceiver) =
|
||||
mpsc::channel(EVENT_LOOP_CHANNEL_CAPACITY);
|
||||
// kernel receives debug messages via this channel, terminal sends messages
|
||||
let (kernel_debug_message_sender, kernel_debug_message_receiver): (DebugSender, DebugReceiver) =
|
||||
mpsc::channel(EVENT_LOOP_DEBUG_CHANNEL_CAPACITY);
|
||||
// websocket sender receives send messages via this channel, kernel send messages
|
||||
let (net_message_sender, net_message_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(WEBSOCKET_SENDER_CHANNEL_CAPACITY);
|
||||
// filesystem receives request messages via this channel, kernel sends messages
|
||||
let (fs_message_sender, fs_message_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(FILESYSTEM_CHANNEL_CAPACITY.clone());
|
||||
// http server channel w/ websockets (eyre)
|
||||
let (http_server_sender, http_server_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(HTTP_CHANNEL_CAPACITY);
|
||||
// http client performs http requests on behalf of processes
|
||||
let (eth_rpc_sender, eth_rpc_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(ETH_RPC_CHANNEL_CAPACITY);
|
||||
let (http_client_sender, http_client_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(HTTP_CLIENT_CHANNEL_CAPACITY);
|
||||
// vfs maintains metadata about files in fs for processes
|
||||
let (vfs_message_sender, vfs_message_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(VFS_CHANNEL_CAPACITY);
|
||||
// encryptor handles end-to-end encryption for client messages
|
||||
let (encryptor_sender, encryptor_receiver): (MessageSender, MessageReceiver) =
|
||||
mpsc::channel(ENCRYPTOR_CHANNEL_CAPACITY);
|
||||
// terminal receives prints via this channel, all other modules send prints
|
||||
let (print_sender, print_receiver): (PrintSender, PrintReceiver) =
|
||||
mpsc::channel(TERMINAL_CHANNEL_CAPACITY);
|
||||
|
||||
// fs config in .env file (todo add -- arguments cleanly (with clap?))
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
let mem_buffer_limit = env::var("MEM_BUFFER_LIMIT")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(1024 * 1024 * 5); // 5mb default
|
||||
|
||||
let chunk_size = env::var("CHUNK_SIZE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(1024 * 256); // 256kb default
|
||||
|
||||
let flush_to_cold_interval = env::var("FLUSH_TO_COLD_INTERVAL")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(60); // 60s default
|
||||
|
||||
let encryption = env::var("ENCRYPTION")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(true); // default true
|
||||
|
||||
let cloud_enabled = env::var("CLOUD_ENABLED")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(false); // default false
|
||||
|
||||
let s3_config = if let (Ok(access_key), Ok(secret_key), Ok(region), Ok(bucket), Ok(endpoint)) = (
|
||||
env::var("S3_ACCESS_KEY"),
|
||||
env::var("S3_SECRET_KEY"),
|
||||
env::var("S3_REGION"),
|
||||
env::var("S3_BUCKET"),
|
||||
env::var("S3_ENDPOINT"),
|
||||
) {
|
||||
Some(S3Config {
|
||||
access_key,
|
||||
secret_key,
|
||||
region,
|
||||
bucket,
|
||||
endpoint,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let fs_config = FsConfig {
|
||||
s3_config,
|
||||
mem_buffer_limit,
|
||||
chunk_size,
|
||||
flush_to_cold_interval,
|
||||
encryption,
|
||||
cloud_enabled,
|
||||
};
|
||||
|
||||
// shutdown signal send and await to fs
|
||||
let (fs_kill_send, fs_kill_recv) = oneshot::channel::<()>();
|
||||
let (fs_kill_confirm_send, fs_kill_confirm_recv) = oneshot::channel::<()>();
|
||||
|
||||
println!("finding public IP address...");
|
||||
let our_ip = {
|
||||
if let Ok(Some(ip)) = timeout(std::time::Duration::from_secs(5), public_ip::addr_v4()).await
|
||||
{
|
||||
ip.to_string()
|
||||
} else {
|
||||
println!(
|
||||
"\x1b[38;5;196mfailed to find public IPv4 address: booting as a routed node\x1b[0m"
|
||||
);
|
||||
"localhost".into()
|
||||
}
|
||||
};
|
||||
|
||||
// check if we have keys saved on disk, encrypted
|
||||
// if so, prompt user for "password" to decrypt with
|
||||
|
||||
// once password is received, use to decrypt local keys file,
|
||||
// and pass the keys into boot process as is done in registration.
|
||||
|
||||
// NOTE: when we log in, we MUST check the PKI to make sure our
|
||||
// information matches what we think it should be. this includes
|
||||
// username, networking key, and routing info.
|
||||
// if any do not match, we should prompt user to create a "transaction"
|
||||
// that updates their PKI info on-chain.
|
||||
let http_server_port = http_server::find_open_port(8080).await.unwrap();
|
||||
let (kill_tx, kill_rx) = oneshot::channel::<bool>();
|
||||
let keyfile = fs::read(format!("{}/.keys", home_directory_path)).await;
|
||||
|
||||
let (our, networking_keypair, jwt_secret_bytes, file_key): (
|
||||
Identity,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
) = if keyfile.is_ok() {
|
||||
// LOGIN flow
|
||||
println!(
|
||||
"\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\",
|
||||
format!("http://localhost:{}/login", http_server_port),
|
||||
"Click here to log in to your node.",
|
||||
);
|
||||
println!("(http://localhost:{}/login)", http_server_port);
|
||||
if our_ip != "localhost" {
|
||||
println!(
|
||||
"(if on a remote machine: http://{}:{}/login)",
|
||||
our_ip, http_server_port
|
||||
);
|
||||
}
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<(
|
||||
String,
|
||||
Vec<String>,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
)>(1);
|
||||
let (username, routers, networking_keypair, jwt_secret_bytes, file_key) = tokio::select! {
|
||||
_ = register::login(
|
||||
tx,
|
||||
kill_rx,
|
||||
keyfile.unwrap(),
|
||||
http_server_port,
|
||||
) => panic!("login failed"),
|
||||
(username, routers, networking_keypair, jwt_secret_bytes, file_key) = async {
|
||||
while let Some(fin) = rx.recv().await {
|
||||
return fin
|
||||
}
|
||||
panic!("login failed")
|
||||
} => (username, routers, networking_keypair, jwt_secret_bytes, file_key),
|
||||
};
|
||||
|
||||
// check if Identity for this username has correct networking keys,
|
||||
// if not, prompt user to reset them.
|
||||
let Ok(ws_rpc) = Provider::<Ws>::connect(rpc_url.clone()).await else {
|
||||
panic!("rpc: couldn't connect to blockchain wss endpoint");
|
||||
};
|
||||
let qns_address: EthAddress = QNS_SEPOLIA_ADDRESS.parse().unwrap();
|
||||
let contract = QNSRegistry::new(qns_address, ws_rpc.into());
|
||||
let node_id: U256 = namehash(&username).as_bytes().into();
|
||||
let onchain_id = contract.ws(node_id).call().await.unwrap(); // TODO unwrap
|
||||
|
||||
// double check that routers match on-chain information
|
||||
let namehashed_routers: Vec<[u8; 32]> = routers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let hash = namehash(&name);
|
||||
let mut result = [0u8; 32];
|
||||
result.copy_from_slice(hash.as_bytes());
|
||||
result
|
||||
})
|
||||
.collect();
|
||||
|
||||
// double check that keys match on-chain information
|
||||
if onchain_id.routers != namehashed_routers
|
||||
|| onchain_id.public_key != networking_keypair.public_key().as_ref()
|
||||
// || (onchain_id.ip_and_port > 0 && onchain_id.ip_and_port != combineIpAndPort(
|
||||
// our_ip.clone(),
|
||||
// http_server_port,
|
||||
// ))
|
||||
{
|
||||
panic!("CRITICAL: your routing information does not match on-chain records");
|
||||
}
|
||||
|
||||
let our_identity = Identity {
|
||||
name: username.clone(),
|
||||
networking_key: format!(
|
||||
"0x{}",
|
||||
hex::encode(networking_keypair.public_key().as_ref())
|
||||
),
|
||||
ws_routing: if onchain_id.ip > 0 && onchain_id.port > 0 {
|
||||
let ip = format!(
|
||||
"{}.{}.{}.{}",
|
||||
(onchain_id.ip >> 24) & 0xFF,
|
||||
(onchain_id.ip >> 16) & 0xFF,
|
||||
(onchain_id.ip >> 8) & 0xFF,
|
||||
onchain_id.ip & 0xFF
|
||||
);
|
||||
Some((ip, onchain_id.port))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
allowed_routers: routers,
|
||||
};
|
||||
|
||||
(
|
||||
our_identity.clone(),
|
||||
networking_keypair,
|
||||
jwt_secret_bytes,
|
||||
file_key,
|
||||
)
|
||||
} else {
|
||||
// REGISTER flow
|
||||
println!(
|
||||
"\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\",
|
||||
format!("http://localhost:{}", http_server_port),
|
||||
"Click here to register your node.",
|
||||
);
|
||||
println!("(http://localhost:{})", http_server_port);
|
||||
if our_ip != "localhost" {
|
||||
println!(
|
||||
"(if on a remote machine: http://{}:{})",
|
||||
our_ip, http_server_port
|
||||
);
|
||||
}
|
||||
|
||||
let (tx, mut rx) = mpsc::channel::<(Identity, String, Document, Vec<u8>)>(1);
|
||||
let (mut our, password, serialized_networking_keypair, jwt_secret_bytes) = tokio::select! {
|
||||
_ = register::register(tx, kill_rx, our_ip.clone(), http_server_port, http_server_port)
|
||||
=> panic!("registration failed"),
|
||||
(our, password, serialized_networking_keypair, jwt_secret_bytes) = async {
|
||||
while let Some(fin) = rx.recv().await {
|
||||
return fin
|
||||
}
|
||||
panic!("registration failed")
|
||||
} => (our, password, serialized_networking_keypair, jwt_secret_bytes),
|
||||
};
|
||||
|
||||
println!(
|
||||
"saving encrypted networking keys to {}/.keys",
|
||||
home_directory_path
|
||||
);
|
||||
|
||||
let networking_keypair =
|
||||
signature::Ed25519KeyPair::from_pkcs8(serialized_networking_keypair.as_ref()).unwrap();
|
||||
|
||||
// TODO fix register frontend so this isn't necessary
|
||||
our.networking_key = format!("0x{}", our.networking_key);
|
||||
|
||||
let file_key = keygen::generate_file_key();
|
||||
|
||||
fs::write(
|
||||
format!("{}/.keys", home_directory_path),
|
||||
keygen::encode_keyfile(
|
||||
password,
|
||||
our.name.clone(),
|
||||
our.allowed_routers.clone(),
|
||||
serialized_networking_keypair,
|
||||
jwt_secret_bytes.clone(),
|
||||
file_key.clone(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("registration complete!");
|
||||
(
|
||||
our,
|
||||
networking_keypair,
|
||||
jwt_secret_bytes.to_vec(),
|
||||
file_key.to_vec(),
|
||||
)
|
||||
};
|
||||
// bootstrap FS.
|
||||
let _ = print_sender
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: "bootstrapping fs...".to_string(),
|
||||
})
|
||||
.await;
|
||||
|
||||
let (kernel_process_map, manifest) = filesystem::bootstrap(
|
||||
our.name.clone(),
|
||||
home_directory_path.clone(),
|
||||
file_key,
|
||||
fs_config,
|
||||
)
|
||||
.await
|
||||
.expect("fs bootstrap failed!");
|
||||
|
||||
let _ = kill_tx.send(true);
|
||||
let _ = print_sender
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("{} now online", our.name),
|
||||
})
|
||||
.await;
|
||||
let _ = print_sender
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("our networking public key: {}", our.networking_key),
|
||||
})
|
||||
.await;
|
||||
|
||||
/*
|
||||
* the kernel module will handle our userspace processes and receives
|
||||
* all "messages", the basic message format for uqbar.
|
||||
*
|
||||
* if any of these modules fail, the program exits with an error.
|
||||
*/
|
||||
let networking_keypair_arc = Arc::new(networking_keypair);
|
||||
|
||||
let mut tasks = tokio::task::JoinSet::<Result<()>>::new();
|
||||
tasks.spawn(kernel::kernel(
|
||||
our.clone(),
|
||||
networking_keypair_arc.clone(),
|
||||
home_directory_path.into(),
|
||||
kernel_process_map.clone(),
|
||||
caps_oracle_sender.clone(),
|
||||
caps_oracle_receiver,
|
||||
kernel_message_sender.clone(),
|
||||
print_sender.clone(),
|
||||
kernel_message_receiver,
|
||||
network_error_receiver,
|
||||
kernel_debug_message_receiver,
|
||||
net_message_sender.clone(),
|
||||
fs_message_sender,
|
||||
http_server_sender,
|
||||
http_client_sender,
|
||||
eth_rpc_sender,
|
||||
vfs_message_sender,
|
||||
encryptor_sender,
|
||||
));
|
||||
tasks.spawn(net::networking(
|
||||
our.clone(),
|
||||
our_ip,
|
||||
networking_keypair_arc.clone(),
|
||||
kernel_message_sender.clone(),
|
||||
network_error_sender,
|
||||
print_sender.clone(),
|
||||
net_message_sender,
|
||||
net_message_receiver,
|
||||
));
|
||||
tasks.spawn(filesystem::fs_sender(
|
||||
our.name.clone(),
|
||||
manifest,
|
||||
kernel_message_sender.clone(),
|
||||
print_sender.clone(),
|
||||
fs_message_receiver,
|
||||
fs_kill_recv,
|
||||
fs_kill_confirm_send,
|
||||
));
|
||||
tasks.spawn(http_server::http_server(
|
||||
our.name.clone(),
|
||||
http_server_port,
|
||||
jwt_secret_bytes.clone(),
|
||||
http_server_receiver,
|
||||
kernel_message_sender.clone(),
|
||||
print_sender.clone(),
|
||||
));
|
||||
tasks.spawn(http_client::http_client(
|
||||
our.name.clone(),
|
||||
kernel_message_sender.clone(),
|
||||
http_client_receiver,
|
||||
print_sender.clone(),
|
||||
));
|
||||
tasks.spawn(eth_rpc::eth_rpc(
|
||||
our.name.clone(),
|
||||
rpc_url.clone(),
|
||||
kernel_message_sender.clone(),
|
||||
eth_rpc_receiver,
|
||||
print_sender.clone(),
|
||||
));
|
||||
tasks.spawn(vfs::vfs(
|
||||
our.name.clone(),
|
||||
kernel_process_map,
|
||||
kernel_message_sender.clone(),
|
||||
print_sender.clone(),
|
||||
vfs_message_receiver,
|
||||
caps_oracle_sender.clone(),
|
||||
));
|
||||
tasks.spawn(encryptor::encryptor(
|
||||
our.name.clone(),
|
||||
networking_keypair_arc.clone(),
|
||||
kernel_message_sender.clone(),
|
||||
encryptor_receiver,
|
||||
print_sender.clone(),
|
||||
));
|
||||
// if a runtime task exits, try to recover it,
|
||||
// unless it was terminal signaling a quit
|
||||
let quit_msg: String = tokio::select! {
|
||||
Some(res) = tasks.join_next() => {
|
||||
if let Err(e) = res {
|
||||
format!("what does this mean? {:?}", e)
|
||||
} else if let Ok(Err(e)) = res {
|
||||
format!(
|
||||
"\x1b[38;5;196muh oh, a kernel process crashed: {}\x1b[0m",
|
||||
e
|
||||
)
|
||||
// TODO restart the task
|
||||
} else {
|
||||
format!("what does this mean???")
|
||||
// TODO restart the task
|
||||
}
|
||||
}
|
||||
quit = terminal::terminal(
|
||||
our.clone(),
|
||||
VERSION,
|
||||
home_directory_path.into(),
|
||||
kernel_message_sender.clone(),
|
||||
kernel_debug_message_sender,
|
||||
print_sender.clone(),
|
||||
print_receiver,
|
||||
) => {
|
||||
match quit {
|
||||
Ok(_) => "graceful exit".into(),
|
||||
Err(e) => e.to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
// shutdown signal to fs for flush
|
||||
let _ = fs_kill_send.send(());
|
||||
let _ = fs_kill_confirm_recv.await;
|
||||
// println!("fs shutdown complete.");
|
||||
|
||||
// gracefully abort all running processes in kernel
|
||||
let _ = kernel_message_sender
|
||||
.send(KernelMessage {
|
||||
id: 0,
|
||||
source: Address {
|
||||
node: our.name.clone(),
|
||||
process: ProcessId::Name("kernel".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: our.name.clone(),
|
||||
process: ProcessId::Name("kernel".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(serde_json::to_string(&KernelCommand::Shutdown).unwrap()),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
})
|
||||
.await;
|
||||
// abort all remaining tasks
|
||||
tasks.shutdown().await;
|
||||
let _ = crossterm::terminal::disable_raw_mode();
|
||||
println!("");
|
||||
println!("\x1b[38;5;196m{}\x1b[0m", quit_msg);
|
||||
return;
|
||||
}
|
555
src/net/connections.rs
Normal file
555
src/net/connections.rs
Normal file
@ -0,0 +1,555 @@
|
||||
use crate::net::*;
|
||||
use chacha20poly1305::{
|
||||
aead::{Aead, AeadCore, KeyInit, OsRng},
|
||||
XChaCha20Poly1305, XNonce,
|
||||
};
|
||||
use elliptic_curve::ecdh::SharedSecret;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_tungstenite::tungstenite;
|
||||
|
||||
pub async fn build_connection(
|
||||
our: Identity,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
pki: OnchainPKI,
|
||||
keys: PeerKeys,
|
||||
peers: Peers,
|
||||
websocket: WebSocket,
|
||||
kernel_message_tx: MessageSender,
|
||||
net_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
with: Option<String>,
|
||||
) -> (
|
||||
UnboundedSender<(NetworkMessage, Option<ErrorShuttle>)>,
|
||||
JoinHandle<Option<String>>,
|
||||
) {
|
||||
// println!("building new connection\r");
|
||||
let (message_tx, message_rx) = unbounded_channel::<(NetworkMessage, Option<ErrorShuttle>)>();
|
||||
let handle = tokio::spawn(maintain_connection(
|
||||
our,
|
||||
with,
|
||||
keypair,
|
||||
pki,
|
||||
keys,
|
||||
peers,
|
||||
websocket,
|
||||
message_tx.clone(),
|
||||
message_rx,
|
||||
kernel_message_tx,
|
||||
net_message_tx,
|
||||
network_error_tx,
|
||||
));
|
||||
return (message_tx, handle);
|
||||
}
|
||||
|
||||
/// Keeps a connection alive and handles sending and receiving of NetworkMessages through it.
|
||||
/// TODO add a keepalive PING/PONG system
|
||||
/// TODO kill this after a certain amount of inactivity
|
||||
pub async fn maintain_connection(
|
||||
our: Identity,
|
||||
with: Option<String>,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
pki: OnchainPKI,
|
||||
keys: PeerKeys,
|
||||
peers: Peers,
|
||||
websocket: WebSocket,
|
||||
message_tx: UnboundedSender<(NetworkMessage, Option<ErrorShuttle>)>,
|
||||
mut message_rx: UnboundedReceiver<(NetworkMessage, Option<ErrorShuttle>)>,
|
||||
kernel_message_tx: MessageSender,
|
||||
net_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
) -> Option<String> {
|
||||
// let conn_id: u64 = rand::random();
|
||||
// println!("maintaining connection {conn_id}\r");
|
||||
|
||||
// accept messages on the websocket in one task, and send messages in another
|
||||
let (mut write_stream, mut read_stream) = websocket.split();
|
||||
|
||||
let (forwarding_ack_tx, mut forwarding_ack_rx) = unbounded_channel::<MessageResult>();
|
||||
// manage outstanding ACKs from messages sent over the connection
|
||||
// TODO replace with more performant data structure
|
||||
let ack_map = Arc::new(RwLock::new(HashMap::<u64, ErrorShuttle>::new()));
|
||||
let sender_ack_map = ack_map.clone();
|
||||
|
||||
let forwarder_message_tx = message_tx.clone();
|
||||
let ack_forwarder = tokio::spawn(async move {
|
||||
while let Some(result) = forwarding_ack_rx.recv().await {
|
||||
match result {
|
||||
Ok(NetworkMessage::Ack(id)) => {
|
||||
// println!("net: got forwarding ack for message {}\r", id);
|
||||
forwarder_message_tx
|
||||
.send((NetworkMessage::Ack(id), None))
|
||||
.unwrap();
|
||||
}
|
||||
Ok(NetworkMessage::Nack(id)) => {
|
||||
// println!("net: got forwarding nack for message {}\r", id);
|
||||
forwarder_message_tx
|
||||
.send((NetworkMessage::Nack(id), None))
|
||||
.unwrap();
|
||||
}
|
||||
Ok(NetworkMessage::HandshakeAck(handshake)) => {
|
||||
// println!(
|
||||
// "net: got forwarding handshakeAck for message {}\r",
|
||||
// handshake.id
|
||||
// );
|
||||
forwarder_message_tx
|
||||
.send((NetworkMessage::HandshakeAck(handshake), None))
|
||||
.unwrap();
|
||||
}
|
||||
Err((message_id, _e)) => {
|
||||
// println!("net: got forwarding error from ack_rx: {:?}\r", e);
|
||||
// what do we do here?
|
||||
forwarder_message_tx
|
||||
.send((NetworkMessage::Nack(message_id), None))
|
||||
.unwrap();
|
||||
}
|
||||
_ => {
|
||||
// println!("net: weird none ack\r");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// receive messages from over the websocket and route them to the correct peer handler,
|
||||
// or create it, if necessary.
|
||||
let ws_receiver = tokio::spawn(async move {
|
||||
while let Some(Ok(tungstenite::Message::Binary(bin))) = read_stream.next().await {
|
||||
// TODO use a language-netural serialization format here!
|
||||
let Ok(net_message) = bincode::deserialize::<NetworkMessage>(&bin) else {
|
||||
// just kill the connection if we get a non-Uqbar message
|
||||
break;
|
||||
};
|
||||
match net_message {
|
||||
NetworkMessage::Ack(id) => {
|
||||
let Some(result_tx) = ack_map.write().await.remove(&id) else {
|
||||
// println!("conn {conn_id}: got unexpected Ack {id}\r");
|
||||
continue;
|
||||
};
|
||||
// println!("conn {conn_id}: got Ack {id}\r");
|
||||
let _ = result_tx.send(Ok(net_message));
|
||||
continue;
|
||||
}
|
||||
NetworkMessage::Nack(id) => {
|
||||
let Some(result_tx) = ack_map.write().await.remove(&id) else {
|
||||
// println!("net: got unexpected Nack\r");
|
||||
continue;
|
||||
};
|
||||
let _ = result_tx.send(Ok(net_message));
|
||||
continue;
|
||||
}
|
||||
NetworkMessage::Msg {
|
||||
ref id,
|
||||
ref from,
|
||||
ref to,
|
||||
ref contents,
|
||||
} => {
|
||||
// println!("conn {conn_id}: handling msg {id}\r");
|
||||
// if the message is *directed to us*, try to handle with the
|
||||
// matching peer handler "decrypter".
|
||||
//
|
||||
if to == &our.name {
|
||||
// if we have the peer, send the message to them.
|
||||
if let Some(peer) = peers.read().await.get(from) {
|
||||
let _ = peer
|
||||
.decrypter
|
||||
.send((contents.to_owned(), forwarding_ack_tx.clone()));
|
||||
continue;
|
||||
}
|
||||
// if we don't have the peer, see if we have the keys to create them.
|
||||
// if we don't have their keys, throw a nack.
|
||||
if let Some((peer_id, secret)) = keys.read().await.get(from) {
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
peer_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret.clone(),
|
||||
message_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
net_message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
let _ = new_peer
|
||||
.decrypter
|
||||
.send((contents.to_owned(), forwarding_ack_tx.clone()));
|
||||
peers.write().await.insert(peer_id.name.clone(), new_peer);
|
||||
} else {
|
||||
// println!("net: nacking message {id}\r");
|
||||
message_tx.send((NetworkMessage::Nack(*id), None)).unwrap();
|
||||
}
|
||||
} else {
|
||||
// if the message is *directed to someone else*, try to handle
|
||||
// with the matching peer handler "sender".
|
||||
//
|
||||
if let Some(peer) = peers.read().await.get(to) {
|
||||
let _ = peer.sender.send((
|
||||
PeerMessage::Net(net_message),
|
||||
Some(forwarding_ack_tx.clone()),
|
||||
));
|
||||
} else {
|
||||
// if we don't have the peer, throw a nack.
|
||||
// println!("net: nacking message with id {id}\r");
|
||||
message_tx.send((NetworkMessage::Nack(*id), None)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkMessage::Handshake(ref handshake) => {
|
||||
// when we get a handshake, if we are the target,
|
||||
// 1. verify it against the PKI
|
||||
// 2. send a response handshakeAck
|
||||
// 3. create a Peer and save, replacing old one if it existed
|
||||
// as long as we are the target, we also get to kill this connection
|
||||
// if the handshake is invalid, since it must be directly "to" us.
|
||||
if handshake.target == our.name {
|
||||
let Some(peer_id) = pki.read().await.get(&handshake.from).cloned() else {
|
||||
// println!(
|
||||
// "net: failed handshake with unknown node {}\r",
|
||||
// handshake.from
|
||||
// );
|
||||
message_tx
|
||||
.send((NetworkMessage::Nack(handshake.id), None))
|
||||
.unwrap();
|
||||
break;
|
||||
};
|
||||
let their_ephemeral_pk = match validate_handshake(&handshake, &peer_id) {
|
||||
Ok(pk) => pk,
|
||||
Err(e) => {
|
||||
println!("net: invalid handshake from {}: {}\r", handshake.from, e);
|
||||
message_tx
|
||||
.send((NetworkMessage::Nack(handshake.id), None))
|
||||
.unwrap();
|
||||
break;
|
||||
}
|
||||
};
|
||||
let (secret, handshake) = make_secret_and_handshake(
|
||||
&our,
|
||||
keypair.clone(),
|
||||
&handshake.from,
|
||||
Some(handshake.id),
|
||||
);
|
||||
message_tx
|
||||
.send((NetworkMessage::HandshakeAck(handshake), None))
|
||||
.unwrap();
|
||||
let secret = Arc::new(secret.diffie_hellman(&their_ephemeral_pk));
|
||||
// save the handshake to our Keys map
|
||||
keys.write()
|
||||
.await
|
||||
.insert(peer_id.name.clone(), (peer_id.clone(), secret.clone()));
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
peer_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret,
|
||||
message_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
net_message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
// we might be replacing an old peer, so we need to remove it first
|
||||
// we can't rely on the hashmap for this, because the dropped peer
|
||||
// will trigger a drop of the sender, which will kill the peer_handler
|
||||
peers.write().await.remove(&peer_id.name);
|
||||
peers.write().await.insert(peer_id.name.clone(), new_peer);
|
||||
} else {
|
||||
// if we are NOT the target,
|
||||
// try to send it to the matching peer handler "sender"
|
||||
if let Some(peer) = peers.read().await.get(&handshake.target) {
|
||||
let _ = peer.sender.send((
|
||||
PeerMessage::Net(net_message),
|
||||
Some(forwarding_ack_tx.clone()),
|
||||
));
|
||||
} else {
|
||||
// if we don't have the peer, throw a nack.
|
||||
// println!("net: nacking handshake with id {}\r", handshake.id);
|
||||
message_tx
|
||||
.send((NetworkMessage::Nack(handshake.id), None))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkMessage::HandshakeAck(ref handshake) => {
|
||||
let Some(result_tx) = ack_map.write().await.remove(&handshake.id) else {
|
||||
continue;
|
||||
};
|
||||
let _ = result_tx.send(Ok(net_message));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::select! {
|
||||
_ = ws_receiver => {
|
||||
// println!("ws_receiver died\r");
|
||||
},
|
||||
_ = ack_forwarder => {
|
||||
// println!("ack_forwarder died\r");
|
||||
}
|
||||
// receive messages we would like to send to peers along this connection
|
||||
// and send them to the websocket
|
||||
_ = async {
|
||||
while let Some((message, result_tx)) = message_rx.recv().await {
|
||||
// TODO use a language-netural serialization format here!
|
||||
if let Ok(bytes) = bincode::serialize::<NetworkMessage>(&message) {
|
||||
match &message {
|
||||
NetworkMessage::Msg { id, .. } => {
|
||||
// println!("conn {conn_id}: piping msg {id}\r");
|
||||
sender_ack_map.write().await.insert(*id, result_tx.unwrap());
|
||||
}
|
||||
NetworkMessage::Handshake(h) => {
|
||||
sender_ack_map.write().await.insert(h.id, result_tx.unwrap());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match write_stream.send(tungstenite::Message::Binary(bytes)).await {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
// println!("net: send error: {:?}\r", e);
|
||||
let id = match &message {
|
||||
NetworkMessage::Msg { id, .. } => id,
|
||||
NetworkMessage::Handshake(h) => &h.id,
|
||||
_ => continue,
|
||||
};
|
||||
let Some(result_tx) = sender_ack_map.write().await.remove(&id) else {
|
||||
continue;
|
||||
};
|
||||
// TODO learn how to handle other non-fatal websocket errors.
|
||||
match e {
|
||||
tungstenite::error::Error::Capacity(_)
|
||||
| tungstenite::Error::Io(_) => {
|
||||
let _ = result_tx.send(Err((*id, SendErrorKind::Timeout)));
|
||||
}
|
||||
_ => {
|
||||
let _ = result_tx.send(Ok(NetworkMessage::Nack(*id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} => {
|
||||
// println!("ws_sender died\r");
|
||||
},
|
||||
};
|
||||
return with;
|
||||
}
|
||||
|
||||
/// After a successful handshake, use information to spawn a new `peer_handler` task
|
||||
/// and save a `Peer` in our peers mapping. Returns a sender to use for sending messages
|
||||
/// to this peer, which will also be saved in its Peer struct.
|
||||
pub fn create_new_peer(
|
||||
our: Identity,
|
||||
new_peer_id: Identity,
|
||||
peers: Peers,
|
||||
keys: PeerKeys,
|
||||
secret: Arc<SharedSecret<Secp256k1>>,
|
||||
conn_sender: UnboundedSender<(NetworkMessage, Option<ErrorShuttle>)>,
|
||||
kernel_message_tx: MessageSender,
|
||||
net_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
) -> Peer {
|
||||
let (message_tx, message_rx) = unbounded_channel::<(PeerMessage, Option<ErrorShuttle>)>();
|
||||
let (decrypter_tx, decrypter_rx) = unbounded_channel::<(Vec<u8>, ErrorShuttle)>();
|
||||
let peer_id_name = new_peer_id.name.clone();
|
||||
let peer_conn_sender = conn_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
match peer_handler(
|
||||
our,
|
||||
peer_id_name.clone(),
|
||||
secret,
|
||||
message_rx,
|
||||
decrypter_rx,
|
||||
peer_conn_sender,
|
||||
kernel_message_tx,
|
||||
network_error_tx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
None => {
|
||||
// println!("net: dropping peer handler but not deleting\r");
|
||||
}
|
||||
Some(km) => {
|
||||
// println!("net: ok actually deleting peer+keys now and retrying send\r");
|
||||
peers.write().await.remove(&peer_id_name);
|
||||
keys.write().await.remove(&peer_id_name);
|
||||
let _ = net_message_tx.send(km).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
return Peer {
|
||||
identity: new_peer_id,
|
||||
sender: message_tx,
|
||||
decrypter: decrypter_tx,
|
||||
socket_tx: conn_sender,
|
||||
};
|
||||
}
|
||||
|
||||
/// 1. take in messages from a specific peer, decrypt them, and send to kernel
|
||||
/// 2. take in messages targeted at specific peer and either:
|
||||
/// - encrypt them, and send to proper connection
|
||||
/// - forward them untouched along the connection
|
||||
async fn peer_handler(
|
||||
our: Identity,
|
||||
who: String,
|
||||
secret: Arc<SharedSecret<Secp256k1>>,
|
||||
mut message_rx: UnboundedReceiver<(PeerMessage, Option<ErrorShuttle>)>,
|
||||
mut decrypter_rx: UnboundedReceiver<(Vec<u8>, ErrorShuttle)>,
|
||||
socket_tx: UnboundedSender<(NetworkMessage, Option<ErrorShuttle>)>,
|
||||
kernel_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
) -> Option<KernelMessage> {
|
||||
// println!("peer_handler\r");
|
||||
let mut key = [0u8; 32];
|
||||
secret
|
||||
.extract::<sha2::Sha256>(None)
|
||||
.expand(&[], &mut key)
|
||||
.unwrap();
|
||||
let cipher = XChaCha20Poly1305::new(generic_array::GenericArray::from_slice(&key));
|
||||
|
||||
let (ack_tx, mut ack_rx) = unbounded_channel::<MessageResult>();
|
||||
// TODO use a more efficient data structure
|
||||
let ack_map = Arc::new(RwLock::new(HashMap::<u64, KernelMessage>::new()));
|
||||
let recv_ack_map = ack_map.clone();
|
||||
tokio::select! {
|
||||
//
|
||||
// take in messages from a specific peer, decrypt them, and send to kernel
|
||||
//
|
||||
_ = async {
|
||||
while let Some((encrypted_bytes, result_tx)) = decrypter_rx.recv().await {
|
||||
let nonce = XNonce::from_slice(&encrypted_bytes[..24]);
|
||||
if let Ok(decrypted) = cipher.decrypt(&nonce, &encrypted_bytes[24..]) {
|
||||
if let Ok(message) = bincode::deserialize::<KernelMessage>(&decrypted) {
|
||||
if message.source.node == who {
|
||||
// println!("net: got peer message {}, acking\r", message.id);
|
||||
let _ = result_tx.send(Ok(NetworkMessage::Ack(message.id)));
|
||||
let _ = kernel_message_tx.send(message).await;
|
||||
continue;
|
||||
}
|
||||
println!("net: got message 'from' wrong person! cheater/liar!\r");
|
||||
break;
|
||||
}
|
||||
println!("net: failed to deserialize message from {}\r", who);
|
||||
continue;
|
||||
}
|
||||
println!("net: failed to decrypt message from {}, could be spoofer\r", who);
|
||||
continue;
|
||||
}
|
||||
} => {
|
||||
// println!("net: lost peer {who}\r");
|
||||
return None
|
||||
}
|
||||
//
|
||||
// take in messages targeted at specific peer and either:
|
||||
// - encrypt them, and send to proper connection
|
||||
// - forward them untouched along the connection
|
||||
//
|
||||
_ = async {
|
||||
// if we get a result_tx, rather than track it here, let a different
|
||||
// part of the code handle whatever comes back from the socket.
|
||||
while let Some((message, maybe_result_tx)) = message_rx.recv().await {
|
||||
// if message is raw, we should encrypt.
|
||||
// otherwise, simply send
|
||||
match message {
|
||||
PeerMessage::Raw(message) => {
|
||||
let id = message.id;
|
||||
if let Ok(bytes) = bincode::serialize::<KernelMessage>(&message) {
|
||||
// generating a random nonce for each message.
|
||||
// this isn't really as secure as we could get: should
|
||||
// add a counter and then throw away the key when we hit a
|
||||
// certain # of messages. TODO.
|
||||
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
||||
if let Ok(encrypted) = cipher.encrypt(&nonce, bytes.as_ref()) {
|
||||
if maybe_result_tx.is_none() {
|
||||
ack_map.write().await.insert(id, message);
|
||||
}
|
||||
match socket_tx.send((
|
||||
NetworkMessage::Msg {
|
||||
from: our.name.clone(),
|
||||
to: who.clone(),
|
||||
id: id,
|
||||
contents: [nonce.to_vec(), encrypted].concat(),
|
||||
},
|
||||
Some(maybe_result_tx.unwrap_or(ack_tx.clone())),
|
||||
)) {
|
||||
Ok(()) => tokio::task::yield_now().await,
|
||||
Err(tokio::sync::mpsc::error::SendError((_, result_tx))) => {
|
||||
// println!("net: lost socket with {who}\r");
|
||||
let _ = result_tx.unwrap().send(Ok(NetworkMessage::Nack(id)));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PeerMessage::Net(net_message) => {
|
||||
match socket_tx.send((net_message, Some(maybe_result_tx.unwrap_or(ack_tx.clone())))) {
|
||||
Ok(()) => continue,
|
||||
Err(tokio::sync::mpsc::error::SendError((net_message, result_tx))) => {
|
||||
// println!("net: lost *forwarding* socket with {who}\r");
|
||||
let id = match net_message {
|
||||
NetworkMessage::Msg { id, .. } => id,
|
||||
NetworkMessage::Handshake(h) => h.id,
|
||||
_ => continue,
|
||||
};
|
||||
let _ = result_tx.unwrap().send(Ok(NetworkMessage::Nack(id)));
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} => return None,
|
||||
//
|
||||
// receive acks and nacks from our socket
|
||||
// throw away acks, but kill this peer and retry the send on nacks.
|
||||
//
|
||||
maybe_km = async {
|
||||
while let Some(result) = ack_rx.recv().await {
|
||||
match result {
|
||||
Ok(NetworkMessage::Ack(id)) => {
|
||||
// println!("net: got ack for message {}\r", id);
|
||||
recv_ack_map.write().await.remove(&id);
|
||||
continue;
|
||||
}
|
||||
Ok(NetworkMessage::Nack(id)) => {
|
||||
// println!("net: got nack for message {}\r", id);
|
||||
let Some(km) = recv_ack_map.write().await.remove(&id) else {
|
||||
continue;
|
||||
};
|
||||
// when we get a Nack, **delete this peer** and try to send the message again!
|
||||
return Some(km)
|
||||
}
|
||||
Err((message_id, e)) => {
|
||||
// println!("net: got error from ack_rx: {:?}\r", e);
|
||||
// in practice this is always a timeout in current implementation
|
||||
let Some(km) = recv_ack_map.write().await.remove(&message_id) else {
|
||||
continue;
|
||||
};
|
||||
let _ = network_error_tx
|
||||
.send(WrappedSendError {
|
||||
id: km.id,
|
||||
source: km.source,
|
||||
error: SendError {
|
||||
kind: e,
|
||||
target: km.target,
|
||||
message: km.message,
|
||||
payload: km.payload,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
return None
|
||||
}
|
||||
_ => {
|
||||
// println!("net: weird none ack\r");
|
||||
return None
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
} => {
|
||||
// println!("net: exiting peer due to nackage\r");
|
||||
return maybe_km
|
||||
},
|
||||
}
|
||||
}
|
898
src/net/mod.rs
Normal file
898
src/net/mod.rs
Normal file
@ -0,0 +1,898 @@
|
||||
use crate::net::connections::*;
|
||||
use crate::net::types::*;
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
use elliptic_curve::ecdh::EphemeralSecret;
|
||||
use elliptic_curve::PublicKey;
|
||||
use ethers::prelude::k256::{self, Secp256k1};
|
||||
use ring::signature::{self, Ed25519KeyPair};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{mpsc::unbounded_channel, RwLock};
|
||||
use tokio::task::JoinSet;
|
||||
use tokio::time::timeout;
|
||||
use tokio_tungstenite::{accept_async, connect_async, MaybeTlsStream};
|
||||
|
||||
mod connections;
|
||||
mod types;
|
||||
|
||||
// only used in connection initialization, otherwise, nacks and Responses are only used for "timeouts"
|
||||
const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
|
||||
|
||||
/// Entry point from the main kernel task. Runs forever, spawns listener and sender tasks.
|
||||
pub async fn networking(
|
||||
our: Identity,
|
||||
our_ip: String,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
kernel_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
print_tx: PrintSender,
|
||||
message_tx: MessageSender,
|
||||
mut message_rx: MessageReceiver,
|
||||
) -> Result<()> {
|
||||
// TODO persist this here
|
||||
let pki: OnchainPKI = Arc::new(RwLock::new(HashMap::new()));
|
||||
let keys: PeerKeys = Arc::new(RwLock::new(HashMap::new()));
|
||||
// mapping from QNS namehash to username
|
||||
let names: PKINames = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
// this only lives during a given run of the kernel
|
||||
let peers: Peers = Arc::new(RwLock::new(HashMap::new()));
|
||||
|
||||
// listener task either kickstarts our networking by establishing active connections
|
||||
// with one or more routers, or spawns a websocket listener if we are a direct node.
|
||||
let listener = match &our.ws_routing {
|
||||
None => {
|
||||
// indirect node: connect to router(s)
|
||||
tokio::spawn(connect_to_routers(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
our_ip.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
print_tx.clone(),
|
||||
))
|
||||
}
|
||||
Some((_ip, port)) => {
|
||||
// direct node: spawn the websocket listener
|
||||
tokio::spawn(receive_incoming_connections(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
*port,
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let _ = tokio::join!(listener, async {
|
||||
while let Some(km) = message_rx.recv().await {
|
||||
// got a message from kernel to send out over the network
|
||||
let target = &km.target.node;
|
||||
// if the message is for us, it's either a protocol-level "hello" message,
|
||||
// or a debugging command issued from our terminal. handle it here:
|
||||
if target == &our.name {
|
||||
handle_incoming_message(
|
||||
&our,
|
||||
km,
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
pki.clone(),
|
||||
names.clone(),
|
||||
print_tx.clone(),
|
||||
)
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
let peers_read = peers.read().await;
|
||||
//
|
||||
// we have the target as an active peer, meaning we can send the message directly
|
||||
//
|
||||
if let Some(peer) = peers_read.get(target) {
|
||||
// println!("net: direct send to known peer\r");
|
||||
let _ = peer.sender.send((PeerMessage::Raw(km.clone()), None));
|
||||
continue;
|
||||
}
|
||||
drop(peers_read);
|
||||
//
|
||||
// we don't have the target as a peer yet, but we have shaken hands with them
|
||||
// before, and can try to reuse that shared secret to send a message.
|
||||
// first, we'll need to open a websocket and create a Peer struct for them.
|
||||
//
|
||||
if let Some((peer_id, secret)) = keys.read().await.get(target).cloned() {
|
||||
//
|
||||
// we can establish a connection directly with this peer
|
||||
//
|
||||
if let Some((ref ip, ref port)) = peer_id.ws_routing {
|
||||
// println!("net: creating direct connection to peer with known keys\r");
|
||||
let Ok(ws_url) = make_ws_url(&our_ip, ip, port) else {
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
let Ok(Ok((websocket, _response))) =
|
||||
timeout(TIMEOUT, connect_async(ws_url)).await
|
||||
else {
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
let (socket_tx, conn_handle) = build_connection(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
websocket,
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
peer_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret,
|
||||
socket_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
let (temp_result_tx, mut temp_result_rx) = unbounded_channel::<MessageResult>();
|
||||
let _ = new_peer
|
||||
.sender
|
||||
.send((PeerMessage::Raw(km.clone()), Some(temp_result_tx)));
|
||||
match timeout(TIMEOUT, temp_result_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::Ack(_id)))) => {
|
||||
peers.write().await.insert(peer_id.name.clone(), new_peer);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// instead of throwing Offline now, throw away their keys and
|
||||
// try again.
|
||||
keys.write().await.remove(target);
|
||||
conn_handle.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// need to find a router that will connect to this peer!
|
||||
//
|
||||
else {
|
||||
// println!("net: finding router for peer with known keys\r");
|
||||
// get their ID from PKI so we have their most up-to-date router list
|
||||
let Some(peer_id) = pki.read().await.get(target).cloned() else {
|
||||
// this target cannot be found in the PKI!
|
||||
// throw an Offline error.
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
let mut success = false;
|
||||
for router_namehash in &peer_id.allowed_routers {
|
||||
let km = km.clone();
|
||||
let Some(router_name) = names.read().await.get(router_namehash).cloned()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(router_id) = pki.read().await.get(&router_name).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let Some((ref ip, ref port)) = router_id.ws_routing else {
|
||||
continue;
|
||||
};
|
||||
//
|
||||
// otherwise, attempt to connect to the router's IP+port and send through that
|
||||
//
|
||||
// if we already have this router as a peer, use that socket_tx
|
||||
let (socket_tx, maybe_conn_handle) =
|
||||
if let Some(router) = peers.read().await.get(&router_name) {
|
||||
(router.socket_tx.clone(), None)
|
||||
} else {
|
||||
let Ok(ws_url) = make_ws_url(&our_ip, ip, port) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(Ok((websocket, _response))) =
|
||||
timeout(TIMEOUT, connect_async(ws_url)).await
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let (socket_tx, conn_handle) = build_connection(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
websocket,
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
(socket_tx, Some(conn_handle))
|
||||
};
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
peer_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret.clone(),
|
||||
socket_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
let (temp_result_tx, mut temp_result_rx) =
|
||||
unbounded_channel::<MessageResult>();
|
||||
let _ = new_peer
|
||||
.sender
|
||||
.send((PeerMessage::Raw(km.clone()), Some(temp_result_tx)));
|
||||
match timeout(TIMEOUT, temp_result_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::Ack(_id)))) => {
|
||||
peers.write().await.insert(peer_id.name.clone(), new_peer);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if let Some(conn_handle) = maybe_conn_handle {
|
||||
conn_handle.abort();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
// instead of throwing Offline now, throw away their keys and
|
||||
// try again.
|
||||
keys.write().await.remove(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// sending a message to a peer for which we don't have active networking info.
|
||||
// this means that we need to search the PKI for the peer, and then attempt to
|
||||
// exchange handshakes with them.
|
||||
//
|
||||
let Some(peer_id) = pki.read().await.get(target).cloned() else {
|
||||
// this target cannot be found in the PKI!
|
||||
// throw an Offline error.
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
//
|
||||
// we can establish a connection directly with this peer, then send a handshake
|
||||
//
|
||||
if let Some((ref ip, ref port)) = peer_id.ws_routing {
|
||||
// println!("net: creating direct connection to peer in PKI\r");
|
||||
let Ok(ws_url) = make_ws_url(&our_ip, ip, port) else {
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
let Ok(Ok((websocket, _response))) = timeout(TIMEOUT, connect_async(ws_url)).await
|
||||
else {
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
let (socket_tx, conn_handle) = build_connection(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
websocket,
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let (secret, handshake) =
|
||||
make_secret_and_handshake(&our, keypair.clone(), target, None);
|
||||
let (handshake_tx, mut handshake_rx) = unbounded_channel::<MessageResult>();
|
||||
socket_tx
|
||||
.send((NetworkMessage::Handshake(handshake), Some(handshake_tx)))
|
||||
.unwrap();
|
||||
let response_shake = match timeout(TIMEOUT, handshake_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::HandshakeAck(shake)))) => shake,
|
||||
_ => {
|
||||
// println!("net: failed handshake with {target}\r");
|
||||
error_offline(km, &network_error_tx).await;
|
||||
conn_handle.abort();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let Ok(their_ephemeral_pk) = validate_handshake(&response_shake, &peer_id) else {
|
||||
// println!("net: failed handshake with {target}\r");
|
||||
error_offline(km, &network_error_tx).await;
|
||||
conn_handle.abort();
|
||||
continue;
|
||||
};
|
||||
let secret = Arc::new(secret.diffie_hellman(&their_ephemeral_pk));
|
||||
// save the handshake to our Keys map
|
||||
keys.write()
|
||||
.await
|
||||
.insert(peer_id.name.clone(), (peer_id.clone(), secret.clone()));
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
peer_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret,
|
||||
socket_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
// can't do a self_tx.send here because we need to maintain ordering of messages
|
||||
// already queued.
|
||||
let (temp_result_tx, mut temp_result_rx) = unbounded_channel::<MessageResult>();
|
||||
let _ = new_peer
|
||||
.sender
|
||||
.send((PeerMessage::Raw(km.clone()), Some(temp_result_tx)));
|
||||
match timeout(TIMEOUT, temp_result_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::Ack(_id)))) => {
|
||||
peers.write().await.insert(peer_id.name.clone(), new_peer);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
// instead of throwing Offline now, throw away their keys and
|
||||
// try again.
|
||||
keys.write().await.remove(target);
|
||||
conn_handle.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// need to find a router that will connect to this peer, then do a handshake
|
||||
//
|
||||
else {
|
||||
// println!("net: looking for router to create connection to peer in PKI\r");
|
||||
let Some(peer_id) = pki.read().await.get(target).cloned() else {
|
||||
// this target cannot be found in the PKI!
|
||||
// throw an Offline error.
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
};
|
||||
let mut success = false;
|
||||
for router_namehash in &peer_id.allowed_routers {
|
||||
let km = km.clone();
|
||||
let Some(router_name) = names.read().await.get(router_namehash).cloned() else {
|
||||
continue;
|
||||
};
|
||||
if router_name == our.name {
|
||||
// don't try to connect to ourselves!
|
||||
continue;
|
||||
}
|
||||
let Some(router_id) = pki.read().await.get(&router_name).cloned() else {
|
||||
continue;
|
||||
};
|
||||
let Some((ref ip, ref port)) = router_id.ws_routing else {
|
||||
continue;
|
||||
};
|
||||
//
|
||||
// attempt to connect to the router's IP+port and send through that
|
||||
// if we already have this router as a peer, use that socket_tx
|
||||
let (socket_tx, maybe_conn_handle) =
|
||||
if let Some(router) = peers.read().await.get(&router_name) {
|
||||
(router.socket_tx.clone(), None)
|
||||
} else {
|
||||
let Ok(ws_url) = make_ws_url(&our_ip, ip, port) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(Ok((websocket, _response))) =
|
||||
timeout(TIMEOUT, connect_async(ws_url)).await
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let (socket_tx, conn_handle) = build_connection(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
websocket,
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
(socket_tx, Some(conn_handle))
|
||||
};
|
||||
let (secret, handshake) =
|
||||
make_secret_and_handshake(&our, keypair.clone(), target, None);
|
||||
let (handshake_tx, mut handshake_rx) = unbounded_channel::<MessageResult>();
|
||||
socket_tx
|
||||
.send((NetworkMessage::Handshake(handshake), Some(handshake_tx)))
|
||||
.unwrap();
|
||||
let response_shake = match timeout(TIMEOUT, handshake_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::HandshakeAck(shake)))) => shake,
|
||||
_ => {
|
||||
if let Some(conn_handle) = maybe_conn_handle {
|
||||
conn_handle.abort();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let Ok(their_ephemeral_pk) = validate_handshake(&response_shake, &peer_id)
|
||||
else {
|
||||
if let Some(conn_handle) = maybe_conn_handle {
|
||||
conn_handle.abort();
|
||||
}
|
||||
continue;
|
||||
};
|
||||
let secret = Arc::new(secret.diffie_hellman(&their_ephemeral_pk));
|
||||
// save the handshake to our Keys map
|
||||
keys.write()
|
||||
.await
|
||||
.insert(peer_id.name.clone(), (peer_id.clone(), secret.clone()));
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
peer_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret,
|
||||
socket_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
let (temp_result_tx, mut temp_result_rx) = unbounded_channel::<MessageResult>();
|
||||
let _ = new_peer
|
||||
.sender
|
||||
.send((PeerMessage::Raw(km.clone()), Some(temp_result_tx)));
|
||||
match timeout(TIMEOUT, temp_result_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::Ack(_id)))) => {
|
||||
peers.write().await.insert(peer_id.name.clone(), new_peer);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
if let Some(conn_handle) = maybe_conn_handle {
|
||||
conn_handle.abort();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !success {
|
||||
error_offline(km, &network_error_tx).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Err(anyhow::anyhow!("networking task exited"))
|
||||
}
|
||||
|
||||
async fn error_offline(km: KernelMessage, network_error_tx: &NetworkErrorSender) {
|
||||
let _ = network_error_tx
|
||||
.send(WrappedSendError {
|
||||
id: km.id,
|
||||
source: km.source,
|
||||
error: SendError {
|
||||
kind: SendErrorKind::Offline,
|
||||
target: km.target,
|
||||
message: km.message,
|
||||
payload: km.payload,
|
||||
},
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Only used if an indirect node.
|
||||
async fn connect_to_routers(
|
||||
our: Identity,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
our_ip: String,
|
||||
pki: OnchainPKI,
|
||||
keys: PeerKeys,
|
||||
peers: Peers,
|
||||
kernel_message_tx: MessageSender,
|
||||
net_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
print_tx: PrintSender,
|
||||
) {
|
||||
// as soon as we boot, need to try and connect to all of our allowed_routers
|
||||
// we can "throw away" routers that have a bad URL setup
|
||||
//
|
||||
// any time we *lose* a router, we need to try and reconnect on a loop
|
||||
// we should always be trying to connect to all "good" routers we don't already have
|
||||
|
||||
let (routers_to_try_tx, mut routers_to_try_rx) = unbounded_channel::<String>();
|
||||
let mut active_routers = JoinSet::<Result<Option<String>, tokio::task::JoinError>>::new();
|
||||
|
||||
// at start, add all our routers to list
|
||||
for router_name in our.allowed_routers.clone() {
|
||||
routers_to_try_tx.send(router_name).unwrap();
|
||||
}
|
||||
|
||||
loop {
|
||||
// we sleep here in order not to BLAST routers with connections
|
||||
// if we have a PKI mismatch with them for some amount of time.
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
tokio::select! {
|
||||
Some(Ok(Ok(Some(dead_router)))) = active_routers.join_next() => {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("net: connection to router {dead_router} died"),
|
||||
})
|
||||
.await;
|
||||
peers.write().await.remove(&dead_router);
|
||||
if active_routers.is_empty() {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("net: no working routers, we are offline!"),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
let _ = routers_to_try_tx.send(dead_router);
|
||||
}
|
||||
Some(router_name) = routers_to_try_rx.recv() => {
|
||||
if peers.read().await.contains_key(&router_name) {
|
||||
continue;
|
||||
}
|
||||
let Some(router_id) = pki.read().await.get(&router_name).cloned() else {
|
||||
let _ = routers_to_try_tx.send(router_name);
|
||||
continue;
|
||||
};
|
||||
let Some((ref ip, ref port)) = router_id.ws_routing else {
|
||||
// this is a bad router, can remove from our list
|
||||
continue;
|
||||
};
|
||||
let Ok(ws_url) = make_ws_url(&our_ip, ip, port) else {
|
||||
// this is a bad router, can remove from our list
|
||||
continue;
|
||||
};
|
||||
let Ok(Ok((websocket, _response))) = timeout(TIMEOUT, connect_async(ws_url)).await else {
|
||||
let _ = routers_to_try_tx.send(router_name);
|
||||
continue;
|
||||
};
|
||||
// we never try to reuse keys with routers because we need to make
|
||||
// sure we have a live connection with them.
|
||||
// connect to their websocket and then send a handshake.
|
||||
// save the handshake in our keys map, then save them as an active peer.
|
||||
let (socket_tx, conn_handle) = build_connection(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
websocket,
|
||||
kernel_message_tx.clone(),
|
||||
net_message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
Some(router_name.clone()),
|
||||
)
|
||||
.await;
|
||||
let (secret, handshake) =
|
||||
make_secret_and_handshake(&our, keypair.clone(), &router_name, None);
|
||||
let (handshake_tx, mut handshake_rx) = unbounded_channel::<MessageResult>();
|
||||
socket_tx
|
||||
.send((NetworkMessage::Handshake(handshake), Some(handshake_tx)))
|
||||
.unwrap();
|
||||
let response_shake = match timeout(TIMEOUT, handshake_rx.recv()).await {
|
||||
Ok(Some(Ok(NetworkMessage::HandshakeAck(shake)))) => shake,
|
||||
_ => {
|
||||
println!("net: failed handshake with {router_name}\r");
|
||||
conn_handle.abort();
|
||||
let _ = routers_to_try_tx.send(router_name);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let Ok(their_ephemeral_pk) = validate_handshake(&response_shake, &router_id) else {
|
||||
println!("net: failed handshake with {router_name}\r");
|
||||
conn_handle.abort();
|
||||
let _ = routers_to_try_tx.send(router_name);
|
||||
continue;
|
||||
};
|
||||
let secret = Arc::new(secret.diffie_hellman(&their_ephemeral_pk));
|
||||
// save the handshake to our Keys map
|
||||
keys.write().await.insert(
|
||||
router_id.name.clone(),
|
||||
(router_id.clone(), secret.clone()),
|
||||
);
|
||||
let new_peer = create_new_peer(
|
||||
our.clone(),
|
||||
router_id.clone(),
|
||||
peers.clone(),
|
||||
keys.clone(),
|
||||
secret,
|
||||
socket_tx.clone(),
|
||||
kernel_message_tx.clone(),
|
||||
net_message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
);
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("net: connected to router {router_name}"),
|
||||
})
|
||||
.await;
|
||||
peers.write().await.insert(router_id.name.clone(), new_peer);
|
||||
active_routers.spawn(conn_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// only used if direct. should live forever
|
||||
async fn receive_incoming_connections(
|
||||
our: Identity,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
port: u16,
|
||||
pki: OnchainPKI,
|
||||
keys: PeerKeys,
|
||||
peers: Peers,
|
||||
kernel_message_tx: MessageSender,
|
||||
net_message_tx: MessageSender,
|
||||
network_error_tx: NetworkErrorSender,
|
||||
) {
|
||||
let tcp = TcpListener::bind(format!("0.0.0.0:{}", port))
|
||||
.await
|
||||
.expect(format!("fatal error: can't listen on port {port}").as_str());
|
||||
|
||||
while let Ok((stream, _socket_addr)) = tcp.accept().await {
|
||||
// TODO we can perform some amount of validation here
|
||||
// to prevent some amount of potential DDoS attacks.
|
||||
// can block based on socket_addr, but not QNS ID.
|
||||
match accept_async(MaybeTlsStream::Plain(stream)).await {
|
||||
Ok(websocket) => {
|
||||
// println!("received incoming connection\r");
|
||||
tokio::spawn(build_connection(
|
||||
our.clone(),
|
||||
keypair.clone(),
|
||||
pki.clone(),
|
||||
keys.clone(),
|
||||
peers.clone(),
|
||||
websocket,
|
||||
kernel_message_tx.clone(),
|
||||
net_message_tx.clone(),
|
||||
network_error_tx.clone(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
// ignore connections we failed to accept
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// net module only handles requests, will never return a response
|
||||
async fn handle_incoming_message(
|
||||
our: &Identity,
|
||||
km: KernelMessage,
|
||||
peers: Peers,
|
||||
keys: PeerKeys,
|
||||
pki: OnchainPKI,
|
||||
names: PKINames,
|
||||
print_tx: PrintSender,
|
||||
) {
|
||||
let data = match km.message {
|
||||
Message::Response(_) => return,
|
||||
Message::Request(request) => match request.ipc {
|
||||
None => return,
|
||||
Some(ipc) => ipc,
|
||||
},
|
||||
};
|
||||
|
||||
if km.source.node != our.name {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("\x1b[3;32m{}: {}\x1b[0m", km.source.node, data,),
|
||||
})
|
||||
.await;
|
||||
} else {
|
||||
// available commands: "peers", "QnsUpdate" (see qns_indexer module)
|
||||
// first parse as raw string, then deserialize to NetActions object
|
||||
match data.as_ref() {
|
||||
"peers" => {
|
||||
let peer_read = peers.read().await;
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("{:?}", peer_read.keys()),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
"keys" => {
|
||||
let keys_read = keys.read().await;
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("{:?}", keys_read.keys()),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
"pki" => {
|
||||
let pki_read = pki.read().await;
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("{:?}", pki_read),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
"names" => {
|
||||
let names_read = names.read().await;
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: format!("{:?}", names_read),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => {
|
||||
let Ok(act) = serde_json::from_str::<NetActions>(&data) else {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: "net: got unknown command".into(),
|
||||
})
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
match act {
|
||||
NetActions::QnsUpdate(log) => {
|
||||
if km.source.process != ProcessId::Name("qns_indexer".to_string()) {
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0,
|
||||
content: "net: only qns_indexer can update qns data".into(),
|
||||
})
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let _ = print_tx
|
||||
.send(Printout {
|
||||
verbosity: 0, // TODO 1
|
||||
content: format!("net: got QNS update for {}", log.name),
|
||||
})
|
||||
.await;
|
||||
|
||||
let _ = pki.write().await.insert(
|
||||
log.name.clone(),
|
||||
Identity {
|
||||
name: log.name.clone(),
|
||||
networking_key: log.public_key,
|
||||
ws_routing: if log.ip == "0.0.0.0".to_string() || log.port == 0 {
|
||||
None
|
||||
} else {
|
||||
Some((log.ip, log.port))
|
||||
},
|
||||
allowed_routers: log.routers,
|
||||
},
|
||||
);
|
||||
let _ = names.write().await.insert(log.node, log.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* networking utils
|
||||
*/
|
||||
|
||||
fn make_ws_url(our_ip: &str, ip: &str, port: &u16) -> Result<url::Url, SendErrorKind> {
|
||||
// if we have the same public IP as target, route locally,
|
||||
// otherwise they will appear offline due to loopback stuff
|
||||
let ip = if our_ip == ip { "localhost" } else { ip };
|
||||
match url::Url::parse(&format!("ws://{}:{}/ws", ip, port)) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => Err(SendErrorKind::Offline),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* handshake utils
|
||||
*/
|
||||
|
||||
/// take in handshake and PKI identity, and confirm that the handshake is valid.
|
||||
/// takes in optional nonce, which must be the one that connection initiator created.
|
||||
fn validate_handshake(
|
||||
handshake: &Handshake,
|
||||
their_id: &Identity,
|
||||
) -> Result<Arc<PublicKey<Secp256k1>>, String> {
|
||||
let their_networking_key = signature::UnparsedPublicKey::new(
|
||||
&signature::ED25519,
|
||||
hex::decode(&strip_0x(&their_id.networking_key))
|
||||
.map_err(|_| "failed to decode networking key")?,
|
||||
);
|
||||
|
||||
if !(their_networking_key
|
||||
.verify(
|
||||
// TODO use language-neutral serialization here too
|
||||
&bincode::serialize(their_id).map_err(|_| "failed to serialize their identity")?,
|
||||
&handshake.id_signature,
|
||||
)
|
||||
.is_ok()
|
||||
&& their_networking_key
|
||||
.verify(
|
||||
&handshake.ephemeral_public_key,
|
||||
&handshake.ephemeral_public_key_signature,
|
||||
)
|
||||
.is_ok())
|
||||
{
|
||||
// improper signatures on identity info, close connection
|
||||
return Err("got improperly signed networking info".into());
|
||||
}
|
||||
|
||||
match PublicKey::<Secp256k1>::from_sec1_bytes(&handshake.ephemeral_public_key) {
|
||||
Ok(v) => return Ok(Arc::new(v)),
|
||||
Err(_) => return Err("error".into()),
|
||||
};
|
||||
}
|
||||
|
||||
/// given an identity and networking key-pair, produces a handshake message along
|
||||
/// with an ephemeral secret to be used in a specific connection.
|
||||
fn make_secret_and_handshake(
|
||||
our: &Identity,
|
||||
keypair: Arc<Ed25519KeyPair>,
|
||||
target: &str,
|
||||
id: Option<u64>,
|
||||
) -> (Arc<EphemeralSecret<Secp256k1>>, Handshake) {
|
||||
// produce ephemeral keys for DH exchange and subsequent symmetric encryption
|
||||
let ephemeral_secret = Arc::new(EphemeralSecret::<k256::Secp256k1>::random(
|
||||
&mut rand::rngs::OsRng,
|
||||
));
|
||||
let ephemeral_public_key = ephemeral_secret.public_key();
|
||||
// sign the ephemeral public key with our networking management key
|
||||
let signed_pk = keypair
|
||||
.sign(&ephemeral_public_key.to_sec1_bytes())
|
||||
.as_ref()
|
||||
.to_vec();
|
||||
|
||||
// before signing our identity, convert router names to namehashes
|
||||
// to match the exact onchain representation of our identity
|
||||
let mut our_onchain_id = our.clone();
|
||||
our_onchain_id.allowed_routers = our
|
||||
.allowed_routers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|namehash| {
|
||||
let hash = crate::namehash(&namehash);
|
||||
let mut result = [0u8; 32];
|
||||
result.copy_from_slice(hash.as_bytes());
|
||||
format!("0x{}", hex::encode(result))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO use language-neutral serialization here too
|
||||
let signed_id = keypair
|
||||
.sign(&bincode::serialize(&our_onchain_id).unwrap())
|
||||
.as_ref()
|
||||
.to_vec();
|
||||
|
||||
let handshake = Handshake {
|
||||
id: id.unwrap_or(rand::random()),
|
||||
from: our.name.clone(),
|
||||
target: target.to_string(),
|
||||
id_signature: signed_id,
|
||||
ephemeral_public_key: ephemeral_public_key.to_sec1_bytes().to_vec(),
|
||||
ephemeral_public_key_signature: signed_pk,
|
||||
};
|
||||
|
||||
(ephemeral_secret, handshake)
|
||||
}
|
||||
|
||||
fn strip_0x(s: &str) -> String {
|
||||
if s.starts_with("0x") {
|
||||
s[2..].to_string()
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
74
src/net/types.rs
Normal file
74
src/net/types.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
use elliptic_curve::ecdh::SharedSecret;
|
||||
use ethers::prelude::k256::Secp256k1;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
|
||||
pub type PeerKeys = Arc<RwLock<HashMap<String, (Identity, Arc<SharedSecret<Secp256k1>>)>>>;
|
||||
pub type Peers = Arc<RwLock<HashMap<String, Peer>>>;
|
||||
pub type WebSocket = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
pub type MessageResult = Result<NetworkMessage, (u64, SendErrorKind)>;
|
||||
pub type ErrorShuttle = mpsc::UnboundedSender<MessageResult>;
|
||||
|
||||
/// stored in mapping by their username
|
||||
pub struct Peer {
|
||||
pub identity: Identity,
|
||||
// send messages here to have them encrypted and sent across an active connection
|
||||
pub sender: mpsc::UnboundedSender<(PeerMessage, Option<ErrorShuttle>)>,
|
||||
// send encrypted messages from this peer here to have them decrypted and sent to kernel
|
||||
pub decrypter: mpsc::UnboundedSender<(Vec<u8>, ErrorShuttle)>,
|
||||
pub socket_tx: mpsc::UnboundedSender<(NetworkMessage, Option<ErrorShuttle>)>,
|
||||
}
|
||||
|
||||
/// parsed from Binary websocket message
|
||||
/// TODO add a version number somewhere in the serialized format!!
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum NetworkMessage {
|
||||
Ack(u64),
|
||||
Nack(u64),
|
||||
Msg {
|
||||
id: u64,
|
||||
from: String,
|
||||
to: String,
|
||||
contents: Vec<u8>,
|
||||
},
|
||||
Handshake(Handshake),
|
||||
HandshakeAck(Handshake),
|
||||
}
|
||||
|
||||
pub enum PeerMessage {
|
||||
Raw(KernelMessage),
|
||||
Net(NetworkMessage),
|
||||
}
|
||||
|
||||
/// contains identity and encryption keys, used in initial handshake.
|
||||
/// parsed from Text websocket message
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Handshake {
|
||||
pub id: u64,
|
||||
pub from: String,
|
||||
pub target: String,
|
||||
pub id_signature: Vec<u8>,
|
||||
pub ephemeral_public_key: Vec<u8>,
|
||||
pub ephemeral_public_key_signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum NetActions {
|
||||
QnsUpdate(QnsUpdate),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct QnsUpdate {
|
||||
pub name: String, // actual username / domain name
|
||||
pub owner: String,
|
||||
pub node: String, // hex namehash of node
|
||||
pub public_key: String,
|
||||
pub ip: String,
|
||||
pub port: u16,
|
||||
pub routers: Vec<String>,
|
||||
}
|
142
src/process_lib.rs
Normal file
142
src/process_lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::bindings::component::uq_process::types::*;
|
||||
use super::bindings::{get_payload, send_request, Address, Payload};
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_and_await_response(
|
||||
target: &Address,
|
||||
inherit: bool,
|
||||
ipc: Option<Json>,
|
||||
metadata: Option<Json>,
|
||||
payload: Option<&Payload>,
|
||||
timeout: u64,
|
||||
) -> Result<(Address, Message), SendError> {
|
||||
super::bindings::send_and_await_response(
|
||||
target,
|
||||
&Request {
|
||||
inherit,
|
||||
expects_response: Some(timeout),
|
||||
ipc,
|
||||
metadata,
|
||||
},
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_state(our: String) -> Option<Payload> {
|
||||
let _ = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::GetState).unwrap()),
|
||||
None,
|
||||
None,
|
||||
5, // TODO evaluate timeout
|
||||
);
|
||||
get_payload()
|
||||
}
|
||||
|
||||
pub fn set_state(our: String, bytes: Vec<u8>) {
|
||||
send_request(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
&Request {
|
||||
inherit: false,
|
||||
expects_response: Some(5), // TODO evaluate timeout
|
||||
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
metadata: None,
|
||||
},
|
||||
None,
|
||||
Some(&Payload { mime: None, bytes }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn await_set_state<T>(our: String, state: &T)
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
// Request/Response stays local -> no SendError
|
||||
let (_, response) = send_and_await_response(
|
||||
&Address {
|
||||
node: our,
|
||||
process: ProcessId::Name("filesystem".to_string()),
|
||||
},
|
||||
false,
|
||||
Some(serde_json::to_string(&FsAction::SetState).unwrap()),
|
||||
None,
|
||||
Some(&Payload {
|
||||
mime: None,
|
||||
bytes: bincode::serialize(state).unwrap(),
|
||||
}),
|
||||
5, // TODO evaluate timeout
|
||||
)
|
||||
.unwrap();
|
||||
match response {
|
||||
Message::Request(_) => panic!("got request from filesystem"),
|
||||
Message::Response((response, _context)) => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>
|
||||
where
|
||||
for<'a> T: serde::Deserialize<'a>,
|
||||
{
|
||||
let parsed: T = serde_json::from_str(
|
||||
json_string
|
||||
.ok_or(anyhow::anyhow!("json payload empty"))?
|
||||
.as_str(),
|
||||
)?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
// move these to better place!
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
// process state management
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file_uuid: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
244
src/register.rs
Normal file
244
src/register.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use aes_gcm::aead::KeyInit;
|
||||
use hmac::Hmac;
|
||||
use jwt::SignWithKey;
|
||||
use ring::pkcs8::Document;
|
||||
use ring::rand::SystemRandom;
|
||||
use ring::signature;
|
||||
use sha2::Sha256;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use warp::{
|
||||
http::header::{HeaderValue, SET_COOKIE},
|
||||
Filter, Rejection, Reply,
|
||||
};
|
||||
|
||||
use crate::http_server;
|
||||
use crate::keygen;
|
||||
use crate::types::*;
|
||||
|
||||
type RegistrationSender = mpsc::Sender<(Identity, String, Document, Vec<u8>)>;
|
||||
|
||||
pub fn generate_jwt(jwt_secret_bytes: &[u8], username: String) -> Option<String> {
|
||||
let jwt_secret: Hmac<Sha256> = match Hmac::new_from_slice(&jwt_secret_bytes) {
|
||||
Ok(secret) => secret,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
let claims = JwtClaims {
|
||||
username: username.clone(),
|
||||
expiration: 0,
|
||||
};
|
||||
|
||||
match claims.sign_with_key(&jwt_secret) {
|
||||
Ok(token) => Some(token),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serve the registration page and receive POSTs and PUTs from it
|
||||
pub async fn register(
|
||||
tx: RegistrationSender,
|
||||
kill_rx: oneshot::Receiver<bool>,
|
||||
ip: String,
|
||||
port: u16,
|
||||
redir_port: u16,
|
||||
) {
|
||||
let our = Arc::new(Mutex::new(None));
|
||||
let pw = Arc::new(Mutex::new(None));
|
||||
let networking_keypair = Arc::new(Mutex::new(None));
|
||||
|
||||
let our_post = our.clone();
|
||||
let pw_post = pw.clone();
|
||||
let networking_keypair_post = networking_keypair.clone();
|
||||
|
||||
let static_files = warp::path("static").and(warp::fs::dir("./src/register_app/static/"));
|
||||
let react_app = warp::path::end()
|
||||
.and(warp::get())
|
||||
.and(warp::fs::file("./src/register_app/index.html"));
|
||||
|
||||
let api = warp::path("get-ws-info").and(
|
||||
// 1. Get uqname (already on chain) and return networking information
|
||||
warp::post()
|
||||
.and(warp::body::content_length_limit(1024 * 16))
|
||||
.and(warp::body::json())
|
||||
.and(warp::any().map(move || ip.clone()))
|
||||
.and(warp::any().map(move || our_post.clone()))
|
||||
.and(warp::any().map(move || pw_post.clone()))
|
||||
.and(warp::any().map(move || networking_keypair_post.clone()))
|
||||
.and_then(handle_post)
|
||||
// 2. trigger for finalizing registration once on-chain actions are done
|
||||
.or(warp::put()
|
||||
.and(warp::body::content_length_limit(1024 * 16))
|
||||
.and(warp::any().map(move || tx.clone()))
|
||||
.and(warp::any().map(move || our.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || pw.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || networking_keypair.lock().unwrap().take().unwrap()))
|
||||
.and(warp::any().map(move || redir_port))
|
||||
.and_then(handle_put)),
|
||||
);
|
||||
|
||||
let routes = static_files.or(react_app).or(api);
|
||||
|
||||
let _ = open::that(format!("http://localhost:{}/", port));
|
||||
warp::serve(routes)
|
||||
.bind_with_graceful_shutdown(([0, 0, 0, 0], port), async {
|
||||
kill_rx.await.ok();
|
||||
})
|
||||
.1
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_post(
|
||||
info: Registration,
|
||||
ip: String,
|
||||
our_post: Arc<Mutex<Option<Identity>>>,
|
||||
pw_post: Arc<Mutex<Option<String>>>,
|
||||
networking_keypair_post: Arc<Mutex<Option<Document>>>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
// 1. Generate networking keys
|
||||
let (public_key, serialized_networking_keypair) = keygen::generate_networking_key();
|
||||
*networking_keypair_post.lock().unwrap() = Some(serialized_networking_keypair);
|
||||
|
||||
// 2. generate ws and routing information
|
||||
// TODO: if IP is localhost, assign a router...
|
||||
let ws_port = http_server::find_open_port(9000).await.unwrap();
|
||||
let our = Identity {
|
||||
name: info.username.clone(),
|
||||
networking_key: public_key,
|
||||
ws_routing: if ip == "localhost" || !info.direct {
|
||||
None
|
||||
} else {
|
||||
Some((ip.clone(), ws_port))
|
||||
},
|
||||
allowed_routers: if ip == "localhost" || !info.direct {
|
||||
vec![
|
||||
"uqbar-router-1.uq".into(), // "0x8d9e54427c50660c6d4802f63edca86a9ca5fd6a78070c4635950e9d149ed441".into(),
|
||||
"uqbar-router-2.uq".into(), // "0x06d331ed65843ecf0860c73292005d8103af20820546b2f8f9007d01f60595b1".into(),
|
||||
"uqbar-router-3.uq".into(), // "0xe6ab611eb62e8aee0460295667f8179cda4315982717db4b0b3da6022deecac1".into(),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
};
|
||||
*our_post.lock().unwrap() = Some(our.clone());
|
||||
*pw_post.lock().unwrap() = Some(info.password);
|
||||
// Return a response containing all networking information
|
||||
Ok(warp::reply::json(&our))
|
||||
}
|
||||
|
||||
async fn handle_put(
|
||||
sender: RegistrationSender,
|
||||
our: Identity,
|
||||
pw: String,
|
||||
networking_keypair: Document,
|
||||
_redir_port: u16,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let seed = SystemRandom::new();
|
||||
let mut jwt_secret = [0u8; 32];
|
||||
ring::rand::SecureRandom::fill(&seed, &mut jwt_secret).unwrap();
|
||||
|
||||
let token = match generate_jwt(&jwt_secret, our.name.clone()) {
|
||||
Some(token) => token,
|
||||
None => return Err(warp::reject()),
|
||||
};
|
||||
let cookie_value = format!("uqbar-auth_{}={};", &our.name, &token);
|
||||
let ws_cookie_value = format!("uqbar-ws-auth_{}={};", &our.name, &token);
|
||||
|
||||
let mut response = warp::reply::html("Success".to_string()).into_response();
|
||||
|
||||
let headers = response.headers_mut();
|
||||
headers.append(SET_COOKIE, HeaderValue::from_str(&cookie_value).unwrap());
|
||||
headers.append(SET_COOKIE, HeaderValue::from_str(&ws_cookie_value).unwrap());
|
||||
|
||||
sender
|
||||
.send((our, pw, networking_keypair, jwt_secret.to_vec()))
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Serve the login page, just get a password
|
||||
pub async fn login(
|
||||
tx: mpsc::Sender<(
|
||||
String,
|
||||
Vec<String>,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
)>,
|
||||
kill_rx: oneshot::Receiver<bool>,
|
||||
keyfile: Vec<u8>,
|
||||
port: u16,
|
||||
) {
|
||||
let login_page = include_str!("login.html");
|
||||
let redirect_to_login =
|
||||
warp::path::end().map(|| warp::redirect(warp::http::Uri::from_static("/login")));
|
||||
let routes = warp::path("login")
|
||||
.and(
|
||||
// 1. serve login.html right here
|
||||
warp::get()
|
||||
.map(move || warp::reply::html(login_page))
|
||||
// 2. await a single POST
|
||||
// - password
|
||||
.or(warp::post()
|
||||
.and(warp::body::content_length_limit(1024 * 16))
|
||||
.and(warp::body::json())
|
||||
.and(warp::any().map(move || keyfile.clone()))
|
||||
.and(warp::any().map(move || tx.clone()))
|
||||
.and_then(handle_password)),
|
||||
)
|
||||
.or(redirect_to_login);
|
||||
|
||||
let _ = open::that(format!("http://localhost:{}/login", port));
|
||||
warp::serve(routes)
|
||||
.bind_with_graceful_shutdown(([0, 0, 0, 0], port), async {
|
||||
kill_rx.await.ok();
|
||||
})
|
||||
.1
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn handle_password(
|
||||
password: serde_json::Value,
|
||||
keyfile: Vec<u8>,
|
||||
tx: mpsc::Sender<(
|
||||
String,
|
||||
Vec<String>,
|
||||
signature::Ed25519KeyPair,
|
||||
Vec<u8>,
|
||||
Vec<u8>,
|
||||
)>,
|
||||
) -> Result<impl Reply, Rejection> {
|
||||
let password = match password["password"].as_str() {
|
||||
Some(p) => p,
|
||||
None => return Err(warp::reject()),
|
||||
};
|
||||
// use password to decrypt networking keys
|
||||
let (username, routers, networking_keypair, jwt_secret_bytes, file_key) =
|
||||
keygen::decode_keyfile(keyfile, password);
|
||||
|
||||
let token = match generate_jwt(&jwt_secret_bytes, username.clone()) {
|
||||
Some(token) => token,
|
||||
None => return Err(warp::reject()),
|
||||
};
|
||||
let cookie_value = format!("uqbar-auth_{}={};", &username, &token);
|
||||
let ws_cookie_value = format!("uqbar-ws-auth_{}={};", &username, &token);
|
||||
|
||||
let mut response = warp::reply::html("Success".to_string()).into_response();
|
||||
|
||||
let headers = response.headers_mut();
|
||||
headers.append(SET_COOKIE, HeaderValue::from_str(&cookie_value).unwrap());
|
||||
headers.append(SET_COOKIE, HeaderValue::from_str(&ws_cookie_value).unwrap());
|
||||
|
||||
tx.send((
|
||||
username,
|
||||
routers,
|
||||
networking_keypair,
|
||||
jwt_secret_bytes.to_vec(),
|
||||
file_key.to_vec(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
// TODO unhappy paths where key has changed / can't be decrypted
|
||||
Ok(response)
|
||||
}
|
13
src/register_app/asset-manifest.json
Normal file
13
src/register_app/asset-manifest.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.9b4dbe66.css",
|
||||
"main.js": "/static/js/main.f54b619d.js",
|
||||
"index.html": "/index.html",
|
||||
"main.9b4dbe66.css.map": "/static/css/main.9b4dbe66.css.map",
|
||||
"main.f54b619d.js.map": "/static/js/main.f54b619d.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.9b4dbe66.css",
|
||||
"static/js/main.f54b619d.js"
|
||||
]
|
||||
}
|
1
src/register_app/index.html
Normal file
1
src/register_app/index.html
Normal file
File diff suppressed because one or more lines are too long
25
src/register_app/manifest.json
Normal file
25
src/register_app/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
src/register_app/robots.txt
Normal file
3
src/register_app/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
2
src/register_app/static/css/main.9b4dbe66.css
Normal file
2
src/register_app/static/css/main.9b4dbe66.css
Normal file
@ -0,0 +1,2 @@
|
||||
:root{--uq-vlightpurple:#d9dcfc;--uq-lightpurple:#c7cafa;--uq-purple:#727bf2;--uq-darkpurple:#5761ef;--midnightpurp:#0a1170;--forgottenpurp:#45475e;--uq-lightpink:#f3ced2;--uq-pink:#dd7c8a;--uq-darkpink:#cd3c52;--blush:#d55d6f;--celeste:#adebe5;--lturq:#6bdbd0;--turq:#3acfc0;--celadon:#21897e;--deep-jungle:#14524c;--old-mint:#659792;--washed-gray:rgba(0,0,0,.03);--light-gray:rgba(0,0,0,.1);--medium-gray:rgba(0,0,0,.2);--dark-gray:rgba(0,0,0,.5);--charcoal:#333}body{background-color:#027;color:#fff;font-family:Press Start\ 2P;font-size:16px;margin:0;padding:0}h1,h2,h3,h4,h5,h6{line-height:1.5em}.col{flex-direction:column}.col,.row{align-items:center;display:flex}.row{flex-direction:row}input[type=password],input[type=text]{border:1px solid #ccc;border-radius:4px;padding:.5em .75em}button,input[type=password],input[type=text]{box-sizing:border-box;font-size:1em;margin-bottom:.5em;width:100%}button{background-color:#dd7c8a;background-color:var(--uq-pink);border:1px solid #dd7c8a;border:1px solid var(--uq-pink);border-radius:4px;box-shadow:0 1px 2px #cd3c52;box-shadow:0 1px 2px var(--uq-darkpink);color:#fff;cursor:pointer;font-family:Press Start\ 2P;margin-top:1em;padding:1em;transition:all .1s}button:hover{background-color:#cd3c52;background-color:var(--uq-darkpink);border:1px solid #cd3c52;border:1px solid var(--uq-darkpink)}#signup-page{display:flex;flex:1 1;height:100%;max-width:calc(100vw - 4em);padding:2em;width:100%}label{font-size:.75em}.label-row{align-self:flex-start;margin:.5em 0}.tooltip-container{cursor:pointer;display:inline-block;position:relative}.tooltip-button{border:2px solid #fff;border-radius:50%;font-size:16px;height:1em;line-height:.5em;margin-left:.5em;text-align:center;width:1em}.tooltip-content{background-color:#555;border-radius:6px;color:#fff;font-family:sans-serif;left:50%;line-height:1.5em;margin-left:-60px;min-width:200px;opacity:0;padding:.5em;position:absolute;text-align:center;top:125%;transition:opacity .3s;visibility:hidden;z-index:1}.tooltip-content:after{border:5px solid transparent;border-bottom-color:#555;bottom:100%;content:"";left:30%;margin-left:-5px;position:absolute}.tooltip-container:hover .tooltip-content{opacity:1;visibility:visible}#signup-form{max-width:420px}#current-address{font-family:Courier New,Courier,monospace;font-size:1.25em;font-weight:600;margin-left:1em}.current-username{border:1px solid #fff;border-radius:4px;cursor:pointer;margin:1em 0;padding:.5em}.current-username:hover{background-color:#fff;border:1px solid #fff;color:#027}#connect-wallet{max-width:420px}#wallet-required-message{line-height:1.5em;max-width:500px;text-align:center}#loader{display:inline-block;height:48px;margin-top:16px;position:relative;width:48px}#loader div{-webkit-animation:loader 1.2s cubic-bezier(.5,0,.5,1) infinite;animation:loader 1.2s cubic-bezier(.5,0,.5,1) infinite;border:6px solid transparent;border-radius:50%;border-top-color:#fff;box-sizing:border-box;display:block;height:36px;margin:6px;position:absolute;width:36px}#loader div:first-child{-webkit-animation-delay:-.45s;animation-delay:-.45s}#loader div:nth-child(2){-webkit-animation-delay:-.3s;animation-delay:-.3s}#loader div:nth-child(3){-webkit-animation-delay:-.15s;animation-delay:-.15s}@-webkit-keyframes loader{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes loader{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}
|
||||
/*# sourceMappingURL=main.9b4dbe66.css.map*/
|
1
src/register_app/static/css/main.9b4dbe66.css.map
Normal file
1
src/register_app/static/css/main.9b4dbe66.css.map
Normal file
File diff suppressed because one or more lines are too long
3
src/register_app/static/js/main.f54b619d.js
Normal file
3
src/register_app/static/js/main.f54b619d.js
Normal file
File diff suppressed because one or more lines are too long
103
src/register_app/static/js/main.f54b619d.js.LICENSE.txt
Normal file
103
src/register_app/static/js/main.f54b619d.js.LICENSE.txt
Normal file
@ -0,0 +1,103 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remix-run/router v1.9.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router DOM v6.16.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.16.0
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* [js-sha3]{@link https://github.com/emn178/js-sha3}
|
||||
*
|
||||
* @version 0.8.0
|
||||
* @author Chen, Yi-Cyuan [emn178@gmail.com]
|
||||
* @copyright Chen, Yi-Cyuan 2015-2018
|
||||
* @license MIT
|
||||
*/
|
1
src/register_app/static/js/main.f54b619d.js.map
Normal file
1
src/register_app/static/js/main.f54b619d.js.map
Normal file
File diff suppressed because one or more lines are too long
674
src/terminal.rs
Normal file
674
src/terminal.rs
Normal file
@ -0,0 +1,674 @@
|
||||
use anyhow::Result;
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{
|
||||
DisableBracketedPaste, EnableBracketedPaste, Event, EventStream, KeyCode, KeyEvent,
|
||||
KeyModifiers,
|
||||
},
|
||||
execute,
|
||||
style::Print,
|
||||
terminal::{self, disable_raw_mode, enable_raw_mode, ClearType},
|
||||
};
|
||||
use futures::{future::FutureExt, StreamExt};
|
||||
use std::collections::VecDeque;
|
||||
use std::fs::{read_to_string, File, OpenOptions};
|
||||
use std::io::{stdout, BufWriter, Write};
|
||||
|
||||
use crate::types::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CommandHistory {
|
||||
pub lines: VecDeque<String>,
|
||||
pub working_line: Option<String>,
|
||||
pub max_size: usize,
|
||||
pub index: usize,
|
||||
pub history_writer: BufWriter<File>,
|
||||
}
|
||||
|
||||
impl CommandHistory {
|
||||
fn new(max_size: usize, history: String, history_writer: BufWriter<File>) -> Self {
|
||||
let mut lines = VecDeque::with_capacity(max_size);
|
||||
for line in history.lines() {
|
||||
lines.push_front(line.to_string());
|
||||
}
|
||||
Self {
|
||||
lines,
|
||||
working_line: None,
|
||||
max_size,
|
||||
index: 0,
|
||||
history_writer,
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, line: String) {
|
||||
self.working_line = None;
|
||||
// only add line to history if it's not exactly the same
|
||||
// as the previous line
|
||||
if &line != self.lines.front().unwrap_or(&"".into()) {
|
||||
let _ = writeln!(self.history_writer, "{}", &line);
|
||||
self.lines.push_front(line);
|
||||
}
|
||||
self.index = 0;
|
||||
if self.lines.len() > self.max_size {
|
||||
self.lines.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prev(&mut self, working_line: &str) -> Option<String> {
|
||||
if self.lines.len() == 0 || self.index == self.lines.len() {
|
||||
return None;
|
||||
}
|
||||
self.index += 1;
|
||||
if self.index == 1 {
|
||||
self.working_line = Some(working_line.into());
|
||||
}
|
||||
let line = self.lines[self.index - 1].clone();
|
||||
Some(line)
|
||||
}
|
||||
|
||||
fn get_next(&mut self) -> Option<String> {
|
||||
if self.lines.len() == 0 || self.index == 0 || self.index == 1 {
|
||||
self.index = 0;
|
||||
if let Some(line) = self.working_line.clone() {
|
||||
self.working_line = None;
|
||||
return Some(line);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
self.index -= 1;
|
||||
Some(self.lines[self.index - 1].clone())
|
||||
}
|
||||
|
||||
/// if depth = 0, find most recent command in history that contains the
|
||||
/// provided string. otherwise, skip the first <depth> matches.
|
||||
/// yes this is O(n) to provide desired ordering, can revisit if slow
|
||||
fn search(&mut self, find: &str, depth: usize) -> Option<String> {
|
||||
let mut skips = 0;
|
||||
for line in &self.lines {
|
||||
if line.contains(find) && skips == depth {
|
||||
return Some(line.to_string());
|
||||
}
|
||||
skips += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* terminal driver
|
||||
*/
|
||||
pub async fn terminal(
|
||||
our: Identity,
|
||||
version: &str,
|
||||
home_directory_path: String,
|
||||
event_loop: MessageSender,
|
||||
debug_event_loop: DebugSender,
|
||||
print_tx: PrintSender,
|
||||
mut print_rx: PrintReceiver,
|
||||
) -> Result<()> {
|
||||
let mut stdout = stdout();
|
||||
execute!(
|
||||
stdout,
|
||||
EnableBracketedPaste,
|
||||
terminal::SetTitle(format!("{}@{}", our.name, "uqbar"))
|
||||
)?;
|
||||
|
||||
// print initial splash screen
|
||||
println!(
|
||||
"\x1b[38;5;128m{}\x1b[0m",
|
||||
format!(
|
||||
r#"
|
||||
|
||||
,, UU
|
||||
s# lUL UU !p
|
||||
!UU lUL UU !UUlb
|
||||
#U !UU lUL UU !UUUUU#
|
||||
UU !UU lUL UU !UUUUUUUb
|
||||
UU !UU %" ;- !UUUUUUUU#
|
||||
$ UU !UU @UU#p !UUUUUUUUU#
|
||||
]U UU !# @UUUUS !UUUUUUUUUUb
|
||||
@U UU ! @UUUUUUlUUUUUUUUUUU 888
|
||||
UU UU ! @UUUUUUUUUUUUUUUUUU 888
|
||||
@U UU ! @UUUUUU!UUUUUUUUUUU 888
|
||||
'U UU !# @UUUU# !UUUUUUUUUU~ 888 888 .d88888 88888b. 8888b. 888d888
|
||||
\ UU !UU @UU#^ !UUUUUUUUU# 888 888 d88" 888 888 "88b "88b 888P"
|
||||
UU !UU @Np ,," !UUUUUUUU# 888 888 888 888 888 888 .d888888 888
|
||||
UU !UU lUL UU !UUUUUUU^ Y88b 888 Y88b 888 888 d88P 888 888 888
|
||||
"U !UU lUL UU !UUUUUf "Y88888 "Y88888 88888P" "Y888888 888
|
||||
!UU lUL UU !UUl^ 888
|
||||
`" lUL UU '^ 888 {}
|
||||
"" 888 version {}
|
||||
|
||||
"#,
|
||||
our.name, version
|
||||
)
|
||||
);
|
||||
|
||||
enable_raw_mode()?;
|
||||
let mut reader = EventStream::new();
|
||||
let mut current_line = format!("{} > ", our.name);
|
||||
let prompt_len: usize = our.name.len() + 3;
|
||||
let (mut win_cols, mut win_rows) = terminal::size().unwrap();
|
||||
let mut cursor_col: u16 = prompt_len.try_into().unwrap();
|
||||
let mut line_col: usize = cursor_col as usize;
|
||||
let mut in_step_through: bool = false;
|
||||
// TODO add more verbosity levels as needed?
|
||||
// defaulting to TRUE for now, as we are BUIDLING
|
||||
// DEMO: default to false
|
||||
let mut verbose_mode: bool = false;
|
||||
let mut search_mode: bool = false;
|
||||
let mut search_depth: usize = 0;
|
||||
|
||||
let history_path = std::fs::canonicalize(&home_directory_path)
|
||||
.unwrap()
|
||||
.join(".terminal_history");
|
||||
let history = read_to_string(&history_path).unwrap_or_default();
|
||||
let history_handle = OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&history_path)
|
||||
.unwrap();
|
||||
let history_writer = BufWriter::new(history_handle);
|
||||
// TODO make adjustable max history length
|
||||
let mut command_history = CommandHistory::new(1000, history, history_writer);
|
||||
|
||||
let log_path = std::fs::canonicalize(&home_directory_path)
|
||||
.unwrap()
|
||||
.join(".terminal_log");
|
||||
let log_handle = OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&log_path)
|
||||
.unwrap();
|
||||
let mut log_writer = BufWriter::new(log_handle);
|
||||
|
||||
loop {
|
||||
let event = reader.next().fuse();
|
||||
|
||||
tokio::select! {
|
||||
prints = print_rx.recv() => match prints {
|
||||
Some(printout) => {
|
||||
let _ = writeln!(log_writer, "{}", printout.content);
|
||||
if match printout.verbosity {
|
||||
0 => false,
|
||||
1 => !verbose_mode,
|
||||
_ => true
|
||||
} {
|
||||
continue;
|
||||
}
|
||||
let mut stdout = stdout.lock();
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows - 1),
|
||||
terminal::Clear(ClearType::CurrentLine)
|
||||
)?;
|
||||
for line in printout.content.lines() {
|
||||
execute!(
|
||||
stdout,
|
||||
Print(format!("\x1b[38;5;238m{}\x1b[0m\r\n", line)),
|
||||
)?;
|
||||
}
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
},
|
||||
None => {
|
||||
write!(stdout.lock(), "terminal: lost print channel, crashing")?;
|
||||
break;
|
||||
}
|
||||
},
|
||||
maybe_event = event => match maybe_event {
|
||||
Some(Ok(event)) => {
|
||||
let mut stdout = stdout.lock();
|
||||
match event {
|
||||
// resize is super annoying because this event trigger often
|
||||
// comes "too late" to stop terminal from messing with the
|
||||
// already-printed lines. TODO figure out the right way
|
||||
// to compensate for this cross-platform and do this in a
|
||||
// generally stable way.
|
||||
Event::Resize(width, height) => {
|
||||
win_cols = width;
|
||||
win_rows = height;
|
||||
},
|
||||
// handle pasting of text from outside
|
||||
Event::Paste(pasted) => {
|
||||
current_line.insert_str(line_col, &pasted);
|
||||
line_col = current_line.len();
|
||||
cursor_col = std::cmp::min(line_col.try_into().unwrap_or(win_cols), win_cols);
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
}
|
||||
// CTRL+C, CTRL+D: turn off the node
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) |
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('d'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
execute!(stdout, DisableBracketedPaste, terminal::SetTitle(""))?;
|
||||
disable_raw_mode()?;
|
||||
break;
|
||||
},
|
||||
// CTRL+V: toggle verbose mode
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('v'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
let _ = print_tx.send(
|
||||
Printout {
|
||||
verbosity: 0,
|
||||
content: match verbose_mode {
|
||||
true => "verbose mode off".into(),
|
||||
false => "verbose mode on".into(),
|
||||
}
|
||||
}
|
||||
).await;
|
||||
verbose_mode = !verbose_mode;
|
||||
},
|
||||
// CTRL+J: toggle debug mode -- makes system-level event loop step-through
|
||||
// CTRL+S: step through system-level event loop
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('j'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
let _ = print_tx.send(
|
||||
Printout {
|
||||
verbosity: 0,
|
||||
content: match in_step_through {
|
||||
true => "debug mode off".into(),
|
||||
false => "debug mode on: use CTRL+S to step through events".into(),
|
||||
}
|
||||
}
|
||||
).await;
|
||||
let _ = debug_event_loop.send(DebugCommand::Toggle).await;
|
||||
in_step_through = !in_step_through;
|
||||
},
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
let _ = debug_event_loop.send(DebugCommand::Step).await;
|
||||
},
|
||||
//
|
||||
// UP / CTRL+P: go up one command in history
|
||||
// DOWN / CTRL+N: go down one command in history
|
||||
//
|
||||
Event::Key(KeyEvent { code: KeyCode::Up, .. }) |
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('p'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
// go up one command in history
|
||||
match command_history.get_prev(¤t_line[prompt_len..]) {
|
||||
Some(line) => {
|
||||
current_line = format!("{} > {}", our.name, line);
|
||||
line_col = current_line.len();
|
||||
},
|
||||
None => {
|
||||
print!("\x07");
|
||||
},
|
||||
}
|
||||
cursor_col = std::cmp::min(current_line.len() as u16, win_cols);
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
terminal::Clear(ClearType::CurrentLine),
|
||||
Print(truncate_rightward(¤t_line, prompt_len, win_cols)),
|
||||
)?;
|
||||
},
|
||||
Event::Key(KeyEvent { code: KeyCode::Down, .. }) |
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('n'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
// go down one command in history
|
||||
match command_history.get_next() {
|
||||
Some(line) => {
|
||||
current_line = format!("{} > {}", our.name, line);
|
||||
line_col = current_line.len();
|
||||
},
|
||||
None => {
|
||||
print!("\x07");
|
||||
},
|
||||
}
|
||||
cursor_col = std::cmp::min(current_line.len() as u16, win_cols);
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
terminal::Clear(ClearType::CurrentLine),
|
||||
Print(truncate_rightward(¤t_line, prompt_len, win_cols)),
|
||||
)?;
|
||||
},
|
||||
//
|
||||
// CTRL+A: jump to beginning of line
|
||||
//
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
line_col = prompt_len;
|
||||
cursor_col = prompt_len.try_into().unwrap();
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_from_left(¤t_line, prompt_len, win_cols, line_col)),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
},
|
||||
//
|
||||
// CTRL+E: jump to end of line
|
||||
//
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('e'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
line_col = current_line.len();
|
||||
cursor_col = std::cmp::min(line_col.try_into().unwrap_or(win_cols), win_cols);
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_from_right(¤t_line, prompt_len, win_cols, line_col)),
|
||||
)?;
|
||||
},
|
||||
//
|
||||
// CTRL+R: enter search mode
|
||||
// if already in search mode, increase search depth
|
||||
//
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('r'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
if search_mode {
|
||||
search_depth += 1;
|
||||
}
|
||||
search_mode = true;
|
||||
if let Some(result) = command_history.search(¤t_line[prompt_len..], search_depth) {
|
||||
// todo show search result with search query underlined
|
||||
// and cursor in correct spot
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(
|
||||
&format!("{} * {}", our.name, result),
|
||||
prompt_len,
|
||||
win_cols,
|
||||
(line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
} else {
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
}
|
||||
},
|
||||
//
|
||||
// CTRL+G: exit search mode
|
||||
//
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('g'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
..
|
||||
}) => {
|
||||
// just show true current line as usual
|
||||
search_mode = false;
|
||||
search_depth = 0;
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
terminal::Clear(ClearType::CurrentLine),
|
||||
Print(truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
},
|
||||
//
|
||||
// handle keypress events
|
||||
//
|
||||
Event::Key(k) => {
|
||||
match k.code {
|
||||
KeyCode::Char(c) => {
|
||||
current_line.insert(line_col, c);
|
||||
if cursor_col < win_cols {
|
||||
cursor_col += 1;
|
||||
}
|
||||
line_col += 1;
|
||||
if search_mode {
|
||||
if let Some(result) = command_history.search(¤t_line[prompt_len..], search_depth) {
|
||||
// todo show search result with search query underlined
|
||||
// and cursor in correct spot
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(
|
||||
&format!("{} * {}", our.name, result),
|
||||
prompt_len,
|
||||
win_cols,
|
||||
(line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
continue
|
||||
}
|
||||
}
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
},
|
||||
KeyCode::Backspace => {
|
||||
if line_col == prompt_len {
|
||||
continue;
|
||||
}
|
||||
if cursor_col as usize == line_col {
|
||||
cursor_col -= 1;
|
||||
}
|
||||
line_col -= 1;
|
||||
current_line.remove(line_col);
|
||||
if search_mode {
|
||||
if let Some(result) = command_history.search(¤t_line[prompt_len..], search_depth) {
|
||||
// todo show search result with search query underlined
|
||||
// and cursor in correct spot
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_in_place(
|
||||
&format!("{} * {}", our.name, result),
|
||||
prompt_len,
|
||||
win_cols,
|
||||
(line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
continue
|
||||
}
|
||||
}
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
terminal::Clear(ClearType::CurrentLine),
|
||||
Print(truncate_in_place(¤t_line, prompt_len, win_cols, (line_col, cursor_col))),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
},
|
||||
KeyCode::Left => {
|
||||
if cursor_col as usize == prompt_len {
|
||||
if line_col == prompt_len {
|
||||
// at the very beginning of the current typed line
|
||||
continue;
|
||||
} else {
|
||||
// virtual scroll leftward through line
|
||||
line_col -= 1;
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_from_left(¤t_line, prompt_len, win_cols, line_col)),
|
||||
cursor::MoveTo(cursor_col, win_rows),
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
// simply move cursor and line position left
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveLeft(1),
|
||||
)?;
|
||||
cursor_col -= 1;
|
||||
line_col -= 1;
|
||||
}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
if line_col == current_line.len() {
|
||||
// at the very end of the current typed line
|
||||
continue;
|
||||
}
|
||||
if cursor_col < (win_cols - 1) {
|
||||
// simply move cursor and line position right
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveRight(1),
|
||||
)?;
|
||||
cursor_col += 1;
|
||||
line_col += 1;
|
||||
} else {
|
||||
// virtual scroll rightward through line
|
||||
line_col += 1;
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
Print(truncate_from_right(¤t_line, prompt_len, win_cols, line_col)),
|
||||
)?;
|
||||
}
|
||||
},
|
||||
KeyCode::Enter => {
|
||||
// if we were in search mode, pull command from that
|
||||
let command = if !search_mode {
|
||||
current_line[prompt_len..].to_string()
|
||||
} else {
|
||||
command_history.search(
|
||||
¤t_line[prompt_len..],
|
||||
search_depth
|
||||
).unwrap_or(current_line[prompt_len..].to_string())
|
||||
};
|
||||
let next = format!("{} > ", our.name);
|
||||
execute!(
|
||||
stdout,
|
||||
cursor::MoveTo(0, win_rows),
|
||||
terminal::Clear(ClearType::CurrentLine),
|
||||
Print(¤t_line),
|
||||
Print("\r\n"),
|
||||
Print(&next),
|
||||
)?;
|
||||
search_mode = false;
|
||||
search_depth = 0;
|
||||
current_line = next;
|
||||
command_history.add(command.clone());
|
||||
cursor_col = prompt_len.try_into().unwrap();
|
||||
line_col = prompt_len;
|
||||
let _err = event_loop.send(
|
||||
KernelMessage {
|
||||
id: rand::random(),
|
||||
source: Address {
|
||||
node: our.name.clone(),
|
||||
process: ProcessId::Name("terminal".into()),
|
||||
},
|
||||
target: Address {
|
||||
node: our.name.clone(),
|
||||
process: ProcessId::Name("terminal".into()),
|
||||
},
|
||||
rsvp: None,
|
||||
message: Message::Request(Request {
|
||||
inherit: false,
|
||||
expects_response: None,
|
||||
ipc: Some(command),
|
||||
metadata: None,
|
||||
}),
|
||||
payload: None,
|
||||
signed_capabilities: None,
|
||||
}
|
||||
).await;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => println!("Error: {:?}\r", e),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
execute!(stdout.lock(), DisableBracketedPaste, terminal::SetTitle(""))?;
|
||||
disable_raw_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate_rightward(s: &str, prompt_len: usize, width: u16) -> String {
|
||||
if s.len() <= width as usize {
|
||||
// no adjustment to be made
|
||||
return s.to_string();
|
||||
}
|
||||
let sans_prompt = &s[prompt_len..];
|
||||
s[..prompt_len].to_string() + &sans_prompt[(s.len() - width as usize)..]
|
||||
}
|
||||
|
||||
/// print prompt, then as many chars as will fit in term starting from line_col
|
||||
fn truncate_from_left(s: &str, prompt_len: usize, width: u16, line_col: usize) -> String {
|
||||
if s.len() <= width as usize {
|
||||
// no adjustment to be made
|
||||
return s.to_string();
|
||||
}
|
||||
s[..prompt_len].to_string() + &s[line_col..(width as usize - prompt_len + line_col)]
|
||||
}
|
||||
|
||||
/// print prompt, then as many chars as will fit in term leading up to line_col
|
||||
fn truncate_from_right(s: &str, prompt_len: usize, width: u16, line_col: usize) -> String {
|
||||
if s.len() <= width as usize {
|
||||
// no adjustment to be made
|
||||
return s.to_string();
|
||||
}
|
||||
s[..prompt_len].to_string() + &s[(prompt_len + (line_col - width as usize))..line_col]
|
||||
}
|
||||
|
||||
/// if line is wider than the terminal, truncate it intelligently,
|
||||
/// keeping the cursor in the same relative position.
|
||||
fn truncate_in_place(
|
||||
s: &str,
|
||||
prompt_len: usize,
|
||||
width: u16,
|
||||
(line_col, cursor_col): (usize, u16),
|
||||
) -> String {
|
||||
if s.len() <= width as usize {
|
||||
// no adjustment to be made
|
||||
return s.to_string();
|
||||
}
|
||||
// always keep prompt at left
|
||||
let prompt = &s[..prompt_len];
|
||||
// print as much of the command fits left of col_in_command before cursor_col,
|
||||
// then fill out the rest up to width
|
||||
let end = width as usize + line_col - cursor_col as usize;
|
||||
if end > s.len() {
|
||||
return s.to_string();
|
||||
}
|
||||
prompt.to_string() + &s[(prompt_len + line_col - cursor_col as usize)..end]
|
||||
}
|
893
src/types.rs
Normal file
893
src/types.rs
Normal file
@ -0,0 +1,893 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
//
|
||||
// internal message pipes between kernel and runtime modules
|
||||
//
|
||||
|
||||
// keeps the from address so we know where to pipe error
|
||||
pub type NetworkErrorSender = tokio::sync::mpsc::Sender<WrappedSendError>;
|
||||
pub type NetworkErrorReceiver = tokio::sync::mpsc::Receiver<WrappedSendError>;
|
||||
|
||||
pub type MessageSender = tokio::sync::mpsc::Sender<KernelMessage>;
|
||||
pub type MessageReceiver = tokio::sync::mpsc::Receiver<KernelMessage>;
|
||||
|
||||
pub type PrintSender = tokio::sync::mpsc::Sender<Printout>;
|
||||
pub type PrintReceiver = tokio::sync::mpsc::Receiver<Printout>;
|
||||
|
||||
pub type DebugSender = tokio::sync::mpsc::Sender<DebugCommand>;
|
||||
pub type DebugReceiver = tokio::sync::mpsc::Receiver<DebugCommand>;
|
||||
|
||||
pub type CapMessageSender = tokio::sync::mpsc::UnboundedSender<CapMessage>;
|
||||
pub type CapMessageReceiver = tokio::sync::mpsc::UnboundedReceiver<CapMessage>;
|
||||
|
||||
//
|
||||
// types used for UQI: uqbar's identity system
|
||||
//
|
||||
pub type PKINames = Arc<RwLock<HashMap<String, String>>>; // TODO maybe U256 to String
|
||||
pub type OnchainPKI = Arc<RwLock<HashMap<String, Identity>>>;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Registration {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub direct: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Identity {
|
||||
pub name: String,
|
||||
pub networking_key: String,
|
||||
pub ws_routing: Option<(String, u16)>,
|
||||
pub allowed_routers: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdentityTransaction {
|
||||
pub from: String,
|
||||
pub signature: Option<String>,
|
||||
pub to: String, // contract address
|
||||
pub town_id: u32,
|
||||
pub calldata: Identity,
|
||||
pub nonce: String,
|
||||
}
|
||||
|
||||
//
|
||||
// process-facing kernel types, used for process
|
||||
// management and message-passing
|
||||
// matches types in uqbar.wit
|
||||
//
|
||||
|
||||
pub type Context = String; // JSON-string
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ProcessId {
|
||||
Id(u64),
|
||||
Name(String),
|
||||
}
|
||||
|
||||
impl PartialEq for ProcessId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ProcessId::Id(i1), ProcessId::Id(i2)) => i1 == i2,
|
||||
(ProcessId::Name(s1), ProcessId::Name(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<&str> for ProcessId {
|
||||
fn eq(&self, other: &&str) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(_) => false,
|
||||
ProcessId::Name(s) => s == other,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq<u64> for ProcessId {
|
||||
fn eq(&self, other: &u64) -> bool {
|
||||
match self {
|
||||
ProcessId::Id(i) => i == other,
|
||||
ProcessId::Name(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Address {
|
||||
pub node: String,
|
||||
pub process: ProcessId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Payload {
|
||||
pub mime: Option<String>, // MIME type
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Request {
|
||||
pub inherit: bool,
|
||||
pub expects_response: Option<u64>, // number of seconds until timeout
|
||||
pub ipc: Option<String>, // JSON-string
|
||||
pub metadata: Option<String>, // JSON-string
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Response {
|
||||
pub ipc: Option<String>, // JSON-string
|
||||
pub metadata: Option<String>, // JSON-string
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Message {
|
||||
Request(Request),
|
||||
Response((Response, Option<Context>)),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Capability {
|
||||
pub issuer: Address,
|
||||
pub params: String, // JSON-string
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SignedCapability {
|
||||
pub issuer: Address,
|
||||
pub params: String, // JSON-string
|
||||
pub signature: Vec<u8>, // signed by the kernel, so we can verify that the kernel issued it
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SendError {
|
||||
pub kind: SendErrorKind,
|
||||
pub target: Address, // what the message was trying to reach
|
||||
pub message: Message,
|
||||
pub payload: Option<Payload>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum SendErrorKind {
|
||||
Offline,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OnPanic {
|
||||
None,
|
||||
Restart,
|
||||
Requests(Vec<(Address, Request, Option<Payload>)>),
|
||||
}
|
||||
|
||||
impl OnPanic {
|
||||
pub fn is_restart(&self) -> bool {
|
||||
match self {
|
||||
OnPanic::None => false,
|
||||
OnPanic::Restart => true,
|
||||
OnPanic::Requests(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// kernel types that runtime modules use
|
||||
//
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ProcessMetadata {
|
||||
pub our: Address,
|
||||
pub wasm_bytes_handle: u128,
|
||||
pub on_panic: OnPanic,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct KernelMessage {
|
||||
pub id: u64,
|
||||
pub source: Address,
|
||||
pub target: Address,
|
||||
pub rsvp: Rsvp,
|
||||
pub message: Message,
|
||||
pub payload: Option<Payload>,
|
||||
pub signed_capabilities: Option<Vec<SignedCapability>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WrappedSendError {
|
||||
pub id: u64,
|
||||
pub source: Address,
|
||||
pub error: SendError,
|
||||
}
|
||||
|
||||
/// A terminal printout. Verbosity level is from low to high, and for
|
||||
/// now, only 0 and 1 are used. Level 0 is always printed, level 1 is
|
||||
/// only printed if the terminal is in verbose mode. Numbers greater
|
||||
/// than 1 are reserved for future use and will be ignored for now.
|
||||
pub struct Printout {
|
||||
pub verbosity: u8,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
// kernel sets in case, e.g.,
|
||||
// A requests response from B does not request response from C
|
||||
// -> kernel sets `Some(A) = Rsvp` for B's request to C
|
||||
pub type Rsvp = Option<Address>;
|
||||
|
||||
//
|
||||
// boot/startup specific types???
|
||||
//
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SequentializeRequest {
|
||||
QueueMessage(QueueMessage),
|
||||
RunQueue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct QueueMessage {
|
||||
pub target: ProcessId,
|
||||
pub request: Request,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct BootOutboundRequest {
|
||||
pub target_process: ProcessId,
|
||||
pub json: Option<String>,
|
||||
pub bytes: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum DebugCommand {
|
||||
Toggle,
|
||||
Step,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum KernelCommand {
|
||||
StartProcess {
|
||||
name: Option<String>,
|
||||
wasm_bytes_handle: u128,
|
||||
on_panic: OnPanic,
|
||||
initial_capabilities: HashSet<Capability>,
|
||||
},
|
||||
KillProcess(ProcessId), // this is extrajudicial killing: we might lose messages!
|
||||
RebootProcess {
|
||||
// kernel only
|
||||
process_id: ProcessId,
|
||||
persisted: PersistedProcess,
|
||||
},
|
||||
Shutdown,
|
||||
// capabilities creation
|
||||
GrantCapability {
|
||||
to_process: ProcessId,
|
||||
params: String, // JSON-string
|
||||
},
|
||||
}
|
||||
|
||||
pub enum CapMessage {
|
||||
Add {
|
||||
on: ProcessId,
|
||||
cap: Capability,
|
||||
},
|
||||
Drop {
|
||||
// not used yet!
|
||||
on: ProcessId,
|
||||
cap: Capability,
|
||||
},
|
||||
Has {
|
||||
// a bool is given in response here
|
||||
on: ProcessId,
|
||||
cap: Capability,
|
||||
responder: tokio::sync::oneshot::Sender<bool>,
|
||||
},
|
||||
GetAll {
|
||||
on: ProcessId,
|
||||
responder: tokio::sync::oneshot::Sender<HashSet<Capability>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum KernelResponse {
|
||||
StartedProcess(ProcessMetadata),
|
||||
KilledProcess(ProcessId),
|
||||
}
|
||||
|
||||
pub type ProcessMap = HashMap<ProcessId, PersistedProcess>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct PersistedProcess {
|
||||
pub wasm_bytes_handle: u128,
|
||||
pub on_panic: OnPanic,
|
||||
pub capabilities: HashSet<Capability>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ProcessContext {
|
||||
// store ultimate in order to set prompting message if needed
|
||||
pub prompting_message: Option<KernelMessage>,
|
||||
// can be empty if a request doesn't set context, but still needs to inherit
|
||||
pub context: Option<Context>,
|
||||
}
|
||||
|
||||
//
|
||||
// runtime-module-specific types
|
||||
//
|
||||
|
||||
//
|
||||
// filesystem.rs types
|
||||
//
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsAction {
|
||||
Write,
|
||||
Replace(u128),
|
||||
WriteOffset((u128, u64)),
|
||||
Append(Option<u128>),
|
||||
Read(u128),
|
||||
ReadChunk(ReadChunkRequest),
|
||||
Delete(u128),
|
||||
Length(u128),
|
||||
SetLength((u128, u64)),
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ReadChunkRequest {
|
||||
pub file: u128,
|
||||
pub start: u64,
|
||||
pub length: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum FsResponse {
|
||||
Write(u128),
|
||||
Read(u128),
|
||||
ReadChunk(u128),
|
||||
Append(u128),
|
||||
Delete(u128),
|
||||
Length(u64),
|
||||
GetState,
|
||||
SetState,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct S3Config {
|
||||
pub access_key: String,
|
||||
pub secret_key: String,
|
||||
pub region: String,
|
||||
pub bucket: String,
|
||||
pub endpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FsConfig {
|
||||
pub s3_config: Option<S3Config>,
|
||||
pub mem_buffer_limit: usize,
|
||||
pub chunk_size: usize,
|
||||
pub flush_to_cold_interval: usize,
|
||||
pub encryption: bool,
|
||||
pub cloud_enabled: bool,
|
||||
// pub flush_to_wal_interval: usize,
|
||||
}
|
||||
|
||||
impl VfsError {
|
||||
pub fn kind(&self) -> &str {
|
||||
match *self {
|
||||
VfsError::BadIdentifier => "BadIdentifier",
|
||||
VfsError::BadDescriptor => "BadDescriptor",
|
||||
VfsError::NoCap => "NoCap",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum VfsError {
|
||||
BadIdentifier,
|
||||
BadDescriptor,
|
||||
NoCap,
|
||||
}
|
||||
|
||||
impl FileSystemError {
|
||||
pub fn kind(&self) -> &str {
|
||||
match *self {
|
||||
FileSystemError::BadUri { .. } => "BadUri",
|
||||
FileSystemError::BadJson { .. } => "BadJson",
|
||||
FileSystemError::BadBytes { .. } => "BadBytes",
|
||||
FileSystemError::IllegalAccess { .. } => "IllegalAccess",
|
||||
FileSystemError::AlreadyOpen { .. } => "AlreadyOpen",
|
||||
FileSystemError::NotCurrentlyOpen { .. } => "NotCurrentlyOpen",
|
||||
FileSystemError::BadPathJoin { .. } => "BadPathJoin",
|
||||
FileSystemError::CouldNotMakeDir { .. } => "CouldNotMakeDir",
|
||||
FileSystemError::ReadFailed { .. } => "ReadFailed",
|
||||
FileSystemError::WriteFailed { .. } => "WriteFailed",
|
||||
FileSystemError::OpenFailed { .. } => "OpenFailed",
|
||||
FileSystemError::FsError { .. } => "FsError",
|
||||
FileSystemError::LFSError { .. } => "LFSErrror",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemError {
|
||||
// bad input from user
|
||||
#[error("Malformed URI: {uri}. Problem with {bad_part_name}: {:?}.", bad_part)]
|
||||
BadUri {
|
||||
uri: String,
|
||||
bad_part_name: String,
|
||||
bad_part: Option<String>,
|
||||
},
|
||||
#[error(
|
||||
"JSON payload could not be parsed to FileSystemRequest: {error}. Got {:?}.",
|
||||
json
|
||||
)]
|
||||
BadJson { json: String, error: String },
|
||||
#[error("Bytes payload required for {action}.")]
|
||||
BadBytes { action: String },
|
||||
#[error("{process_name} not allowed to access {attempted_dir}. Process may only access within {sandbox_dir}.")]
|
||||
IllegalAccess {
|
||||
process_name: String,
|
||||
attempted_dir: String,
|
||||
sandbox_dir: String,
|
||||
},
|
||||
#[error("Already have {path} opened with mode {:?}.", mode)]
|
||||
AlreadyOpen { path: String, mode: FileSystemMode },
|
||||
#[error("Don't have {path} opened with mode {:?}.", mode)]
|
||||
NotCurrentlyOpen { path: String, mode: FileSystemMode },
|
||||
// path or underlying fs problems
|
||||
#[error("Failed to join path: base: '{base_path}'; addend: '{addend}'.")]
|
||||
BadPathJoin { base_path: String, addend: String },
|
||||
#[error("Failed to create dir at {path}: {error}.")]
|
||||
CouldNotMakeDir { path: String, error: String },
|
||||
#[error("Failed to read {path}: {error}.")]
|
||||
ReadFailed { path: String, error: String },
|
||||
#[error("Failed to write {path}: {error}.")]
|
||||
WriteFailed { path: String, error: String },
|
||||
#[error("Failed to open {path} for {:?}: {error}.", mode)]
|
||||
OpenFailed {
|
||||
path: String,
|
||||
mode: FileSystemMode,
|
||||
error: String,
|
||||
},
|
||||
#[error("Filesystem error while {what} on {path}: {error}.")]
|
||||
FsError {
|
||||
what: String,
|
||||
path: String,
|
||||
error: String,
|
||||
},
|
||||
#[error("LFS error: {error}.")]
|
||||
LFSError { error: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FileSystemRequest {
|
||||
pub uri_string: String,
|
||||
pub action: FileSystemAction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemAction {
|
||||
Read,
|
||||
Write,
|
||||
GetMetadata,
|
||||
ReadDir,
|
||||
Open(FileSystemMode),
|
||||
Close(FileSystemMode),
|
||||
Append,
|
||||
ReadChunkFromOpen(u64),
|
||||
SeekWithinOpen(FileSystemSeekFrom),
|
||||
}
|
||||
|
||||
// copy of std::io::SeekFrom with Serialize/Deserialize
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemSeekFrom {
|
||||
Start(u64),
|
||||
End(i64),
|
||||
Current(i64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemResponse {
|
||||
Read(FileSystemUriHash),
|
||||
Write(String),
|
||||
GetMetadata(FileSystemMetadata),
|
||||
ReadDir(Vec<FileSystemMetadata>),
|
||||
Open {
|
||||
uri_string: String,
|
||||
mode: FileSystemMode,
|
||||
},
|
||||
Close {
|
||||
uri_string: String,
|
||||
mode: FileSystemMode,
|
||||
},
|
||||
Append(String),
|
||||
ReadChunkFromOpen(FileSystemUriHash),
|
||||
SeekWithinOpen(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FileSystemUriHash {
|
||||
pub uri_string: String,
|
||||
pub hash: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FileSystemMetadata {
|
||||
pub uri_string: String,
|
||||
pub hash: Option<u64>,
|
||||
pub entry_type: FileSystemEntryType,
|
||||
pub len: u64,
|
||||
}
|
||||
|
||||
#[derive(Eq, Hash, PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemMode {
|
||||
Read,
|
||||
Append,
|
||||
AppendOverwrite,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum FileSystemEntryType {
|
||||
Symlink,
|
||||
File,
|
||||
Dir,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum VfsRequest {
|
||||
New {
|
||||
identifier: String,
|
||||
},
|
||||
Add {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
entry_type: AddEntryType,
|
||||
},
|
||||
Rename {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
new_full_path: String,
|
||||
},
|
||||
Delete {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
},
|
||||
WriteOffset {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
offset: u64,
|
||||
},
|
||||
GetPath {
|
||||
identifier: String,
|
||||
hash: u128,
|
||||
},
|
||||
GetEntry {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
},
|
||||
GetFileChunk {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
},
|
||||
GetEntryLength {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum AddEntryType {
|
||||
Dir,
|
||||
NewFile, // add a new file to fs and add name in vfs
|
||||
ExistingFile { hash: u128 }, // link an existing file in fs to a new name in vfs
|
||||
// ... // symlinks?
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum GetEntryType {
|
||||
Dir,
|
||||
File,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum VfsResponse {
|
||||
New {
|
||||
identifier: String,
|
||||
},
|
||||
Add {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
},
|
||||
Rename {
|
||||
identifier: String,
|
||||
new_full_path: String,
|
||||
},
|
||||
Delete {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
},
|
||||
GetPath {
|
||||
identifier: String,
|
||||
hash: u128,
|
||||
full_path: Option<String>,
|
||||
},
|
||||
GetEntry {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
children: Vec<String>,
|
||||
},
|
||||
GetFileChunk {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
offset: u64,
|
||||
length: u64,
|
||||
},
|
||||
WriteOffset {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
offset: u64,
|
||||
},
|
||||
GetEntryLength {
|
||||
identifier: String,
|
||||
full_path: String,
|
||||
length: u64,
|
||||
},
|
||||
}
|
||||
|
||||
//
|
||||
// http_client.rs types
|
||||
//
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct HttpClientRequest {
|
||||
pub uri: String,
|
||||
pub method: String,
|
||||
pub headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct HttpClientResponse {
|
||||
pub status: u16,
|
||||
pub headers: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum HttpClientError {
|
||||
#[error("http_client: rsvp is None but message is expecting response")]
|
||||
BadRsvp,
|
||||
#[error("http_client: no json in request")]
|
||||
NoJson,
|
||||
#[error(
|
||||
"http_client: JSON payload could not be parsed to HttpClientRequest: {error}. Got {:?}.",
|
||||
json
|
||||
)]
|
||||
BadJson { json: String, error: String },
|
||||
#[error("http_client: http method not supported: {:?}", method)]
|
||||
BadMethod { method: String },
|
||||
#[error("http_client: failed to execute request {:?}", error)]
|
||||
RequestFailed { error: String },
|
||||
}
|
||||
|
||||
impl HttpClientError {
|
||||
pub fn kind(&self) -> &str {
|
||||
match *self {
|
||||
HttpClientError::BadRsvp { .. } => "BadRsvp",
|
||||
HttpClientError::NoJson { .. } => "NoJson",
|
||||
HttpClientError::BadJson { .. } => "BadJson",
|
||||
HttpClientError::BadMethod { .. } => "BadMethod",
|
||||
HttpClientError::RequestFailed { .. } => "RequestFailed",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// custom kernel displays
|
||||
//
|
||||
|
||||
impl std::fmt::Display for KernelMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{{\n id: {},\n source: {},\n target: {},\n rsvp: {:?},\n message: {}\n}}",
|
||||
self.id, self.source, self.target, self.rsvp, self.message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Message {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Message::Request(request) => write!(
|
||||
f,
|
||||
"Request(\n inherit: {},\n expects_response: {:#?},\n ipc: {},\n metadata: {}\n)",
|
||||
request.inherit,
|
||||
request.expects_response,
|
||||
&request.ipc.as_ref().unwrap_or(&"None".into()),
|
||||
&request.metadata.as_ref().unwrap_or(&"None".into()),
|
||||
),
|
||||
Message::Response((response, context)) => write!(
|
||||
f,
|
||||
"Response(\n ipc: {},\n metadata: {},\n context: {}\n)",
|
||||
&response.ipc.as_ref().unwrap_or(&"None".into()),
|
||||
&response.metadata.as_ref().unwrap_or(&"None".into()),
|
||||
&context.as_ref().unwrap_or(&"None".into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// http_server.rs types
|
||||
//
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct HttpResponse {
|
||||
pub status: u16,
|
||||
pub headers: HashMap<String, String>,
|
||||
pub body: Option<Vec<u8>>, // TODO does this use a lot of memory?
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum HttpServerError {
|
||||
#[error("http_server: json is None")]
|
||||
NoJson,
|
||||
#[error("http_server: response not ok")]
|
||||
ResponseError,
|
||||
#[error("http_server: bytes are None")]
|
||||
NoBytes,
|
||||
#[error(
|
||||
"http_server: JSON payload could not be parsed to HttpClientRequest: {error}. Got {:?}.",
|
||||
json
|
||||
)]
|
||||
BadJson { json: String, error: String },
|
||||
}
|
||||
|
||||
impl HttpServerError {
|
||||
pub fn kind(&self) -> &str {
|
||||
match *self {
|
||||
HttpServerError::NoJson { .. } => "NoJson",
|
||||
HttpServerError::NoBytes { .. } => "NoBytes",
|
||||
HttpServerError::BadJson { .. } => "BadJson",
|
||||
HttpServerError::ResponseError { .. } => "ResponseError",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JwtClaims {
|
||||
pub username: String,
|
||||
pub expiration: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WebSocketServerTarget {
|
||||
pub node: String,
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WebSocketPush {
|
||||
pub target: WebSocketServerTarget,
|
||||
pub is_text: Option<bool>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Address {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Address {
|
||||
node,
|
||||
process: ProcessId::Id(id),
|
||||
} => format!("{}/{}", node, id),
|
||||
Address {
|
||||
node,
|
||||
process: ProcessId::Name(name),
|
||||
} => format!("{}/{}", node, name),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ServerAction {
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum HttpServerMessage {
|
||||
WebSocketPush(WebSocketPush),
|
||||
ServerAction(ServerAction),
|
||||
WsRegister(WsRegister), // Coming from a proxy
|
||||
WsProxyDisconnect(WsProxyDisconnect), // Coming from a proxy
|
||||
WsMessage(WsMessage), // Coming from a proxy
|
||||
EncryptedWsMessage(EncryptedWsMessage), // Coming from a proxy
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WsRegister {
|
||||
pub ws_auth_token: String,
|
||||
pub auth_token: String,
|
||||
pub channel_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WsProxyDisconnect {
|
||||
// Doesn't require auth because it's coming from the proxy
|
||||
pub channel_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WsMessage {
|
||||
pub ws_auth_token: String,
|
||||
pub auth_token: String,
|
||||
pub channel_id: String,
|
||||
pub target: Address,
|
||||
pub json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EncryptedWsMessage {
|
||||
pub ws_auth_token: String,
|
||||
pub auth_token: String,
|
||||
pub channel_id: String,
|
||||
pub target: Address,
|
||||
pub encrypted: String, // Encrypted JSON as hex with the 32-byte authentication tag appended
|
||||
pub nonce: String, // Hex of the 12-byte nonce
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum WebSocketClientMessage {
|
||||
WsRegister(WsRegister),
|
||||
WsMessage(WsMessage),
|
||||
EncryptedWsMessage(EncryptedWsMessage),
|
||||
}
|
||||
// http_server End
|
||||
|
||||
// encryptor Start
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct GetKeyAction {
|
||||
pub channel_id: String,
|
||||
pub public_key_hex: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DecryptAndForwardAction {
|
||||
pub channel_id: String,
|
||||
pub forward_to: Address, // node, process
|
||||
pub json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EncryptAndForwardAction {
|
||||
pub channel_id: String,
|
||||
pub forward_to: Address, // node, process
|
||||
pub json: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DecryptAction {
|
||||
pub channel_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EncryptAction {
|
||||
pub channel_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum EncryptorMessage {
|
||||
GetKeyAction(GetKeyAction),
|
||||
DecryptAndForwardAction(DecryptAndForwardAction),
|
||||
EncryptAndForwardAction(EncryptAndForwardAction),
|
||||
DecryptAction(DecryptAction),
|
||||
EncryptAction(EncryptAction),
|
||||
}
|
||||
// encryptor End
|
1377
src/vfs.rs
Normal file
1377
src/vfs.rs
Normal file
File diff suppressed because it is too large
Load Diff
BIN
wasi_snapshot_preview1.wasm
Normal file
BIN
wasi_snapshot_preview1.wasm
Normal file
Binary file not shown.
26
wit/deps/random/insecure-seed.wit
Normal file
26
wit/deps/random/insecure-seed.wit
Normal file
@ -0,0 +1,26 @@
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/432b5471ec4bf6d51173def284cd418be6849a49/crates/wasi/wit/deps/random/insecure-seed.wit
|
||||
|
||||
/// The insecure-seed interface for seeding hash-map DoS resistance.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
interface insecure-seed {
|
||||
/// Return a 128-bit value that may contain a pseudo-random value.
|
||||
///
|
||||
/// The returned value is not required to be computed from a CSPRNG, and may
|
||||
/// even be entirely deterministic. Host implementations are encouraged to
|
||||
/// provide pseudo-random values to any program exposed to
|
||||
/// attacker-controlled content, to enable DoS protection built into many
|
||||
/// languages' hash-map implementations.
|
||||
///
|
||||
/// This function is intended to only be called once, by a source language
|
||||
/// to initialize Denial Of Service (DoS) protection in its hash-map
|
||||
/// implementation.
|
||||
///
|
||||
/// # Expected future evolution
|
||||
///
|
||||
/// This will likely be changed to a value import, to prevent it from being
|
||||
/// called multiple times and potentially used for purposes other than DoS
|
||||
/// protection.
|
||||
insecure-seed: func() -> tuple<u64, u64>
|
||||
}
|
23
wit/deps/random/insecure.wit
Normal file
23
wit/deps/random/insecure.wit
Normal file
@ -0,0 +1,23 @@
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/432b5471ec4bf6d51173def284cd418be6849a49/crates/wasi/wit/deps/random/insecure.wit
|
||||
|
||||
/// The insecure interface for insecure pseudo-random numbers.
|
||||
///
|
||||
/// It is intended to be portable at least between Unix-family platforms and
|
||||
/// Windows.
|
||||
interface insecure {
|
||||
/// Return `len` insecure pseudo-random bytes.
|
||||
///
|
||||
/// This function is not cryptographically secure. Do not use it for
|
||||
/// anything related to security.
|
||||
///
|
||||
/// There are no requirements on the values of the returned bytes, however
|
||||
/// implementations are encouraged to return evenly distributed values with
|
||||
/// a long period.
|
||||
get-insecure-random-bytes: func(len: u64) -> list<u8>
|
||||
|
||||
/// Return an insecure pseudo-random `u64` value.
|
||||
///
|
||||
/// This function returns the same type of pseudo-random data as
|
||||
/// `get-insecure-random-bytes`, represented as a `u64`.
|
||||
get-insecure-random-u64: func() -> u64
|
||||
}
|
23
wit/deps/random/random.wit
Normal file
23
wit/deps/random/random.wit
Normal file
@ -0,0 +1,23 @@
|
||||
// https://github.com/bytecodealliance/wasmtime/blob/432b5471ec4bf6d51173def284cd418be6849a49/crates/wasi/wit/deps/random/random.wit
|
||||
|
||||
package wasi:random
|
||||
|
||||
interface random {
|
||||
/// Return `len` cryptographically-secure pseudo-random bytes.
|
||||
///
|
||||
/// This function must produce data from an adequately seeded
|
||||
/// cryptographically-secure pseudo-random number generator (CSPRNG), so it
|
||||
/// must not block, from the perspective of the calling program, and the
|
||||
/// returned data is always unpredictable.
|
||||
///
|
||||
/// This function must always return fresh pseudo-random data. Deterministic
|
||||
/// environments must omit this function, rather than implementing it with
|
||||
/// deterministic data.
|
||||
get-random-bytes: func(len: u64) -> list<u8>
|
||||
|
||||
/// Return a cryptographically-secure pseudo-random `u64` value.
|
||||
///
|
||||
/// This function returns the same type of pseudo-random data as
|
||||
/// `get-random-bytes`, represented as a `u64`.
|
||||
get-random-u64: func() -> u64
|
||||
}
|
178
wit/uqbar.wit
Normal file
178
wit/uqbar.wit
Normal file
@ -0,0 +1,178 @@
|
||||
package component:uq-process
|
||||
|
||||
interface types {
|
||||
// JSON is passed over WASM boundary as a string.
|
||||
type json = string
|
||||
|
||||
// context is a string of UTF-8 JSON.
|
||||
// it is used when building a Request to save information
|
||||
// that will not be part of a Response, in order to more
|
||||
// easily handle ("contextualize") that Response.
|
||||
type context = json
|
||||
|
||||
variant process-id {
|
||||
id(u64),
|
||||
name(string),
|
||||
}
|
||||
|
||||
// TODO better name for this
|
||||
record address {
|
||||
node: string,
|
||||
process: process-id,
|
||||
}
|
||||
|
||||
record payload {
|
||||
mime: option<string>,
|
||||
bytes: list<u8>,
|
||||
}
|
||||
|
||||
record request {
|
||||
// if true, this request inherits context AND payload of incipient
|
||||
// request, and cannot have its own context.
|
||||
inherit: bool,
|
||||
// if Some, this request expects a response in the number of seconds given
|
||||
expects-response: option<u64>,
|
||||
ipc: option<json>,
|
||||
metadata: option<json>,
|
||||
// to grab payload, use get_payload()
|
||||
}
|
||||
|
||||
record response {
|
||||
ipc: option<json>,
|
||||
metadata: option<json>,
|
||||
// to grab payload, use get_payload()
|
||||
}
|
||||
|
||||
// a message can be a request or a response.
|
||||
// within a response, there is a result which surfaces any error
|
||||
// that happened because of a request.
|
||||
// a successful response will contain the context of the request
|
||||
// it matches, if any was set.
|
||||
variant message {
|
||||
request(request),
|
||||
response(tuple<response, option<context>>),
|
||||
}
|
||||
|
||||
variant capabilities {
|
||||
none,
|
||||
all,
|
||||
some(list<signed-capability>),
|
||||
}
|
||||
|
||||
record signed-capability {
|
||||
issuer: address,
|
||||
params: json,
|
||||
signature: list<u8>,
|
||||
}
|
||||
|
||||
// network errors come from trying to send a message to another node.
|
||||
// a message can fail by timing out, or by the node being entirely unreachable (offline).
|
||||
// in either case, the message is not delivered, and the process that sent it
|
||||
// receives that message along with any assigned context and/or payload,
|
||||
// and is free to handle it as it sees fit.
|
||||
// note that if the message is a response, the process can issue a response again,
|
||||
// and it will be directed to the same (remote) request as the original.
|
||||
record send-error {
|
||||
kind: send-error-kind,
|
||||
message: message,
|
||||
payload: option<payload>,
|
||||
}
|
||||
|
||||
enum send-error-kind {
|
||||
offline,
|
||||
timeout,
|
||||
}
|
||||
|
||||
// on-panic is a setting that determines what happens when a process panics.
|
||||
// NOTE: requests should have expects-response set to false, will always be set to that by kernel
|
||||
variant on-panic {
|
||||
none,
|
||||
restart,
|
||||
requests(list<tuple<address, request, option<payload>>>),
|
||||
}
|
||||
}
|
||||
|
||||
world uq-process {
|
||||
use types.{
|
||||
json,
|
||||
context,
|
||||
address,
|
||||
process-id,
|
||||
|
||||
payload,
|
||||
request,
|
||||
response,
|
||||
message,
|
||||
|
||||
capabilities,
|
||||
signed-capability,
|
||||
|
||||
send-error,
|
||||
send-error-kind,
|
||||
on-panic,
|
||||
}
|
||||
|
||||
// entry point to all programs
|
||||
export init: func(our: address)
|
||||
|
||||
// system utils:
|
||||
|
||||
import print-to-terminal: func(verbosity: u8, message: string)
|
||||
import get-unix-time: func() -> u64
|
||||
import get-eth-block: func() -> u64
|
||||
|
||||
// process management:
|
||||
|
||||
import set-on-panic: func(on-panic: on-panic)
|
||||
|
||||
// what should bytes_uri be?? need vfs?
|
||||
import spawn: func(id: process-id, bytes-uri: string, on-panic: on-panic, capabilities: capabilities) ->
|
||||
option<process-id>
|
||||
|
||||
// capabilities management
|
||||
|
||||
// gives us all our signed capabilities so we can send them to others
|
||||
import get-capabilities: func() -> list<signed-capability>
|
||||
|
||||
// gets a single specific capability
|
||||
import get-capability: func(issuer: address, params: json) -> option<signed-capability>
|
||||
|
||||
// attaches a specific signed capability to our next message
|
||||
import attach-capability: func(capability: signed-capability)
|
||||
|
||||
// saves capabilities to our store, so we can use them
|
||||
import save-capabilities: func(capabilities: list<signed-capability>)
|
||||
|
||||
// check to see if the sender of a prompting message has a given capability, issued by us
|
||||
// if the prompting message has a remote source, they must have attached it.
|
||||
import has-capability: func(params: json) -> bool
|
||||
|
||||
|
||||
// message I/O:
|
||||
|
||||
// ingest next message when it arrives along with its source.
|
||||
// almost all long-running processes will call this in a loop
|
||||
import receive: func() -> result<tuple<address, message>, tuple<send-error, option<context>>>
|
||||
|
||||
// gets payload, if any, of the message we just received
|
||||
import get-payload: func() -> option<payload>
|
||||
|
||||
// send message(s) to target(s)
|
||||
import send-request:
|
||||
func(target: address, request: request, context: option<context>, payload: option<payload>)
|
||||
import send-requests:
|
||||
func(requests: list<tuple<address, request, option<context>, option<payload>>>)
|
||||
import send-response:
|
||||
func(response: response, payload: option<payload>)
|
||||
|
||||
// send a single request, then block (internally) until its response
|
||||
// the type is Message but will always contain Response
|
||||
import send-and-await-response:
|
||||
func(target: address, request: request, payload: option<payload>) ->
|
||||
result<tuple<address, message>, send-error>
|
||||
|
||||
// wasi
|
||||
import wasi:random/insecure
|
||||
import wasi:random/insecure-seed
|
||||
import wasi:random/random
|
||||
}
|
Loading…
Reference in New Issue
Block a user