import from previous repo

This commit is contained in:
dr-frmr 2023-10-02 16:02:53 -04:00
commit 3bce8c973b
No known key found for this signature in database
96 changed files with 32319 additions and 0 deletions

13
.github/workflows/main.yml vendored Normal file
View 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
View File

@ -0,0 +1,8 @@
target/
.vscode
.app-signing
*.swp
/home
modules/**/wit
target.wasm
world

3
Cargo-component.lock Normal file
View 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

File diff suppressed because it is too large Load Diff

57
Cargo.toml Normal file
View 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
View 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
View 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
View 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

View 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
View 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",
]

View 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]

File diff suppressed because one or more lines are too long

View 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(),
}),
);
}
}
}
}
}
}
}
}

View 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,
}

View 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
View 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
View 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]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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
View 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;
}
}
}
}
}
}
}

View 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,
}

View 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
View 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",
]

View 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]

View 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

File diff suppressed because one or more lines are too long

View 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,
}

View 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
View 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",
]

View 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]

File diff suppressed because one or more lines are too long

View 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();
}
}
}
}

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

File diff suppressed because it is too large Load Diff

View 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,
}

View 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
View 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",
]

View 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
View 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;
}
}
}
}
}

View 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,
}

View 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

File diff suppressed because it is too large Load Diff

View 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]

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

View 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,
}

View 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
View 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
View 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
View 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;
}
}
}
}
}
}

View 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

File diff suppressed because one or more lines are too long

View 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
View 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",
]

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

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

File diff suppressed because it is too large Load Diff

580
src/filesystem/mod.rs Normal file
View 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
View 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
View 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
}

View 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

File diff suppressed because it is too large Load Diff

150
src/kernel/utils.rs Normal file
View 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
View 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
View 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

File diff suppressed because one or more lines are too long

537
src/main.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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)
}

View 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"
]
}

File diff suppressed because one or more lines are too long

View 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"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
*/

File diff suppressed because one or more lines are too long

674
src/terminal.rs Normal file
View 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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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(
&current_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(&current_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
View 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

File diff suppressed because it is too large Load Diff

BIN
wasi_snapshot_preview1.wasm Normal file

Binary file not shown.

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

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

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