merged main into this branch

This commit is contained in:
realisation 2023-10-13 19:43:54 -04:00
commit 0919dae011
88 changed files with 6584 additions and 3461 deletions

View File

@ -5,7 +5,7 @@
### Default values automatically set (s3 defaults to None)
# MEM_BUFFER=5242880 # 5mb
# MEM_BUFFER_LIMIT=5242880 # 5mb
# CHUNK_SIZE=262144 # 256kb
# FLUSH_TO_COLD_INTERVAL=60 # 60s
# ENCRYPTION=true # true
@ -16,4 +16,4 @@
# S3_SECRET__KEY=minioadmin
# S3_REGION=eu-north-1
# S3_BUCKET=uqbar
# S3_ENDPOINT=http://localhost:9000
# S3_ENDPOINT=http://localhost:9000

6
.gitignore vendored
View File

@ -1,8 +1,12 @@
target/
.vscode
.app-signing
.DS_Store
*.swp
*.swo
*.zip
/home
modules/**/pkg/*.wasm
modules/**/wit
target.wasm
world
world

10
Cargo.lock generated
View File

@ -525,14 +525,16 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.30"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets",
]
@ -4829,6 +4831,7 @@ dependencies = [
"bytes",
"cap-std",
"chacha20poly1305",
"chrono",
"cita_trie",
"crossterm",
"digest 0.10.7",
@ -4836,6 +4839,7 @@ dependencies = [
"elliptic-curve",
"ethers",
"ethers-providers",
"flate2",
"futures",
"generic-array",
"getrandom",
@ -4866,9 +4870,11 @@ dependencies = [
"tokio-tungstenite 0.20.0",
"url",
"uuid 1.4.1",
"walkdir",
"warp",
"wasmtime",
"wasmtime-wasi",
"zip",
]
[[package]]

View File

@ -5,6 +5,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
sha2 = "0.10"
walkdir = "2.4"
zip = "0.6"
[dependencies]
aes-gcm = "0.10.2"
anyhow = "1.0.71"
@ -15,6 +21,7 @@ blake3 = "1.4.1"
bytes = "1.4.0"
cap-std = "2.0.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.31"
cita_trie = "4.0.0"
crossterm = { version = "0.26.1", features = ["event-stream", "bracketed-paste"] }
digest = "0.10"
@ -22,6 +29,7 @@ dotenv = "0.15.0"
elliptic-curve = { version = "0.13.5", features = ["ecdh"] }
ethers = "2.0"
ethers-providers = "2.0.9"
flate2 = "1.0"
futures = "0.3"
generic-array = "0.14"
getrandom = "0.2.10"
@ -55,3 +63,4 @@ uuid = { version = "1.1.2", features = ["serde", "v4"] }
warp = "0.3.5"
wasmtime = "12.0.1"
wasmtime-wasi = "12.0.1"
zip = "0.6"

View File

@ -2,12 +2,9 @@
debug_flag="--release"
if [ $# -ne 1 ] && [ $# -ne 2 ]; then
echo "Usage: $0 <name> [--debug]"
exit 1
fi
name="$1"
# Grab the full path to the target
target_path="$1"
name=$(basename "$target_path")
if [[ "$2" == "--debug" ]]; then
debug_flag=""
@ -15,31 +12,26 @@ fi
pwd=$(pwd)
# Check if the --debug flag is present
if [[ "$@" == *"--debug"* ]]; then
debug_flag="--release"
fi
rm -rf "$target_path/wit" || { echo "Command failed"; exit 1; }
cp -r wit "$target_path" || { echo "Command failed"; exit 1; }
mkdir -p "$target_path/target/bindings/$name" || { echo "Command failed"; exit 1; }
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 "$target_path/target/bindings/$name/" || { echo "Command failed"; exit 1; }
cp world "$target_path/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; }
mkdir -p "$target_path/target/wasm32-unknown-unknown/release" || { echo "Command failed"; exit 1; }
# Build the module using Cargo
cargo build \
cargo +nightly build \
$debug_flag \
--no-default-features \
--manifest-path="$pwd/modules/$name/Cargo.toml"\
--manifest-path="$target_path/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; }
wasm-tools component new "$target_path/target/wasm32-wasi/release/$name.wasm" -o "$target_path/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; }
wasm-tools component embed wit --world uq-process "$target_path/target/wasm32-wasi/release/${name}_adapted.wasm" -o "$target_path/target/wasm32-unknown-unknown/release/$name.wasm" || { echo "Command failed"; exit 1; }

315
build.rs
View File

@ -1,5 +1,8 @@
use std::process::Command;
use std::{fs, io};
use std::{
fs, io,
io::{Read, Write},
};
fn run_command(cmd: &mut Command) -> io::Result<()> {
let status = cmd.status()?;
@ -10,11 +13,151 @@ fn run_command(cmd: &mut Command) -> io::Result<()> {
}
}
fn file_outdated<P1, P2>(input: P1, output: P2) -> io::Result<bool>
where
P1: AsRef<std::path::Path>,
P2: AsRef<std::path::Path>,
{
let out_meta = std::fs::metadata(output);
if let Ok(meta) = out_meta {
let output_mtime = meta.modified()?;
// if input file is more recent than our output, we are outdated
let input_meta = fs::metadata(input)?;
let input_mtime = input_meta.modified()?;
Ok(input_mtime > output_mtime)
} else {
// output file not found, we are outdated
Ok(true)
}
}
fn build_app(target_path: &str, name: &str, parent_pkg_path: Option<&str>) {
let pwd = std::env::current_dir().unwrap();
// Copy in newly-made wit IF old one is outdated
if file_outdated(
format!("{}/wit/", pwd.display()),
format!("{}/modules/{}/wit/", target_path, name),
)
.unwrap_or(true)
{
run_command(Command::new("cp").args(&["-r", "wit", target_path])).unwrap();
// create target/bindings directory
fs::create_dir_all(&format!("{}/target/bindings/{}", target_path, name,)).unwrap();
// copy newly-made target.wasm into target/bindings
run_command(Command::new("cp").args(&[
"target.wasm",
&format!("{}/target/bindings/{}/", target_path, name,),
]))
.unwrap();
// copy newly-made world into target/bindings
run_command(Command::new("cp").args(&[
"world",
&format!("{}/target/bindings/{}/", target_path, name,),
]))
.unwrap();
}
// Build the module targeting wasm32-wasi
run_command(Command::new("cargo").args(&[
"build",
"--release",
"--no-default-features",
&format!("--manifest-path={}/Cargo.toml", target_path),
"--target",
"wasm32-wasi",
]))
.unwrap();
// Adapt module to component with adapter based on wasi_snapshot_preview1.wasm
run_command(Command::new("wasm-tools").args(&[
"component",
"new",
&format!("{}/target/wasm32-wasi/release/{}.wasm", target_path, name),
"-o",
&format!(
"{}/target/wasm32-wasi/release/{}_adapted.wasm",
target_path, name
),
"--adapt",
&format!("{}/wasi_snapshot_preview1.wasm", pwd.display()),
]))
.unwrap();
// Determine the destination for the .wasm file after embedding wit
let wasm_dest_path = if let Some(parent_pkg) = parent_pkg_path {
format!("{}/{}.wasm", parent_pkg, name)
} else {
let pkg_folder = format!("{}/pkg/", target_path);
let _ = run_command(Command::new("mkdir").args(&["-p", &pkg_folder]));
format!("{}/{}.wasm", pkg_folder, name)
};
// Embed "wit" into the component
run_command(Command::new("wasm-tools").args(&[
"component",
"embed",
"wit",
"--world",
"uq-process",
&format!(
"{}/target/wasm32-wasi/release/{}_adapted.wasm",
target_path, name
),
"-o",
&wasm_dest_path,
]))
.unwrap();
}
fn main() {
if std::env::var("SKIP_BUILD_SCRIPT").is_ok() {
println!("Skipping build script");
return;
}
let build_enabled = std::env::var("BUILD_APPS")
.map(|v| v == "true")
.unwrap_or(true); // run by default
if !build_enabled {
return;
}
// only execute if one of the modules has source code changes
const WASI_APPS: [&str; 9] = [
"app_tracker",
"chess",
"homepage",
"http_bindings",
"http_proxy",
"orgs",
"qns_indexer",
"rpc",
"terminal",
];
// NOT YET building KV, waiting for deps to be ready
const NESTED_WASI_APPS: [(&str, &str); 2] = [
("key_value", "key_value"),
("key_value", "key_value_worker"),
];
if std::env::var("REBUILD_ALL").is_ok() {
} else {
for name in &WASI_APPS {
println!("cargo:rerun-if-changed=modules/{}/src", name);
println!("cargo:rerun-if-changed=modules/{}/Cargo.toml", name);
println!("cargo:rerun-if-changed=modules/{}/pkg/manifest.json", name);
println!("cargo:rerun-if-changed=modules/{}/pkg/metadata.json", name);
}
for (outer, inner) in &NESTED_WASI_APPS {
println!("cargo:rerun-if-changed=modules/{}/{}/src", outer, inner);
println!(
"cargo:rerun-if-changed=modules/{}/{}/Cargo.toml",
outer, inner
);
println!("cargo:rerun-if-changed=modules/{}/pkg/manifest.json", outer);
println!("cargo:rerun-if-changed=modules/{}/pkg/metadata.json", outer);
}
}
let pwd = std::env::current_dir().unwrap();
@ -34,124 +177,66 @@ fn main() {
run_command(Command::new("touch").args(&[&format!("{}/world", pwd.display())])).unwrap();
// Build wasm32-wasi apps.
const WASI_APPS: [&str; 8] = [
"homepage",
"chess",
"http_bindings",
"http_proxy",
"orgs",
"qns_indexer",
"rpc",
"terminal",
];
for name in WASI_APPS {
// only execute if one of the modules has source code changes
println!("cargo:rerun-if-changed=modules/{}/src", name);
// copy in the wit files
run_command(
Command::new("rm").args(&["-rf", &format!("{}/modules/{}/wit", pwd.display(), name)]),
)
.unwrap();
run_command(Command::new("cp").args(&[
"-r",
"wit",
&format!("{}/modules/{}", pwd.display(), name),
]))
.unwrap();
let modules_dir = format!("{}/modules", pwd.display());
for entry in std::fs::read_dir(&modules_dir).unwrap() {
let entry_path = entry.unwrap().path();
let package_name = entry_path.file_name().unwrap().to_str().unwrap();
// NOT YET building KV, waiting for deps to be ready
if package_name == "key_value" {
return;
}
fs::create_dir_all(&format!(
"{}/modules/{}/target/bindings/{}",
// If Cargo.toml is present, build the app
let parent_pkg_path = format!("{}/pkg", entry_path.display());
if entry_path.join("Cargo.toml").exists() {
build_app(&entry_path.display().to_string(), &package_name, None);
} else if entry_path.is_dir() {
fs::create_dir_all(&parent_pkg_path).unwrap();
// Otherwise, consider it a directory containing subdirectories with potential apps
for sub_entry in std::fs::read_dir(&entry_path).unwrap() {
let sub_entry_path = sub_entry.unwrap().path();
if sub_entry_path.join("Cargo.toml").exists() {
build_app(
&sub_entry_path.display().to_string(),
&sub_entry_path.file_name().unwrap().to_str().unwrap(),
Some(&parent_pkg_path),
);
}
}
}
// After processing all sub-apps, zip the parent's pkg/ directory
let writer = std::fs::File::create(format!(
"{}/target/{}.zip",
pwd.display(),
name,
name
entry_path.file_name().unwrap().to_str().unwrap()
))
.unwrap();
run_command(Command::new("cp").args(&[
"target.wasm",
&format!(
"{}/modules/{}/target/bindings/{}/",
pwd.display(),
name,
name
),
]))
.unwrap();
run_command(Command::new("cp").args(&[
"world",
&format!(
"{}/modules/{}/target/bindings/{}/",
pwd.display(),
name,
name
),
]))
.unwrap();
let options = zip::write::FileOptions::default()
.compression_method(zip::CompressionMethod::Stored)
.unix_permissions(0o755);
let mut zip = zip::ZipWriter::new(writer);
for sub_entry in walkdir::WalkDir::new(&parent_pkg_path) {
let sub_entry = sub_entry.unwrap();
let path = sub_entry.path();
let name = path
.strip_prefix(std::path::Path::new(&parent_pkg_path))
.unwrap();
fs::create_dir_all(&format!(
"{}/modules/{}/target/wasm32-unknown-unknown/release",
pwd.display(),
name
))
.unwrap();
// build the module
run_command(Command::new("cargo").args(&[
"build",
"--release",
"--no-default-features",
&format!(
"--manifest-path={}/modules/{}/Cargo.toml",
pwd.display(),
name
),
"--target",
"wasm32-wasi",
]))
.unwrap();
// adapt module to component with adaptor
run_command(Command::new("wasm-tools").args(&[
"component",
"new",
&format!(
"{}/modules/{}/target/wasm32-wasi/release/{}.wasm",
pwd.display(),
name,
name
),
"-o",
&format!(
"{}/modules/{}/target/wasm32-wasi/release/{}_adapted.wasm",
pwd.display(),
name,
name
),
"--adapt",
&format!("{}/wasi_snapshot_preview1.wasm", pwd.display()),
]))
.unwrap();
// put wit into component & place where boot sequence expects to find it
run_command(Command::new("wasm-tools").args(&[
"component",
"embed",
"wit",
"--world",
"uq-process",
&format!(
"{}/modules/{}/target/wasm32-wasi/release/{}_adapted.wasm",
pwd.display(),
name,
name
),
"-o",
&format!(
"{}/modules/{}/target/wasm32-unknown-unknown/release/{}.wasm",
pwd.display(),
name,
name
),
]))
.unwrap();
// Write a directory or file to the ZIP archive
if path.is_file() {
zip.start_file(name.to_string_lossy().into_owned(), options)
.unwrap();
let mut file = std::fs::File::open(path).unwrap();
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).unwrap();
zip.write_all(&buffer).unwrap();
} else if name.as_os_str().len() != 0 {
zip.add_directory(name.to_string_lossy().into_owned(), options)
.unwrap();
}
}
zip.finish().unwrap();
}
}

View File

@ -1,58 +0,0 @@
#!/bin/bash
all=false
debug="--release"
# prase arguments (--all, --release)
for arg in "$@"; do
case "$arg" in
--all)
all=true
;;
--debug)
debug="--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" $debug
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 $debug
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

453
modules/app_tracker/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 = "app_tracker"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"cargo-component-bindings",
"serde",
"serde_json",
"wit-bindgen",
]
[[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 = "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 = "app_tracker"
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]

View File

@ -0,0 +1,21 @@
[
{
"process_name": "app_tracker",
"process_wasm_path": "/app_tracker.wasm",
"on_panic": "Restart",
"request_networking": true,
"request_messaging": [
"http_bindings:http_bindings:uqbar",
"terminal:terminal:uqbar",
"filesystem:sys:uqbar",
"http_server:sys:uqbar",
"http_client:sys:uqbar",
"encryptor:sys:uqbar",
"net:sys:uqbar",
"vfs:sys:uqbar",
"kernel:sys:uqbar",
"eth_rpc:sys:uqbar"
],
"grant_messaging": []
}
]

View File

@ -0,0 +1,5 @@
{
"package": "app_tracker",
"publisher": "uqbar",
"desc": "A package manager + app store. This JSON field is optional and you can add whatever you want in addition to this."
}

View File

@ -0,0 +1,15 @@
### App Tracker: our built-in package manager that lives in userspace
*note: 'app' and 'package' will be used interchangeably, but they are the same thing. generally, end users should see 'apps', and developers and the system itself should see 'packages'*
### Backend
Tracker requires full read-write to filesystem, along with caps for every other distro app and runtime module. It takes all the caps because it needs the ability to grant them to packages we install!
In order to load in the currently installed packages, Tracker will access the VFS and read from a hardcoded set of
### Frontend
Tracker will present a frontend that shows all the apps you currently have installed. You can see some metadata about them, and uninstall them from this list.
Tracker will contain a "store" to browse for new apps to install. TODO

View File

@ -0,0 +1 @@
../../../src/kernel_types.rs

View File

@ -0,0 +1,342 @@
cargo_component_bindings::generate!();
use bindings::{
component::uq_process::types::*, get_capability, get_payload, print_to_terminal, receive, Guest, send_request, send_response
};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[allow(dead_code)]
mod kernel_types;
use kernel_types as kt;
#[allow(dead_code)]
mod process_lib;
struct Component;
#[derive(Debug, Serialize, Deserialize)]
pub enum AppTrackerRequest {
New { package: String },
Install { package: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ApptrackerResponse {
New { package: String },
Install { package: String },
Error { error: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageManifestEntry {
pub process_name: String,
pub process_wasm_path: String,
pub on_panic: kt::OnPanic,
pub request_networking: bool,
pub request_messaging: Vec<String>,
pub grant_messaging: Vec<String>, // special logic for the string "all": makes process public
}
fn parse_command(our: &Address, request_string: String) -> anyhow::Result<ApptrackerResponse> {
match serde_json::from_str(&request_string)? {
AppTrackerRequest::New { package } => {
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no payload"));
};
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::New,
})
.unwrap(),
),
None,
None,
5,
)?;
// add zip bytes
let _ = process_lib::send_and_await_response(
&vfs_address,
true,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::Add {
full_path: package.clone().into(),
entry_type: kt::AddEntryType::ZipArchive,
},
})
.unwrap(),
),
None,
Some(&payload),
5,
)?;
Ok(ApptrackerResponse::New { package })
}
AppTrackerRequest::Install { package } => {
let vfs_address = Address {
node: our.node.clone(),
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
};
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry("/manifest.json".into()),
})
.unwrap(),
),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no payload"));
};
let manifest = String::from_utf8(payload.bytes)?;
let manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&manifest).unwrap();
for entry in manifest {
let path = if entry.process_wasm_path.starts_with("/") {
entry.process_wasm_path
} else {
format!("/{}", entry.process_wasm_path)
};
let (_, hash_response) = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetHash(path.clone()),
})
.unwrap(),
),
None,
None,
5,
)?;
let Message::Response((Response { ipc: Some(ipc), .. }, _)) = hash_response else {
return Err(anyhow::anyhow!("bad vfs response"));
};
let kt::VfsResponse::GetHash(Some(hash)) = serde_json::from_str(&ipc).unwrap() else {
return Err(anyhow::anyhow!("no hash in vfs"));
};
// build initial caps
let mut initial_capabilities: HashSet<kt::SignedCapability> = HashSet::new();
if entry.request_networking {
let Some(networking_cap) = get_capability(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
},
&"\"network\"".to_string(),
) else {
return Err(anyhow::anyhow!("app_tracker: no net cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(networking_cap));
}
let Some(read_cap) = get_capability(
&vfs_address.clone(),
&serde_json::to_string(&serde_json::json!({
"kind": "read",
"drive": package,
})).unwrap(),
) else {
return Err(anyhow::anyhow!("app_tracker: no read cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(read_cap));
let Some(write_cap) = get_capability(
&vfs_address.clone(),
&serde_json::to_string(&serde_json::json!({
"kind": "write",
"drive": package,
})).unwrap(),
) else {
return Err(anyhow::anyhow!("app_tracker: no write cap"));
};
initial_capabilities.insert(kt::de_wit_signed_capability(write_cap));
let mut public = false;
for process_name in &entry.grant_messaging {
if process_name == "all" {
public = true;
continue;
}
let Ok(parsed_process_id) = ProcessId::from_str(&process_name) else {
continue;
};
let Some(messaging_cap) = get_capability(
&Address {
node: our.node.clone(),
process: parsed_process_id.clone(),
},
&"\"messaging\"".into()
) else {
return Err(anyhow::anyhow!(format!("app_tracker: no cap for {}", process_name)));
};
initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap));
}
// // TODO fix request?
// for process_name in &entry.request_messaging {
// let Ok(parsed_process_id) = ProcessId::from_str(process_name) else {
// continue;
// };
// let Some(messaging_cap) = get_capability(
// &Address {
// node: our.node.clone(),
// process: parsed_process_id.clone(),
// },
// &serde_json::to_string(&serde_json::json!({
// "messaging": kt::ProcessId::de_wit(parsed_process_id),
// })).unwrap(),
// ) else {
// return Err(anyhow::anyhow!(format!("app_tracker: no cap for {}", process_name)));
// };
// initial_capabilities.insert(kt::de_wit_signed_capability(messaging_cap));
// }
let process_id = format!("{}:{}", entry.process_name, package.clone());
let Ok(parsed_new_process_id) = ProcessId::from_str(&process_id) else {
return Err(anyhow::anyhow!("app_tracker: invalid process id!"));
};
let _ = process_lib::send_request(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
},
false,
Some(
serde_json::to_string(
&kt::KernelCommand::KillProcess(kt::ProcessId::de_wit(parsed_new_process_id.clone()))).unwrap()),
None,
None,
None,
);
// kernel start process takes bytes as payload + wasm_bytes_handle...
// reconsider perhaps
let (_, _bytes_response) = process_lib::send_and_await_response(
&vfs_address,
false,
Some(
serde_json::to_string(&kt::VfsRequest {
drive: package.clone(),
action: kt::VfsAction::GetEntry(path),
})
.unwrap(),
),
None,
None,
5,
)?;
let Some(payload) = get_payload() else {
return Err(anyhow::anyhow!("no wasm bytes payload."));
};
let _ = process_lib::send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("kernel:sys:uqbar").unwrap(),
},
false,
Some(
serde_json::to_string(&kt::KernelCommand::StartProcess {
id: kt::ProcessId::de_wit(parsed_new_process_id),
wasm_bytes_handle: hash,
on_panic: entry.on_panic,
initial_capabilities,
public,
})
.unwrap(),
),
None,
Some(&payload),
5,
)?;
}
Ok(ApptrackerResponse::Install { package })
}
}
}
impl Guest for Component {
fn init(our: Address) {
assert_eq!(our.process.to_string(), "app_tracker:app_tracker:uqbar");
print_to_terminal(0, &format!("app_tracker: start"));
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 { ipc, expects_response, metadata, .. }) => {
let Some(command) = ipc else {
continue;
};
match parse_command(&our, command) {
Ok(response) => {
if let Some(_) = expects_response {
let _ = send_response(
&Response {
ipc: Some(serde_json::to_string(&response).unwrap()),
metadata,
},
None,
);
};
}
Err(e) => {
print_to_terminal(0, &format!("app_tracker: got error {}", e));
if let Some(_) = expects_response {
let error = ApptrackerResponse::Error {
error: format!("{}", e),
};
let _ = send_response(
&Response {
ipc: Some(serde_json::to_string(&error).unwrap()),
metadata,
},
None,
);
};
}
}
}
_ => continue,
}
}
}
}

View File

@ -0,0 +1 @@
../../../src/process_lib.rs

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/pkg/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
[
{
"process_name": "chess",
"process_wasm_path": "/chess.wasm",
"on_panic": "Restart",
"request_networking": true,
"request_messaging": [
"http_bindings:http_bindings:uqbar",
"encryptor:sys:uqbar",
"http_server:sys:uqbar"
],
"grant_messaging": []
}
]

View File

@ -0,0 +1,4 @@
{
"package": "chess",
"publisher": "uqbar"
}

View File

@ -12,6 +12,7 @@ extern crate base64;
extern crate pleco;
use pleco::Board;
#[allow(dead_code)]
mod process_lib;
struct Component;
@ -104,7 +105,7 @@ fn send_ws_update(our_name: String, game: Game) {
send_request(
&Address {
node: our_name.clone(),
process: ProcessId::Name("encryptor".to_string()),
process: ProcessId::from_str("encryptor:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -160,13 +161,9 @@ fn response_success() -> bool {
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) {
fn save_chess_state(state: ChessState) {
let stored_state = convert_state(state);
process_lib::set_state(our, bincode::serialize(&stored_state).unwrap());
process_lib::set_state::<StoredChessState>(&stored_state);
}
const CHESS_PAGE: &str = include_str!("chess.html");
@ -179,7 +176,7 @@ impl Guest for Component {
let bindings_address = Address {
node: our.node.clone(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("http_bindings:http_bindings:uqbar").unwrap(),
};
// <address, request, option<context>, option<payload>>
@ -226,48 +223,42 @@ impl Guest for Component {
];
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,
let mut state: ChessState = match process_lib::get_state::<StoredChessState>() {
Some(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,
},
);
}
}
Err(_) => ChessState {
games: HashMap::new(),
records: HashMap::new(),
},
},
ChessState {
games,
records: state.records,
}
}
None => ChessState {
games: HashMap::new(),
records: HashMap::new(),
@ -296,7 +287,7 @@ impl Guest for Component {
print_to_terminal(1, "chess: parsed ipc JSON");
if source.process == ProcessId::Name("chess".to_string()) {
if source.process.to_string() == "chess:sys:uqbar" {
let action = message_json["action"].as_str().unwrap_or("");
let game_id = source.node.clone();
@ -336,7 +327,7 @@ impl Guest for Component {
send_ws_update(our.node.clone(), game.clone());
save_chess_state(our.node.clone(), state.clone());
save_chess_state(state.clone());
send_response(
&Response {
@ -410,7 +401,7 @@ impl Guest for Component {
}
send_ws_update(our.node.clone(), game.clone());
save_chess_state(our.node.clone(), state.clone());
save_chess_state(state.clone());
send_response(
&Response {
@ -462,7 +453,7 @@ impl Guest for Component {
}
send_ws_update(our.node.clone(), game.clone());
save_chess_state(our.node.clone(), state.clone());
save_chess_state(state.clone());
send_response(
&Response {
@ -480,7 +471,7 @@ impl Guest for Component {
continue;
}
}
} else if source.process == ProcessId::Name("http_bindings".to_string()) {
} else if source.process.to_string() == "http_bindings:http_bindings:uqbar" {
let path = message_json["path"].as_str().unwrap_or("");
let method = message_json["method"].as_str().unwrap_or("");
@ -489,17 +480,12 @@ impl Guest for Component {
// 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("${process}", &source.process.to_string())
.replace("${js}", CHESS_JS)
.replace("${css}", CHESS_CSS)
.to_string()
@ -573,7 +559,8 @@ impl Guest for Component {
let response = send_and_await_response(
&Address {
node: game_id.clone(),
process: ProcessId::Name("chess".to_string()),
process: ProcessId::from_str("chess:sys:uqbar")
.unwrap(),
},
&Request {
inherit: false,
@ -617,10 +604,7 @@ impl Guest for Component {
.games
.insert(game_id.clone(), game.clone());
save_chess_state(
our.node.clone(),
state.clone(),
);
save_chess_state(state.clone());
send_http_response(
200,
@ -727,13 +711,14 @@ impl Guest for Component {
let response = send_and_await_response(
&Address {
node: game_id.clone(),
process: ProcessId::Name(
"chess".to_string(),
),
process: ProcessId::from_str(
"chess:sys:uqbar",
)
.unwrap(),
},
&Request {
inherit: false,
expects_response: Some(30), // TODO check this!
expects_response: Some(30), // TODO check this!
ipc: Some(
serde_json::json!({
"action": "make_move",
@ -816,10 +801,7 @@ impl Guest for Component {
}
let game = game.clone();
save_chess_state(
our.node.clone(),
state.clone(),
);
save_chess_state(state.clone());
// return the game
send_http_response(
200,
@ -881,7 +863,8 @@ impl Guest for Component {
let response = send_and_await_response(
&Address {
node: game_id.clone(),
process: ProcessId::Name("chess".to_string()),
process: ProcessId::from_str("chess:sys:uqbar")
.unwrap(),
},
&Request {
inherit: false,
@ -924,10 +907,7 @@ impl Guest for Component {
}
let game = game.clone();
save_chess_state(
our.node.clone(),
state.clone(),
);
save_chess_state(state.clone());
// return the game
send_http_response(

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -0,0 +1,14 @@
[
{
"process_name": "homepage",
"process_wasm_path": "/homepage.wasm",
"on_panic": "Restart",
"request_networking": false,
"request_messaging": [
"http_bindings:http_bindings:uqbar",
"http_server:sys:uqbar",
"encryptor:sys:uqbar"
],
"grant_messaging": []
}
]

View File

@ -0,0 +1,4 @@
{
"package": "homepage",
"publisher": "uqbar"
}

View File

@ -4,6 +4,7 @@ use bindings::{print_to_terminal, receive, send_request, send_requests, send_res
use bindings::component::uq_process::types::*;
use serde_json::json;
#[allow(dead_code)]
mod process_lib;
struct Component;
@ -35,7 +36,7 @@ impl Guest for Component {
let bindings_address = Address {
node: our.node.clone(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("http_bindings:http_bindings:uqbar").unwrap(),
};
// <address, request, option<context>, option<payload>>
@ -129,7 +130,7 @@ impl Guest for Component {
send_request(
&Address {
node: our.node.clone(),
process: ProcessId::Name("encryptor".to_string()),
process: ProcessId::from_str("encryptor:sys:uqbar").unwrap(),
},
&Request {
inherit: false,

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -0,0 +1,17 @@
[
{
"process_name": "http_bindings",
"process_wasm_path": "/http_bindings.wasm",
"on_panic": "Restart",
"request_networking": false,
"request_messaging": [
"http_server:sys:uqbar",
"http_client:sys:uqbar",
"encryptor:sys:uqbar",
"vfs:sys:uqbar"
],
"grant_messaging": [
"http_server:sys:uqbar"
]
}
]

View File

@ -0,0 +1,4 @@
{
"package": "http_bindings",
"publisher": "uqbar"
}

View File

@ -10,6 +10,7 @@ use url::form_urlencoded;
use bindings::component::uq_process::types::*;
use bindings::{get_payload, print_to_terminal, receive, send_request, send_response, Guest};
#[allow(dead_code)]
mod process_lib;
struct Component;
@ -104,7 +105,7 @@ fn send_http_response(status: u16, headers: HashMap<String, String>, payload_byt
impl Guest for Component {
fn init(our: Address) {
print_to_terminal(1, "http_bindings: start");
print_to_terminal(0, "http_bindings: start");
let mut path_bindings: HashMap<String, BoundPath> = HashMap::new();
let mut jwt_secret: Option<Hmac<Sha256>> = None;
@ -112,7 +113,7 @@ impl Guest for Component {
send_request(
&Address {
node: our.node.clone(),
process: ProcessId::Name("http_server".to_string()),
process: ProcessId::from_str("http_server:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -158,10 +159,7 @@ impl Guest for Component {
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(),
};
let app: String = source.process.to_string();
print_to_terminal(1, "http_bindings: got message");
@ -187,33 +185,39 @@ impl Guest for Component {
},
None,
);
} else if action == "bind-app" && path != "" && app != "" {
} else if action == "bind-app" && path != "" {
print_to_terminal(1, "http_bindings: binding app 1");
let path_segments = path
.trim_start_matches('/')
.split("/")
.collect::<Vec<&str>>();
if app != "homepage"
if app != "homepage:homepage:uqbar"
&& (path_segments.is_empty()
|| path_segments[0] != app.clone().replace("_", "-"))
|| path_segments[0] != app.clone().split(':').next().unwrap_or_default().to_string().replace("_", "-"))
{
print_to_terminal(
1,
format!(
"http_bindings: first path segment does not match process: {}",
path
"http_bindings: first path segment {} does not match process: {}",
path,
app.clone().replace("_", "-"),
)
.as_str(),
);
continue;
} else {
if !app.clone().ends_with(":sys:uqbar") && path_bindings.contains_key(path) {
print_to_terminal(0, &format!("http_bindings: path already bound {}", path));
continue;
}
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(),
app,
authenticated: message_json
.get("authenticated")
.and_then(|v| v.as_bool())
@ -424,7 +428,7 @@ impl Guest for Component {
send_request(
&Address {
node: our.node.clone(),
process: ProcessId::Name("encryptor".to_string()),
process: ProcessId::from_str("encryptor:sys:uqbar").unwrap(),
},
&Request {
inherit: true,
@ -580,7 +584,7 @@ impl Guest for Component {
send_request(
&Address {
node: our.node.clone(),
process: ProcessId::Name(app.to_string()),
process: ProcessId::from_str(app).unwrap(),
},
&Request {
inherit: true,

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -0,0 +1,14 @@
[
{
"process_name": "http_proxy",
"process_wasm_path": "/http_proxy.wasm",
"on_panic": "Restart",
"request_networking": false,
"request_messaging": [
"http_bindings:http_bindings:uqbar",
"encryptor:sys:uqbar",
"http_server:sys:uqbar"
],
"grant_messaging": []
}
]

View File

@ -0,0 +1,4 @@
{
"package": "http_proxy",
"publisher": "uqbar"
}

View File

@ -1,12 +1,15 @@
cargo_component_bindings::generate!();
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use bindings::{print_to_terminal, receive, send_requests, send_request, send_response, get_payload, Guest};
use bindings::component::uq_process::types::*;
use bindings::{
get_payload, print_to_terminal, receive, send_request, send_requests, send_response, Guest,
};
#[allow(dead_code)]
mod process_lib;
const PROXY_HOME_PAGE: &str = include_str!("http_proxy.html");
@ -24,28 +27,31 @@ pub struct FileSystemRequest {
pub action: FileSystemAction,
}
fn send_http_response(
status: u16,
headers: HashMap<String, String>,
payload_bytes: Vec<u8>,
) {
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()),
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())
fn send_not_found() {
send_http_response(
404,
HashMap::new(),
"Not Found".to_string().as_bytes().to_vec(),
)
}
impl Guest for Component {
@ -56,87 +62,103 @@ impl Guest for Component {
let bindings_address = Address {
node: our.node.clone(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("http_bindings:http_bindings:uqbar").unwrap(),
};
// <address, request, option<context>, option<payload>>
let http_endpoint_binding_requests: [(Address, Request, Option<Context>, Option<Payload>); 5] = [
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()),
ipc: Some(
serde_json::json!({
"action": "bind-app",
"path": "/http-proxy",
"authenticated": true,
"app": "http_proxy",
})
.to_string(),
),
metadata: None,
},
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()),
ipc: Some(
serde_json::json!({
"action": "bind-app",
"path": "/http-proxy/static/*",
"authenticated": true,
"app": "http_proxy",
})
.to_string(),
),
metadata: None,
},
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()),
ipc: Some(
serde_json::json!({
"action": "bind-app",
"path": "/http-proxy/list",
"app": "http_proxy",
})
.to_string(),
),
metadata: None,
},
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()),
ipc: Some(
serde_json::json!({
"action": "bind-app",
"path": "/http-proxy/register",
"app": "http_proxy",
})
.to_string(),
),
metadata: None,
},
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()),
ipc: Some(
serde_json::json!({
"action": "bind-app",
"path": "/http-proxy/serve/:username/*",
"app": "http_proxy",
})
.to_string(),
),
metadata: None,
},
None,
None
None,
),
];
send_requests(&http_endpoint_binding_requests);
@ -164,21 +186,27 @@ impl Guest for Component {
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());
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()),
ipc: Some(
serde_json::json!({
"action": "response",
"status": 200,
"headers": {
"Content-Type": "text/html",
},
})
.to_string(),
),
metadata: None,
},
Some(&Payload {
@ -189,16 +217,20 @@ impl Guest for Component {
.to_vec(),
}),
);
} else if message_json["path"] == "/http-proxy/list" && message_json["method"] == "GET" {
} 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()),
ipc: Some(
serde_json::json!({
"action": "response",
"status": 200,
"headers": {
"Content-Type": "application/json",
},
})
.to_string(),
),
metadata: None,
},
Some(&Payload {
@ -209,7 +241,9 @@ impl Guest for Component {
.to_vec(),
}),
);
} else if message_json["path"] == "/http-proxy/register" && message_json["method"] == "POST" {
} else if message_json["path"] == "/http-proxy/register"
&& message_json["method"] == "POST"
{
let mut status = 204;
let Some(payload) = get_payload() else {
@ -237,26 +271,37 @@ impl Guest for Component {
send_response(
&Response {
ipc: Some(serde_json::json!({
"action": "response",
"status": status,
"headers": {
"Content-Type": "text/html",
},
}).to_string()),
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(),
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" {
} 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 username = message_json["query_params"]["username"]
.as_str()
.unwrap_or("");
let mut status = 204;
@ -268,25 +313,34 @@ impl Guest for Component {
send_response(
&Response {
ipc: Some(serde_json::json!({
"action": "response",
"status": status,
"headers": {
"Content-Type": "text/html",
},
}).to_string()),
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()
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 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());
@ -295,21 +349,21 @@ impl Guest for Component {
} else if !registrations.contains_key(username) {
send_response(
&Response {
ipc: Some(json!({
"action": "response",
"status": 403,
"headers": {
"Content-Type": "text/html",
},
}).to_string()),
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(),
bytes: "Not Authorized".to_string().as_bytes().to_vec(),
}),
);
} else {
@ -317,7 +371,7 @@ impl Guest for Component {
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("/"));
proxied_path = format!("/{}", path_parts[pos + 2..].join("/"));
print_to_terminal(1, format!("Path to proxy: {}", proxied_path).as_str());
}
@ -326,19 +380,22 @@ impl Guest for Component {
send_request(
&Address {
node: username.into(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("http_bindings:http_bindings:uqbar").unwrap(),
},
&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()),
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,

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

453
modules/key_value/key_value/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 = "key_value"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"cargo-component-bindings",
"serde",
"serde_json",
"wit-bindgen",
]
[[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.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 = "key_value"
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]

View File

@ -0,0 +1 @@
../../../../src/kernel_types.rs

View File

@ -0,0 +1,214 @@
cargo_component_bindings::generate!();
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use bindings::component::uq_process::types::*;
use bindings::{get_capability, has_capability, Guest, print_to_terminal, receive, send_request, send_requests, spawn};
mod kernel_types;
use kernel_types as kt;
mod process_lib;
struct Component;
const PREFIX: &str = "key_value-";
fn make_cap(kind: &str, drive: &str) -> String {
serde_json::to_string(&serde_json::json!({
"kind": kind,
"drive": drive,
})).unwrap()
}
fn handle_message (
our: &Address,
drive_to_process: &mut HashMap<String, ProcessId>,
) -> anyhow::Result<()> {
let (source, message) = receive().unwrap();
// let (source, message) = receive()?;
if our.node != source.node {
return Err(anyhow::anyhow!(
"rejecting foreign Message from {:?}",
source,
));
}
match message {
Message::Response(r) => {
return Err(anyhow::anyhow!("key_value: unexpected Response: {:?}", r));
},
Message::Request(Request { inherit: _ , expects_response: _, ipc, metadata: _ }) => {
match process_lib::parse_message_ipc(ipc.clone())? {
kt::KeyValueMessage::New { ref drive } => {
// TODO: make atomic
// (1): create vfs
// (2): spin up worker, granting vfs caps
// (3): issue new caps
// (4): persist
if drive_to_process.contains_key(drive) {
return Err(anyhow::anyhow!(
"rejecting New for drive that already exists: {}",
drive,
))
}
// (1)
let vfs_address = Address {
node: our.node.clone(),
process: kt::ProcessId::new("vfs", "sys", "uqbar").en_wit(),
};
let vfs_drive = format!("{}{}", PREFIX, drive);
let _ = process_lib::send_and_await_response(
&vfs_address,
false,
Some(serde_json::to_string(&kt::VfsRequest {
drive: vfs_drive.clone(),
action: kt::VfsAction::New,
}).unwrap()),
None,
None,
15,
).unwrap();
// (2)
let vfs_read = get_capability(
&vfs_address,
&make_cap("read", &vfs_drive),
).ok_or(anyhow::anyhow!("New failed: no vfs 'read' capability found"))?;
let vfs_write = get_capability(
&vfs_address,
&make_cap("write", &vfs_drive),
).ok_or(anyhow::anyhow!("New failed: no vfs 'write' capability found"))?;
let spawned_process_id = match spawn(
None,
"/key_value_worker.wasm",
&OnPanic::None, // TODO: notify us
&Capabilities::Some(vec![vfs_read, vfs_write]),
false, // not public
) {
Ok(spawned_process_id) => spawned_process_id,
Err(e) => {
print_to_terminal(0, &format!("couldn't spawn: {}", e));
panic!("couldn't spawn"); // TODO
},
};
// grant caps
bindings::create_capability(&source.process, &make_cap("read", drive));
bindings::create_capability(&source.process, &make_cap("write", drive));
// initialize worker
send_request(
&Address {
node: our.node.clone(),
process: spawned_process_id.clone(),
},
&Request {
inherit: false,
expects_response: None,
ipc,
metadata: None,
},
None,
None,
);
// (4)
drive_to_process.insert(drive.into(), spawned_process_id);
// TODO
},
kt::KeyValueMessage::Write { ref drive, .. } => {
// if has_capability(&make_cap("write", &drive)) {
// forward
let Some(process_id) = drive_to_process.get(drive) else {
// TODO
return Err(anyhow::anyhow!(
"cannot write to non-existent drive {}",
drive,
));
};
send_request(
&Address {
node: our.node.clone(),
process: process_id.clone(),
},
&Request {
inherit: true,
expects_response: None,
ipc,
metadata: None,
},
None,
None,
);
// } else {
// // reject
// // TODO
// return Err(anyhow::anyhow!(
// "cannot write to drive: missing 'write' capability; {}",
// drive,
// ));
// }
},
kt::KeyValueMessage::Read { ref drive, .. } => {
// if has_capability(&make_cap("read", &drive)) {
// forward
let Some(process_id) = drive_to_process.get(drive) else {
// TODO
return Err(anyhow::anyhow!(
"cannot read from non-existent drive {}",
drive,
));
};
send_request(
&Address {
node: our.node.clone(),
process: process_id.clone(),
},
&Request {
inherit: true,
expects_response: None,
ipc,
metadata: None,
},
None,
None,
);
// } else {
// // reject
// // TODO
// return Err(anyhow::anyhow!(
// "cannot read from drive: missing 'read' capability; {}",
// drive,
// ));
// }
},
}
Ok(())
},
}
}
impl Guest for Component {
fn init(our: Address) {
print_to_terminal(0, "key_value: begin");
let mut drive_to_process: HashMap<String, ProcessId> = HashMap::new();
loop {
match handle_message(&our, &mut drive_to_process) {
Ok(()) => {},
Err(e) => {
// TODO: should we send an error on failure?
print_to_terminal(0, format!(
"key_value: error: {:?}",
e,
).as_str());
},
};
}
}
}

View File

@ -0,0 +1 @@
../../../../src/process_lib.rs

View File

@ -0,0 +1,671 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[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 = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[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 = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[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 = "key_value_worker"
version = "0.1.0"
dependencies = [
"anyhow",
"bincode",
"cargo-component-bindings",
"redb",
"serde",
"serde_json",
"wit-bindgen",
]
[[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 = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[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 = "pyo3-build-config"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5"
dependencies = [
"once_cell",
"target-lexicon",
]
[[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.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redb"
version = "1.2.0"
dependencies = [
"libc",
"pyo3-build-config",
"rand",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[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 = "target-lexicon"
version = "0.12.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]]
name = "thiserror"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[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 = "tokio"
version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [
"backtrace",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[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 = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[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,34 @@
[package]
name = "key_value_worker"
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" }
redb = { path = "../../../../redb" }
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]
[package.metadata.component.target.dependencies]
"redb:redb" = { path = "../../../../redb/wit" }

View File

@ -0,0 +1 @@
../../../../src/kernel_types.rs

View File

@ -0,0 +1,191 @@
cargo_component_bindings::generate!();
use std::collections::HashMap;
use redb::ReadableTable;
use serde::{Deserialize, Serialize};
use bindings::component::uq_process::types::*;
use bindings::{get_payload, Guest, print_to_terminal, receive, send_and_await_response, send_response};
mod kernel_types;
use kernel_types as kt;
mod process_lib;
struct Component;
const PREFIX: &str = "key_value-";
const TABLE: redb::TableDefinition<&[u8], &[u8]> = redb::TableDefinition::new("process");
fn get_payload_wrapped() -> Option<(Option<String>, Vec<u8>)> {
match get_payload() {
None => None,
Some(Payload { mime, bytes }) => Some((mime, bytes)),
}
}
fn send_and_await_response_wrapped(
target_node: String,
target_process: String,
target_package: String,
target_publisher: String,
request_ipc: Option<String>,
request_metadata: Option<String>,
payload: Option<(Option<String>, Vec<u8>)>,
timeout: u64,
) -> (Option<String>, Option<String>) {
let payload = match payload {
None => None,
Some((mime, bytes)) => Some(Payload { mime, bytes }),
};
let (
_,
Message::Response((Response { ipc, metadata }, _)),
) = send_and_await_response(
&Address {
node: target_node,
process: kt::ProcessId::new(
&target_process,
&target_package,
&target_publisher,
).en_wit(),
},
&Request {
inherit: false,
expects_response: Some(timeout),
ipc: request_ipc,
metadata: request_metadata,
},
match payload {
None => None,
Some(ref p) => Some(p),
},
).unwrap() else {
panic!("");
};
(ipc, metadata)
}
fn handle_message (
our: &Address,
db: &mut Option<redb::Database>,
) -> anyhow::Result<()> {
let (source, message) = receive().unwrap();
// let (source, message) = receive()?;
if our.node != source.node {
return Err(anyhow::anyhow!(
"rejecting foreign Message from {:?}",
source,
));
}
match message {
Message::Response(_) => { unimplemented!() },
Message::Request(Request { inherit: _ , expects_response: _, ipc, metadata: _ }) => {
match process_lib::parse_message_ipc(ipc.clone())? {
kt::KeyValueMessage::New { drive: kv_drive } => {
let vfs_drive = format!("{}{}", PREFIX, kv_drive);
match db {
Some(_) => {
return Err(anyhow::anyhow!("cannot send New more than once"));
},
None => {
print_to_terminal(0, "key_value_worker: Create");
*db = Some(redb::Database::create(
format!(
"/{}.redb",
kv_drive,
),
our.node.clone(),
vfs_drive,
get_payload_wrapped,
send_and_await_response_wrapped,
)?);
print_to_terminal(0, "key_value_worker: Create done");
},
}
},
// kt::KeyValueMessage::Write { ref key, .. } => {
kt::KeyValueMessage::Write { ref key, .. } => {
let Some(db) = db else {
return Err(anyhow::anyhow!("cannot send New more than once"));
};
let Payload { mime: _, ref bytes } = get_payload().ok_or(anyhow::anyhow!("couldnt get bytes for Write"))?;
let write_txn = db.begin_write()?;
{
let mut table = write_txn.open_table(TABLE)?;
table.insert(&key[..], &bytes[..])?;
}
write_txn.commit()?;
send_response(
&Response {
ipc,
metadata: None,
},
None,
);
},
kt::KeyValueMessage::Read { ref key, .. } => {
let Some(db) = db else {
return Err(anyhow::anyhow!("cannot send New more than once"));
};
let read_txn = db.begin_read()?;
let table = read_txn.open_table(TABLE)?;
match table.get(&key[..])? {
None => {
send_response(
&Response {
ipc,
metadata: None,
},
None,
);
},
Some(v) => {
send_response(
&Response {
ipc,
metadata: None,
},
Some(&Payload {
mime: None,
bytes: v.value().to_vec(),
}),
);
},
};
},
}
Ok(())
},
}
}
impl Guest for Component {
fn init(our: Address) {
print_to_terminal(1, "key_value_worker: begin");
let mut db: Option<redb::Database> = None;
loop {
match handle_message(&our, &mut db) {
Ok(()) => {},
Err(e) => {
// TODO: should we send an error on failure?
print_to_terminal(0, format!(
"key_value_worker: error: {:?}",
e,
).as_str());
},
};
}
}
}

View File

@ -0,0 +1 @@
../../../../src/process_lib.rs

View File

@ -0,0 +1,14 @@
[
{
"process_name": "key_value",
"process_wasm_path": "/key_value.wasm",
"on_panic": "Restart",
"request_networking": false,
"request_messaging": [
"vfs:sys:uqbar"
],
"grant_messaging": [
"all"
]
}
]

View File

@ -0,0 +1,4 @@
{
"package": "key_value",
"publisher": "uqbar"
}

View File

@ -0,0 +1,12 @@
[
{
"process_name": "orgs",
"process_wasm_path": "/orgs.wasm",
"on_panic": "Restart",
"request_networking": true,
"request_messaging": [
"http_bindings:http_bindings:uqbar"
],
"grant_messaging": []
}
]

View File

@ -0,0 +1,4 @@
{
"package": "orgs",
"publisher": "uqbar"
}

View File

@ -10,12 +10,9 @@ use serde_json::{json, to_vec};
use std::collections::HashMap;
extern crate base64;
#[allow(dead_code)]
mod process_lib;
// process_lib::set_state our, bytes
// process_lib::await_set_state our, any serializable type
// process_lib::get_state -> Option<Payload> gets the entire state
struct Component;
type Contact = HashMap<String, String>;
@ -170,7 +167,7 @@ fn send_http_client_request(
send_request(
&Address {
node: our_name,
process: ProcessId::Name("http_client".to_string()),
process: ProcessId::from_str("http_client:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -304,7 +301,7 @@ fn handle_telegram_update(
send_request(
&Address {
node: our_name.clone(),
process: ProcessId::Name("encryptor".to_string()),
process: ProcessId::from_str("encryptor:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -353,7 +350,7 @@ fn handle_telegram_update(
let response = send_and_await_response(
&Address {
node: our_name.clone(),
process: ProcessId::Name("http_client".to_string()),
process: ProcessId::from_str("http_client:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -552,7 +549,7 @@ fn serve_html(our: Address, default_headers: HashMap<String, String>) {
let response = send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::Name("vfs".to_string()),
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -560,7 +557,7 @@ fn serve_html(our: Address, default_headers: HashMap<String, String>) {
ipc: Some(
json!({
"GetEntry": {
"identifier": "orgs_static",
"drive": "orgs_static",
"full_path": "/index.html"
}
})
@ -600,7 +597,7 @@ fn serve_static(raw_path: &str, our: Address, default_headers: HashMap<String, S
let response = send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::Name("vfs".to_string()),
process: ProcessId::from_str("vfs:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -608,7 +605,7 @@ fn serve_static(raw_path: &str, our: Address, default_headers: HashMap<String, S
ipc: Some(
json!({
"GetEntry": {
"identifier": "orgs_static",
"drive": "orgs_static",
"full_path": file_path // everything after "/orgs/static"
}
})
@ -639,10 +636,10 @@ fn serve_static(raw_path: &str, our: Address, default_headers: HashMap<String, S
impl Guest for Component {
fn init(our: Address) {
print_to_terminal(0, "RPC: start");
print_to_terminal(0, "orgs: start");
let mut state: OrgsState = match process_lib::get_state(our.node.clone()) {
Some(payload) => match serde_json::from_slice::<OrgsState>(&payload.bytes) {
let mut state: OrgsState = match bindings::get_state() {
Some(bytes) => match serde_json::from_slice::<OrgsState>(&bytes) {
Ok(state) => state,
Err(_) => OrgsState {
our_contact_info: HashMap::new(),
@ -665,7 +662,7 @@ impl Guest for Component {
let bindings_address = Address {
node: our.node.clone(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("http_bindings:http_bindings:uqbar").unwrap(),
};
// <address, request, option<context>, option<payload>>
@ -728,7 +725,7 @@ impl Guest for Component {
save_capabilities(&[SignedCapability {
issuer: Address {
node,
process: ProcessId::Name(process),
process: ProcessId::from_str(&process).unwrap(),
},
params,
signature,
@ -758,7 +755,7 @@ impl Guest for Component {
serde_json::from_slice::<Contact>(&payload.bytes)
{
state.address_book.insert(source.node.clone(), contact_info.clone());
process_lib::set_state(our.node.clone(), to_vec(&state).unwrap());
bindings::set_state(&to_vec(&state).unwrap());
send_response(
&Response {
ipc: Some(
@ -794,7 +791,7 @@ impl Guest for Component {
}
continue;
} else if source.node == our.node
&& source.process == ProcessId::Name("http_bindings".to_string())
&& source.process.to_string() == "http_binding:sys:uqbar"
{
// Handle http request
let mut default_headers = HashMap::new();
@ -956,7 +953,7 @@ impl Guest for Component {
state.our_contact_info.insert(key, value);
}
process_lib::set_state(our.node.clone(), to_vec(&state).unwrap());
bindings::set_state(&to_vec(&state).unwrap());
send_http_response(
201,
@ -1053,9 +1050,7 @@ impl Guest for Component {
send_request(
&Address {
node: username.clone(),
process: ProcessId::Name(
"orgs".to_string(),
),
process: ProcessId::from_str("orgs:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -1075,9 +1070,7 @@ impl Guest for Component {
send_request(
&Address {
node: username.clone(),
process: ProcessId::Name(
"orgs".to_string(),
),
process: ProcessId::from_str("orgs:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -1188,7 +1181,7 @@ impl Guest for Component {
let response = send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::Name("http_client".to_string()),
process: ProcessId::from_str("http_client:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -1346,7 +1339,7 @@ impl Guest for Component {
let response = send_and_await_response(
&Address {
node: our.node.clone(),
process: ProcessId::Name("http_client".to_string()),
process: ProcessId::from_str("http_client:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -1684,7 +1677,7 @@ impl Guest for Component {
}
}
Message::Response((response, context)) => {
if source.process == ProcessId::Name("http_client".to_string()) {
if source.process.to_string() == "http_client:sys:uqbar" {
let Some(bot_id_string) = context else {
print_to_terminal(0, "orgs: got response without context");
continue;
@ -1753,7 +1746,7 @@ impl Guest for Component {
);
}
}
} else if source.process == ProcessId::Name("orgs".to_string()) {
} else if source.process.to_string() == "orgs:sys:uqbar" {
if let Some(json) = response.ipc {
let message_json: serde_json::Value = match serde_json::from_str(&json)
{

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -0,0 +1,17 @@
[
{
"process_name": "qns_indexer",
"process_wasm_path": "/qns_indexer.wasm",
"on_panic": "Restart",
"request_networking": true,
"request_messaging": [
"net:sys:uqbar",
"http_bindings:http_bindings:uqbar",
"eth_rpc:sys:uqbar"
],
"grant_messaging": [
"eth_rpc:sys:uqbar",
"filesystem:sys:uqbar"
]
}
]

View File

@ -0,0 +1,4 @@
{
"package": "qns_indexer",
"publisher": "uqbar"
}

View File

@ -1,14 +1,16 @@
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 bindings::component::uq_process::types::*;
use bindings::{print_to_terminal, receive, send_request, send_response, UqProcess};
use hex;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use std::string::FromUtf8Error;
#[allow(dead_code)]
mod process_lib;
struct Component;
@ -19,7 +21,7 @@ struct State {
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>,
nodes: HashMap<String, QnsUpdate>,
// last block we read from
block: u64,
}
@ -30,16 +32,34 @@ enum AllActions {
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EthEvent {
address: String,
blockHash: String,
blockNumber: String,
block_hash: String,
block_number: String,
data: String,
logIndex: String,
log_index: String,
removed: bool,
topics: Vec<String>,
transactionHash: String,
transactionIndex: String,
transaction_hash: String,
transaction_index: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NetActions {
QnsUpdate(QnsUpdate),
QnsBatchUpdate(Vec<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>,
}
sol! {
@ -72,7 +92,8 @@ fn subscribe_to_qns(from_block: u64) -> String {
"topic2": null,
"topic3": null,
}
}).to_string()
})
.to_string()
}
impl UqProcess for Component {
@ -84,72 +105,76 @@ impl UqProcess for Component {
};
// 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;
},
}
},
match process_lib::get_state::<State>() {
Some(s) => {
state = s;
}
None => {}
}
bindings::print_to_terminal(0, &format!("qns_indexer: starting at block {}", state.block));
// 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,
bindings::print_to_terminal(
0,
&format!("qns_indexer: starting at block {}", state.block),
);
let _register_endpoint = send_request(
&Address{
// shove all state into net::net
send_request(
&Address {
node: our.node.clone(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("net:sys:uqbar").unwrap(),
},
&Request{
&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()),
ipc: Some(
serde_json::to_string(&NetActions::QnsBatchUpdate(
state.nodes.values().cloned().collect::<Vec<_>>(),
))
.unwrap(),
),
},
None,
None,
);
let _ = send_request(
&Address {
node: our.node.clone(),
process: ProcessId::from_str("eth_rpc:sys:uqbar").unwrap(),
},
&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 http_bindings_process_id_str = "http_bindings:http_bindings:uqbar";
let http_bindings_process_id = ProcessId::from_str(http_bindings_process_id_str).unwrap();
let _register_endpoint = send_request(
&Address {
node: our.node.clone(),
process: http_bindings_process_id.clone(),
},
&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,
@ -167,25 +192,33 @@ impl UqProcess for Component {
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 source.process.to_string() == http_bindings_process_id_str {
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()),
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(),
})
bytes: serde_json::to_string(&node)
.unwrap()
.as_bytes()
.to_vec(),
}),
);
continue;
}
@ -194,49 +227,49 @@ impl UqProcess for Component {
}
send_response(
&Response {
ipc: Some(serde_json::json!({
"status": 404,
"headers": {
"Content-Type": "application/json",
},
}).to_string()),
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");
let Ok(msg) = serde_json::from_str::<AllActions>(request.ipc.as_ref().unwrap()) else {
print_to_terminal(0, &format!("qns_indexer: got invalid message: {}", request.ipc.unwrap_or_default()));
continue;
};
match msg {
// Probably more message types later...maybe not...
AllActions::EventSubscription(e) => {
state.block = hex_to_u64(&e.blockNumber).unwrap();
state.block = hex_to_u64(&e.block_number).unwrap();
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());
let Ok(name) = dnswire_decode(decoded.0.clone()) else {
bindings::print_to_terminal(0, &format!("qns_indexer: failed to decode name: {:?}", decoded.0));
continue;
};
state.names.insert(node.to_string(), name);
}
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;
@ -253,56 +286,60 @@ impl UqProcess for Component {
})
.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 Some(name) = state.names.get(node) else {
bindings::print_to_terminal(0, &format!("qns_indexer: failed to find name for node during WsChanged: {:?}", node));
continue;
};
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();
let update = QnsUpdate {
name: name.clone(),
owner: "0x".to_string(), // TODO or get rid of
node: node.clone(),
public_key: format!("0x{}", public_key),
ip: format!(
"{}.{}.{}.{}",
(ip >> 24) & 0xFF,
(ip >> 16) & 0xFF,
(ip >> 8) & 0xFF,
ip & 0xFF
),
port,
routers,
};
state.nodes.insert(name.clone(), json_payload.clone());
state.nodes.insert(name.clone(), update.clone());
send_request(
&Address{
&Address {
node: our.node.clone(),
process: ProcessId::Name("net".to_string()),
process: ProcessId::from_str("net:sys:uqbar").unwrap(),
},
&Request{
&Request {
inherit: false,
expects_response: None,
metadata: None,
ipc: Some(json_payload),
ipc: Some(
serde_json::to_string(&NetActions::QnsUpdate(
update.clone(),
))
.unwrap(),
),
},
None,
None,
);
}
event => {
bindings::print_to_terminal(0, format!("qns_indexer: got unknown event: {:?}", event).as_str());
bindings::print_to_terminal(
0,
format!("qns_indexer: got unknown event: {:?}", event).as_str(),
);
}
}
}
}
process_lib::await_set_state(our.node.clone(), &state);
process_lib::set_state::<State>(&state);
}
}
}
@ -311,11 +348,7 @@ impl UqProcess for Component {
// 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 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]);
@ -324,11 +357,7 @@ fn decode_hex(s: &str) -> FixedBytes<32> {
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
};
let hex_part = if s.starts_with("0x") { &s[2..] } else { s };
hex::decode(hex_part).unwrap()
}
@ -342,28 +371,30 @@ fn hex_to_u64(hex: &str) -> Result<u64, std::num::ParseIntError> {
u64::from_str_radix(without_prefix, 16)
}
fn dnswire_decode(wire_format_bytes: Vec<u8>) -> String {
fn dnswire_decode(wire_format_bytes: Vec<u8>) -> Result<String, FromUtf8Error> {
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; }
if len == 0 {
break;
}
let end = i + len + 1;
let mut span = wire_format_bytes[i+1..end].to_vec();
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();
let name = String::from_utf8(flat)?;
// Remove the trailing '.' if it exists (it should always exist)
if name.ends_with('.') {
name[0..name.len()-1].to_string()
Ok(name[0..name.len()-1].to_string())
} else {
name
Ok(name)
}
}
}

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -0,0 +1,14 @@
[
{
"process_name": "rpc",
"process_wasm_path": "/rpc.wasm",
"on_panic": "Restart",
"request_networking": false,
"request_messaging": [
"http_bindings:http_bindings:uqbar",
"app_tracker:app_tracker:uqbar",
"http_server:sys:uqbar"
],
"grant_messaging": []
}
]

View File

@ -0,0 +1,4 @@
{
"package": "rpc",
"publisher": "uqbar"
}

View File

@ -5,13 +5,16 @@ use bindings::{
get_capabilities, get_capability, get_payload, print_to_terminal, receive,
send_and_await_response, send_request, send_requests, send_response, Guest,
};
use kernel_types::Capability;
use kernel_types::de_wit_signed_capability;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::{HashMap, HashSet};
extern crate base64;
#[allow(dead_code)]
mod kernel_types;
#[allow(dead_code)]
mod process_lib;
struct Component;
@ -48,15 +51,15 @@ struct CapabilitiesTransfer {
#[derive(Debug, Deserialize)]
struct WriteFileId {
Write: u128,
write: u128,
}
#[derive(Debug, Deserialize)]
struct WriteFileResult {
Ok: WriteFileId,
ok: WriteFileId,
}
// 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}'
// curl http://localhost:8080/rpc/message -H 'content-type: application/json' -d '{"node": "hosted", "process": "vfs", "inherit": false, "expects_response": null, "ipc": "{\"New\": {\"drive\": \"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(
@ -85,7 +88,7 @@ impl Guest for Component {
let bindings_address = Address {
node: our.node.clone(),
process: ProcessId::Name("http_bindings".to_string()),
process: ProcessId::from_str("http_bindings:http_bindings:uqbar").unwrap(),
};
let http_endpoint_binding_requests: [(Address, Request, Option<Context>, Option<Payload>);
@ -231,14 +234,10 @@ impl Guest for Component {
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,
"process": cap.issuer.process.to_string(),
},
"params": cap.params.clone(),
})
@ -316,7 +315,7 @@ impl Guest for Component {
let result = send_and_await_response(
&Address {
node: body_json.node,
process: ProcessId::Name(body_json.process),
process: ProcessId::from_str(&body_json.process).unwrap(),
},
&Request {
inherit: false,
@ -441,7 +440,7 @@ impl Guest for Component {
let write_wasm_result = send_and_await_response(
&Address {
node: node.clone(),
process: ProcessId::Name("filesystem".to_string()),
process: ProcessId::from_str("filesystem:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
@ -478,7 +477,7 @@ impl Guest for Component {
let wasm_bytes_handle = match response.ipc {
Some(ipc) => {
match serde_json::from_str::<WriteFileResult>(&ipc) {
Ok(result) => result.Ok.Write,
Ok(result) => result.ok.write,
Err(_) => {
send_http_response(
500,
@ -502,20 +501,23 @@ impl Guest for Component {
}
};
let mut capabilities: HashSet<Capability> = HashSet::new();
let mut capabilities_to_grant: HashSet<
kernel_types::SignedCapability,
> = HashSet::new();
match body_json.capabilities {
Some(caps) => {
for cap in caps {
capabilities.insert(Capability {
issuer: kernel_types::Address {
node: node.clone(),
process: kernel_types::ProcessId::Name(
cap.0,
),
},
params: cap.1,
});
let addr = Address {
node: our.node.clone(),
process: ProcessId::from_str(&cap.0).unwrap(),
};
let Some(signed) = bindings::get_capability(&addr, &cap.1) else {
bindings::print_to_terminal(0, &format!("rpc: failed to get capability {} {}", cap.0, cap.1));
continue;
};
capabilities_to_grant
.insert(de_wit_signed_capability(signed));
}
}
None => (),
@ -523,15 +525,15 @@ impl Guest for Component {
let stop_process_command =
kernel_types::KernelCommand::KillProcess(
kernel_types::ProcessId::Name(
body_json.process.clone(),
),
kernel_types::ProcessId::from_str(&body_json.process)
.unwrap(),
);
send_request(
&Address {
node: node.clone(),
process: ProcessId::Name("kernel".to_string()),
process: ProcessId::from_str("kernel:sys:uqbar")
.unwrap(),
},
&Request {
inherit: false,
@ -548,10 +550,11 @@ impl Guest for Component {
let start_process_command =
kernel_types::KernelCommand::StartProcess {
name: Some(body_json.process),
id: kernel_types::ProcessId::from_str(&body_json.process).unwrap(),
wasm_bytes_handle,
on_panic: kernel_types::OnPanic::Restart,
initial_capabilities: capabilities,
initial_capabilities: capabilities_to_grant,
public: false, // TODO ADD TO RPC
};
let ipc = match serde_json::to_string(&start_process_command) {
@ -573,7 +576,8 @@ impl Guest for Component {
let start_wasm_result = send_and_await_response(
&Address {
node,
process: ProcessId::Name("kernel".to_string()),
process: ProcessId::from_str("kernel:sys:uqbar")
.unwrap(),
},
&Request {
inherit: false,
@ -674,7 +678,7 @@ impl Guest for Component {
let capability = get_capability(
&Address {
node: body_json.node,
process: ProcessId::Name(body_json.process),
process: ProcessId::from_str(&body_json.process).unwrap(),
},
&body_json.params,
);
@ -686,14 +690,14 @@ impl Guest for Component {
match capability {
Some(capability) => {
let process = match capability.issuer.process {
ProcessId::Name(name) => name,
ProcessId::Id(id) => id.to_string(),
};
let process = capability.issuer.process.to_string();
send_request(
&Address {
node: body_json.destination_node,
process: ProcessId::Name(body_json.destination_process),
process: ProcessId::from_str(
&body_json.destination_process,
)
.unwrap(),
},
&Request {
inherit: false,

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -0,0 +1,14 @@
[
{
"process_name": "terminal",
"process_wasm_path": "/terminal.wasm",
"on_panic": "Restart",
"request_networking": true,
"request_messaging": [
"net:sys:uqbar"
],
"grant_messaging": [
"all"
]
}
]

View File

@ -0,0 +1,4 @@
{
"package": "terminal",
"publisher": "uqbar"
}

View File

@ -1,7 +1,10 @@
cargo_component_bindings::generate!();
use bindings::{component::uq_process::types::*, print_to_terminal, receive, send_request, Guest};
#[allow(dead_code)]
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, ""));
@ -22,11 +25,11 @@ fn parse_command(our_name: &str, line: String) {
} else {
target.into()
},
process: ProcessId::Name("net".into()),
process: ProcessId::from_str("net:sys:uqbar").unwrap(),
},
&Request {
inherit: false,
expects_response: None,
expects_response: Some(5),
ipc: Some(message.into()),
metadata: None,
},
@ -51,6 +54,7 @@ fn parse_command(our_name: &str, line: String) {
};
// 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" {
@ -58,7 +62,9 @@ fn parse_command(our_name: &str, line: String) {
} else {
target_node.into()
},
process: ProcessId::Name(target_process.into()),
process: ProcessId::from_str(target_process).unwrap_or_else(|_| {
ProcessId::from_str(&format!("{}:sys:uqbar", target_process)).unwrap()
}),
},
&Request {
inherit: false,
@ -78,16 +84,11 @@ fn parse_command(our_name: &str, line: String) {
impl Guest for Component {
fn init(our: Address) {
assert_eq!(our.process, ProcessId::Name("terminal".into()));
print_to_terminal(0, &format!("terminal: running"));
assert_eq!(our.process.to_string(), "terminal:terminal:uqbar");
print_to_terminal(1, &format!("terminal: start"));
loop {
let message = match receive() {
Ok((source, message)) => {
if our.node != source.node {
continue;
}
message
}
let (source, message) = match receive() {
Ok((source, message)) => (source, message),
Err((error, _context)) => {
print_to_terminal(0, &format!("net error: {:?}!", error.kind));
continue;
@ -99,12 +100,19 @@ impl Guest for Component {
ipc,
..
}) => {
if our.node != source.node || our.process != source.process {
continue;
}
let Some(command) = ipc else {
continue;
};
parse_command(&our.node, command);
}
_ => continue
Message::Response((Response { ipc, metadata }, _)) => {
if let Some(txt) = &ipc {
print_to_terminal(0, &format!("net response: {}", txt));
}
}
}
}
}

View File

@ -1,142 +0,0 @@
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 @@
../../../src/process_lib.rs

View File

@ -145,7 +145,7 @@ pub async fn encryptor(
Some(rsvp) => rsvp,
None => Address {
node: source.node.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
};
// Generate and send the response
@ -153,7 +153,7 @@ pub async fn encryptor(
id: *id,
source: Address {
node: our.clone(),
process: ProcessId::Name("encryptor".into()),
process: ENCRYPTOR_PROCESS_ID.clone(),
},
target,
rsvp: None,
@ -226,7 +226,7 @@ pub async fn encryptor(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("encryptor".into()),
process: ENCRYPTOR_PROCESS_ID.clone(),
},
target: forward_to,
rsvp: None,
@ -280,7 +280,7 @@ pub async fn encryptor(
id,
source: Address {
node: our.clone(),
process: ProcessId::Name("encryptor".into()),
process: ENCRYPTOR_PROCESS_ID.clone(),
},
target: forward_to,
rsvp: None,
@ -334,7 +334,7 @@ pub async fn encryptor(
id: *id,
source: Address {
node: our.clone(),
process: ProcessId::Name("encryptor".into()),
process: ENCRYPTOR_PROCESS_ID.clone(),
},
target: source,
rsvp: None,
@ -389,7 +389,7 @@ pub async fn encryptor(
id: *id,
source: Address {
node: our.clone(),
process: ProcessId::Name("encryptor".into()),
process: ENCRYPTOR_PROCESS_ID.clone(),
},
target: source,
rsvp: None,

View File

@ -1,199 +0,0 @@
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
}

View File

@ -7,8 +7,6 @@ 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 {
@ -35,7 +33,7 @@ pub enum EthRpcError {
EventSubscriptionFailed,
}
impl EthRpcError {
pub fn kind(&self) -> &str {
pub fn _kind(&self) -> &str {
match *self {
EthRpcError::NoRsvp { .. } => "NoRsvp",
EthRpcError::BadJson { .. } => "BapJson",
@ -60,15 +58,13 @@ pub async fn eth_rpc(
let print_tx = print_tx.clone();
let KernelMessage {
id,
source,
ref source,
ref rsvp,
message:
Message::Request(Request {
inherit: _,
expects_response,
ipc: json,
metadata: _,
ipc: ref json,
..
}),
..
} = message
@ -86,8 +82,7 @@ pub async fn eth_rpc(
send_to_loop
.send(make_error_message(
our.clone(),
id.clone(),
source.clone(),
&message,
EthRpcError::NoRsvp,
))
.await
@ -103,8 +98,7 @@ pub async fn eth_rpc(
send_to_loop
.send(make_error_message(
our.clone(),
id.clone(),
source.clone(),
&message,
EthRpcError::NoJson,
))
.await
@ -116,8 +110,7 @@ pub async fn eth_rpc(
send_to_loop
.send(make_error_message(
our.clone(),
id.clone(),
source.clone(),
&message,
EthRpcError::BadJson,
))
.await
@ -127,21 +120,25 @@ pub async fn eth_rpc(
match action {
EthRpcAction::SubscribeEvents(sub) => {
let id: u64 = rand::random();
send_to_loop
.send(KernelMessage {
id: id.clone(),
id: message.id,
source: Address {
node: our.clone(),
process: ProcessId::Name("eth_rpc".into()),
process: ETH_RPC_PROCESS_ID.clone(),
},
target: match &message.rsvp {
None => message.source.clone(),
Some(rsvp) => rsvp.clone(),
},
target: target.clone(),
rsvp: None,
message: Message::Response((
Response {
ipc: Some(
serde_json::to_string::<Result<u64, EthRpcError>>(&Ok(id))
.unwrap(),
serde_json::to_string::<Result<u64, EthRpcError>>(&Ok(
message.id
))
.unwrap(),
),
metadata: None,
},
@ -229,7 +226,7 @@ pub async fn eth_rpc(
id: rand::random(),
source: Address {
node: our.clone(),
process: ProcessId::Name("eth_rpc".into()),
process: ETH_RPC_PROCESS_ID.clone(),
},
target: target.clone(),
rsvp: None,
@ -259,7 +256,7 @@ pub async fn eth_rpc(
};
}
});
subscriptions.insert(id, handle);
subscriptions.insert(message.id, handle);
}
EthRpcAction::Unsubscribe(sub_id) => {
let _ = print_tx
@ -290,14 +287,17 @@ pub async fn eth_rpc(
// helpers
//
fn make_error_message(our: String, id: u64, source: Address, error: EthRpcError) -> KernelMessage {
fn make_error_message(our_name: String, km: &KernelMessage, error: EthRpcError) -> KernelMessage {
KernelMessage {
id,
id: km.id,
source: Address {
node: our.clone(),
process: ProcessId::Name("eth_rpc".into()),
node: our_name.clone(),
process: FILESYSTEM_PROCESS_ID.clone(),
},
target: match &km.rsvp {
None => km.source.clone(),
Some(rsvp) => rsvp.clone(),
},
target: source,
rsvp: None,
message: Message::Response((
Response {

View File

@ -102,7 +102,6 @@ impl InMemoryFile {
length: u64,
) -> Vec<(u64, ([u8; 32], u64, ChunkLocation, bool))> {
let end = start + length;
self.chunks
.iter()
.filter(
@ -336,6 +335,7 @@ impl Manifest {
{
if let ChunkLocation::Memory(offset) = location {
*location = ChunkLocation::WAL(wal_length_before_flush + *offset);
in_memory_file.wal_chunks.push(start);
}
}
}
@ -348,6 +348,7 @@ impl Manifest {
}
}
}
in_memory_file.mem_chunks.clear();
}
memory_buffer.clear();
@ -359,7 +360,6 @@ impl Manifest {
// called from main, locks manifest
// other flush_to_wal gets buffer and others passed in.
// potentially unify with options.
let mut manifest = self.manifest.write().await;
let mut memory_buffer = self.memory_buffer.write().await;
let mut wal_file = self.wal_file.write().await;
@ -375,6 +375,7 @@ impl Manifest {
{
if let ChunkLocation::Memory(offset) = location {
*location = ChunkLocation::WAL(wal_length_before_flush + *offset);
in_memory_file.wal_chunks.push(start);
}
}
}
@ -387,10 +388,10 @@ impl Manifest {
}
}
}
in_memory_file.mem_chunks.clear();
}
memory_buffer.clear();
Ok(())
}
@ -504,6 +505,130 @@ impl Manifest {
Ok(())
}
// TODO: factor this out
pub async fn read_from_file(
&self,
file: &InMemoryFile,
memory_buffer: &Vec<u8>,
start: Option<u64>,
length: Option<u64>,
) -> Result<Vec<u8>, FsError> {
let cipher = &self.cipher;
let mut data = Vec::new();
// filter chunks based on start and length if they are defined
let filtered_chunks = if let (Some(start), Some(length)) = (start, length) {
file.find_chunks_in_range(start, length)
} else {
file.chunks
.iter()
.map(|(&start, value)| (start, value.clone()))
.collect()
};
for (start_chunk, (hash, len, location, encrypted)) in filtered_chunks {
let mut chunk_data = match location {
ChunkLocation::Memory(offset) => {
let len = if encrypted {
len + ENCRYPTION_OVERHEAD as u64
} else {
len
};
if offset as usize + len as usize > memory_buffer.len() {
return Err(FsError::MemoryBufferError {
error: format!(
"Out of bounds read: offset={}, len={}, memory_buffer size={}",
offset,
len,
memory_buffer.len()
),
});
}
let mut chunk_data =
memory_buffer[offset as usize..(offset + len) as usize].to_vec();
if encrypted {
chunk_data = decrypt(&cipher, &chunk_data)?;
}
chunk_data
}
ChunkLocation::WAL(offset) => {
let mut wal_file = self.wal_file.write().await;
wal_file
.seek(SeekFrom::Start(offset))
.await
.map_err(|e| FsError::IOError {
error: format!("Local WAL seek failed: {}", e),
})?;
let len = if encrypted {
len + ENCRYPTION_OVERHEAD as u64
} else {
len
};
let mut buffer = vec![0u8; len as usize];
wal_file
.read_exact(&mut buffer)
.await
.map_err(|e| FsError::IOError {
error: format!("Local WAL read failed: {}", e),
})?;
if encrypted {
buffer = decrypt(&cipher, &buffer)?;
}
buffer
}
ChunkLocation::ColdStorage(local) => {
if local {
let path = self.fs_directory_path.join(hex::encode(hash));
let mut buffer = fs::read(path).await.map_err(|e| FsError::IOError {
error: format!("Local Cold read failed: {}", e),
})?;
if encrypted {
buffer = decrypt(&*self.cipher, &buffer)?;
}
buffer
} else {
let file_name = hex::encode(hash);
let (client, bucket) = self.s3_client.as_ref().unwrap();
let req = GetObjectRequest {
bucket: bucket.clone(),
key: file_name.clone(),
..Default::default()
};
let res = client.get_object(req).await?;
let body = res.body.unwrap();
let mut stream = body.into_async_read();
let mut buffer = Vec::new();
stream.read_to_end(&mut buffer).await?;
if encrypted {
buffer = decrypt(&*self.cipher, &buffer)?;
}
buffer
}
}
};
// adjust the chunk data based on the start and length
if let Some(start) = start {
if start > start_chunk {
chunk_data.drain(..(start - start_chunk) as usize);
}
}
if let Some(length) = length {
let end = start.unwrap_or(0) + length;
if end < start_chunk + len {
chunk_data.truncate((end - start_chunk) as usize);
}
}
data.append(&mut chunk_data);
}
Ok(data)
}
pub async fn read(
&self,
file_id: &FileIdentifier,
@ -636,12 +761,13 @@ impl Manifest {
offset: u64,
data: &[u8],
) -> Result<(), FsError> {
let mut manifest = self.manifest.write().await;
let mut file = self.get(file_id).await.ok_or(FsError::NotFound {
file: file_id.to_uuid().unwrap_or_default(),
})?;
let mut manifest = self.manifest.write().await;
let mut memory_buffer = self.memory_buffer.write().await;
let cipher: Option<&XChaCha20Poly1305> = if self.encryption {
Some(&self.cipher)
} else {
@ -653,6 +779,7 @@ impl Manifest {
let tx_id = rand::random::<u64>(); // uuid instead?
let initial_length = file.get_len();
for (start, (_hash, length, _location, _encrypted)) in affected_chunks {
let chunk_data_start = if start < offset {
(offset - start) as usize
@ -663,8 +790,14 @@ impl Manifest {
let remaining_data = data.len() - data_offset;
let write_length = remaining_length.min(remaining_data);
let mut chunk_data = self.read(file_id, Some(start), Some(length)).await?;
chunk_data.resize(chunk_data_start + write_length, 0); // extend the chunk data if necessary
// let mut chunk_data = self.read(file_id, Some(start), Some(length)).await?;
let mut chunk_data = self
.read_from_file(&file, &memory_buffer, Some(start), Some(length))
.await?;
chunk_data.resize(
std::cmp::max(chunk_data_start + write_length, initial_length as usize),
0,
); // extend the chunk data if necessary
let data_to_write = &data[data_offset..data_offset + write_length as usize];
chunk_data[chunk_data_start..chunk_data_start + write_length as usize]
@ -817,8 +950,13 @@ impl Manifest {
let mut hash_index = self.hash_index.write().await;
let mut memory_buffer = self.memory_buffer.write().await;
let mut to_flush: Vec<(
FileIdentifier,
Vec<([u8; 32], u64, u64, ChunkLocation, bool)>,
)> = Vec::new();
for (file_id, in_memory_file) in manifest_lock.iter_mut() {
let mut chunks_to_flush: Vec<([u8; 32], u64, u64, ChunkLocation, bool)> = Vec::new();
for &start in &in_memory_file.mem_chunks {
if let Some((hash, length, location, encrypted)) = in_memory_file.chunks.get(&start)
{
@ -848,8 +986,14 @@ impl Manifest {
}
}
}
if !chunks_to_flush.is_empty() {
to_flush.push((file_id.clone(), chunks_to_flush));
}
}
for (hash, start, length, location, encrypted) in chunks_to_flush.iter() {
for (file_id, chunks) in to_flush.iter() {
let in_memory_file = manifest_lock.get_mut(file_id).unwrap();
for (hash, start, length, location, encrypted) in chunks.iter() {
let total_len = if *encrypted {
length + ENCRYPTION_OVERHEAD as u64
} else {
@ -896,7 +1040,7 @@ impl Manifest {
client.put_object(req).await?;
} else {
let path = self.fs_directory_path.join(hex::encode(hash));
fs::write(path, &buffer).await?;
fs::write(path, buffer).await?;
}
// add a manifest entry with the new hash and removed wal_position
in_memory_file.chunks.insert(
@ -908,56 +1052,54 @@ impl Manifest {
*encrypted,
),
);
chunk_hashes.insert(*hash, !self.cloud_enabled);
}
in_memory_file.mem_chunks.clear();
in_memory_file.wal_chunks.clear();
if !chunks_to_flush.is_empty() {
// add updated manifest entries to the manifest file
let entry = ManifestRecord::Backup(BackupEntry {
file: file_id.clone(),
chunks: in_memory_file
.chunks
.iter()
.map(|(&k, v)| {
let local = match v.2 {
ChunkLocation::ColdStorage(local) => local,
_ => true, // WAL is always local
};
(v.0, k, v.1, v.3, local)
})
.collect::<Vec<_>>(),
});
let serialized_entry = bincode::serialize(&entry).unwrap();
let entry_length = serialized_entry.len() as u64;
// chunks have been flushed, let's add a manifest entry.
let entry = ManifestRecord::Backup(BackupEntry {
file: file_id.clone(),
chunks: in_memory_file
.chunks
.iter()
.map(|(&k, v)| {
let local = match v.2 {
ChunkLocation::ColdStorage(local) => local,
_ => true, // WAL is always local
};
(v.0, k, v.1, v.3, local)
})
.collect::<Vec<_>>(),
});
let serialized_entry = bincode::serialize(&entry).unwrap();
let entry_length = serialized_entry.len() as u64;
let mut buffer = Vec::new();
buffer.extend_from_slice(&entry_length.to_le_bytes());
buffer.extend_from_slice(&serialized_entry);
manifest_file.write_all(&buffer).await?;
if self.cloud_enabled {
let (client, bucket) = self.s3_client.as_ref().unwrap();
let mut buffer = Vec::new();
buffer.extend_from_slice(&entry_length.to_le_bytes());
buffer.extend_from_slice(&serialized_entry);
manifest_file.seek(SeekFrom::Start(0)).await?;
manifest_file.read_to_end(&mut buffer).await?;
manifest_file.write_all(&buffer).await?;
if self.cloud_enabled {
let (client, bucket) = self.s3_client.as_ref().unwrap();
let mut buffer = Vec::new();
manifest_file.seek(SeekFrom::Start(0)).await?;
manifest_file.read_to_end(&mut buffer).await?;
let req = PutObjectRequest {
bucket: bucket.clone(),
key: "manifest.bin".to_string(),
body: Some(StreamingBody::from(buffer)),
..Default::default()
};
client.put_object(req).await?;
}
hash_index.insert(in_memory_file.hash(), file_id.clone());
let req = PutObjectRequest {
bucket: bucket.clone(),
key: "manifest.bin".to_string(),
body: Some(StreamingBody::from(buffer)),
..Default::default()
};
client.put_object(req).await?;
}
hash_index.insert(in_memory_file.hash(), file_id.clone());
}
// clear the WAL file and memory buffer
wal_file.set_len(0).await?;
memory_buffer.clear();
Ok(())
}

View File

@ -3,6 +3,7 @@ use crate::types::*;
/// log structured filesystem
use anyhow::Result;
use std::collections::{HashMap, HashSet, VecDeque};
use std::io::Read;
use std::sync::Arc;
use tokio::fs;
use tokio::sync::oneshot::{Receiver, Sender};
@ -10,21 +11,19 @@ use tokio::sync::Mutex;
use tokio::time::{interval, Duration};
mod manifest;
pub async fn bootstrap(
pub async fn load_fs(
our_name: String,
home_directory_path: String,
file_key: Vec<u8>,
fs_config: FsConfig,
) -> Result<(ProcessMap, Manifest), FsError> {
// 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);
}
) -> Result<(ProcessMap, Manifest, Vec<KernelMessage>), FsError> {
// load/create fs directory, manifest + log if none.
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);
if let Err(e) = fs::create_dir_all(&fs_directory_path_str).await {
panic!("failed creating fs dir! {:?}", e);
}
let fs_directory_path: std::path::PathBuf =
fs::canonicalize(fs_directory_path_str).await.unwrap();
@ -51,7 +50,7 @@ pub async fn bootstrap(
.expect("fs: failed to open WAL file");
// in memory details about files.
let manifest = Manifest::load(
let mut manifest = Manifest::load(
manifest_file,
wal_file,
&fs_directory_path,
@ -60,115 +59,329 @@ pub async fn bootstrap(
)
.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()));
// get kernel state for booting up
let kernel_process_id = FileIdentifier::Process(KERNEL_PROCESS_ID.clone());
let mut process_map: ProcessMap = HashMap::new();
// get current processes' wasm_bytes handles. GetState(kernel)
match manifest.read(&kernel_process_id, None, None).await {
let vfs_messages = match manifest.read(&kernel_process_id, None, None).await {
Err(_) => {
// first time!
// bootstrap filesystem
bootstrap(
&our_name,
&kernel_process_id,
&mut process_map,
&mut manifest,
)
.await
.expect("fresh bootstrap failed!")
}
Ok(bytes) => {
process_map = bincode::deserialize(&bytes).expect("state map deserialization error!");
vec![]
}
}
};
// 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.
Ok((process_map, manifest, vfs_messages))
}
// 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",
/// function run only upon fresh boot.
///
/// for each folder in /modules, looks for a package.zip file, extracts the contents,
/// sends the contents to VFS, and reads the manifest.json.
///
/// the manifest.json contains instructions for which processes to boot and what
/// capabilities to give them. since we are inside runtime, can spawn those out of
/// thin air.
async fn bootstrap(
our_name: &str,
kernel_process_id: &FileIdentifier,
process_map: &mut ProcessMap,
manifest: &mut Manifest,
) -> Result<Vec<KernelMessage>> {
println!("bootstrapping node...\r");
const RUNTIME_MODULES: [(&str, bool); 8] = [
("filesystem:sys:uqbar", false),
("http_server:sys:uqbar", true), // TODO evaluate
("http_client:sys:uqbar", false),
("encryptor:sys:uqbar", false),
("net:sys:uqbar", false),
("vfs:sys:uqbar", true),
("kernel:sys:uqbar", false),
("eth_rpc:sys:uqbar", true), // TODO evaluate
];
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: "\"messaging\"".into(),
});
}
let mut runtime_caps: HashSet<Capability> = HashSet::new();
for runtime_module in RUNTIME_MODULES {
special_capabilities.insert(Capability {
runtime_caps.insert(Capability {
issuer: Address {
node: our_name.clone(),
process: ProcessId::Name(runtime_module.into()),
node: our_name.to_string(),
process: ProcessId::from_str(runtime_module.0).unwrap(),
},
params: "\"messaging\"".into(),
});
}
// give all distro processes the ability to send messages across the network
special_capabilities.insert(Capability {
// give all runtime processes the ability to send messages across the network
runtime_caps.insert(Capability {
issuer: Address {
node: our_name.clone(),
process: ProcessId::Name("kernel".into()),
node: our_name.to_string(),
process: KERNEL_PROCESS_ID.clone(),
},
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()))
process_map
.entry(ProcessId::from_str(runtime_module.0).unwrap())
.or_insert(PersistedProcess {
wasm_bytes_handle: 0,
on_panic: OnPanic::Restart,
capabilities: HashSet::new(),
capabilities: runtime_caps.clone(),
public: runtime_module.1,
});
entry.capabilities.extend(special_capabilities.clone());
}
let packages: Vec<(String, zip::ZipArchive<std::io::Cursor<Vec<u8>>>)> =
get_zipped_packages().await;
// need to grant all caps at the end, after process_map has been filled in!
let mut caps_to_grant = Vec::<(ProcessId, Capability)>::new();
let mut vfs_messages = Vec::new();
for (package_name, mut package) in packages {
println!("fs: handling package {package_name}...\r");
// create a new package in VFS
vfs_messages.push(KernelMessage {
id: rand::random(),
source: Address {
node: our_name.to_string(),
process: FILESYSTEM_PROCESS_ID.clone(),
},
target: Address {
node: our_name.to_string(),
process: VFS_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
inherit: false,
expects_response: None,
ipc: Some(
serde_json::to_string::<VfsRequest>(&VfsRequest {
drive: package_name.clone(),
action: VfsAction::New,
})
.unwrap(),
),
metadata: None,
}),
payload: None,
signed_capabilities: None,
});
// for each file in package.zip, recursively through all dirs, send a newfile KM to VFS
for i in 0..package.len() {
let mut file = package.by_index(i).unwrap();
if file.is_file() {
let file_path = file
.enclosed_name()
.expect("fs: name error reading package.zip")
.to_owned();
let mut file_path = file_path.to_string_lossy().to_string();
if !file_path.starts_with("/") {
file_path = format!("/{}", file_path);
}
println!("fs: found file {}...\r", file_path);
let mut file_content = Vec::new();
file.read_to_end(&mut file_content).unwrap();
vfs_messages.push(KernelMessage {
id: rand::random(),
source: Address {
node: our_name.to_string(),
process: FILESYSTEM_PROCESS_ID.clone(),
},
target: Address {
node: our_name.to_string(),
process: VFS_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
inherit: false,
expects_response: None,
ipc: Some(
serde_json::to_string::<VfsRequest>(&VfsRequest {
drive: package_name.clone(),
action: VfsAction::Add {
full_path: file_path,
entry_type: AddEntryType::NewFile,
},
})
.unwrap(),
),
metadata: None,
}),
payload: Some(Payload {
mime: None,
bytes: file_content,
}),
signed_capabilities: None,
});
}
}
// get and read metadata.json
let Ok(mut package_metadata_zip) = package.by_name("metadata.json") else {
println!(
"fs: missing metadata for package {}, skipping",
package_name
);
continue;
};
let mut metadata_content = Vec::new();
package_metadata_zip
.read_to_end(&mut metadata_content)
.unwrap();
drop(package_metadata_zip);
let package_metadata: serde_json::Value =
serde_json::from_slice(&metadata_content).expect("fs: metadata parse error");
println!("fs: found package metadata: {:?}\r", package_metadata);
let package_name = package_metadata["package"]
.as_str()
.expect("fs: metadata parse error: bad package name");
let package_publisher = package_metadata["publisher"]
.as_str()
.expect("fs: metadata parse error: bad publisher name");
// get and read manifest.json
let Ok(mut package_manifest_zip) = package.by_name("manifest.json") else {
println!(
"fs: missing manifest for package {}, skipping",
package_name
);
continue;
};
let mut manifest_content = Vec::new();
package_manifest_zip
.read_to_end(&mut manifest_content)
.unwrap();
drop(package_manifest_zip);
let package_manifest = String::from_utf8(manifest_content)?;
let package_manifest = serde_json::from_str::<Vec<PackageManifestEntry>>(&package_manifest)
.expect("fs: manifest parse error");
// for each process-entry in manifest.json:
for mut entry in package_manifest {
let wasm_bytes = &mut Vec::new();
let mut file_path = format!("{}", entry.process_wasm_path);
if file_path.starts_with("/") {
file_path = format!("{}", &file_path[1..]);
}
package
.by_name(&file_path)
.expect("fs: no wasm found in package!")
.read_to_end(wasm_bytes)
.unwrap();
// spawn the requested capabilities
// remember: out of thin air, because this is the root distro
let mut requested_caps = HashSet::new();
let our_process_id = format!(
"{}:{}:{}",
entry.process_name, package_name, package_publisher
);
entry.request_messaging.push(our_process_id.clone());
for process_name in &entry.request_messaging {
requested_caps.insert(Capability {
issuer: Address {
node: our_name.to_string(),
process: ProcessId::from_str(process_name).unwrap(),
},
params: "\"messaging\"".into(),
});
}
if entry.request_networking {
requested_caps.insert(Capability {
issuer: Address {
node: our_name.to_string(),
process: KERNEL_PROCESS_ID.clone(),
},
params: "\"network\"".into(),
});
}
// give access to package_name vfs
requested_caps.insert(Capability {
issuer: Address {
node: our_name.into(),
process: VFS_PROCESS_ID.clone(),
},
params: serde_json::to_string(&serde_json::json!({
"kind": "read",
"drive": package_name,
}))
.unwrap(),
});
requested_caps.insert(Capability {
issuer: Address {
node: our_name.into(),
process: VFS_PROCESS_ID.clone(),
},
params: serde_json::to_string(&serde_json::json!({
"kind": "write",
"drive": package_name,
}))
.unwrap(),
});
let mut public_process = false;
// queue the granted capabilities
for process_name in &entry.grant_messaging {
if process_name == "all" {
public_process = true;
continue;
}
let process_id = ProcessId::from_str(process_name).unwrap();
caps_to_grant.push((
process_id.clone(),
Capability {
issuer: Address {
node: our_name.to_string(),
process: ProcessId::from_str(&our_process_id).unwrap(),
},
params: "\"messaging\"".into(),
},
));
}
// save in process map
let file = FileIdentifier::new_uuid();
manifest.write(&file, &wasm_bytes).await.unwrap();
let wasm_bytes_handle = file.to_uuid().unwrap();
process_map.insert(
ProcessId::new(Some(&entry.process_name), package_name, package_publisher),
PersistedProcess {
wasm_bytes_handle,
on_panic: entry.on_panic,
capabilities: requested_caps,
public: public_process,
},
);
}
}
// grant queued capabilities from all packages
for (to, cap) in caps_to_grant {
let Some(proc) = process_map.get_mut(&to) else {
continue;
};
proc.capabilities.insert(cap);
}
// save kernel process state. FsAction::SetState(kernel)
@ -181,41 +394,36 @@ pub async fn bootstrap(
.write(&kernel_process_id, &serialized_process_map)
.await;
}
Ok((process_map, manifest))
Ok(vfs_messages)
}
async fn get_processes_from_directories() -> Vec<(String, Vec<u8>)> {
let mut processes = Vec::new();
/// go into /target folder and get all .zip package files
async fn get_zipped_packages() -> Vec<(String, zip::ZipArchive<std::io::Cursor<Vec<u8>>>)> {
println!("fs: reading distro packages...\r");
let target_path = std::path::Path::new("target");
// Get the path to the /modules directory
let modules_path = std::path::Path::new("modules");
let mut packages = Vec::new();
// Read the /modules directory
if let Ok(mut entries) = fs::read_dir(modules_path).await {
// Loop through the entries in the directory
if let Ok(mut entries) = fs::read_dir(target_path).await {
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));
}
if entry.file_name().to_string_lossy().ends_with(".zip") {
let package_name = entry
.file_name()
.to_string_lossy()
.trim_end_matches(".zip")
.to_string();
if let Ok(bytes) = fs::read(entry.path()).await {
if let Ok(zip) = zip::ZipArchive::new(std::io::Cursor::new(bytes)) {
// add to list of packages
println!("fs: found package: {}\r", package_name);
packages.push((package_name, zip));
}
}
}
}
}
processes
return packages;
}
pub async fn fs_sender(
@ -228,7 +436,7 @@ pub async fn fs_sender(
send_kill_confirm: Sender<()>,
) -> Result<()> {
// process queues for consistency
// todo: use file_identifier for moar concurrency!
// todo: use file_drive for moar concurrency!
let process_queues = Arc::new(Mutex::new(
HashMap::<ProcessId, VecDeque<KernelMessage>>::new(),
));
@ -250,53 +458,52 @@ pub async fn fs_sender(
continue;
}
// internal structures have Arc::clone setup.
let manifest_clone = manifest.clone();
// 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 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;
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);
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();
// clone Arc for thread
let process_lock_clone = process_queues.clone();
tokio::spawn(async move {
let mut process_lock = process_lock_clone.lock().await;
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();
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
{
let _ = send_to_loop
.send(make_error_message(our_name.clone(), &km, e))
.await;
}
}
}
// 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);
// 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 {
@ -364,6 +571,7 @@ async fn handle_request(
action: "Write".into(),
});
};
// println!("fs: got write from {:?} with len {:?}", source.process, &payload.bytes.len());
let file_uuid = FileIdentifier::new_uuid();
match manifest.write(&file_uuid, &payload.bytes).await {
@ -498,14 +706,15 @@ async fn handle_request(
(FsResponse::Length(length), None)
}
// process state handlers
FsAction::SetState => {
FsAction::SetState(process_id) => {
let Some(ref payload) = payload else {
return Err(FsError::BadBytes {
action: "SetState".into(),
});
};
let file = FileIdentifier::Process(source.process.clone());
// println!("setting state for process {:?} with len {:?}", process_id, &payload.bytes.len());
let file = FileIdentifier::Process(process_id);
match manifest.write(&file, &payload.bytes).await {
Ok(_) => (),
Err(e) => {
@ -518,14 +727,14 @@ async fn handle_request(
(FsResponse::SetState, None)
}
FsAction::DeleteState => {
let file = FileIdentifier::Process(source.process.clone());
FsAction::DeleteState(process_id) => {
let file = FileIdentifier::Process(process_id);
manifest.delete(&file).await?;
(FsResponse::Delete(0), None)
}
FsAction::GetState => {
let file = FileIdentifier::Process(source.process.clone());
FsAction::GetState(process_id) => {
let file = FileIdentifier::Process(process_id);
match manifest.read(&file, None, None).await {
Err(e) => return Err(e),
@ -539,10 +748,13 @@ async fn handle_request(
id: id.clone(),
source: Address {
node: our_name.clone(),
process: ProcessId::Name("filesystem".into()),
process: FILESYSTEM_PROCESS_ID.clone(),
},
target: source.clone(),
rsvp,
target: match rsvp {
None => source,
Some(rsvp) => rsvp,
},
rsvp: None,
message: Message::Response((
Response {
ipc: Some(
@ -552,10 +764,10 @@ async fn handle_request(
},
None,
)),
payload: Some(Payload {
mime: None,
bytes: bytes.unwrap_or_default(),
}),
payload: match bytes {
Some(bytes) => Some(Payload { mime: None, bytes }),
None => None,
},
signed_capabilities: None,
};
@ -578,28 +790,17 @@ pub fn hash_bytes(bytes: &[u8]) -> [u8; 32] {
hasher.finalize().into()
}
async fn create_dir_if_dne(path: &str) -> Result<(), FsError> {
if let Err(_) = fs::read_dir(&path).await {
match fs::create_dir_all(&path).await {
Ok(_) => Ok(()),
Err(e) => Err(FsError::CreateInitialDirError {
path: path.into(),
error: format!("{}", e),
}),
}
} else {
Ok(())
}
}
fn make_error_message(our_name: String, id: u64, target: Address, error: FsError) -> KernelMessage {
fn make_error_message(our_name: String, km: &KernelMessage, error: FsError) -> KernelMessage {
KernelMessage {
id,
id: km.id,
source: Address {
node: our_name.clone(),
process: ProcessId::Name("fileystem".into()),
process: FILESYSTEM_PROCESS_ID.clone(),
},
target: match &km.rsvp {
None => km.source.clone(),
Some(rsvp) => rsvp.clone(),
},
target,
rsvp: None,
message: Message::Response((
Response {

View File

@ -138,7 +138,7 @@ async fn handle_message(
id,
source: Address {
node: our,
process: ProcessId::Name("http_client".to_string()),
process: ProcessId::new(Some("http_client"), "sys", "uqbar"),
},
target,
rsvp: None,

View File

@ -351,11 +351,11 @@ async fn http_handle_messages(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: proxy_node.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
@ -428,12 +428,12 @@ async fn http_handle_messages(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: source,
rsvp: Some(Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
}),
message: Message::Request(Request {
inherit: false,
@ -656,15 +656,15 @@ async fn handler(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: our.clone(),
process: ProcessId::Name("http_bindings".into()),
process: ProcessId::new(Some("http_bindings"), "http_bindings", "uqbar"),
},
rsvp: Some(Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
}),
message: Message::Request(Request {
inherit: false,

View File

@ -223,11 +223,11 @@ pub async fn handle_ws_register(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: node.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
@ -272,7 +272,7 @@ pub async fn handle_ws_message(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: target.clone(),
rsvp: None,
@ -314,11 +314,11 @@ pub async fn handle_encrypted_ws_message(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: target.node.clone(),
process: ProcessId::Name("encryptor".into()),
process: ENCRYPTOR_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
@ -363,11 +363,11 @@ pub async fn proxy_ws_message(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node,
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
@ -411,11 +411,11 @@ pub async fn send_ws_disconnect(
id: id.clone(),
source: Address {
node: our.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: node.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {
@ -451,7 +451,7 @@ pub fn make_error_message(
id,
source: Address {
node: our_name.clone(),
process: ProcessId::Name("http_server".into()),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target,
rsvp: None,

File diff suppressed because it is too large Load Diff

View File

@ -2,43 +2,9 @@ use crate::kernel::component::uq_process::types as wit;
use crate::types as t;
//
// conversions between wit types and kernel types (annoying)
// 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)),
@ -117,19 +83,19 @@ pub fn en_wit_payload(payload: Option<t::Payload>) -> Option<wit::Payload> {
pub fn de_wit_signed_capability(wit: wit::SignedCapability) -> t::SignedCapability {
t::SignedCapability {
issuer: de_wit_address(wit.issuer),
issuer: t::Address::de_wit(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 en_wit_signed_capability(cap: t::SignedCapability) -> wit::SignedCapability {
// wit::SignedCapability {
// issuer: cap.issuer.en_wit().to_owned(),
// params: cap.params,
// signature: cap.signature,
// }
// }
pub fn de_wit_on_panic(wit: wit::OnPanic) -> t::OnPanic {
match wit {
@ -139,7 +105,7 @@ pub fn de_wit_on_panic(wit: wit::OnPanic) -> t::OnPanic {
reqs.into_iter()
.map(|(address, request, payload)| {
(
de_wit_address(address),
t::Address::de_wit(address),
de_wit_request(request),
de_wit_payload(payload),
)

View File

@ -1,8 +1,6 @@
use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use super::bindings::component::uq_process::types as wit;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
//
// process-facing kernel types, used for process
@ -11,45 +9,118 @@ use super::bindings::component::uq_process::types as wit;
//
pub type Context = String; // JSON-string
pub type NodeId = String; // QNS domain name
#[derive(Clone, Debug, Eq, Hash, Serialize, Deserialize)]
pub enum ProcessId {
Id(u64),
Name(String),
/// process ID is a formatted unique identifier that contains
/// the publishing node's ID, the package name, and finally the process name.
/// the process name can be a random number, or a name chosen by the user.
/// the formatting is as follows:
/// `[process name]:[package name]:[node ID]`
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ProcessId {
process_name: String,
package_name: String,
publisher_node: NodeId,
}
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,
#[allow(dead_code)]
impl ProcessId {
/// generates a random u64 number if process_name is not declared
pub fn new(process_name: &str, package_name: &str, publisher_node: &str) -> Self {
ProcessId {
process_name: process_name.into(),
package_name: package_name.into(),
publisher_node: publisher_node.into(),
}
}
}
impl PartialEq<&str> for ProcessId {
fn eq(&self, other: &&str) -> bool {
match self {
ProcessId::Id(_) => false,
ProcessId::Name(s) => s == other,
pub fn from_str(input: &str) -> Result<Self, ProcessIdParseError> {
// split string on colons into 3 segments
let mut segments = input.split(':');
let process_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let package_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let publisher_node = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
if segments.next().is_some() {
return Err(ProcessIdParseError::TooManyColons);
}
Ok(ProcessId {
process_name,
package_name,
publisher_node,
})
}
pub fn to_string(&self) -> String {
[
self.process_name.as_str(),
self.package_name.as_str(),
self.publisher_node.as_str(),
]
.join(":")
}
pub fn process(&self) -> &str {
&self.process_name
}
pub fn package(&self) -> &str {
&self.package_name
}
pub fn publisher_node(&self) -> &str {
&self.publisher_node
}
pub fn en_wit(&self) -> wit::ProcessId {
wit::ProcessId {
process_name: self.process_name.clone(),
package_name: self.package_name.clone(),
publisher_node: self.publisher_node.clone(),
}
}
}
impl PartialEq<u64> for ProcessId {
fn eq(&self, other: &u64) -> bool {
match self {
ProcessId::Id(i) => i == other,
ProcessId::Name(s) => false,
pub fn de_wit(wit: wit::ProcessId) -> ProcessId {
ProcessId {
process_name: wit.process_name,
package_name: wit.package_name,
publisher_node: wit.publisher_node,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
#[derive(Debug)]
pub enum ProcessIdParseError {
TooManyColons,
MissingField,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct Address {
pub node: String,
pub node: NodeId,
pub process: ProcessId,
}
impl Address {
pub fn en_wit(&self) -> wit::Address {
wit::Address {
node: self.node.clone(),
process: self.process.en_wit(),
}
}
pub fn de_wit(wit: wit::Address) -> Address {
Address {
node: wit.node,
process: ProcessId {
process_name: wit.process.process_name,
package_name: wit.process.package_name,
publisher_node: wit.process.publisher_node,
},
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Payload {
pub mime: Option<String>, // MIME type
@ -59,9 +130,9 @@ pub struct Payload {
#[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
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)]
@ -76,13 +147,13 @@ pub enum Message {
Response((Response, Option<Context>)),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Capability {
pub issuer: Address,
pub params: String, // JSON-string
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct SignedCapability {
pub issuer: Address,
pub params: String, // JSON-string
@ -107,76 +178,166 @@ pub enum SendErrorKind {
pub enum OnPanic {
None,
Restart,
Requests(Vec<(Address, Request)>),
Requests(Vec<(Address, Request, Option<Payload>)>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PersistedProcess {
pub wasm_bytes_handle: u128,
pub on_panic: OnPanic,
pub capabilities: HashSet<Capability>,
impl OnPanic {
pub fn is_restart(&self) -> bool {
match self {
OnPanic::None => false,
OnPanic::Restart => true,
OnPanic::Requests(_) => false,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KernelCommand {
StartProcess {
name: Option<String>,
id: ProcessId,
wasm_bytes_handle: u128,
on_panic: OnPanic,
initial_capabilities: HashSet<Capability>,
initial_capabilities: HashSet<SignedCapability>,
public: bool,
},
KillProcess(ProcessId), // this is extrajudicial killing: we might lose messages!
// kernel only
RebootProcess {
// kernel only
process_id: ProcessId,
persisted: PersistedProcess,
},
Shutdown,
// capabilities creation
GrantCapability {
to_process: ProcessId,
params: String, // JSON-string
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KernelResponse {
StartedProcess,
StartProcessError,
KilledProcess(ProcessId),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PersistedProcess {
pub wasm_bytes_handle: u128,
// pub drive: String,
// pub full_path: String,
pub on_panic: OnPanic,
pub capabilities: HashSet<Capability>,
pub public: bool, // marks if a process allows messages from any process
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VfsRequest {
pub drive: String,
pub action: VfsAction,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum VfsAction {
New,
Add {
full_path: String,
entry_type: AddEntryType,
},
Rename {
full_path: String,
new_full_path: String,
},
Delete(String),
WriteOffset {
full_path: String,
offset: u64,
},
SetSize {
full_path: String,
size: u64,
},
GetPath(u128),
GetHash(String),
GetEntry(String),
GetFileChunk {
full_path: String,
offset: u64,
length: u64,
},
GetEntryLength(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
ZipArchive,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum GetEntryType {
Dir,
File,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum VfsResponse {
Ok,
Err(VfsError),
GetPath(Option<String>),
GetHash(Option<u128>),
GetEntry {
// file bytes in payload, if entry was a file
is_file: bool,
children: Vec<String>,
},
GetFileChunk, // chunk in payload
GetEntryLength(u64),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum VfsError {
BadDriveName,
BadDescriptor,
NoCap,
}
#[allow(dead_code)]
impl VfsError {
pub fn kind(&self) -> &str {
match *self {
VfsError::BadDriveName => "BadDriveName",
VfsError::BadDescriptor => "BadDescriptor",
VfsError::NoCap => "NoCap",
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KeyValueMessage {
New { drive: String },
Write { drive: String, key: Vec<u8> },
Read { drive: String, key: Vec<u8> },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KeyValueError {
BadDriveName,
NoCap,
NoBytes,
}
impl KeyValueError {
pub fn kind(&self) -> &str {
match *self {
KeyValueError::BadDriveName => "BadDriveName",
KeyValueError::NoCap => "NoCap",
KeyValueError::NoBytes => "NoBytes",
}
}
}
//
// conversions between wit types and kernel types (annoying)
// 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,
@ -231,7 +392,14 @@ pub fn en_wit_payload(load: Option<Payload>) -> Option<wit::Payload> {
pub fn de_wit_signed_capability(wit: wit::SignedCapability) -> SignedCapability {
SignedCapability {
issuer: de_wit_address(wit.issuer),
issuer: Address {
node: wit.issuer.node,
process: ProcessId {
process_name: wit.issuer.process.process_name,
package_name: wit.issuer.process.package_name,
publisher_node: wit.issuer.process.publisher_node,
},
},
params: wit.params,
signature: wit.signature,
}
@ -239,7 +407,7 @@ pub fn de_wit_signed_capability(wit: wit::SignedCapability) -> SignedCapability
pub fn en_wit_signed_capability(cap: SignedCapability) -> wit::SignedCapability {
wit::SignedCapability {
issuer: en_wit_address(cap.issuer),
issuer: cap.issuer.en_wit(),
params: cap.params,
signature: cap.signature,
}

View File

@ -34,6 +34,7 @@ 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 CAP_CHANNEL_CAPACITY: usize = 1_000;
const QNS_SEPOLIA_ADDRESS: &str = "0x9e5ed0e7873E0d7f10eEb6dE72E87fE087A12776";
@ -71,7 +72,8 @@ async fn main() {
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>();
let (caps_oracle_sender, caps_oracle_receiver): (CapMessageSender, CapMessageReceiver) =
mpsc::channel(CAP_CHANNEL_CAPACITY);
// networking module sends error messages to kernel
let (network_error_sender, network_error_receiver): (NetworkErrorSender, NetworkErrorReceiver) =
mpsc::channel(EVENT_LOOP_CHANNEL_CAPACITY);
@ -233,10 +235,20 @@ async fn main() {
// 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 {
let Ok(Ok(ws_rpc)) = timeout(
tokio::time::Duration::from_secs(10),
Provider::<Ws>::connect(rpc_url.clone()),
)
.await
else {
panic!("rpc: couldn't connect to blockchain wss endpoint. you MUST set an endpoint with --rpc flag, go to alchemy.com and get a free API key, then use the wss endpoint that looks like this: wss://eth-sepolia.g.alchemy.com/v2/<your-api-key>");
};
let Ok(_) = ws_rpc.get_block_number().await else {
let Ok(Ok(_)) = timeout(
tokio::time::Duration::from_secs(10),
ws_rpc.get_block_number(),
)
.await
else {
panic!("error: RPC endpoint not responding, try setting one with --rpc flag");
};
let qns_address: EthAddress = QNS_SEPOLIA_ADDRESS.parse().unwrap();
@ -245,6 +257,13 @@ async fn main() {
let Ok(onchain_id) = contract.ws(node_id).call().await else {
panic!("error: RPC endpoint failed to fetch our node_id");
};
print_sender
.send(Printout {
verbosity: 0,
content: "established connection to Sepolia RPC".to_string(),
})
.await
.unwrap();
// double check that routers match on-chain information
let namehashed_routers: Vec<[u8; 32]> = routers
.clone()
@ -355,30 +374,17 @@ async fn main() {
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(
let (kernel_process_map, manifest, vfs_messages) = filesystem::load_fs(
our.name.clone(),
home_directory_path.clone(),
file_key,
fs_config,
)
.await
.expect("fs bootstrap failed!");
.expect("fs load 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,
@ -457,11 +463,11 @@ async fn main() {
));
tasks.spawn(vfs::vfs(
our.name.clone(),
kernel_process_map,
kernel_message_sender.clone(),
print_sender.clone(),
vfs_message_receiver,
caps_oracle_sender.clone(),
vfs_messages,
));
tasks.spawn(encryptor::encryptor(
our.name.clone(),
@ -510,14 +516,14 @@ async fn main() {
// gracefully abort all running processes in kernel
let _ = kernel_message_sender
.send(KernelMessage {
id: 0,
id: rand::random(),
source: Address {
node: our.name.clone(),
process: ProcessId::Name("kernel".into()),
process: KERNEL_PROCESS_ID.clone(),
},
target: Address {
node: our.name.clone(),
process: ProcessId::Name("kernel".into()),
process: KERNEL_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {

View File

@ -73,6 +73,24 @@ pub async fn maintain_connection(
let ack_map = Arc::new(RwLock::new(HashMap::<u64, ErrorShuttle>::new()));
let sender_ack_map = ack_map.clone();
let last_pong = Arc::new(RwLock::new(tokio::time::Instant::now()));
let ping_last_pong = last_pong.clone();
let ping_tx = message_tx.clone();
// Ping/Pong keepalive task
let ping_task = tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(30)).await;
if ping_last_pong.read().await.elapsed() > tokio::time::Duration::from_secs(60) {
break;
}
if let Err(_) = ping_tx.send((NetworkMessage::Ping, None)) {
// Failed to send Ping, kill the connection
break;
}
}
});
let forwarder_message_tx = message_tx.clone();
let ack_forwarder = tokio::spawn(async move {
while let Some(result) = forwarding_ack_rx.recv().await {
@ -115,13 +133,28 @@ pub async fn maintain_connection(
// 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 {
while let Some(incoming) = read_stream.next().await {
let Ok(tungstenite::Message::Binary(bin)) = incoming else {
if let Ok(tungstenite::Message::Ping(_)) = incoming {
// let _ = write_stream.send(tungstenite::Message::Pong(vec![])).await;
}
continue;
};
// 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::Pong => {
*last_pong.write().await = tokio::time::Instant::now();
continue;
}
NetworkMessage::Ping => {
// respond with a Pong
let _ = message_tx.send((NetworkMessage::Pong, None));
continue;
}
NetworkMessage::Ack(id) => {
let Some(result_tx) = ack_map.write().await.remove(&id) else {
// println!("conn {conn_id}: got unexpected Ack {id}\r");
@ -184,10 +217,18 @@ pub async fn maintain_connection(
// with the matching peer handler "sender".
//
if let Some(peer) = peers.read().await.get(to) {
let _ = peer.sender.send((
let id = *id;
let to = to.clone();
match peer.sender.send((
PeerMessage::Net(net_message),
Some(forwarding_ack_tx.clone()),
));
)) {
Ok(_) => {}
Err(_) => {
peers.write().await.remove(&to);
message_tx.send((NetworkMessage::Nack(id), None)).unwrap();
}
}
} else {
// if we don't have the peer, throw a nack.
// println!("net: nacking message with id {id}\r");
@ -286,7 +327,10 @@ pub async fn maintain_connection(
},
_ = ack_forwarder => {
// println!("ack_forwarder died\r");
}
},
_ = ping_task => {
// println!("ping_task died\r");
},
// receive messages we would like to send to peers along this connection
// and send them to the websocket
_ = async {
@ -431,10 +475,10 @@ async fn peer_handler(
break;
}
println!("net: failed to deserialize message from {}\r", who);
continue;
break;
}
println!("net: failed to decrypt message from {}, could be spoofer\r", who);
continue;
break;
}
} => {
// println!("net: lost peer {who}\r");

View File

@ -87,6 +87,7 @@ pub async fn networking(
keys.clone(),
pki.clone(),
names.clone(),
kernel_message_tx.clone(),
print_tx.clone(),
)
.await;
@ -98,8 +99,16 @@ pub async fn networking(
//
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;
match peer.sender.send((PeerMessage::Raw(km.clone()), None)) {
Ok(_) => continue,
Err(_) => {
// println!("net: failed to send to known peer\r");
drop(peers_read);
peers.write().await.remove(target);
error_offline(km, &network_error_tx).await;
continue;
}
}
}
drop(peers_read);
//
@ -585,14 +594,14 @@ async fn connect_to_routers(
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");
// 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");
// println!("net: failed handshake with {router_name}\r");
conn_handle.abort();
let _ = routers_to_try_tx.send(router_name);
continue;
@ -677,10 +686,13 @@ async fn handle_incoming_message(
keys: PeerKeys,
pki: OnchainPKI,
names: PKINames,
kernel_message_tx: MessageSender,
print_tx: PrintSender,
) {
let data = match km.message {
Message::Response(_) => return,
Message::Response(_) => {
return;
}
Message::Request(request) => match request.ipc {
None => return,
Some(ipc) => ipc,
@ -688,10 +700,31 @@ async fn handle_incoming_message(
};
if km.source.node != our.name {
// respond to a text message with a simple "delivered" response
let _ = print_tx
.send(Printout {
verbosity: 0,
content: format!("\x1b[3;32m{}: {}\x1b[0m", km.source.node, data,),
content: format!("\x1b[3;32m{}: {}\x1b[0m", km.source.node, data),
})
.await;
let _ = kernel_message_tx
.send(KernelMessage {
id: km.id,
source: Address {
node: our.name.clone(),
process: ProcessId::from_str("net:sys:uqbar").unwrap(),
},
target: km.rsvp.unwrap_or(km.source),
rsvp: None,
message: Message::Response((
Response {
ipc: Some("delivered".into()),
metadata: None,
},
None,
)),
payload: None,
signed_capabilities: None,
})
.await;
} else {
@ -746,15 +779,6 @@ async fn handle_incoming_message(
};
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
@ -777,6 +801,34 @@ async fn handle_incoming_message(
);
let _ = names.write().await.insert(log.node, log.name);
}
NetActions::QnsBatchUpdate(log_list) => {
let _ = print_tx
.send(Printout {
verbosity: 0, // TODO 1
content: format!(
"net: got QNS update with {} peers",
log_list.len()
),
})
.await;
for log in log_list {
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);
}
}
}
}
}

View File

@ -38,6 +38,9 @@ pub enum NetworkMessage {
},
Handshake(Handshake),
HandshakeAck(Handshake),
// only used in implementation, not part of protocol
Ping,
Pong,
}
pub enum PeerMessage {
@ -60,6 +63,7 @@ pub struct Handshake {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum NetActions {
QnsUpdate(QnsUpdate),
QnsBatchUpdate(Vec<QnsUpdate>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]

View File

@ -1,32 +1,73 @@
use serde::{Deserialize, Serialize};
use super::bindings::component::uq_process::types::*;
use super::bindings::{get_payload, send_request, Address, Payload};
use super::bindings::{Address, Payload, ProcessId, SendError};
#[allow(dead_code)]
impl ProcessId {
/// generates a random u64 number if process_name is not declared
pub fn new(process_name: &str, package_name: &str, publisher_node: &str) -> Self {
ProcessId {
process_name: process_name.into(),
package_name: package_name.into(),
publisher_node: publisher_node.into(),
}
}
pub fn from_str(input: &str) -> Result<Self, ProcessIdParseError> {
// split string on colons into 3 segments
let mut segments = input.split(':');
let process_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let package_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let publisher_node = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
if segments.next().is_some() {
return Err(ProcessIdParseError::TooManyColons);
}
Ok(ProcessId {
process_name,
package_name,
publisher_node,
})
}
pub fn to_string(&self) -> String {
[
self.process_name.as_str(),
self.package_name.as_str(),
self.publisher_node.as_str(),
]
.join(":")
}
pub fn process(&self) -> &str {
&self.process_name
}
pub fn package(&self) -> &str {
&self.package_name
}
pub fn publisher_node(&self) -> &str {
&self.publisher_node
}
}
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,
}
self.process_name == other.process_name
&& self.package_name == other.package_name
&& self.publisher_node == other.publisher_node
}
}
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(Debug)]
pub enum ProcessIdParseError {
TooManyColons,
MissingField,
}
pub fn send_and_await_response(
@ -49,62 +90,42 @@ pub fn send_and_await_response(
)
}
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()),
},
pub fn send_request(
target: &Address,
inherit: bool,
ipc: Option<Json>,
metadata: Option<Json>,
context: Option<&Json>,
payload: Option<&Payload>,
) {
super::bindings::send_request(
target,
&Request {
inherit: false,
expects_response: Some(5), // TODO evaluate timeout
ipc: Some(serde_json::to_string(&FsAction::SetState).unwrap()),
metadata: None,
inherit,
expects_response: None,
ipc,
metadata,
},
None,
Some(&Payload { mime: None, bytes }),
);
context,
payload,
)
}
pub fn await_set_state<T>(our: String, state: &T)
pub fn get_state<T: serde::de::DeserializeOwned>() -> Option<T> {
match super::bindings::get_state() {
Some(bytes) => match bincode::deserialize::<T>(&bytes) {
Ok(state) => Some(state),
Err(_) => None,
},
None => None,
}
}
pub fn set_state<T>(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,
}
super::bindings::set_state(&bincode::serialize(state).unwrap());
}
pub fn parse_message_ipc<T>(json_string: Option<String>) -> anyhow::Result<T>

View File

@ -167,23 +167,19 @@ pub async fn login(
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 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)),
);
let _ = open::that(format!("http://localhost:{}/login", port));
warp::serve(routes)

View File

@ -1,4 +1,6 @@
use crate::types::*;
use anyhow::Result;
use chrono::{Datelike, Local, Timelike};
use crossterm::{
cursor,
event::{
@ -14,8 +16,6 @@ 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>,
@ -186,9 +186,11 @@ pub async fn terminal(
let event = reader.next().fuse();
tokio::select! {
// aaa
prints = print_rx.recv() => match prints {
Some(printout) => {
let _ = writeln!(log_writer, "{}", printout.content);
let now = Local::now();
let _ = writeln!(log_writer, "{} {}", now.to_rfc2822(), printout.content);
if match printout.verbosity {
0 => false,
1 => !verbose_mode,
@ -200,7 +202,14 @@ pub async fn terminal(
execute!(
stdout,
cursor::MoveTo(0, win_rows - 1),
terminal::Clear(ClearType::CurrentLine)
terminal::Clear(ClearType::CurrentLine),
Print(format!("{} {}/{} {:02}:{:02} ",
now.weekday(),
now.month(),
now.day(),
now.hour(),
now.minute(),
)),
)?;
for line in printout.content.lines() {
execute!(
@ -584,16 +593,17 @@ pub async fn terminal(
command_history.add(command.clone());
cursor_col = prompt_len.try_into().unwrap();
line_col = prompt_len;
// println!("terminal: sending\r");
let _err = event_loop.send(
KernelMessage {
id: rand::random(),
source: Address {
node: our.name.clone(),
process: ProcessId::Name("terminal".into()),
process: TERMINAL_PROCESS_ID.clone(),
},
target: Address {
node: our.name.clone(),
process: ProcessId::Name("terminal".into()),
process: TERMINAL_PROCESS_ID.clone(),
},
rsvp: None,
message: Message::Request(Request {

View File

@ -1,3 +1,4 @@
use crate::kernel::component::uq_process::types as wit;
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
@ -6,6 +7,17 @@ use std::{
use thiserror::Error;
use tokio::sync::RwLock;
lazy_static::lazy_static! {
pub static ref ENCRYPTOR_PROCESS_ID: ProcessId = ProcessId::new(Some("encryptor"), "sys", "uqbar");
pub static ref ETH_RPC_PROCESS_ID: ProcessId = ProcessId::new(Some("eth_rpc"), "sys", "uqbar");
pub static ref FILESYSTEM_PROCESS_ID: ProcessId = ProcessId::new(Some("filesystem"), "sys", "uqbar");
pub static ref HTTP_CLIENT_PROCESS_ID: ProcessId = ProcessId::new(Some("http_client"), "sys", "uqbar");
pub static ref HTTP_SERVER_PROCESS_ID: ProcessId = ProcessId::new(Some("http_server"), "sys", "uqbar");
pub static ref KERNEL_PROCESS_ID: ProcessId = ProcessId::new(Some("kernel"), "sys", "uqbar");
pub static ref TERMINAL_PROCESS_ID: ProcessId = ProcessId::new(Some("terminal"), "terminal", "uqbar");
pub static ref VFS_PROCESS_ID: ProcessId = ProcessId::new(Some("vfs"), "sys", "uqbar");
}
//
// internal message pipes between kernel and runtime modules
//
@ -23,28 +35,29 @@ 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>;
pub type CapMessageSender = tokio::sync::mpsc::Sender<CapMessage>;
pub type CapMessageReceiver = tokio::sync::mpsc::Receiver<CapMessage>;
//
// types used for UQI: uqbar's identity system
//
pub type PKINames = Arc<RwLock<HashMap<String, String>>>; // TODO maybe U256 to String
pub type NodeId = String;
pub type PKINames = Arc<RwLock<HashMap<String, NodeId>>>; // TODO maybe U256 to String
pub type OnchainPKI = Arc<RwLock<HashMap<String, Identity>>>;
#[derive(Debug, Serialize, Deserialize)]
pub struct Registration {
pub username: String,
pub username: NodeId,
pub password: String,
pub direct: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Identity {
pub name: String,
pub name: NodeId,
pub networking_key: String,
pub ws_routing: Option<(String, u16)>,
pub allowed_routers: Vec<String>,
pub allowed_routers: Vec<NodeId>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -65,44 +78,119 @@ pub struct IdentityTransaction {
pub type Context = String; // JSON-string
#[derive(Clone, Debug, Eq, Hash, Serialize, Deserialize)]
pub enum ProcessId {
Id(u64),
Name(String),
/// process ID is a formatted unique identifier that contains
/// the publishing node's ID, the package name, and finally the process name.
/// the process name can be a random number, or a name chosen by the user.
/// the formatting is as follows:
/// `[process name]:[package name]:[node ID]`
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ProcessId {
process_name: String,
package_name: String,
publisher_node: NodeId,
}
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,
#[allow(dead_code)]
impl ProcessId {
/// generates a random u64 number if process_name is not declared
pub fn new(process_name: Option<&str>, package_name: &str, publisher_node: &str) -> Self {
ProcessId {
process_name: match process_name {
Some(name) => name.to_string(),
None => rand::random::<u64>().to_string(),
},
package_name: package_name.into(),
publisher_node: publisher_node.into(),
}
}
pub fn from_str(input: &str) -> Result<Self, ProcessIdParseError> {
// split string on colons into 3 segments
let mut segments = input.split(':');
let process_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let package_name = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
let publisher_node = segments
.next()
.ok_or(ProcessIdParseError::MissingField)?
.to_string();
if segments.next().is_some() {
return Err(ProcessIdParseError::TooManyColons);
}
Ok(ProcessId {
process_name,
package_name,
publisher_node,
})
}
pub fn to_string(&self) -> String {
[
self.process_name.as_str(),
self.package_name.as_str(),
self.publisher_node.as_str(),
]
.join(":")
}
pub fn process(&self) -> &str {
&self.process_name
}
pub fn package(&self) -> &str {
&self.package_name
}
pub fn publisher_node(&self) -> &str {
&self.publisher_node
}
pub fn en_wit(&self) -> wit::ProcessId {
wit::ProcessId {
process_name: self.process_name.clone(),
package_name: self.package_name.clone(),
publisher_node: self.publisher_node.clone(),
}
}
pub fn de_wit(wit: wit::ProcessId) -> ProcessId {
ProcessId {
process_name: wit.process_name,
package_name: wit.package_name,
publisher_node: wit.publisher_node,
}
}
}
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(Debug)]
pub enum ProcessIdParseError {
TooManyColons,
MissingField,
}
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct Address {
pub node: String,
pub node: NodeId,
pub process: ProcessId,
}
impl Address {
pub fn en_wit(&self) -> wit::Address {
wit::Address {
node: self.node.clone(),
process: self.process.en_wit(),
}
}
pub fn de_wit(wit: wit::Address) -> Address {
Address {
node: wit.node,
process: ProcessId {
process_name: wit.process.process_name,
package_name: wit.process.package_name,
publisher_node: wit.process.publisher_node,
},
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Payload {
pub mime: Option<String>, // MIME type
@ -135,7 +223,7 @@ pub struct Capability {
pub params: String, // JSON-string
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct SignedCapability {
pub issuer: Address,
pub params: String, // JSON-string
@ -182,6 +270,7 @@ pub struct ProcessMetadata {
pub our: Address,
pub wasm_bytes_handle: u128,
pub on_panic: OnPanic,
pub public: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -220,19 +309,6 @@ 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,
@ -249,34 +325,34 @@ pub enum DebugCommand {
#[derive(Debug, Serialize, Deserialize)]
pub enum KernelCommand {
StartProcess {
name: Option<String>,
id: ProcessId,
wasm_bytes_handle: u128,
on_panic: OnPanic,
initial_capabilities: HashSet<Capability>,
initial_capabilities: HashSet<SignedCapability>,
public: bool,
},
KillProcess(ProcessId), // this is extrajudicial killing: we might lose messages!
// kernel only
RebootProcess {
// kernel only
process_id: ProcessId,
persisted: PersistedProcess,
},
Shutdown,
// capabilities creation
GrantCapability {
to_process: ProcessId,
params: String, // JSON-string
},
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum CapMessage {
Add {
on: ProcessId,
cap: Capability,
responder: tokio::sync::oneshot::Sender<bool>,
},
Drop {
// not used yet!
on: ProcessId,
cap: Capability,
responder: tokio::sync::oneshot::Sender<bool>,
},
Has {
// a bool is given in response here
@ -292,7 +368,8 @@ pub enum CapMessage {
#[derive(Debug, Serialize, Deserialize)]
pub enum KernelResponse {
StartedProcess(ProcessMetadata),
StartedProcess,
StartProcessError,
KilledProcess(ProcessId),
}
@ -301,8 +378,11 @@ pub type ProcessMap = HashMap<ProcessId, PersistedProcess>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PersistedProcess {
pub wasm_bytes_handle: u128,
// pub drive: String,
// pub full_path: String,
pub on_panic: OnPanic,
pub capabilities: HashSet<Capability>,
pub public: bool, // marks if a process allows messages from any process
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -321,6 +401,16 @@ pub struct ProcessContext {
// filesystem.rs types
//
#[derive(Debug, Serialize, Deserialize)]
pub struct PackageManifestEntry {
pub process_name: String,
pub process_wasm_path: String,
pub on_panic: OnPanic,
pub request_networking: bool,
pub request_messaging: Vec<String>,
pub grant_messaging: Vec<String>, // special logic for the string "all": makes process public
}
#[derive(Serialize, Deserialize, Debug)]
pub enum FsAction {
Write,
@ -332,9 +422,9 @@ pub enum FsAction {
Delete(u128),
Length(u128),
SetLength((u128, u64)),
GetState,
SetState,
DeleteState,
GetState(ProcessId),
SetState(ProcessId),
DeleteState(ProcessId),
}
#[derive(Serialize, Deserialize, Debug)]
@ -344,11 +434,11 @@ pub struct ReadChunkRequest {
pub length: u64,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum FsResponse {
Write(u128),
Read(u128),
ReadChunk(u128),
ReadChunk(u128), // TODO: remove?
Append(u128),
Delete(u128),
Length(u64),
@ -410,6 +500,7 @@ pub enum FsError {
CreateInitialDirError { path: String, error: String },
}
#[allow(dead_code)]
impl FsError {
pub fn kind(&self) -> &str {
match *self {
@ -430,73 +521,49 @@ impl FsError {
}
}
impl VfsError {
pub fn kind(&self) -> &str {
match *self {
VfsError::BadIdentifier => "BadIdentifier",
VfsError::BadDescriptor => "BadDescriptor",
VfsError::NoCap => "NoCap",
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VfsRequest {
pub drive: String,
pub action: VfsAction,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum VfsError {
BadIdentifier,
BadDescriptor,
NoCap,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum VfsRequest {
New {
identifier: String,
},
pub enum VfsAction {
New,
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,
},
Delete(String),
WriteOffset {
identifier: String,
full_path: String,
offset: u64,
},
GetPath {
identifier: String,
hash: u128,
},
GetEntry {
identifier: String,
SetSize {
full_path: String,
size: u64,
},
GetPath(u128),
GetHash(String),
GetEntry(String),
GetFileChunk {
identifier: String,
full_path: String,
offset: u64,
length: u64,
},
GetEntryLength {
identifier: String,
full_path: String,
},
GetEntryLength(String),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum AddEntryType {
Dir,
NewFile, // add a new file to fs and add name in vfs
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?
ZipArchive,
}
#[derive(Debug, Serialize, Deserialize)]
@ -507,47 +574,60 @@ pub enum GetEntryType {
#[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>,
},
Ok,
Err(VfsError),
GetPath(Option<String>),
GetHash(Option<u128>),
GetEntry {
identifier: String,
full_path: String,
// file bytes in payload, if entry was a file
is_file: bool,
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,
},
GetFileChunk, // chunk in payload, if file exists
GetEntryLength(Option<u64>),
}
#[derive(Debug, Serialize, Deserialize)]
pub enum VfsError {
BadDriveName,
BadDescriptor,
NoCap,
}
#[allow(dead_code)]
impl VfsError {
pub fn kind(&self) -> &str {
match *self {
VfsError::BadDriveName => "BadDriveName",
VfsError::BadDescriptor => "BadDescriptor",
VfsError::NoCap => "NoCap",
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KeyValueMessage {
New { drive: String },
Write { drive: String, key: Vec<u8> },
Read { drive: String, key: Vec<u8> },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KeyValueError {
BadDriveName,
NoCap,
NoBytes,
}
#[allow(dead_code)]
impl KeyValueError {
pub fn kind(&self) -> &str {
match *self {
KeyValueError::BadDriveName => "BadDriveName",
KeyValueError::NoCap => "NoCap",
KeyValueError::NoBytes => "NoBytes",
}
}
}
//
@ -584,6 +664,7 @@ pub enum HttpClientError {
RequestFailed { error: String },
}
#[allow(dead_code)]
impl HttpClientError {
pub fn kind(&self) -> &str {
match *self {
@ -600,6 +681,18 @@ impl HttpClientError {
// custom kernel displays
//
impl std::fmt::Display for ProcessId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}@{}", self.node, self.process.to_string(),)
}
}
impl std::fmt::Display for KernelMessage {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
@ -615,7 +708,7 @@ impl std::fmt::Display for Message {
match self {
Message::Request(request) => write!(
f,
"Request(\n inherit: {},\n expects_response: {:#?},\n ipc: {},\n metadata: {}\n)",
"Request(\n inherit: {},\n expects_response: {:?},\n ipc: {},\n metadata: {}\n )",
request.inherit,
request.expects_response,
&request.ipc.as_ref().unwrap_or(&"None".into()),
@ -623,7 +716,7 @@ impl std::fmt::Display for Message {
),
Message::Response((response, context)) => write!(
f,
"Response(\n ipc: {},\n metadata: {},\n context: {}\n)",
"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()),
@ -658,6 +751,7 @@ pub enum HttpServerError {
BadJson { json: String, error: String },
}
#[allow(dead_code)]
impl HttpServerError {
pub fn kind(&self) -> &str {
match *self {
@ -687,25 +781,6 @@ pub struct WebSocketPush {
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,

1034
src/vfs.rs

File diff suppressed because it is too large Load Diff

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
}

183
terminal/uqbar.wit Normal file
View File

@ -0,0 +1,183 @@
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)
import get-state: func() -> option<list<u8>>
import set-state: func(bytes: list<u8>)
import clear-state: func()
import spawn: func(id: process-id, %package: string, full-path: 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
}

View File

@ -4,20 +4,23 @@ interface types {
// JSON is passed over WASM boundary as a string.
type json = string
type node-id = 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),
record process-id {
process-name: string,
package-name: string,
publisher-node: node-id,
}
// TODO better name for this
record address {
node: string,
node: node-id,
process: process-id,
}
@ -65,6 +68,14 @@ interface types {
signature: list<u8>,
}
// 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>>>),
}
// 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
@ -83,21 +94,20 @@ interface types {
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>>>),
enum spawn-error {
name-taken,
no-file-at-path,
// TODO more here?
}
}
world uq-process {
use types.{
json,
node-id,
context,
address,
process-id,
address,
payload,
request,
@ -107,9 +117,10 @@ world uq-process {
capabilities,
signed-capability,
on-panic,
send-error,
send-error-kind,
on-panic,
spawn-error
}
// entry point to all programs
@ -125,9 +136,19 @@ world uq-process {
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>
import get-state: func() -> option<list<u8>>
import set-state: func(bytes: list<u8>)
import clear-state: func()
import spawn: func(
name: option<string>,
wasm-path: string, // must be located within package's drive
on-panic: on-panic,
capabilities: capabilities,
public: bool
) -> result<process-id, spawn-error>
// capabilities management
@ -147,6 +168,10 @@ world uq-process {
// if the prompting message has a remote source, they must have attached it.
import has-capability: func(params: json) -> bool
// generates a new capability with our process as the issuer and gives it to the target,
// which must be a locally-running process.
import create-capability: func(to: process-id, params: json)
// message I/O: